Skip to content

Commit 3a6e5c8

Browse files
authored
Merge pull request #14 from apple/dn-cms-asn1
Cryptographic Message Syntax ASN.1 de/serialisation
2 parents 5fed1c9 + c33a826 commit 3a6e5c8

File tree

8 files changed

+501
-12
lines changed

8 files changed

+501
-12
lines changed

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ let package = Package(
3131
dependencies: [
3232
.package(url: "https://github.com/apple/swift-crypto.git", from: "2.2.1"),
3333
// swift-asn1 repo is private, so we can't access it anonymously yet
34-
// .package(url: "https://github.com/apple/swift-asn1.git", .upToNextMinor(from: "0.3.0")),
35-
.package(url: "[email protected]:apple/swift-asn1.git", .upToNextMinor(from: "0.3.0")),
34+
// .package(url: "https://github.com/apple/swift-asn1.git", .upToNextMinor(from: "0.4.0")),
35+
.package(url: "[email protected]:apple/swift-asn1.git", .upToNextMinor(from: "0.4.0")),
3636
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
3737
],
3838
targets: [

Sources/X509/CryptographicMessageSyntax/CMSContentInfo.swift

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,70 @@
1414

1515
import SwiftASN1
1616

17+
extension ASN1ObjectIdentifier {
18+
/// Cryptographic Message Syntax (CMS) Signed Data.
19+
///
20+
/// ASN.1 definition:
21+
/// ```
22+
/// id-signedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
23+
/// us(840) rsadsi(113549) pkcs(1) pkcs7(7) 2 }
24+
/// ```
25+
static let cmsSignedData: ASN1ObjectIdentifier = [1, 2, 840, 113549, 1, 7, 2]
26+
}
27+
1728
/// ``ContentInfo`` is defined in ASN.1 as:
1829
/// ```
1930
/// ContentInfo ::= SEQUENCE {
2031
/// contentType ContentType,
2132
/// content [0] EXPLICIT ANY DEFINED BY contentType }
2233
/// ContentType ::= OBJECT IDENTIFIER
2334
/// ```
24-
struct CMSContentInfo {
35+
struct CMSContentInfo: DERImplicitlyTaggable, Hashable {
36+
static var defaultIdentifier: ASN1Identifier {
37+
.sequence
38+
}
39+
2540
var contentType: ASN1ObjectIdentifier
2641
var content: ASN1Any
42+
43+
init(contentType: ASN1ObjectIdentifier, content: ASN1Any) {
44+
self.contentType = contentType
45+
self.content = content
46+
}
47+
48+
init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
49+
self = try DER.sequence(rootNode, identifier: identifier) { nodes in
50+
let contentType = try ASN1ObjectIdentifier(derEncoded: &nodes)
51+
52+
let content = try DER.explicitlyTagged(&nodes, tagNumber: 0, tagClass: .contextSpecific) { node in
53+
ASN1Any(derEncoded: node)
54+
}
55+
return .init(contentType: contentType, content: content)
56+
}
57+
}
58+
59+
func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws {
60+
try coder.appendConstructedNode(identifier: identifier) { coder in
61+
try coder.serialize(contentType)
62+
try coder.serialize(explicitlyTaggedWithTagNumber: 0, tagClass: .contextSpecific) { coder in
63+
try coder.serialize(content)
64+
}
65+
}
66+
}
2767
}
2868

2969
extension CMSContentInfo {
30-
init(_ signedData: CMSSignedData) {
31-
fatalError("TODO: not implemented")
70+
init(_ signedData: CMSSignedData) throws {
71+
self.contentType = .cmsSignedData
72+
self.content = try ASN1Any(erasing: signedData)
3273
}
3374

3475
var signedData: CMSSignedData? {
3576
get throws {
36-
fatalError("TODO: not implemented")
77+
guard contentType == .cmsSignedData else {
78+
return nil
79+
}
80+
return try CMSSignedData(asn1Any: content)
3781
}
3882
}
3983
}

Sources/X509/CryptographicMessageSyntax/CMSEncapsulatedContentInfo.swift

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,38 @@ import SwiftASN1
2121
/// eContent [0] EXPLICIT OCTET STRING OPTIONAL }
2222
/// ContentType ::= OBJECT IDENTIFIER
2323
/// ```
24-
struct CMSEncapsulatedContentInfo {
24+
struct CMSEncapsulatedContentInfo: DERImplicitlyTaggable, Hashable {
25+
static var defaultIdentifier: ASN1Identifier {
26+
.sequence
27+
}
28+
2529
var eContentType: ASN1ObjectIdentifier
2630
var eContent: ASN1OctetString?
31+
32+
init(eContentType: ASN1ObjectIdentifier, eContent: ASN1OctetString? = nil) {
33+
self.eContentType = eContentType
34+
self.eContent = eContent
35+
}
36+
37+
init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
38+
self = try DER.sequence(rootNode, identifier: identifier) { nodes in
39+
let eContentType = try ASN1ObjectIdentifier(derEncoded: &nodes)
40+
let eContent = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 0, tagClass: .contextSpecific) { node in
41+
try ASN1OctetString(derEncoded: node)
42+
}
43+
44+
return .init(eContentType: eContentType, eContent: eContent)
45+
}
46+
}
47+
48+
func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws {
49+
try coder.appendConstructedNode(identifier: identifier, { coder in
50+
try coder.serialize(eContentType)
51+
if let eContent {
52+
try coder.serialize(explicitlyTaggedWithTagNumber: 0, tagClass: .contextSpecific) { coder in
53+
try coder.serialize(eContent)
54+
}
55+
}
56+
})
57+
}
2758
}

Sources/X509/CryptographicMessageSyntax/CMSIssuerAndSerialNumber.swift

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,34 @@ import SwiftASN1
2222
/// ```
2323
/// The definition of `Name` is taken from X.501 [X.501-88], and the
2424
/// definition of `CertificateSerialNumber` is taken from X.509 [X.509-97].
25-
struct CMSIssuerAndSerialNumber {
25+
struct CMSIssuerAndSerialNumber: DERImplicitlyTaggable, Hashable {
26+
static var defaultIdentifier: ASN1Identifier {
27+
.sequence
28+
}
29+
2630
var issuer: DistinguishedName
2731
var serialNumber: Certificate.SerialNumber
32+
33+
init(
34+
issuer: DistinguishedName,
35+
serialNumber: Certificate.SerialNumber
36+
) {
37+
self.issuer = issuer
38+
self.serialNumber = serialNumber
39+
}
40+
41+
init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
42+
self = try DER.sequence(rootNode, identifier: identifier) { nodes in
43+
let issuer = try DistinguishedName(derEncoded: &nodes)
44+
let serialNumber = try ArraySlice<UInt8>(derEncoded: &nodes)
45+
return .init(issuer: issuer, serialNumber: .init(bytes: serialNumber))
46+
}
47+
}
48+
49+
func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws {
50+
try coder.appendConstructedNode(identifier: identifier) { coder in
51+
try coder.serialize(self.issuer)
52+
try coder.serialize(self.serialNumber.bytes)
53+
}
54+
}
2855
}

Sources/X509/CryptographicMessageSyntax/CMSSignedData.swift

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import SwiftASN1
2727
/// DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
2828
/// DigestAlgorithmIdentifier ::= AlgorithmIdentifier
2929
/// SignerInfos ::= SET OF SignerInfo
30+
/// CertificateSet ::= SET OF CertificateChoices
3031
///
3132
/// CertificateChoices ::= CHOICE {
3233
/// certificate Certificate,
@@ -40,10 +41,88 @@ import SwiftASN1
4041
/// otherCert ANY DEFINED BY otherCertFormat }
4142
/// ```
4243
/// - Note: At the moment we don't support `crls` (`RevocationInfoChoices`)
43-
struct CMSSignedData {
44+
struct CMSSignedData: DERImplicitlyTaggable, Hashable {
45+
private enum Error: Swift.Error {
46+
case multipleDigestAlgorithmsAreNotSupportedYet
47+
case multipleSignerInfosAreNotSupportedYet
48+
}
49+
static var defaultIdentifier: ASN1Identifier {
50+
.sequence
51+
}
52+
4453
var version: CMSVersion
4554
var digestAlgorithms: [AlgorithmIdentifier]
4655
var encapContentInfo: CMSEncapsulatedContentInfo
4756
var certificates: [Certificate]?
4857
var signerInfos: [CMSSignerInfo]
58+
59+
init(
60+
version: CMSVersion,
61+
digestAlgorithms: [AlgorithmIdentifier],
62+
encapContentInfo: CMSEncapsulatedContentInfo,
63+
certificates: [Certificate]?,
64+
signerInfos: [CMSSignerInfo]
65+
) {
66+
self.version = version
67+
self.digestAlgorithms = digestAlgorithms
68+
self.encapContentInfo = encapContentInfo
69+
self.certificates = certificates
70+
self.signerInfos = signerInfos
71+
}
72+
73+
init(derEncoded: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
74+
self = try DER.sequence(derEncoded, identifier: identifier) { nodes in
75+
let version = try CMSVersion(rawValue: Int.init(derEncoded: &nodes))
76+
let digestAlgorithms = try DER.sequence(of: AlgorithmIdentifier.self, identifier: .set, nodes: &nodes)
77+
// TODO: support multiple digest algorithms. For this we need to validate that the binary representation of each element is lexicographically sorted.
78+
guard digestAlgorithms.count <= 1 else {
79+
throw Error.multipleDigestAlgorithmsAreNotSupportedYet
80+
}
81+
82+
let encapContentInfo = try CMSEncapsulatedContentInfo(derEncoded: &nodes)
83+
let certificates = try DER.optionalImplicitlyTagged(&nodes, tagNumber: 0, tagClass: .contextSpecific) { node in
84+
// TODO: this is actually a SET OF so we need to verify that the binary representation of each element is lexicographically sorted.
85+
try DER.sequence(of: Certificate.self, identifier: .init(tagWithNumber: 0, tagClass: .contextSpecific), rootNode: node)
86+
}
87+
88+
// we need to skip this node even though we don't support it
89+
_ = DER.optionalImplicitlyTagged(&nodes, tagNumber: 1, tagClass: .contextSpecific) { _ in }
90+
91+
let signerInfos = try DER.sequence(of: CMSSignerInfo.self, identifier: .set, nodes: &nodes)
92+
93+
// TODO: support multiple signer infos. For this we need to validate that the binary representation of each element is lexicographically sorted.
94+
guard signerInfos.count <= 1 else {
95+
throw Error.multipleSignerInfosAreNotSupportedYet
96+
}
97+
98+
return .init(
99+
version: version,
100+
digestAlgorithms: digestAlgorithms,
101+
encapContentInfo: encapContentInfo,
102+
certificates: certificates,
103+
signerInfos: signerInfos
104+
)
105+
}
106+
}
107+
108+
func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws {
109+
try coder.appendConstructedNode(identifier: identifier) { coder in
110+
try coder.serialize(version.rawValue)
111+
guard self.digestAlgorithms.count <= 1 else {
112+
throw Error.multipleDigestAlgorithmsAreNotSupportedYet
113+
}
114+
// TODO: this is actually a SET OF. We need to sort the binary representation of each element lexicographically before encoding.
115+
try coder.serializeSequenceOf(self.digestAlgorithms, identifier: .set)
116+
try coder.serialize(self.encapContentInfo)
117+
if let certificates {
118+
// TODO: this is actually a SET OF. We need to sort the binary representation of each element lexicographically before encoding.
119+
try coder.serializeSequenceOf(certificates, identifier: .init(tagWithNumber: 0, tagClass: .contextSpecific))
120+
}
121+
guard self.signerInfos.count <= 1 else {
122+
throw Error.multipleSignerInfosAreNotSupportedYet
123+
}
124+
// TODO: this is actually a SET OF. We need to sort the binary representation of each element lexicographically before encoding.
125+
try coder.serializeSequenceOf(self.signerInfos, identifier: .set)
126+
}
127+
}
49128
}

Sources/X509/CryptographicMessageSyntax/CMSSignerIdentifier.swift

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,34 @@ import SwiftASN1
2020
/// issuerAndSerialNumber IssuerAndSerialNumber,
2121
/// subjectKeyIdentifier [0] SubjectKeyIdentifier }
2222
/// ```
23-
enum CMSSignerIdentifier {
23+
enum CMSSignerIdentifier: DERParseable, DERSerializable, Hashable {
24+
25+
private static let skiIdentifier = ASN1Identifier(tagWithNumber: 0, tagClass: .contextSpecific)
26+
2427
case issuerAndSerialNumber(CMSIssuerAndSerialNumber)
2528
case subjectKeyIdentifier(Certificate.Extensions.SubjectKeyIdentifier)
29+
30+
init(derEncoded node: ASN1Node) throws {
31+
switch node.identifier {
32+
case CMSIssuerAndSerialNumber.defaultIdentifier:
33+
self = try .issuerAndSerialNumber(.init(derEncoded: node))
34+
35+
case Self.skiIdentifier:
36+
self = try .subjectKeyIdentifier(.init(keyIdentifier: .init(derEncoded: node, withIdentifier: Self.skiIdentifier)))
37+
38+
default:
39+
throw ASN1Error.invalidASN1Object
40+
}
41+
}
42+
43+
func serialize(into coder: inout DER.Serializer) throws {
44+
switch self {
45+
case .issuerAndSerialNumber(let issuerAndSerialNumber):
46+
try issuerAndSerialNumber.serialize(into: &coder)
47+
48+
case .subjectKeyIdentifier(let subjectKeyIdentifier):
49+
try subjectKeyIdentifier.keyIdentifier.serialize(into: &coder, withIdentifier: Self.skiIdentifier)
50+
51+
}
52+
}
2653
}

Sources/X509/CryptographicMessageSyntax/CMSSignerInfo.swift

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,94 @@ import SwiftASN1
3333
/// then the `version` MUST be 1. If the `SignerIdentifier` is `subjectKeyIdentifier`,
3434
/// then the `version` MUST be 3.
3535
/// - Note: At the moment we neither support `signedAttrs` (`SignedAttributes`) nor `unsignedAttrs` (`UnsignedAttributes`)
36-
struct CMSSignerInfo {
36+
struct CMSSignerInfo: DERImplicitlyTaggable, Hashable {
37+
enum Error: Swift.Error {
38+
case versionAndSignerIdentifierMismatch(String)
39+
}
40+
static var defaultIdentifier: ASN1Identifier {
41+
.sequence
42+
}
43+
3744
var version: CMSVersion
3845
var signerIdentifier: CMSSignerIdentifier
3946
var digestAlgorithm: AlgorithmIdentifier
4047
var signatureAlgorithm: AlgorithmIdentifier
4148
var signature: ASN1OctetString
49+
50+
init(
51+
signerIdentifier: CMSSignerIdentifier,
52+
digestAlgorithm: AlgorithmIdentifier,
53+
signatureAlgorithm: AlgorithmIdentifier,
54+
signature: ASN1OctetString
55+
) {
56+
switch signerIdentifier {
57+
case .issuerAndSerialNumber:
58+
self.version = .v1
59+
case .subjectKeyIdentifier:
60+
self.version = .v3
61+
}
62+
self.signerIdentifier = signerIdentifier
63+
self.digestAlgorithm = digestAlgorithm
64+
self.signatureAlgorithm = signatureAlgorithm
65+
self.signature = signature
66+
}
67+
68+
init(
69+
version: CMSVersion,
70+
signerIdentifier: CMSSignerIdentifier,
71+
digestAlgorithm: AlgorithmIdentifier,
72+
signatureAlgorithm: AlgorithmIdentifier,
73+
signature: ASN1OctetString
74+
) {
75+
self.version = version
76+
self.signerIdentifier = signerIdentifier
77+
self.digestAlgorithm = digestAlgorithm
78+
self.signatureAlgorithm = signatureAlgorithm
79+
self.signature = signature
80+
}
81+
82+
init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
83+
self = try DER.sequence(rootNode, identifier: identifier) { nodes in
84+
let version = try CMSVersion(rawValue: Int(derEncoded: &nodes))
85+
let signerIdentifier = try CMSSignerIdentifier(derEncoded: &nodes)
86+
switch signerIdentifier {
87+
case .issuerAndSerialNumber:
88+
guard version == .v1 else {
89+
throw Error.versionAndSignerIdentifierMismatch("expected \(CMSVersion.v1) but got \(version) where signerIdentifier is \(signerIdentifier)")
90+
}
91+
case .subjectKeyIdentifier:
92+
guard version == .v3 else {
93+
throw Error.versionAndSignerIdentifierMismatch("expected \(CMSVersion.v3) but got \(version) where signerIdentifier is \(signerIdentifier)")
94+
}
95+
}
96+
let digestAlgorithm = try AlgorithmIdentifier(derEncoded: &nodes)
97+
98+
// we don't support signedAttrs yet but we still need to skip them
99+
_ = DER.optionalImplicitlyTagged(&nodes, tagNumber: 0, tagClass: .contextSpecific) { _ in }
100+
101+
let signatureAlgorithm = try AlgorithmIdentifier(derEncoded: &nodes)
102+
let signature = try ASN1OctetString(derEncoded: &nodes)
103+
104+
// we don't support unsignedAttrs yet but we still need to skip them
105+
_ = DER.optionalImplicitlyTagged(&nodes, tagNumber: 1, tagClass: .contextSpecific) { _ in }
106+
107+
return .init(
108+
version: version,
109+
signerIdentifier: signerIdentifier,
110+
digestAlgorithm: digestAlgorithm,
111+
signatureAlgorithm: signatureAlgorithm,
112+
signature: signature
113+
)
114+
}
115+
}
116+
117+
func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws {
118+
try coder.appendConstructedNode(identifier: identifier) { coder in
119+
try coder.serialize(self.version.rawValue)
120+
try coder.serialize(self.signerIdentifier)
121+
try coder.serialize(self.digestAlgorithm)
122+
try coder.serialize(self.signatureAlgorithm)
123+
try coder.serialize(self.signature)
124+
}
125+
}
42126
}
43-

0 commit comments

Comments
 (0)