From 49e5db9eb9165361b21a80df6bfb01c8289473b0 Mon Sep 17 00:00:00 2001 From: Simon Dieterle Date: Tue, 3 Jan 2023 17:22:54 +0000 Subject: [PATCH] initial pkcs12 --- pkcs12/Cargo.toml | 9 ++ pkcs12/src/authenticated_safe.rs | 163 +++++++++++++++++++++++++++++ pkcs12/src/bag_type.rs | 73 +++++++++++++ pkcs12/src/cert_bag_content.rs | 19 ++++ pkcs12/src/cert_type.rs | 63 +++++++++++ pkcs12/src/content_info.rs | 103 ++++++++++++++++++ pkcs12/src/digest_info.rs | 16 +++ pkcs12/src/lib.rs | 51 +++++++++ pkcs12/src/mac_data.rs | 26 +++++ pkcs12/src/pfx.rs | 40 +++++++ pkcs12/src/safe_bag.rs | 128 ++++++++++++++++++++++ pkcs12/tests/cert_tests.rs | 27 +++++ pkcs12/tests/examples/cert.pem | 17 +++ pkcs12/tests/examples/example.pfx | Bin 0 -> 1875 bytes pkcs12/tests/examples/example2.pfx | Bin 0 -> 1756 bytes pkcs12/tests/examples/gen.sh | 3 + pkcs12/tests/examples/key.pem | 16 +++ 17 files changed, 754 insertions(+) create mode 100644 pkcs12/src/authenticated_safe.rs create mode 100644 pkcs12/src/bag_type.rs create mode 100644 pkcs12/src/cert_bag_content.rs create mode 100644 pkcs12/src/cert_type.rs create mode 100644 pkcs12/src/content_info.rs create mode 100644 pkcs12/src/digest_info.rs create mode 100644 pkcs12/src/mac_data.rs create mode 100644 pkcs12/src/pfx.rs create mode 100644 pkcs12/src/safe_bag.rs create mode 100644 pkcs12/tests/cert_tests.rs create mode 100644 pkcs12/tests/examples/cert.pem create mode 100644 pkcs12/tests/examples/example.pfx create mode 100644 pkcs12/tests/examples/example2.pfx create mode 100644 pkcs12/tests/examples/gen.sh create mode 100644 pkcs12/tests/examples/key.pem diff --git a/pkcs12/Cargo.toml b/pkcs12/Cargo.toml index 379fbde59..ded0edb69 100644 --- a/pkcs12/Cargo.toml +++ b/pkcs12/Cargo.toml @@ -17,3 +17,12 @@ rust-version = "1.65" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +der = { version = "=0.7.0-pre", features = ["oid", "pem"], path = "../der" } +spki = { version = "=0.7.0-pre", path = "../spki" } +x509-cert = { version = "=0.2.0-pre", path = "../x509-cert" } +pkcs7 = { version = "=0.4.0-pre.0", path = "../pkcs7" } +pkcs8 = { version = "=0.10.0-pre", features = ["pkcs5"], path = "../pkcs8" } +[dev-dependencies] +hex-literal = "0.3.3" \ No newline at end of file diff --git a/pkcs12/src/authenticated_safe.rs b/pkcs12/src/authenticated_safe.rs new file mode 100644 index 000000000..a5ce04331 --- /dev/null +++ b/pkcs12/src/authenticated_safe.rs @@ -0,0 +1,163 @@ +use alloc::vec::Vec; +use der::{ + asn1::{ContextSpecific, OctetStringRef}, + Decode, DecodeValue, Encode, EncodeValue, ErrorKind, FixedTag, Header, Length, Reader, + Sequence, Tag, TagMode, TagNumber, Writer, +}; +use pkcs7::encrypted_data_content::EncryptedDataContent; +use pkcs8::ObjectIdentifier; + +use crate::safe_bag::SafeContents; + +const CONTENT_TAG: TagNumber = TagNumber::new(0); + +/// sequence of AuthenticatedSafeItems +pub type AuthenticatedSafe<'a> = Vec>; + +/// TODO +#[derive(Clone, Debug)] +pub enum AuthenticatedSafeItem<'a> { + /// data safe + Data(Option>), + /// encrypted data safe + EncryptedData(Option>), + /// enveloped data safe + EnvelopedData(Option>), +} + +impl<'a> AuthenticatedSafeItem<'a> { + /// return content type of content info + pub fn content_type(&self) -> AuthenticatedSafeContentType { + match self { + Self::Data(_) => AuthenticatedSafeContentType::Data, + Self::EncryptedData(_) => AuthenticatedSafeContentType::EncryptedData, + Self::EnvelopedData(_) => AuthenticatedSafeContentType::EnvelopedData, + } + } +} + +impl<'a> DecodeValue<'a> for AuthenticatedSafeItem<'a> { + fn decode_value>( + reader: &mut R, + header: Header, + ) -> der::Result> { + reader.read_nested(header.length, |reader| { + let bag_type = reader.decode()?; + match bag_type { + AuthenticatedSafeContentType::Data => { + let inner = reader + .context_specific::>(CONTENT_TAG, TagMode::Explicit)?; + let contents = SafeContents::from_der(inner.unwrap().as_bytes())?; + Ok(AuthenticatedSafeItem::Data(Some(contents))) + } + AuthenticatedSafeContentType::EncryptedData => { + Ok(AuthenticatedSafeItem::EncryptedData( + reader.context_specific::>( + CONTENT_TAG, + TagMode::Explicit, + )?, + )) + } + AuthenticatedSafeContentType::EnvelopedData => { + Ok(AuthenticatedSafeItem::EnvelopedData( + reader + .context_specific::>(CONTENT_TAG, TagMode::Explicit)?, + )) + } + } + }) + } +} + +impl<'a> Sequence<'a> for AuthenticatedSafeItem<'a> { + fn fields(&self, f: F) -> der::Result + where + F: FnOnce(&[&dyn Encode]) -> der::Result, + { + match self { + AuthenticatedSafeItem::Data(data) => f(&[ + &self.content_type(), + &data.as_ref().map(|d| ContextSpecific { + tag_number: CONTENT_TAG, + tag_mode: TagMode::Explicit, + value: d.clone(), + }), + ]), + AuthenticatedSafeItem::EncryptedData(data) => f(&[ + &self.content_type(), + &data.as_ref().map(|d| ContextSpecific { + tag_number: CONTENT_TAG, + tag_mode: TagMode::Explicit, + value: d.clone(), + }), + ]), + AuthenticatedSafeItem::EnvelopedData(data) => f(&[ + &self.content_type(), + &data.as_ref().map(|d| ContextSpecific { + tag_number: CONTENT_TAG, + tag_mode: TagMode::Explicit, + value: d.clone(), + }), + ]), + } + } +} + +/// Indicates the type of content. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum AuthenticatedSafeContentType { + /// Plain data content type + Data, + + /// Enveloped-data content type + EnvelopedData, + + /// Encrypted-data content type + EncryptedData, +} + +impl<'a> DecodeValue<'a> for AuthenticatedSafeContentType { + fn decode_value>( + reader: &mut R, + header: Header, + ) -> der::Result { + ObjectIdentifier::decode_value(reader, header)?.try_into() + } +} + +impl EncodeValue for AuthenticatedSafeContentType { + fn value_len(&self) -> der::Result { + ObjectIdentifier::from(*self).value_len() + } + + fn encode_value(&self, writer: &mut dyn Writer) -> der::Result<()> { + ObjectIdentifier::from(*self).encode_value(writer) + } +} + +impl FixedTag for AuthenticatedSafeContentType { + const TAG: Tag = Tag::ObjectIdentifier; +} + +impl From for ObjectIdentifier { + fn from(content_type: AuthenticatedSafeContentType) -> ObjectIdentifier { + match content_type { + AuthenticatedSafeContentType::Data => pkcs7::PKCS_7_DATA_OID, + AuthenticatedSafeContentType::EnvelopedData => pkcs7::PKCS_7_ENVELOPED_DATA_OID, + AuthenticatedSafeContentType::EncryptedData => pkcs7::PKCS_7_ENCRYPTED_DATA_OID, + } + } +} + +impl TryFrom for AuthenticatedSafeContentType { + type Error = der::Error; + + fn try_from(oid: ObjectIdentifier) -> der::Result { + match oid { + pkcs7::PKCS_7_DATA_OID => Ok(Self::Data), + pkcs7::PKCS_7_ENVELOPED_DATA_OID => Ok(Self::EnvelopedData), + pkcs7::PKCS_7_ENCRYPTED_DATA_OID => Ok(Self::EncryptedData), + _ => Err(ErrorKind::OidUnknown { oid }.into()), + } + } +} diff --git a/pkcs12/src/bag_type.rs b/pkcs12/src/bag_type.rs new file mode 100644 index 000000000..f8454aea7 --- /dev/null +++ b/pkcs12/src/bag_type.rs @@ -0,0 +1,73 @@ +use der::asn1::ObjectIdentifier; +use der::{DecodeValue, EncodeValue, ErrorKind, FixedTag, Header, Length, Reader, Tag, Writer}; + +/// Indicates the type of content. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum BagType { + /// Plain data content type + Key, + + /// Signed-data content type + Pkcs8, + + /// Enveloped-data content type + Cert, + + /// Signed-and-enveloped-data content type + Crl, + + /// Digested-data content type + Secret, + + /// Encrypted-data content type + SafeContents, +} + +impl<'a> DecodeValue<'a> for BagType { + fn decode_value>(reader: &mut R, header: Header) -> der::Result { + ObjectIdentifier::decode_value(reader, header)?.try_into() + } +} + +impl EncodeValue for BagType { + fn value_len(&self) -> der::Result { + ObjectIdentifier::from(*self).value_len() + } + + fn encode_value(&self, writer: &mut dyn Writer) -> der::Result<()> { + ObjectIdentifier::from(*self).encode_value(writer) + } +} + +impl FixedTag for BagType { + const TAG: Tag = Tag::ObjectIdentifier; +} + +impl From for ObjectIdentifier { + fn from(content_type: BagType) -> ObjectIdentifier { + match content_type { + BagType::Key => crate::PKCS_12_KEY_BAG_OID, + BagType::Pkcs8 => crate::PKCS_12_PKCS8_KEY_BAG_OID, + BagType::Cert => crate::PKCS_12_CERT_BAG_OID, + BagType::Crl => crate::PKCS_12_CRL_BAG_OID, + BagType::Secret => crate::PKCS_12_SECRET_BAG_OID, + BagType::SafeContents => crate::PKCS_12_SAFE_CONTENTS_BAG_OID, + } + } +} + +impl TryFrom for BagType { + type Error = der::Error; + + fn try_from(oid: ObjectIdentifier) -> der::Result { + match oid { + crate::PKCS_12_KEY_BAG_OID => Ok(Self::Key), + crate::PKCS_12_PKCS8_KEY_BAG_OID => Ok(Self::Pkcs8), + crate::PKCS_12_CERT_BAG_OID => Ok(Self::Cert), + crate::PKCS_12_CRL_BAG_OID => Ok(Self::Crl), + crate::PKCS_12_SECRET_BAG_OID => Ok(Self::Secret), + crate::PKCS_12_SAFE_CONTENTS_BAG_OID => Ok(Self::SafeContents), + _ => Err(ErrorKind::OidUnknown { oid }.into()), + } + } +} diff --git a/pkcs12/src/cert_bag_content.rs b/pkcs12/src/cert_bag_content.rs new file mode 100644 index 000000000..9ac32ba4c --- /dev/null +++ b/pkcs12/src/cert_bag_content.rs @@ -0,0 +1,19 @@ +use der::{asn1::OctetString, Sequence, ValueOrd}; + +use crate::cert_type::CertType; + +/// ```text +/// CertBag ::= SEQUENCE { +/// certId BAG-TYPE.&id ({CertTypes}), +/// certValue [0] EXPLICIT BAG-TYPE.&Type ({CertTypes}{@certId}) +/// } +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)] +pub struct CertBagContent { + /// the cert id + pub id: CertType, + + /// the cert value + #[asn1(context_specific = "0", tag_mode = "EXPLICIT", optional = "true")] + pub bytes: Option, +} diff --git a/pkcs12/src/cert_type.rs b/pkcs12/src/cert_type.rs new file mode 100644 index 000000000..56d117b89 --- /dev/null +++ b/pkcs12/src/cert_type.rs @@ -0,0 +1,63 @@ +use core::cmp::Ordering; + +use der::asn1::ObjectIdentifier; +use der::{ + DecodeValue, EncodeValue, ErrorKind, FixedTag, Header, Length, Reader, Tag, ValueOrd, Writer, +}; + +/// Indicates the type of content. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum CertType { + /// Plain data content type + X509, + + /// Signed-data content type + Sdsi, +} + +impl<'a> DecodeValue<'a> for CertType { + fn decode_value>(reader: &mut R, header: Header) -> der::Result { + ObjectIdentifier::decode_value(reader, header)?.try_into() + } +} + +impl EncodeValue for CertType { + fn value_len(&self) -> der::Result { + ObjectIdentifier::from(*self).value_len() + } + + fn encode_value(&self, writer: &mut dyn Writer) -> der::Result<()> { + ObjectIdentifier::from(*self).encode_value(writer) + } +} + +impl FixedTag for CertType { + const TAG: Tag = Tag::ObjectIdentifier; +} + +impl From for ObjectIdentifier { + fn from(content_type: CertType) -> ObjectIdentifier { + match content_type { + CertType::X509 => crate::PKCS_12_X509_CERT_OID, + CertType::Sdsi => crate::PKCS_12_SDSI_CERT_OID, + } + } +} + +impl TryFrom for CertType { + type Error = der::Error; + + fn try_from(oid: ObjectIdentifier) -> der::Result { + match oid { + crate::PKCS_12_X509_CERT_OID => Ok(Self::X509), + crate::PKCS_12_SDSI_CERT_OID => Ok(Self::Sdsi), + _ => Err(ErrorKind::OidUnknown { oid }.into()), + } + } +} + +impl ValueOrd for CertType { + fn value_cmp(&self, other: &Self) -> der::Result { + Ok(self.cmp(other)) + } +} diff --git a/pkcs12/src/content_info.rs b/pkcs12/src/content_info.rs new file mode 100644 index 000000000..d01584e31 --- /dev/null +++ b/pkcs12/src/content_info.rs @@ -0,0 +1,103 @@ +use der::{ + asn1::OctetStringRef, Decode, DecodeValue, Encode, Header, Reader, Sequence, TagMode, TagNumber, +}; +use pkcs7::ContentType; + +use crate::authenticated_safe::AuthenticatedSafe; + +const CONTENT_TAG: TagNumber = TagNumber::new(0); + +/// TODO +#[derive(Clone, Debug)] +pub enum ContentInfo<'a> { + /// Content type `data` + Data(Option>), + + /// Content type `encrypted-data` + SignedData(Option>), +} + +impl<'a> ContentInfo<'a> { + /// return content type of content info + pub fn content_type(&self) -> ContentType { + match self { + Self::Data(_) => ContentType::Data, + Self::SignedData(_) => ContentType::SignedData, + // _ => panic!("unsupported"), + } + } +} + +impl<'a> ContentInfo<'a> { + /// new ContentInfo of `data` content type + // TODO + // pub fn new_data(content: &'a [u8]) -> Self { + // ContentInfo::Data(Some(content.into())) + // } + + /// new ContentInfo of given content type with empty content + pub fn new_empty(content_type: ContentType) -> Self { + match content_type { + ContentType::Data => ContentInfo::Data(None), + ContentType::SignedData => ContentInfo::SignedData(None), + _ => panic!("Not supported here"), + } + } + + // /// new Content info of given content type with given raw content + // pub fn new_raw(content_type: ContentType, content: &'a [u8]) -> der::Result { + // Ok(ContentInfo::Other(( + // content_type, + // Some(OctetStringRef::new(content)?), + // ))) + // } +} + +impl<'a> DecodeValue<'a> for ContentInfo<'a> { + fn decode_value>(reader: &mut R, header: Header) -> der::Result> { + reader.read_nested(header.length, |reader| { + let content_type = reader.decode()?; + match content_type { + ContentType::Data => { + let inner = reader + .context_specific::>(CONTENT_TAG, TagMode::Explicit)?; + let content = AuthenticatedSafe::from_der(inner.unwrap().as_bytes())?; + Ok(ContentInfo::Data(Some(content))) + } + ContentType::SignedData => Ok(ContentInfo::SignedData( + reader.context_specific(CONTENT_TAG, TagMode::Explicit)?, + )), + + _ => panic!("Not supported here"), + } + }) + } +} + +impl<'a> Sequence<'a> for ContentInfo<'a> { + fn fields(&self, f: F) -> der::Result + where + F: FnOnce(&[&dyn Encode]) -> der::Result, + { + match self { + Self::Data(data) => f(&[ + &self.content_type(), + // TODO + // &data.as_ref().map(|d| ContextSpecific { + // tag_number: CONTENT_TAG, + // tag_mode: TagMode::Explicit, + // value: *d, + // }), + ]), + Self::SignedData(data) => f(&[ + &self.content_type(), + // TODO + // &data.as_ref().map(|d| ContextSpecific { + // tag_number: CONTENT_TAG, + // tag_mode: TagMode::Explicit, + // value: *d, + // }), + ]), + } + } +} diff --git a/pkcs12/src/digest_info.rs b/pkcs12/src/digest_info.rs new file mode 100644 index 000000000..d04e0f4cf --- /dev/null +++ b/pkcs12/src/digest_info.rs @@ -0,0 +1,16 @@ +use der::{asn1::OctetString, Sequence, ValueOrd}; +use pkcs7::signed_data_content::DigestAlgorithmIdentifier; + +/// ```text +/// DigestInfo ::= SEQUENCE { +/// digestAlgorithm DigestAlgorithmIdentifier, +/// digest Digest } +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)] +pub struct DigestInfo<'a> { + /// the algorithm. + pub algorithm: DigestAlgorithmIdentifier<'a>, + + /// the digest + pub digest: OctetString, +} diff --git a/pkcs12/src/lib.rs b/pkcs12/src/lib.rs index d443fd7a2..43c367e2c 100644 --- a/pkcs12/src/lib.rs +++ b/pkcs12/src/lib.rs @@ -15,3 +15,54 @@ )] //! TODO: PKCS#12 crate +//! +//! +// #[cfg(feature = "pem")] + +use spki::ObjectIdentifier; +extern crate alloc; + +pub mod authenticated_safe; +pub mod bag_type; +pub mod cert_bag_content; +pub mod cert_type; +pub mod content_info; +pub mod digest_info; +pub mod mac_data; +pub mod pfx; +pub mod safe_bag; +// bag types + +/// `pkcs-12 keyBag` Object Identifier (OID). +pub const PKCS_12_KEY_BAG_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.2.840.113549.1.12.10.1.1"); + +/// `pkcs-12 pkcs8ShroudedKeyBag` Object Identifier (OID). +pub const PKCS_12_PKCS8_KEY_BAG_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.2.840.113549.1.12.10.1.2"); + +/// `pkcs-12 certBag` Object Identifier (OID). +pub const PKCS_12_CERT_BAG_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.2.840.113549.1.12.10.1.3"); + +/// `pkcs-12 crlBag` Object Identifier (OID). +pub const PKCS_12_CRL_BAG_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.2.840.113549.1.12.10.1.4"); + +/// `pkcs-12 secretBag` Object Identifier (OID). +pub const PKCS_12_SECRET_BAG_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.2.840.113549.1.12.10.1.5"); + +/// `pkcs-12 safeContentsBag` Object Identifier (OID). +pub const PKCS_12_SAFE_CONTENTS_BAG_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.2.840.113549.1.12.10.1.6"); + +// cert types + +/// `pkcs-9 x509Certificate for pkcs-12` Object Identifier (OID). +pub const PKCS_12_X509_CERT_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.2.840.113549.1.9.22.1"); + +/// `pkcs-9 sdsiCertificate for pkcs-12` Object Identifier (OID). +pub const PKCS_12_SDSI_CERT_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.2.840.113549.1.9.22.2"); diff --git a/pkcs12/src/mac_data.rs b/pkcs12/src/mac_data.rs new file mode 100644 index 000000000..f0b0bf9dc --- /dev/null +++ b/pkcs12/src/mac_data.rs @@ -0,0 +1,26 @@ +use crate::digest_info::DigestInfo; +use der::{ + asn1::{Int, OctetString}, + Sequence, ValueOrd, +}; + +/// ```text +/// MacData ::= SEQUENCE { +/// mac DigestInfo, +/// macSalt OCTET STRING, +/// iterations INTEGER DEFAULT 1 +/// -- Note: The default is for historical reasons and its +/// -- use is deprecated. +///} +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)] +pub struct MacData<'a> { + /// the MAC digest info + pub mac: DigestInfo<'a>, + + /// the MAC salt + pub mac_salt: OctetString, + + /// the number of iterations + pub iterations: Int, +} diff --git a/pkcs12/src/pfx.rs b/pkcs12/src/pfx.rs new file mode 100644 index 000000000..3a3638543 --- /dev/null +++ b/pkcs12/src/pfx.rs @@ -0,0 +1,40 @@ +use core::cmp::Ordering; + +use der::{Enumerated, Sequence, ValueOrd}; + +use crate::{content_info::ContentInfo, mac_data::MacData}; + +/// just the version v3 +#[derive(Clone, Copy, Debug, Enumerated, Eq, PartialEq, PartialOrd, Ord)] +#[asn1(type = "INTEGER")] +#[repr(u8)] +pub enum Version { + /// syntax version 3 + V3 = 3, +} + +impl ValueOrd for Version { + fn value_cmp(&self, other: &Self) -> der::Result { + Ok(self.cmp(other)) + } +} + +/// ```text +/// PFX ::= SEQUENCE { +/// version INTEGER {v3(3)}(v3,...), +/// authSafe ContentInfo, +/// macData MacData OPTIONAL +/// } +/// +/// ``` +#[derive(Debug, Sequence)] +pub struct Pfx<'a> { + /// the syntax version number. + pub version: Version, + + /// the authenticated safe + pub auth_safe: ContentInfo<'a>, + + /// the message digest info + pub mac_data: MacData<'a>, +} diff --git a/pkcs12/src/safe_bag.rs b/pkcs12/src/safe_bag.rs new file mode 100644 index 000000000..7430379dc --- /dev/null +++ b/pkcs12/src/safe_bag.rs @@ -0,0 +1,128 @@ +use alloc::vec::Vec; +use der::{ + asn1::{ContextSpecific, OctetString}, + DecodeValue, Encode, Header, Reader, Sequence, TagMode, TagNumber, +}; +use pkcs8::{EncryptedPrivateKeyInfo, PrivateKeyInfo}; +use x509_cert::attr::Attributes; + +use crate::{bag_type::BagType, cert_bag_content::CertBagContent}; + +type Placeholder = OctetString; + +const CONTENT_TAG: TagNumber = TagNumber::new(0); + +/// Sequence of SafeBag +pub type SafeContents<'a> = Vec>; + +/// +/// ```text +/// SafeBag ::= SEQUENCE { +/// bagId BAG-TYPE.&id ({PKCS12BagSet}) +/// bagValue [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}), +/// bagAttributes SET OF PKCS12Attribute OPTIONAL +/// } +/// PKCS12BagSet BAG-TYPE ::= { +/// keyBag | +/// pkcs8ShroudedKeyBag | +/// certBag | +/// crlBag | +/// secretBag | +/// safeContentsBag, +/// .. -- For future extensions +/// } +/// PKCS12Attribute ::= SEQUENCE { +/// attrId ATTRIBUTE.&id ({PKCS12AttrSet}), +/// attrValues SET OF ATTRIBUTE.&Type ({PKCS12AttrSet}{@attrId}) +/// } -- This type is compatible with the X.500 type 'Attribute' +/// ``` +#[derive(Clone, Debug)] +pub enum SafeBag<'a> { + /// key bag + KeyBag(Option>, Option), + /// pkcs8 bag + Pkcs8ShroudedKeyBag(Option>, Option), + /// cert bag + CertBag(Option, Option), + /// crl bag currently unimplemented + CrlBag(Option, Option), + /// secret bag currently unimplemented + SecretBag(Option, Option), + /// safeContents bag currently unimplemented + SafeContentsBag(Option, Option), +} + +impl<'a> SafeBag<'a> { + /// return content type of content info + pub fn content_type(&self) -> BagType { + match self { + Self::KeyBag(_, _) => BagType::Key, + _ => panic!("content type"), + } + } +} + +impl<'a> DecodeValue<'a> for SafeBag<'a> { + fn decode_value>(reader: &mut R, header: Header) -> der::Result> { + reader.read_nested(header.length, |reader| { + let bag_type = reader.decode()?; + match bag_type { + BagType::Key => Ok(SafeBag::KeyBag( + reader + .context_specific::>(CONTENT_TAG, TagMode::Explicit)?, + reader.decode()?, + )), + BagType::Pkcs8 => Ok(SafeBag::Pkcs8ShroudedKeyBag( + reader.context_specific::>( + CONTENT_TAG, + TagMode::Explicit, + )?, + reader.decode()?, + )), + BagType::Cert => Ok(SafeBag::CertBag( + reader.context_specific::(CONTENT_TAG, TagMode::Explicit)?, + reader.decode()?, + )), + _ => todo!("Encoding of other types is currently not supported"), + } + }) + } +} + +impl<'a> Sequence<'a> for SafeBag<'a> { + fn fields(&self, f: F) -> der::Result + where + F: FnOnce(&[&dyn Encode]) -> der::Result, + { + match self { + Self::KeyBag(data, attrs) => f(&[ + &self.content_type(), + &data.as_ref().map(|d| ContextSpecific { + tag_number: CONTENT_TAG, + tag_mode: TagMode::Explicit, + value: d.clone(), + }), + &attrs, + ]), + Self::Pkcs8ShroudedKeyBag(data, attrs) => f(&[ + &self.content_type(), + &data.as_ref().map(|d| ContextSpecific { + tag_number: CONTENT_TAG, + tag_mode: TagMode::Explicit, + value: d.clone(), + }), + &attrs, + ]), + Self::CertBag(data, attrs) => f(&[ + &self.content_type(), + &data.as_ref().map(|d| ContextSpecific { + tag_number: CONTENT_TAG, + tag_mode: TagMode::Explicit, + value: d.clone(), + }), + &attrs, + ]), + _ => todo!("Encoding of other types is currently not supported"), + } + } +} diff --git a/pkcs12/tests/cert_tests.rs b/pkcs12/tests/cert_tests.rs new file mode 100644 index 000000000..ed046be22 --- /dev/null +++ b/pkcs12/tests/cert_tests.rs @@ -0,0 +1,27 @@ +use der::Decode; + +use pkcs12::{content_info::ContentInfo, pfx::Pfx}; + +#[test] +fn decode_sample_pfx() -> der::Result<()> { + let bytes = include_bytes!("examples/example.pfx"); + + let content = Pfx::from_der(bytes).expect("expected valid data"); + + match content.auth_safe { + ContentInfo::Data(_) => Ok(()), + ContentInfo::SignedData(_) => todo!(), + } +} + +#[test] +fn decode_sample_pfx2() -> der::Result<()> { + let bytes = include_bytes!("examples/example2.pfx"); + + let content = Pfx::from_der(bytes).expect("expected valid data"); + + match content.auth_safe { + ContentInfo::Data(_) => Ok(()), + ContentInfo::SignedData(_) => todo!(), + } +} diff --git a/pkcs12/tests/examples/cert.pem b/pkcs12/tests/examples/cert.pem new file mode 100644 index 000000000..3f7664224 --- /dev/null +++ b/pkcs12/tests/examples/cert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICzDCCAjWgAwIBAgIUZMDrcllVoypmH9d8mGdPACkwWRswDQYJKoZIhvcNAQEL +BQAweDELMAkGA1UEBhMCREsxFDASBgNVBAgMC0hvdmVkc3RhZGVuMRUwEwYDVQQH +DAxLw4PCuGJlbmhhdm4xDDAKBgNVBAoMAy4uLjEMMAoGA1UECwwDLi4uMQwwCgYD +VQQDDAMuLi4xEjAQBgkqhkiG9w0BCQEWAy4uLjAeFw0yMzAxMDMxNzIwNTBaFw0y +NDAxMDMxNzIwNTBaMHgxCzAJBgNVBAYTAkRLMRQwEgYDVQQIDAtIb3ZlZHN0YWRl +bjEVMBMGA1UEBwwMS8ODwrhiZW5oYXZuMQwwCgYDVQQKDAMuLi4xDDAKBgNVBAsM +Ay4uLjEMMAoGA1UEAwwDLi4uMRIwEAYJKoZIhvcNAQkBFgMuLi4wgZ8wDQYJKoZI +hvcNAQEBBQADgY0AMIGJAoGBALFxLL2rx1ilbfXiI3iMIrjIenu3ucHysMYh0t1f +6mTyAqDsg7C5VEh5CESFbGEc10vu5ZrGfx3iREpFyuK/sMf4DJ+JHNw5nemMBcRy +edHccx2qLDqeTmtwRQCPaSJm6D1pGACRRjpDMl/s+FFkxPUBeGGerkJlbow+rC5A +nZS3AgMBAAGjUzBRMB0GA1UdDgQWBBR6gNs30Q3+JPqx0nTdyW1ODBWUQzAfBgNV +HSMEGDAWgBR6gNs30Q3+JPqx0nTdyW1ODBWUQzAPBgNVHRMBAf8EBTADAQH/MA0G +CSqGSIb3DQEBCwUAA4GBAAMvY8V9Si+1ZjGRQCqSTtlkaakYOEfgnfqXOUbGNnO6 +7vsJbSX3S0CG0ciMbaIxyMG2TkRzSfJiODOblC5RnXg7EF0nU0oNBIYw3FY40lTD +rCgqQV4aEYScPCmuHS7pGM87VTrcxbMY+1vPq6iSadn4ijifOaBpj7Z5esWwVaUp +-----END CERTIFICATE----- diff --git a/pkcs12/tests/examples/example.pfx b/pkcs12/tests/examples/example.pfx new file mode 100644 index 0000000000000000000000000000000000000000..d591096d83f88893286e1b903dc7342a7222abf0 GIT binary patch literal 1875 zcmajgX*d*&76f=Ss1VK)r~G>q#K}PT?iGeT zS+uRQ0fG1cI6)ZS|E_@$PykLAhVZy!4!j74097Cd)D*8K-8?Ob7db1W(U~@vO{IZx zv7|$_oSUXjLKil5NN=0MZ60E8j-pwq#cbUj@Ga=OghQ(VN2?#^_Q1~)N7YManh7Zm z7CO&eNpJv+qhlvo-kXPvP`4Glbnm>|Q{|T&fkHO182HTrtMV zlWpJFz541Q3VqBdOEj9v@)U}$y_^sV6MCU)zSxin-Q9{Mmeu%JMEyV{@;O&`%!0y= zHs@JG&QjtXI-iV+0PQO29Xm6oD6ag8lkp}KMr~>RmlS0Cn!HsfMLrYr5CZ-@TMA;} z$f^Niva&HmCfeGWRCkM&{VYwtS-?n@jeb*#dB3Ge70i=pup%PaLUX$n__41f=am~3W?av6nWk}O5+37T=fXK=SpG|5 zj+R=3iSS+S<% znQ4sx&6hsB%1-)yc$!-7SdnInqy;SX@vcr>tdot);30iPsXln z=nsRJ+8_{`KBitZ7>}(xp?LML`uBDaT01aFKyHBqf=?fEP=}c~fFF{B=y1mm0Ux{3 zD`&Hfun@f0C&5SQmtjBB7km~2ugt%#B$RSM$W?r0e%@XFoK1O`xn*!hh#en2W8m^N z6`<=jnDLUbyv`qRcEw+@^hb%uHNV~QxG9sqLsg>1)3?`42!;XefFEYcCe33_S^XHf zVYl^;FudL((ZE66=FI$IMrGETS5ay*Z%m8KMB0SH`mOB;gIV&@%L;Pxj>-xt0~o?- z?lVwEXt-Rqxo`&O6sl*_V3g)WRNIY4CBYiPM)qxL{qpxWXs{B523FW#%kGyHOyK|4 z1^I!X0vc!xM+1%g6W{}njpf2(8s7aQU{eKgPhzeWiWgWHgmkX5gwR0L<4o=<$=H_=$7yOi8%#cm(NCSVhoTssAaR2HmRQ&wEBd=n2K~v{)4;=(K*Jh zT}}{mhE7oRQikQMYf@dyQB_%U>x4L6Yt&;sl}yp{$)QYJwN%5Njj^Oj$&}vIMBj-I z!h@i&=(Tz;(wC(RP`Myw-J(pk^*%zV$-=Nmm-kB5Qg9^^wPKlczFBt#IJ@Bd??XQk6NZ&BRdj+WMKi8Y`@% zb%wNMJBArP;dA;AX1sUQwgXJCmPMDL6Y`-~&m*+7KZaiy$4f=GF0*d4Ta+d7O+R0B z30DUJ2)hJ;eOGd~)m#6?<*ppDzG=4K1%yTjS}pXh_lCyFc8mFMg>~tUCB*wC-zL5G z?4?}KFkD(jw*+*fKbHVJL)8^!Kw^J7AHHa(gEx7-gKIdFI#9wt(_u03Jvac-?&2mN zIdrr)69KNBufhi2@m)9GUG?xt@M;n+;bmiN&HJt&oKk&Cf0b)e_ILukbr0Gz6)1iV zd~(*kytI~g+1Ck+-xv*^dH2%(tib4BL?0}#GFzV8aR-;_`MP2;~c8ZNg0 literal 0 HcmV?d00001 diff --git a/pkcs12/tests/examples/example2.pfx b/pkcs12/tests/examples/example2.pfx new file mode 100644 index 0000000000000000000000000000000000000000..6ff4319f7d86afb3cd2a15d4391a5b33e3b0a325 GIT binary patch literal 1756 zcmXqLV!Oe_$ZXKW*2l)F)#lOmotKfFaX}MXJxdc?HBh+Rpo!TUMT%L6rHNSsD69g+ z3T)g^T|8Wj%nO>BUKlhnJz?X53UZ18&0@O3(!_KDD1HWrO&2gTF)}fUq#Sr%6dAf$ zD^31-&5U$^22F!VX#-xkMU3363t7C z1F1KV6X!KDHZU|WHZ(UfFg1t*a!ru91L(5G`N*Nf$jZRn*vnwh*vZt`$gr_cXYcCc z5leHwK2omeQQC2$s(Sm*gP%4WQ@nIH{#D8+rUh@BH|z}YsN`^I%}JEG?)~oRtYh`E zk6gT5Pd(bd;rI`p`JFO%Ea$%LVLeh*dGSuM>?$3rd4Aait_=N|N@*`_GbI=%x>-3J z#lQIxm~!MRV@2Y;bxx^yJ$7sK9Oh2h&cw{fz_>WrAkaV-81}M!EMhDoRSmbzFY^9V z`L*#<$=#E=emtU6oDJkb(#k9n24W4^74U-;2s1MNXJIv922#j=2Br*Vpl6x&laJPV z>2FOloamr6$?s-L=1K_*_Xl%-O}BJAW>&oG-EYoZ)$iU8Z5L1U@3P z^!e2*CS~6I(Pc5;azSSQw#urb8$y?A8mby7BhrniA<(byITH=z1Lr1w%B-DN`ZP%N zLN!A!FprqPm4R{=vnnwAC<29Lfmjkb`!E5s%0pllxoZ#(*TKqb;ANnR;PM#AuyCA8 z?Vj5Fp;DQNiG#s_hm8Z0*_b$iSw@zPGoj6cF_oExQHw>O#D_oGM^i(K%+1#ayMTdMW=Q9`Vm-JSwD6(`HJGaA$t3`0aDJJ2} z{Soz#Z@L`(rC7=(?B@J?pS@!RSI>d=>3-MVe|>s6Z`-tOi6;#nhJQG+!lV48seR^S zNw1XtmRkR7e;##rhl?A;*)o0j8|C-ukBaSe-{scaeaCxjxsBLrcCxy^kejj1{8Re8 zH`#jSOnx1^4Hq50+o3NdTK#%{)yv?`FJ*nsU12s@@$ty#e9gG&Rxju0ay?PH-)uJH zc-yZu)#_L2Q|4I4uU+r0{rOmEURmMc@(L}*>(k<8jeA4Kg!5SJtF3XqUBbmbXc8 zvjLAo#R zo~vFiZ+7kciB%$XL2sY5+|Wr|vq9C-F)=jrMy@bRTl9sDuWF~-K1Tg1V4bXd#?W56 zmt*O+2S=rDm58mC5{=J0q{*20cisLge>&c_DV{BUb*F37quWKFSBF$vyr|+^WmUJp zyY$C`D0R=+^ABA3+ML6V8UKIr>6~J;y-=ZF@LLv-haCS-e^1x_XIn7ua^k$7ujH=R zuq->}%UWK3xcuW??-iO4FEO= SVBvTu;@`7n{ic7QtN{Q{cA5DA literal 0 HcmV?d00001 diff --git a/pkcs12/tests/examples/gen.sh b/pkcs12/tests/examples/gen.sh new file mode 100644 index 000000000..02b71df7d --- /dev/null +++ b/pkcs12/tests/examples/gen.sh @@ -0,0 +1,3 @@ +openssl req -x509 -newkey rsa:1024 -keyout key.pem -out cert.pem -sha256 -days 365 -noenc -subj "/C=DK/ST=Hovedstaden/L=København/O=.../OU=.../CN=.../emailAddress=..." +openssl pkcs12 -export -out example.pfx -inkey key.pem -in cert.pem -passout pass: +openssl pkcs12 -export -out example2.pfx -in cert.pem -inkey key.pem -certpbe NONE -passout pass:1234 \ No newline at end of file diff --git a/pkcs12/tests/examples/key.pem b/pkcs12/tests/examples/key.pem new file mode 100644 index 000000000..2e4e9f85f --- /dev/null +++ b/pkcs12/tests/examples/key.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALFxLL2rx1ilbfXi +I3iMIrjIenu3ucHysMYh0t1f6mTyAqDsg7C5VEh5CESFbGEc10vu5ZrGfx3iREpF +yuK/sMf4DJ+JHNw5nemMBcRyedHccx2qLDqeTmtwRQCPaSJm6D1pGACRRjpDMl/s ++FFkxPUBeGGerkJlbow+rC5AnZS3AgMBAAECgYBoT+8MZbKgI0hcVx+hG0jCNmEC +4AQcx04ye+nZaCyEQV1YOxJDzv+ER1qb5Y/MG0daBUwHTA+ogr7ApvzZhfUm7qB/ +ISf57QmoJc+60x8u45VXxOSMPuTHXWAwY56kSLglFbDFyy0dA02vsXo3PZ/DLa8Q +sEaGmZUMJYgWUGBX+QJBAN1EQE7jiPs3tOizuCNrw8LIxkMPjrm7FU0BTA3CcmqA +K5FXh6WDTeMvp+fstjscpWrg9buSUTBWHR8uO0AUKBUCQQDNS846s57CtFbcldOL +UMfvEkZ73HuaXF6wPb8SoaUPR/9AhQaWCfQuBkzi4rW8ZGPigxl4hS6BA2zkqIk8 +SBCbAkEA0Na6W7smbvYFKh12jvgHrLETb/gfHe4WDLhMsC/3Dc4rUOLshKuJuAQi +1iP1W5WOC3KIfKF9P8IHeoaIJdLggQJBALQeuoZOahCyYTOQUNZ+vaxIAIdT3y6D +tKA0zJvwLv3FUXKuRCUH/rES3gqClqj/+5MVKxfO4gpXkwbbx+yX3dkCQQDaQrrB +HvJ3l5F5SY5jFOsQY27dfXqdhbTRTQtE+uqAd3lwOix0mjROAfiXXXJ6I95cXUFb +EtKC5DoanfxFWn49 +-----END PRIVATE KEY-----