-
Notifications
You must be signed in to change notification settings - Fork 172
x501+x509: CertificateDocument, X509Version, and more additions
#393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 17 commits
f2ab456
213e9a2
945bfe9
c2d3b40
40cf4aa
7504101
30c77d0
66e6b9a
89dc750
b34f48b
bc391a7
c2f9377
1e44e55
fb203b6
ee9f1a3
c226d37
b600b99
6fe650f
a9dab3e
b7a19de
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,17 @@ | ||
| //! Certificate [`Certificate`] and TBSCertificate [`TBSCertificate`] as defined in RFC 5280 | ||
|
|
||
| use der::asn1::{BitString, ContextSpecific, ObjectIdentifier, UIntBytes}; | ||
| use der::{Sequence, TagMode, TagNumber}; | ||
| use der::{ | ||
| DecodeValue, Decoder, EncodeValue, Encoder, Error, FixedTag, Header, Length, Sequence, Tag, | ||
| TagMode, TagNumber, | ||
| }; | ||
| use spki::{AlgorithmIdentifier, SubjectPublicKeyInfo}; | ||
| use x501::name::Name; | ||
| use x501::time::Validity; | ||
|
|
||
| /// returns false in support of integer DEFAULT fields set to 0 | ||
| pub fn default_zero_u8() -> u8 { | ||
| 0 | ||
| pub fn default_v1() -> Version { | ||
| Version::V1 | ||
| } | ||
|
|
||
| /// returns false in support of boolean DEFAULT fields | ||
|
|
@@ -21,9 +24,65 @@ pub fn default_zero() -> u32 { | |
| 0 | ||
| } | ||
|
|
||
| /// only support v3 certificates | ||
| /// only supporting v3 certificates, but all versions are listed here | ||
| /// Version ::= INTEGER { v1(0), v2(1), v3(2) } | ||
| pub const X509_CERT_VERSION: u8 = 2; | ||
|
|
||
| /// Version identifier for X.509 certificates. In practice, only v3 is used. | ||
| #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd)] | ||
| pub enum Version { | ||
| /// Denotes X.509 v1 | ||
| V1 = 0, | ||
|
|
||
| /// Denotes X.509 v2 - added issuerUniqueID and subjectUniqueID (both of which have been deprecated) | ||
| V2 = 1, | ||
|
|
||
| /// Denotes X.509 v3 - add extensions | ||
| V3 = 2, | ||
| } | ||
|
|
||
| impl Default for Version { | ||
| fn default() -> Self { | ||
| Version::V3 | ||
| } | ||
| } | ||
|
|
||
| impl DecodeValue<'_> for Version { | ||
| fn decode_value(decoder: &mut Decoder<'_>, header: Header) -> der::Result<Self> { | ||
| Version::try_from(u8::decode_value(decoder, header)?).map_err(|_| Self::TAG.value_error()) | ||
| } | ||
| } | ||
|
|
||
| impl EncodeValue for Version { | ||
| fn value_len(&self) -> der::Result<der::Length> { | ||
| Ok(Length::ONE) | ||
| } | ||
|
|
||
| fn encode_value(&self, encoder: &mut Encoder<'_>) -> der::Result<()> { | ||
| u8::from(*self).encode_value(encoder) | ||
| } | ||
| } | ||
|
|
||
| impl FixedTag for Version { | ||
| const TAG: Tag = Tag::Integer; | ||
| } | ||
|
|
||
| impl From<Version> for u8 { | ||
| fn from(version: Version) -> Self { | ||
| version as u8 | ||
| } | ||
| } | ||
|
|
||
| impl TryFrom<u8> for Version { | ||
| type Error = Error; | ||
| fn try_from(byte: u8) -> Result<Version, Error> { | ||
| match byte { | ||
| 0 => Ok(Version::V1), | ||
| 1 => Ok(Version::V2), | ||
| 2 => Ok(Version::V3), | ||
| _ => Err(Self::TAG.value_error()), | ||
| } | ||
| } | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @carl-wallace All the changes in this file are obsolete if we merge #410 instead.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. Further review of this until after alignment with changes to dependencies is likely not worthwhile. |
||
|
|
||
| /// X.509 `TBSCertificate` as defined in [RFC 5280 Section 4.1.2.5] | ||
| /// | ||
|
|
@@ -53,8 +112,8 @@ pub const X509_CERT_VERSION: u8 = 2; | |
| #[derive(Clone, Eq, PartialEq)] | ||
| pub struct TBSCertificate<'a> { | ||
| /// version [0] Version DEFAULT v1, | ||
| //#[asn1(context_specific = "0", default = "default_zero_u8")] | ||
| pub version: u8, | ||
| //#[asn1(context_specific = "0", default = "default_v1")] | ||
| pub version: Version, | ||
| /// serialNumber CertificateSerialNumber, | ||
| pub serial_number: UIntBytes<'a>, | ||
| /// signature AlgorithmIdentifier{SIGNATURE-ALGORITHM, {SignatureAlgorithms}}, | ||
|
|
@@ -83,22 +142,33 @@ impl<'a> ::der::Decodable<'a> for TBSCertificate<'a> { | |
| let version = | ||
| ::der::asn1::ContextSpecific::decode_explicit(decoder, ::der::TagNumber::N0)? | ||
| .map(|cs| cs.value) | ||
| .unwrap_or_else(default_zero_u8); | ||
| .unwrap_or_else(default_v1); | ||
| let serial_number = decoder.decode()?; | ||
| let signature = decoder.decode()?; | ||
| let issuer = decoder.decode()?; | ||
| let validity = decoder.decode()?; | ||
| let subject = decoder.decode()?; | ||
| let subject_public_key_info = decoder.decode()?; | ||
| let issuer_unique_id = | ||
|
|
||
| let issuer_unique_id = if Version::V2 <= version { | ||
| ::der::asn1::ContextSpecific::decode_implicit(decoder, ::der::TagNumber::N1)? | ||
| .map(|cs| cs.value); | ||
| let subject_unique_id = | ||
| .map(|cs| cs.value) | ||
| } else { | ||
| None | ||
| }; | ||
| let subject_unique_id = if Version::V2 <= version { | ||
| ::der::asn1::ContextSpecific::decode_implicit(decoder, ::der::TagNumber::N2)? | ||
| .map(|cs| cs.value); | ||
| let extensions = | ||
| .map(|cs| cs.value) | ||
| } else { | ||
| None | ||
| }; | ||
| let extensions = if Version::V3 <= version { | ||
| ::der::asn1::ContextSpecific::decode_explicit(decoder, ::der::TagNumber::N3)? | ||
| .map(|cs| cs.value); | ||
| .map(|cs| cs.value) | ||
| } else { | ||
| None | ||
| }; | ||
|
|
||
| Ok(Self { | ||
| version, | ||
| serial_number, | ||
|
|
@@ -121,27 +191,49 @@ impl<'a> ::der::Sequence<'a> for TBSCertificate<'a> { | |
| where | ||
| F: FnOnce(&[&dyn der::Encodable]) -> ::der::Result<T>, | ||
| { | ||
| let version = &ContextSpecific { | ||
| tag_number: VERSION_TAG, | ||
| tag_mode: TagMode::Explicit, | ||
| value: self.version, | ||
| }; | ||
|
|
||
| let issuer_unique_id = if Version::V2 <= self.version { | ||
| &self.issuer_unique_id | ||
| } else { | ||
| &None | ||
| }; | ||
| let subject_unique_id = if Version::V2 <= self.version { | ||
| &self.subject_unique_id | ||
| } else { | ||
| &None | ||
| }; | ||
| let extensions = if Version::V3 <= self.version { | ||
| self.extensions.as_ref().map(|exts| ContextSpecific { | ||
| tag_number: EXTENSIONS_TAG, | ||
| tag_mode: TagMode::Explicit, | ||
| value: exts.clone(), | ||
| }) | ||
| } else { | ||
| None | ||
| }; | ||
|
|
||
| #[allow(unused_imports)] | ||
| use core::convert::TryFrom; | ||
| f(&[ | ||
| &ContextSpecific { | ||
| tag_number: VERSION_TAG, | ||
| tag_mode: TagMode::Explicit, | ||
| value: self.version, | ||
| }, | ||
| &::der::asn1::OptionalRef(if self.version == Version::V1 { | ||
| None | ||
| } else { | ||
| Some(version) | ||
| }), | ||
| &self.serial_number, | ||
| &self.signature, | ||
| &self.issuer, | ||
| &self.validity, | ||
| &self.subject, | ||
| &self.subject_public_key_info, | ||
| &self.issuer_unique_id, | ||
| &self.subject_unique_id, | ||
| &self.extensions.as_ref().map(|exts| ContextSpecific { | ||
| tag_number: EXTENSIONS_TAG, | ||
| tag_mode: TagMode::Explicit, | ||
| value: exts.clone(), | ||
| }), | ||
| issuer_unique_id, | ||
| subject_unique_id, | ||
| &extensions, | ||
| ]) | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| //! CertificateDocument implementation | ||
|
|
||
| use crate::certificate_traits::*; | ||
| use crate::Certificate; | ||
| use der::{Error, Result}; | ||
|
|
||
| use alloc::vec::Vec; | ||
| use core::fmt; | ||
| use der::{Decodable, Document}; | ||
|
|
||
| #[cfg(feature = "std")] | ||
| use std::path::Path; | ||
|
|
||
| #[cfg(feature = "pem")] | ||
| use { | ||
| alloc::string::String, | ||
| core::str::FromStr, | ||
| der::pem::{self, LineEnding}, | ||
| }; | ||
|
|
||
| /// Certificate document. | ||
| /// | ||
| /// This type provides storage for [`Certificate`] encoded as ASN.1 | ||
| /// DER with the invariant that the contained-document is "well-formed", i.e. | ||
| /// it will parse successfully according to this crate's parsing rules. | ||
| #[derive(Clone)] | ||
| #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] | ||
| pub struct CertificateDocument(Vec<u8>); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tarcieri I know I've implemented one of these document types for
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like a complicated and important enough issue I did a long-form write up on it, so as not to lose the answers to these questions in a PR comment: #421 Some short answers though:
|
||
|
|
||
| impl<'a> TryFrom<&'a [u8]> for Certificate<'a> { | ||
| type Error = Error; | ||
|
|
||
| fn try_from(bytes: &'a [u8]) -> Result<Self> { | ||
| Self::from_der(bytes) | ||
| } | ||
| } | ||
|
|
||
| impl<'a> Document<'a> for CertificateDocument { | ||
| type Message = Certificate<'a>; | ||
| const SENSITIVE: bool = false; | ||
| } | ||
|
|
||
| impl DecodeCertificate for CertificateDocument { | ||
| fn from_certificate_der(bytes: &[u8]) -> Result<Self> { | ||
| Self::from_der(bytes) | ||
| } | ||
|
|
||
| #[cfg(feature = "pem")] | ||
| #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] | ||
| fn from_certificate_pem(s: &str) -> Result<Self> { | ||
| Self::from_pem(s) | ||
| } | ||
|
|
||
| #[cfg(feature = "std")] | ||
| #[cfg_attr(docsrs, doc(cfg(feature = "std")))] | ||
| fn read_certificate_der_file(path: impl AsRef<Path>) -> Result<Self> { | ||
| Self::read_der_file(path) | ||
| } | ||
|
|
||
| #[cfg(all(feature = "pem", feature = "std"))] | ||
| #[cfg_attr(docsrs, doc(cfg(all(feature = "pem", feature = "std"))))] | ||
| fn read_certificate_pem_file(path: impl AsRef<Path>) -> Result<Self> { | ||
| Self::read_pem_file(path) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(any(feature = "alloc", feature = "std"))] | ||
| #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] | ||
| impl EncodeCertificate for CertificateDocument { | ||
| fn to_certificate_der(&self) -> Result<CertificateDocument> { | ||
| Ok(self.clone()) | ||
| } | ||
|
|
||
| #[cfg(feature = "pem")] | ||
| #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] | ||
| fn to_certificate_pem(&self, line_ending: LineEnding) -> Result<String> { | ||
| self.to_pem(line_ending) | ||
| } | ||
|
|
||
| #[cfg(feature = "std")] | ||
| #[cfg_attr(docsrs, doc(cfg(feature = "std")))] | ||
| fn write_certificate_der_file(&self, path: impl AsRef<Path>) -> Result<()> { | ||
| self.write_der_file(path) | ||
| } | ||
|
|
||
| #[cfg(all(feature = "pem", feature = "std"))] | ||
| #[cfg_attr(docsrs, doc(cfg(all(feature = "pem", feature = "std"))))] | ||
| fn write_certificate_pem_file( | ||
| &self, | ||
| path: impl AsRef<Path>, | ||
| line_ending: LineEnding, | ||
| ) -> Result<()> { | ||
| self.write_pem_file(path, line_ending) | ||
| } | ||
| } | ||
|
|
||
| impl AsRef<[u8]> for CertificateDocument { | ||
| fn as_ref(&self) -> &[u8] { | ||
| self.0.as_ref() | ||
| } | ||
| } | ||
|
|
||
| impl TryFrom<&[u8]> for CertificateDocument { | ||
| type Error = Error; | ||
|
|
||
| fn try_from(bytes: &[u8]) -> Result<Self> { | ||
| Self::from_der(bytes) | ||
| } | ||
| } | ||
|
|
||
| impl TryFrom<Certificate<'_>> for CertificateDocument { | ||
| type Error = Error; | ||
|
|
||
| fn try_from(cert: Certificate<'_>) -> Result<CertificateDocument> { | ||
| Self::try_from(&cert) | ||
| } | ||
| } | ||
|
|
||
| impl TryFrom<&Certificate<'_>> for CertificateDocument { | ||
| type Error = Error; | ||
|
|
||
| fn try_from(cert: &Certificate<'_>) -> Result<CertificateDocument> { | ||
| Self::from_msg(cert) | ||
| } | ||
| } | ||
|
|
||
| impl TryFrom<Vec<u8>> for CertificateDocument { | ||
| type Error = der::Error; | ||
|
|
||
| fn try_from(bytes: Vec<u8>) -> der::Result<Self> { | ||
| // Ensure document is well-formed | ||
| Certificate::from_der(bytes.as_slice())?; | ||
| Ok(Self(bytes)) | ||
| } | ||
| } | ||
|
|
||
| impl fmt::Debug for CertificateDocument { | ||
| fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| fmt.debug_tuple("CertificateDocument") | ||
| .field(&self.decode()) | ||
| .finish() | ||
| } | ||
| } | ||
|
|
||
| #[cfg(feature = "pem")] | ||
| #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] | ||
| impl FromStr for CertificateDocument { | ||
| type Err = Error; | ||
|
|
||
| fn from_str(s: &str) -> Result<Self> { | ||
| Self::from_certificate_pem(s) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(feature = "pem")] | ||
| #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] | ||
| impl pem::PemLabel for CertificateDocument { | ||
| const TYPE_LABEL: &'static str = "CERTIFICATE"; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.