Skip to content

Commit 96bc022

Browse files
authored
Adds SignedData type to pkcs7 (#813)
- Adds SignedData type to `ContentInfo` - Two examples + test cases (Apple MDM Signature and SCEP Request from Apple device) originally BER encoded but reencoded in DER - Requires `x509-cert` now for some types
1 parent c581d14 commit 96bc022

12 files changed

+422
-4
lines changed

pkcs7/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ rust-version = "1.65"
1717
[dependencies]
1818
der = { version = "=0.7.0-pre", features = ["oid"], path = "../der" }
1919
spki = { version = "=0.7.0-pre", path = "../spki" }
20+
x509-cert = { version = "=0.2.0-pre", path = "../x509-cert" }
2021

2122
[dev-dependencies]
2223
hex-literal = "0.3"

pkcs7/src/certificate_choices.rs

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//! `CertificateChoices` [RFC 5652 10.2.2](https://datatracker.ietf.org/doc/html/rfc5652#section-10.2.2)
2+
3+
use der::{asn1::BitStringRef, AnyRef, Choice, Sequence, ValueOrd};
4+
use spki::ObjectIdentifier;
5+
use x509_cert::Certificate;
6+
7+
// TODO (smndtrl): Should come from x509 - for now I haven't found a test case in real world
8+
type AttributeCertificateV1<'a> = BitStringRef<'a>;
9+
type AttributeCertificateV2<'a> = BitStringRef<'a>;
10+
type ExtendedCertificate<'a> = BitStringRef<'a>;
11+
12+
/// ```text
13+
/// OtherCertificateFormat ::= SEQUENCE {
14+
/// otherCertFormat OBJECT IDENTIFIER,
15+
/// otherCert ANY DEFINED BY otherCertFormat }
16+
/// ```
17+
#[derive(Clone, Debug, PartialEq, Eq, Sequence, ValueOrd)]
18+
pub struct OtherCertificateFormat<'a> {
19+
other_cert_format: ObjectIdentifier,
20+
other_cert: AnyRef<'a>,
21+
}
22+
23+
/// ```text
24+
/// CertificateChoices ::= CHOICE {
25+
/// certificate Certificate,
26+
/// extendedCertificate [0] IMPLICIT ExtendedCertificate, -- Obsolete
27+
/// v1AttrCert [1] IMPLICIT AttributeCertificateV1, -- Obsolete
28+
/// v2AttrCert [2] IMPLICIT AttributeCertificateV2,
29+
/// other [3] IMPLICIT OtherCertificateFormat }
30+
///
31+
/// OtherCertificateFormat ::= SEQUENCE {
32+
/// otherCertFormat OBJECT IDENTIFIER,
33+
/// otherCert ANY DEFINED BY otherCertFormat }
34+
/// ```
35+
#[derive(Clone, Debug, PartialEq, Eq, Choice, ValueOrd)]
36+
#[allow(clippy::large_enum_variant)]
37+
pub enum CertificateChoices<'a> {
38+
/// X.509 certificate
39+
Certificate(Certificate<'a>),
40+
41+
/// PKCS #6 extended certificate (obsolete)
42+
#[deprecated]
43+
#[asn1(context_specific = "0", tag_mode = "IMPLICIT")]
44+
ExtendedCertificate(ExtendedCertificate<'a>),
45+
46+
/// version 1 X.509 attribute certificate (ACv1) X.509-97 (obsolete)
47+
#[deprecated]
48+
#[asn1(context_specific = "1", tag_mode = "IMPLICIT")]
49+
V1AttrCert(AttributeCertificateV1<'a>),
50+
51+
/// version 2 X.509 attribute certificate (ACv2) X.509-00
52+
#[asn1(context_specific = "2", tag_mode = "IMPLICIT")]
53+
V2AttrCert(AttributeCertificateV2<'a>),
54+
55+
/// any other certificate forma
56+
#[asn1(context_specific = "3", tag_mode = "IMPLICIT")]
57+
Other(OtherCertificateFormat<'a>),
58+
}

pkcs7/src/cms_version.rs

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//! `CMSVersion` [RFC 5652 § 10.2.5](https://datatracker.ietf.org/doc/html/rfc5652#section-10.2.5)
2+
3+
use der::Enumerated;
4+
5+
/// The CMSVersion type gives a syntax version number, for compatibility
6+
/// with future revisions of this specification.
7+
/// ```text
8+
/// CMSVersion ::= INTEGER
9+
/// { v0(0), v1(1), v2(2), v3(3), v4(4), v5(5) }
10+
/// ```
11+
///
12+
/// See [RFC 5652 10.2.5](https://datatracker.ietf.org/doc/html/rfc5652#section-10.2.5).
13+
#[derive(Clone, Copy, Debug, Enumerated, Eq, PartialEq)]
14+
#[asn1(type = "INTEGER")]
15+
#[repr(u8)]
16+
pub enum CmsVersion {
17+
/// syntax version 0
18+
V0 = 0,
19+
/// syntax version 1
20+
V1 = 1,
21+
/// syntax version 2
22+
V2 = 2,
23+
/// syntax version 3
24+
V3 = 3,
25+
/// syntax version 4
26+
V4 = 4,
27+
/// syntax version 5
28+
V5 = 5,
29+
}

pkcs7/src/content_info.rs

+23-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use crate::{data_content::DataContent, encrypted_data_content::EncryptedDataContent, ContentType};
1+
use crate::{
2+
data_content::DataContent, encrypted_data_content::EncryptedDataContent,
3+
signed_data_content::SignedDataContent, ContentType,
4+
};
25

36
use der::{
47
asn1::{ContextSpecific, OctetStringRef},
@@ -22,8 +25,10 @@ pub enum ContentInfo<'a> {
2225
/// Content type `encrypted-data`
2326
EncryptedData(Option<EncryptedDataContent<'a>>),
2427

28+
/// Content type `signed-data`
29+
SignedData(Option<SignedDataContent<'a>>),
30+
2531
/// Catch-all case for content types that are not explicitly supported
26-
/// - signed-data
2732
/// - enveloped-data
2833
/// - signed-and-enveloped-data
2934
/// - digested-data
@@ -36,6 +41,7 @@ impl<'a> ContentInfo<'a> {
3641
match self {
3742
Self::Data(_) => ContentType::Data,
3843
Self::EncryptedData(_) => ContentType::EncryptedData,
44+
Self::SignedData(_) => ContentType::SignedData,
3945
Self::Other((content_type, _)) => *content_type,
4046
}
4147
}
@@ -52,6 +58,7 @@ impl<'a> ContentInfo<'a> {
5258
match content_type {
5359
ContentType::Data => ContentInfo::Data(None),
5460
ContentType::EncryptedData => ContentInfo::EncryptedData(None),
61+
ContentType::SignedData => ContentInfo::SignedData(None),
5562
_ => ContentInfo::Other((content_type, None)),
5663
}
5764
}
@@ -76,6 +83,12 @@ impl<'a> DecodeValue<'a> for ContentInfo<'a> {
7683
ContentType::EncryptedData => Ok(ContentInfo::EncryptedData(
7784
reader.context_specific(CONTENT_TAG, TagMode::Explicit)?,
7885
)),
86+
ContentType::SignedData => Ok(ContentInfo::SignedData(
87+
reader.context_specific::<SignedDataContent<'_>>(
88+
CONTENT_TAG,
89+
TagMode::Explicit,
90+
)?,
91+
)),
7992
_ => Ok(ContentInfo::Other((
8093
content_type,
8194
reader
@@ -108,6 +121,14 @@ impl<'a> Sequence<'a> for ContentInfo<'a> {
108121
value: *d,
109122
}),
110123
]),
124+
Self::SignedData(data) => f(&[
125+
&self.content_type(),
126+
&data.as_ref().map(|d| ContextSpecific {
127+
tag_number: CONTENT_TAG,
128+
tag_mode: TagMode::Explicit,
129+
value: d.clone(),
130+
}),
131+
]),
111132
Self::Other((content_type, opt_oct_str)) => f(&[
112133
content_type,
113134
&opt_oct_str.as_ref().map(|d| ContextSpecific {
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//! `encapsulated-data` content type [RFC 5652 § 5.2](https://datatracker.ietf.org/doc/html/rfc5652#section-5.2)
2+
3+
use der::{AnyRef, Sequence};
4+
use spki::ObjectIdentifier;
5+
6+
/// Encapsulated content information [RFC 5652 § 5.2](https://datatracker.ietf.org/doc/html/rfc5652#section-5.2)
7+
///
8+
/// ```text
9+
/// EncapsulatedContentInfo ::= SEQUENCE {
10+
/// eContentType ContentType,
11+
/// eContent [0] EXPLICIT OCTET STRING OPTIONAL }
12+
/// ```
13+
/// Due to a difference in PKCS #7 and CMS the contents type can be either
14+
/// ```text
15+
/// content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL
16+
/// ```
17+
/// or
18+
/// ```text
19+
/// eContent [0] EXPLICIT OCTET STRING OPTIONAL
20+
/// ```
21+
#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)]
22+
pub struct EncapsulatedContentInfo<'a> {
23+
/// indicates the type of content.
24+
pub e_content_type: ObjectIdentifier,
25+
26+
/// encapsulated content
27+
#[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")]
28+
pub e_content: Option<AnyRef<'a>>,
29+
}

pkcs7/src/lib.rs

+6
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,15 @@ mod content_type;
1313

1414
pub use crate::{content_info::ContentInfo, content_type::ContentType};
1515

16+
pub mod certificate_choices;
17+
pub mod cms_version;
1618
pub mod data_content;
19+
pub mod encapsulated_content_info;
1720
pub mod encrypted_data_content;
1821
pub mod enveloped_data_content;
22+
pub mod revocation_info_choices;
23+
pub mod signed_data_content;
24+
pub mod signer_info;
1925

2026
use der::asn1::ObjectIdentifier;
2127

pkcs7/src/revocation_info_choices.rs

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//! `RevocationInfoChoices` [RFC 5652 10.2.1](https://datatracker.ietf.org/doc/html/rfc5652#section-10.2.1)
2+
3+
use core::cmp::Ordering;
4+
5+
use der::{asn1::SetOfVec, AnyRef, Choice, Sequence, ValueOrd};
6+
use spki::ObjectIdentifier;
7+
use x509_cert::crl::CertificateList;
8+
9+
/// ```text
10+
/// RevocationInfoChoices ::= SET OF RevocationInfoChoice
11+
/// RevocationInfoChoice ::= CHOICE {
12+
/// crl CertificateList,
13+
/// other [1] IMPLICIT OtherRevocationInfoFormat }
14+
/// OtherRevocationInfoFormat ::= SEQUENCE {
15+
/// otherRevInfoFormat OBJECT IDENTIFIER,
16+
/// otherRevInfo ANY DEFINED BY otherRevInfoFormat }
17+
/// ```
18+
#[derive(Clone, Debug, PartialEq, Eq, Choice)]
19+
#[allow(clippy::large_enum_variant)]
20+
pub enum RevocationInfoChoice<'a> {
21+
/// The CertificateList type gives a certificate revocation list (CRL).
22+
Crl(CertificateList<'a>),
23+
24+
/// The OtherRevocationInfoFormat alternative is provided to support any
25+
/// other revocation information format without further modifications to
26+
/// the CMS.
27+
#[asn1(context_specific = "1", tag_mode = "IMPLICIT", constructed = "true")]
28+
Other(OtherRevocationInfoFormat<'a>),
29+
}
30+
31+
/// ```text
32+
/// RevocationInfoChoices ::= SET OF RevocationInfoChoice
33+
/// ```
34+
pub type RevocationInfoChoices<'a> = SetOfVec<RevocationInfoChoice<'a>>;
35+
36+
/// ```text
37+
/// OtherRevocationInfoFormat ::= SEQUENCE {
38+
/// otherRevInfoFormat OBJECT IDENTIFIER,
39+
/// otherRevInfo ANY DEFINED BY otherRevInfoFormat }
40+
/// ```
41+
#[derive(Clone, Debug, PartialEq, Eq, Sequence)]
42+
pub struct OtherRevocationInfoFormat<'a> {
43+
other_rev_info_format: ObjectIdentifier,
44+
other_rev_info: AnyRef<'a>,
45+
}
46+
47+
// TODO: figure out what ordering makes sense - if any
48+
impl ValueOrd for RevocationInfoChoice<'_> {
49+
fn value_cmp(&self, _other: &Self) -> der::Result<Ordering> {
50+
Ok(Ordering::Equal)
51+
}
52+
}

pkcs7/src/signed_data_content.rs

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//! `signed-data` content type [RFC 5652 § 5](https://datatracker.ietf.org/doc/html/rfc5652#section-5)
2+
3+
use crate::{
4+
certificate_choices::CertificateChoices, cms_version::CmsVersion,
5+
encapsulated_content_info::EncapsulatedContentInfo,
6+
revocation_info_choices::RevocationInfoChoices, signer_info::SignerInfos,
7+
};
8+
use der::{asn1::SetOfVec, Sequence};
9+
use spki::AlgorithmIdentifierRef;
10+
11+
/// ```text
12+
/// DigestAlgorithmIdentifier ::= AlgorithmIdentifier
13+
/// ```
14+
type DigestAlgorithmIdentifier<'a> = AlgorithmIdentifierRef<'a>;
15+
16+
/// ```text
17+
/// DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
18+
/// ```
19+
type DigestAlgorithmIdentifiers<'a> = SetOfVec<DigestAlgorithmIdentifier<'a>>;
20+
21+
/// ```text
22+
/// CertificateSet ::= SET OF CertificateChoices
23+
/// ```
24+
type CertificateSet<'a> = SetOfVec<CertificateChoices<'a>>;
25+
26+
/// Signed-data content type [RFC 5652 § 5](https://datatracker.ietf.org/doc/html/rfc5652#section-5)
27+
///
28+
/// ```text
29+
/// SignedData ::= SEQUENCE {
30+
/// version CMSVersion,
31+
/// digestAlgorithms DigestAlgorithmIdentifiers,
32+
/// encapContentInfo EncapsulatedContentInfo,
33+
/// certificates [0] IMPLICIT CertificateSet OPTIONAL,
34+
/// crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
35+
/// signerInfos SignerInfos }
36+
/// ```
37+
#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
38+
pub struct SignedDataContent<'a> {
39+
/// the syntax version number.
40+
pub version: CmsVersion,
41+
42+
/// digest algorithm
43+
pub digest_algorithms: DigestAlgorithmIdentifiers<'a>,
44+
45+
/// content
46+
pub encap_content_info: EncapsulatedContentInfo<'a>,
47+
48+
/// certs
49+
#[asn1(context_specific = "0", optional = "true", tag_mode = "IMPLICIT")]
50+
pub certificates: Option<CertificateSet<'a>>,
51+
52+
/// crls
53+
#[asn1(context_specific = "1", optional = "true", tag_mode = "IMPLICIT")]
54+
pub crls: Option<RevocationInfoChoices<'a>>,
55+
56+
/// signer info
57+
pub signer_infos: SignerInfos<'a>,
58+
}

0 commit comments

Comments
 (0)