rustls / webpki-roots Goto Github PK
View Code? Open in Web Editor NEWCA certificates for use with webpki
License: Other
CA certificates for use with webpki
License: Other
Depending on Python and OpenSSL for a Rust project that aims to replace the OpenSSL dependency looks kind of embarrassing. By moving to a Rust build script, not only would it be faster, but you'd be able to use existing libraries directly rather than delegating to OpenSSL.
There are two ways to do this:
build.rs
. This requires that each consumer download the list of certificates and generate the list of roots themselves, which means any certificate could poof out from under people without any crate version change. That is probably undesirable.cargo-script
.In either case, you'd be eliminating the dependency on competing crypto libraries (which OpenSSL is), and eliminating the dependency on a completely unrelated, external programming language (Python). Modern Linux systems do not even have Python installed by default, so it's one more step for users looking to contribute to this project. Additionally, as a small bonus, the build script would work on Windows. It is a small bonus given Windows' unsuitability to low-level applications, as any serious developer will be using WSL anyway, but even if only for drive-by contributions, it is a small victory.
See https://hg.mozilla.org/projects/nss/rev/1fefea6530e1.
AFAICT these kinds of name constraints aren't applied to roots included in this set. The webpki crate provides a way to add the imposed constraints but currently the webpki-roots build script doesn't know how to find the imposed constraints. It seems like this might have to be done by manual intervention?
I think it makes sense to transition the webpki-roots generation process to use ccadb as its source of truth. There are a few advantages in my mind:
mkcert.org
service called out as potentially problematic as a dependency in #25.With this goal in mind I've been (slowly, on the side) creating a set of CCADB related tools over in cpu/ccadb-utils:
ccadb-csv
crate is a very thin abstraction around raw CSV reports available on CCADB. It does no parsing above destructuring CSV records and presents the content "as-is", in string form.ccadb-csv-fetch
crate is a small binary for fetching the CSV reports to disk. It bakes in exactly one trust anchor, trying to minimize the trust surface required for acquiring metadata from CCADB.ccadb-webpki-roots
crate converts the CCADB Mozilla included roots report into a .rs
for use inside webpki-roots
.ccadb-crl-fetch
crate for downloading disclosed CRLs, mostly as a test corpus.Some open questions I'd like to discuss:
ccadb-utils
as part of a build process in this repo.ccadb-utils
repo into the Rustls organization and use it as part of the build process in this repo.ccadb-utils
repo into this repo.I think I lightly prefer keeping the tooling in a separate repository (and have no strong feelings about whether it lives in the rustls org or not) and using it as a dev-dependency in this repo. If we go this route I would love reviews on the code in ccadb-utils
- it was written solo and could probably use polish/love from more experienced rustaceans.
My thinking is that there's likely to be continued value in having a richer way to work with the CCADB and we can consolidate that work in a separate repo as opposed to having to maintain the webpki-roots specific parts here, and the rest elsewhere. As one example of a future use-case there's in-progress work in the TLS working group to use CCADB data to execute certificate compression (draft-jackson-tls-cert-abridge).
So I've started using Rust more, in 2020 I found and utilised Rustls (thank you)
I listen to Deirdre Connolly who is a massive fan of Rustls (and I am a fan of her so by extension..) and it eventually occurred to me that I should at least verify 'my trust' in 'my' usage.
Inherently a Root CA store must be trusted when used, and to be trustworthy the expectation is the trust we place can be validated.
Being open source is a good start, but it doesn't answer the specific question; The certificates in this Root CA store are verifiable trustworthy
And this repo does not actually address assurance directly either
I can verify what the code does, bare with me as I'm the maintainer of tlstrust on pypi which aims to verify trustworthiness of all Root CA stores, there is no bias, you have not been targeted
Assertion 1. The crate contains curated root certificates for use with the webpki
and anywhere rustls
itself is used. You apply filtering on the subject and produce the resultant Root CA Trust Store.
Assertion 2. Before you process the bundle of certs you fetch over HTTP from mkcert.org, mistakenly referred to as the "Mozilla tooling", which it is not
Assertion 3. Mozilla's original source of these root certificates is the Common CA Database (CCADB) (also the source for Google, Microsoft, Apple, and many more) but the not-Mozilla mkcert tooling consumes the Network Security Services (NSS) bundle instead because it's already curated for their own products
Conclusion 1. The assurance of NSS is not yours to provide, it is gained from Mozilla
Conclusion 2. You cannot verify anything about the environment mkcert.org - what is served over HTTP could have been MitM anywhere along the line. It would have been verifiable had you run the mkcert tooling locally in an environment you control and/or validated the resultant fingerprints from CCADB/NSS
Conclusion 3. The subject is not secure so filtering on it is not a trustworthy mechanism. If additional filtering by you is necessary at all, the most appropriate filtering mechanism would be the Subject Key Identifier (SKI), or the serial number, or a SHA256 fingerprint.
At this stage, using well defined and accepted security characteristics, I can't see how the Rustls Root CA store is verifiable.
Without verification it really is not trustworthy.
And the single most important inherent characteristic of a Root CA store is that is must be trusted.
I was wondering why this crate so frequently has new semver-incompatible releases. Currently a project I work on depends on both 0.19.0 and 0.20.0, then I saw 0.21.0 is out now as well; I generally try to avoid duplicate versions of dependencies. It would be nice if new versions that only update TLS_SERVER_ROOTS
entries but don't change the webpki
version or otherwise have API-breaking changes could be patch releases (so 0.21.0 would have been 0.20.1).
In #36 the TUBITAK1_NAME_CONSTRAINTS
constant was updated:
Lines 252 to 253 in 5bc18e3
There was some back and forth in #36 about the encoded contents, but looking closer today I think we might have arrived at the wrong encoding.
Right now the name_constraint
for this root ends up loaded into the TrustAnchor
's name_constraints
field as:
0xA0 0x0B 0x30 0x09 0xA0 0x07 0x30 0x05 0x82 0x03 0x2E 0x74 0x72
I think the correct encoding is:
0xA0 0x07 0x30 0x05 0x82 0x03 0x2E 0x74 0x72
Notably this drops the first four bytes (0xA0 0x0B 0x30 0x09
) of the existing constraint bytes.
(Side note; I really dislike the encoding format that the lib.rs
has where it uses a byte string with ascii range characters represented as-is and the rest hex escaped. I think it would be better to express this as Some(&[<hex literals>])
)
I arrived at this conclusion in two ways:
The test case added in ff68fd9 doesn't seem to be using the name constraint when making a trust decision.
If you step through execution of the test in a debugger, letting execution get down into the webpki/subject_name/verify.rs
's check_presented_id_conforms_to_constraints_in_subtree
fn that gets called three times (once per cert in the chain) to perform subtree name constraint validation, you'll find that the base
GeneralName
read from match general_subtree(&mut constraints)
is not a GeneralName::DnsName(".tr".as_bytes())
as you'd expect, but GeneralName::Unsupported
.
This happens because when GeneralName::from_der
is called to parse the base
general name it is being given the DER encoded input 0xA0 0x07 0x30 0x05 0x82 0x03 0x2e 0x74 0x72
. This breaks down to a tag of 0xA0
(CONTEXT_SPECIFIC | CONSTRUCTED | 0
, aka OTHER_NAME_TAG
), a length of 0x07
, and the value 0x30 0x05 0x82 0x03 0x2E 0x74 0x72
(aka the nonsense string: "0\x05\x82\x03.tr"
). The GeneralName::from_der
processing of this content matches the OTHER_NAME_TAG
into the GeneralName::Unsupported
arm, returning an unsupported general name as the base of the permitted subtree constraint.
Back in check_presented_id_conforms_to_constraints_in_subtree
execution continues through match (name, base)
where we hit the _
arm because we are never comparing GeneralName::Unsupported
names from a certificate in the chain against the GeneralName::Unsupported
base from the permitted subtrees.
Ultimately check_presented_id_conforms_to_constraints_in_subtree
finds has_permitted_subtrees_mismatch == false
and has_permitted_subtrees_match == false
and returns NameIteration::KeepGoing
. The net result is that validation succeeds, but no permitted subtree base was ever truly examined against a certificate subject name.
It seems obvious that the GeneralName
base for the permitted subtrees should be a DnsName
, but in case there was any doubt that otherName
isn't correct, RFC 5280 4.2.1.10 says:
The syntax and semantics for name constraints for otherName, ediPartyName, and registeredID are not defined by this specification, however, syntax and semantics for name constraints for other name forms may be specified in other documents.
To help double check my reasoning I wrote a quick and dirty Python script to spit out a CA certificate with a name constraint that I think matches the intent for KamuSM:
ca_cert_builder = ca_cert_builder.add_extension(
x509.NameConstraints(permitted_subtrees=[x509.DNSName(".tr")], excluded_subtrees=None),
critical=True,
)
Loading the encoded cert as a TrustAnchor
we end up with a name_constraints
field of Some(&[0xA0, 0x07, 0x30, 0x05, 0x82, 0x03, 0x2E, 0x74, 0x72])
. This matches the last 9 bytes of the current name constraint, but is missing the first four bytes (0xA0 0x0B 0x30 0x09
).
Running through the same debugging session as described above for the existing name constraint we can see that when it comes time to read the base
of the permitted subtree GeneralName::from_der
gets the DER input: 0x82, 0x03, 0x2E, 0x74, 0x72
. This breaks down as tag 0x82 (CONTEXT_SPECIFIC | CONSTRUCTED | 2
, aka DNS_NAME_TAG
), a length of 0x03, and the value 0x2E 0x74 0x72
, aka ".tr"
.
Unlike the previous session, the DNS_NAME_TAG
is matched into a GeneralName::DnsName(...)
, not a GeneralName::Unsupported
.
Back in check_presented_id_conforms_to_constraints_in_subtree
when we come to match (name, base)
we can compare a DnsName
from the end entity certificate to the DnsName
from the base of the permitted subtree. Unlike the previous session, we actually do a comparison against the base of the permitted subtree using dns_name::presented_id_matches_constraint
.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.