use x509 from RustCrypto/formats - #348
Conversation
…o.toml references an x509 crate not in formats repo, this should change as some PRs there are procecessed)
|
cc @str4d |
| fn oid_lookup(oid: &ObjectIdentifier) -> String { | ||
| let oidstr = oid.to_string(); | ||
| if oidstr == "2.5.4.41" { | ||
| return "name".to_string(); | ||
| } else if oidstr == "2.5.4.4" { | ||
| return "sn".to_string(); | ||
| } else if oidstr == "2.5.4.42" { | ||
| return "givenName".to_string(); | ||
| } else if oidstr == "2.5.4.43" { | ||
| return "initials".to_string(); | ||
| } else if oidstr == "2.5.4.44" { | ||
| return "generationQualifier".to_string(); | ||
| } else if oidstr == "2.5.4.3" { | ||
| return "cn".to_string(); | ||
| } else if oidstr == "2.5.4.7" { | ||
| return "l".to_string(); | ||
| } else if oidstr == "2.5.4.8" { | ||
| return "st".to_string(); | ||
| } else if oidstr == "2.5.4.9" { | ||
| return "street".to_string(); | ||
| } else if oidstr == "2.5.4.11" { | ||
| return "ou".to_string(); | ||
| } else if oidstr == "2.5.4.10" { | ||
| return "o".to_string(); | ||
| } else if oidstr == "2.5.4.12" { | ||
| return "title".to_string(); | ||
| } else if oidstr == "2.5.4.46" { | ||
| return "dnQualifier".to_string(); | ||
| } else if oidstr == "2.5.4.6" { | ||
| return "c".to_string(); | ||
| } else if oidstr == "2.5.4.5" { | ||
| return "serialNumber".to_string(); | ||
| } else if oidstr == "2.5.4.65" { | ||
| return "pseudonym".to_string(); | ||
| } else if oidstr == "0.9.2342.19200300.100.1.25" { | ||
| return "dc".to_string(); | ||
| } else if oidstr == "1.2.840.113549.1.9.1" { | ||
| return "emailAddress".to_string(); | ||
| } |
There was a problem hiding this comment.
You should be able to make OID constants for each of these and compare those, and return a &'static str, something like:
const NAME_OID: ObjectIdentifier = ObjectIdentifier::new("2.5.4.41");
fn oid_lookup(oid: &ObjectIdentifier) -> &'static str {
match oid {
NAME_OID => "name",
...
}
}There was a problem hiding this comment.
OK. I will likely move these OIDs and lookup to x509. As noted in the comment, they are duplicated here from certval (but that's probably not the right spot for them either).
There was a problem hiding this comment.
As I was implementing this I recalled why String was used. It was to accommodate the case where the OID does not match and the dot notation form is returned. I will add two functions, one that returns a &'static str with empty on a miss and one that calls that and returns a String with dot notation instead of empty.
str4d
left a comment
There was a problem hiding this comment.
Gave this a brief scan. The API changes from the new x509 crate look nice!
|
|
||
| /// Certificates | ||
| #[derive(Clone, Debug)] | ||
| pub struct Certificate { |
There was a problem hiding this comment.
We should not be removing the owned types and replacing them in the API with Vec<u8>; this loses significant type safety. If the issue is that these names conflict with the ones in the x509 crate, then the latter should be referenced directly as x509::Certificate etc. If the issue is that we want to optimise the parsing case, then we should use a Cow or similar.
There was a problem hiding this comment.
The issue was that the Certificate (or really the Decoder below it) maintains a reference to the buffer. I could add a new type that binds the buffer and an Option together and return that.
There was a problem hiding this comment.
What parts of the certificate borrow? It should probably be an owned data structure
There was a problem hiding this comment.
Decoder has a ByteSlice: https://github.com/RustCrypto/formats/blob/master/der/src/decoder.rs#L15. ByteSlice has a reference here: https://github.com/RustCrypto/formats/blob/master/der/src/byte_slice.rs#L16.
There was a problem hiding this comment.
Yes, but once the certificate is decoded everything should be owned and no longer reference the input buffer.
Where does the lifetime of borrowed data come into play?
There was a problem hiding this comment.
All of the buffers in the structure reference points in the original buffer. So this code snip:
let buf = <read buffer>;
let cert = Certificate::from_der(buf.as_slice());
Yields these addresses in a sample run just now:
0x00007fa5cb009200 - buffer (1281 bytes total)
0x00007fa5cb009337 - spki
0x00007fa5cb009459 - first extension
0x00007fa5cb009601 - signature buffer
There was a problem hiding this comment.
Aah yeah, an owned form of SPKI seems needed.
There's the PublicKeyDocument document type which we could potentially make work for this application. I think it's kind of like what you're describing where it's a newtype for a Vec<u8> which impls the der::Document trait for decoding.
There was a problem hiding this comment.
I will give a look at preparing a CertificateDocument class and return that instead of the buffer in the spots where I moved from struct to buffer. I'll put that in x509 and move the OIDs and nametostr while at it.
There was a problem hiding this comment.
We should not be removing the owned types and replacing them in the API with Vec; this loses significant type safety
There's a "document" type convention for this in the other https://github.com/RustCrypto/formats/ and this PR adds a CertificateDocument type: RustCrypto/formats#393
These types are effectively a newtype wrapper around a Vec<u8> which is guaranteed to parse successfully as a particular type, i.e. a Certificate in this case. They impl the der::Document trait which handles serializing/deserializing DER-encoded documents including PEM (de)serialization as well as reading from and writing to DER or PEM-encoded files.
| } | ||
| } | ||
| let tbs_certificate = TBSCertificate { | ||
| version: 2, |
There was a problem hiding this comment.
I thought there was a bug here: version is set to 2, but extensions may be Some, and according to RFC 5280:
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
extensions [3] EXPLICIT Extensions OPTIONAL
-- If present, version MUST be v3
It turns out this is not a bug, because the new x509 crate doesn't have a type-safe version field, and this value is the raw encoding of the v3 version, not the numeric version v2.
More generally, I dislike this serialization API, enabled as it is by raw-construcing the same struct used as the output of parsing. It is a brittle approach, prone to exactly the kind of mistake I thought was being made here (and could easily be made by someone else trying to use the crate), because no relative constraints are being placed on any of the fields. In particular, I see no handling in the new x509 crate of the above MUST requirements on version; it looks like the user could construct and serialize a certificate that violates them.
There was a problem hiding this comment.
You are correct there is nothing here that addresses the version requirement (or relative requirements in general). I will give a look at that (easiest way in this case is likely to disallow non-v3 at decode/encode time).
There was a problem hiding this comment.
FWIW, many of the other crates in https://github.com/rustcrypto/formats use a Version enum, e.g:
https://docs.rs/pkcs8/latest/pkcs8/enum.Version.html
It's also computed implicitly from the structure of the document, eliminating the possibility of setting it to the wrong value:
https://docs.rs/pkcs8/latest/pkcs8/struct.PrivateKeyInfo.html#method.version
At decode time, the implicitly computed version is checked against the explicit one in the document to ensure it's set correctly.
There was a problem hiding this comment.
I will give a look and do something similar (and follow suit in the OCSP and CMS bits as the evolve). Thanks.
There was a problem hiding this comment.
More generally, I dislike this serialization API, enabled as it is by raw-construcing the same struct used as the output of parsing. It is a brittle approach, prone to exactly the kind of mistake I thought was being made here (and could easily be made by someone else trying to use the crate), because no relative constraints are being placed on any of the fields.
Regarding this in general, it would be good to add something like a CertificateBuilder type which enforces X.509 best practices. We could even use tools like certlint or zlint to ensure that certificates generated with a CertificateBuilder are following those best practices correctly.
The builder type could own the relevant data for various parts of the certificate, and in doing so, that also solves the lifetime-related concerns around a zero-copy certificate parser/serializer which borrows data.
There was a problem hiding this comment.
@tony-iqlusion @carl-wallace My latest PRs both add Version types and make it trivial to derive Version types.
No description provided.