Skip to content
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

Add support for attached signatures (#201) #206

Merged
merged 11 commits into from
Nov 22, 2024
42 changes: 35 additions & 7 deletions Sources/X509/CryptographicMessageSyntax/CMSOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@ public enum CMS {
additionalIntermediateCertificates: [Certificate] = [],
certificate: Certificate,
privateKey: Certificate.PrivateKey,
signingTime: Date? = nil
signingTime: Date? = nil,
detached: Bool = true
) throws -> [UInt8] {
if let signingTime = signingTime {
return try self.signWithSigningTime(
Lukasa marked this conversation as resolved.
Show resolved Hide resolved
bytes,
signatureAlgorithm: signatureAlgorithm,
certificate: certificate,
privateKey: privateKey,
signingTime: signingTime
signingTime: signingTime,
detached: detached
)
}

Expand All @@ -42,7 +44,8 @@ public enum CMS {
signatureBytes: ASN1OctetString(signature),
signatureAlgorithm: signatureAlgorithm,
additionalIntermediateCertificates: additionalIntermediateCertificates,
certificate: certificate
certificate: certificate,
withContent: detached ? nil : bytes
)

return try self.serializeSignedData(signedData)
Expand All @@ -55,7 +58,8 @@ public enum CMS {
additionalIntermediateCertificates: [Certificate] = [],
certificate: Certificate,
privateKey: Certificate.PrivateKey,
signingTime: Date
signingTime: Date,
detached: Bool = true
) throws -> [UInt8] {
var signedAttrs: [CMSAttribute] = []
// As specified in RFC 5652 section 11 when including signedAttrs we need to include a minimum of:
Expand Down Expand Up @@ -91,7 +95,8 @@ public enum CMS {
signatureAlgorithm: signatureAlgorithm,
additionalIntermediateCertificates: additionalIntermediateCertificates,
certificate: certificate,
signedAttrs: signedAttrs
signedAttrs: signedAttrs,
withContent: detached ? nil : bytes
)
return try self.serializeSignedData(signedData)
}
Expand All @@ -108,7 +113,8 @@ public enum CMS {
signatureBytes: signatureBytes,
signatureAlgorithm: signatureAlgorithm,
additionalIntermediateCertificates: additionalIntermediateCertificates,
certificate: certificate
certificate: certificate,
withContent: nil as Data?
)

return try serializeSignedData(signedData)
Expand All @@ -130,9 +136,31 @@ public enum CMS {
additionalIntermediateCertificates: [Certificate],
certificate: Certificate,
signedAttrs: [CMSAttribute]? = nil
) throws -> CMSContentInfo {
return try generateSignedData(
signatureBytes: signatureBytes,
signatureAlgorithm: signatureAlgorithm,
additionalIntermediateCertificates: additionalIntermediateCertificates,
certificate: certificate,
signedAttrs: signedAttrs,
withContent: nil as Data?
)
}

@inlinable
static func generateSignedData<Bytes: DataProtocol>(
signatureBytes: ASN1OctetString,
signatureAlgorithm: Certificate.SignatureAlgorithm,
additionalIntermediateCertificates: [Certificate],
certificate: Certificate,
signedAttrs: [CMSAttribute]? = nil,
withContent content: Bytes? = nil
) throws -> CMSContentInfo {
let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm)
let contentInfo = CMSEncapsulatedContentInfo(eContentType: .cmsData)
var contentInfo = CMSEncapsulatedContentInfo(eContentType: .cmsData)
if let content {
contentInfo.eContent = ASN1OctetString(contentBytes: Array(content)[...])
}

let signerInfo = CMSSignerInfo(
signerIdentifier: .init(issuerAndSerialNumber: certificate),
Expand Down
179 changes: 161 additions & 18 deletions Tests/X509Tests/CMSTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,76 @@ final class CMSTests: XCTestCase {
)
}

func testAttachedSigningVerifying() async throws {
let data: [UInt8] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

let signature = try CMS.sign(
data,
signatureAlgorithm: .ecdsaWithSHA256,
certificate: Self.leaf1Cert,
privateKey: Self.leaf1Key,
detached: false
)
let log = DiagnosticsLog()
let isValidSignature = await CMS.isValidSignature(
dataBytes: data,
signatureBytes: signature,
trustRoots: CertificateStore([Self.rootCert]),
diagnosticCallback: log.append(_:),
allowAttachedContent: true
) { Self.defaultPolicies }
XCTAssertValidSignature(isValidSignature)

XCTAssertEqual(
log,
[
.searchingForIssuerOfPartialChain([Self.leaf1Cert]),
.foundCandidateIssuersOfPartialChainInRootStore([Self.leaf1Cert], issuers: [Self.rootCert]),
.foundValidCertificateChain([Self.leaf1Cert, Self.rootCert]),
]
)
}

func testForbidsDetachedSignatureVerifyingAsAttached() async throws {
let data: [UInt8] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

let signature = try CMS.sign(
data,
signatureAlgorithm: .ecdsaWithSHA256,
certificate: Self.leaf1Cert,
privateKey: Self.leaf1Key,
detached: true
)
let log = DiagnosticsLog()
let isValidAttachedSignature = await CMS.isValidAttachedSignature(
signatureBytes: signature,
trustRoots: CertificateStore([Self.rootCert]),
diagnosticCallback: log.append(_:)
) { Self.defaultPolicies }
XCTAssertInvalidCMSBlock(isValidAttachedSignature)
}

func testToleratesAttachedSignatureVerifyingAsDetached() async throws {
let data: [UInt8] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

let signature = try CMS.sign(
data,
signatureAlgorithm: .ecdsaWithSHA256,
certificate: Self.leaf1Cert,
privateKey: Self.leaf1Key,
detached: false
)
let log = DiagnosticsLog()
let isValidDetachedSignature = await CMS.isValidSignature(
dataBytes: data,
signatureBytes: signature,
trustRoots: CertificateStore([Self.rootCert]),
diagnosticCallback: log.append(_:),
allowAttachedContent: true
) { Self.defaultPolicies }
XCTAssertValidSignature(isValidDetachedSignature)
}

func testParsingSimpleSignature() async throws {
let data: [UInt8] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Expand Down Expand Up @@ -697,18 +767,14 @@ final class CMSTests: XCTestCase {

func testCMSAttachedSignature() async throws {
let data: [UInt8] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
var cmsData = try CMS.generateSignedTestData(
let cmsData = try CMS.generateSignedTestData(
data,
signatureAlgorithm: .ecdsaWithSHA256,
certificate: Self.leaf1Cert,
privateKey: Self.leaf1Key
privateKey: Self.leaf1Key,
detached: false
)

// Let's add the signed data in here!
var signedData = try CMSSignedData(asn1Any: cmsData.content)
signedData.encapContentInfo.eContent = ASN1OctetString(contentBytes: data[...])
cmsData.content = try ASN1Any(erasing: signedData)

let isValidSignature = try await CMS.isValidAttachedSignature(
signatureBytes: cmsData.encodedBytes,
trustRoots: CertificateStore([Self.rootCert])
Expand Down Expand Up @@ -760,19 +826,32 @@ final class CMSTests: XCTestCase {
XCTAssertInvalidCMSBlock(isValidSignature)
}

func testRequireDetachedSignature() async throws {
func testRequireAttachedSignature() async throws {
let data: [UInt8] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
var cmsData = try CMS.generateSignedTestData(
let cmsData = try CMS.generateSignedTestData(
data,
signatureAlgorithm: .ecdsaWithSHA256,
certificate: Self.leaf1Cert,
privateKey: Self.leaf1Key
privateKey: Self.leaf1Key,
detached: true
)

// Let's add the signed data in here!
var signedData = try CMSSignedData(asn1Any: cmsData.content)
signedData.encapContentInfo.eContent = ASN1OctetString(contentBytes: data[...])
cmsData.content = try ASN1Any(erasing: signedData)
let isValidSignature = try await CMS.isValidAttachedSignature(
signatureBytes: cmsData.encodedBytes,
trustRoots: CertificateStore([Self.rootCert])
) {}
XCTAssertInvalidCMSBlock(isValidSignature)
}

func testRequireDetachedSignature() async throws {
let data: [UInt8] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let cmsData = try CMS.generateSignedTestData(
data,
signatureAlgorithm: .ecdsaWithSHA256,
certificate: Self.leaf1Cert,
privateKey: Self.leaf1Key,
detached: false
)

let isValidSignature = try await CMS.isValidSignature(
dataBytes: data,
Expand All @@ -791,7 +870,7 @@ final class CMSTests: XCTestCase {
privateKey: Self.leaf1Key
)

// Let's add the signed data in here!
// Let's add data not matching the signature
var signedData = try CMSSignedData(asn1Any: cmsData.content)
signedData.encapContentInfo.eContent = ASN1OctetString(contentBytes: [0xba, 0xd])
cmsData.content = try ASN1Any(erasing: signedData)
Expand Down Expand Up @@ -973,6 +1052,67 @@ final class CMSTests: XCTestCase {
XCTAssertValidSignature(isValidSignature)
}

func testSigningAttachedWithSigningTimeSignedAttr() async throws {
let data: [UInt8] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let signature = try CMS.sign(
data,
signatureAlgorithm: .ecdsaWithSHA256,
certificate: Self.leaf1Cert,
privateKey: Self.leaf1Key,
signingTime: Date(),
detached: false
)
let isValidSignature = await CMS.isValidSignature(
dataBytes: data,
signatureBytes: signature,
trustRoots: CertificateStore([Self.rootCert]),
allowAttachedContent: true
) {
Self.defaultPolicies
}
XCTAssertValidSignature(isValidSignature)
}

func testToleratesAttachedSignatureWithSigningTimeSignedAttrVerifyingAsDetached() async throws {
let data: [UInt8] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let signature = try CMS.sign(
data,
signatureAlgorithm: .ecdsaWithSHA256,
certificate: Self.leaf1Cert,
privateKey: Self.leaf1Key,
signingTime: Date(),
detached: false
)
let isValidDetachedSignature = await CMS.isValidSignature(
dataBytes: data,
signatureBytes: signature,
trustRoots: CertificateStore([Self.rootCert]),
allowAttachedContent: true
) {
Self.defaultPolicies
}
XCTAssertValidSignature(isValidDetachedSignature)
}

func testForbidsDetachedSignatureWithSigningTimeSignedAttrVerifyingAsAttached() async throws {
let data: [UInt8] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let signature = try CMS.sign(
data,
signatureAlgorithm: .ecdsaWithSHA256,
certificate: Self.leaf1Cert,
privateKey: Self.leaf1Key,
signingTime: Date(),
detached: true
)
let isValidAttachedSignature = await CMS.isValidAttachedSignature(
signatureBytes: signature,
trustRoots: CertificateStore([Self.rootCert])
) {
Self.defaultPolicies
}
XCTAssertInvalidCMSBlock(isValidAttachedSignature)
}

func testSigningContentBytesWithSigningTimeSignedAttrsIsInvalidSignature() async throws {
let data: [UInt8] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Expand Down Expand Up @@ -1045,14 +1185,16 @@ extension CMS {
signatureAlgorithm: Certificate.SignatureAlgorithm,
additionalIntermediateCertificates: [Certificate] = [],
certificate: Certificate,
privateKey: Certificate.PrivateKey
privateKey: Certificate.PrivateKey,
detached: Bool = true
) throws -> CMSContentInfo {
let signature = try privateKey.sign(bytes: bytes, signatureAlgorithm: signatureAlgorithm)
return try generateSignedData(
signatureBytes: ASN1OctetString(signature),
signatureAlgorithm: signatureAlgorithm,
additionalIntermediateCertificates: additionalIntermediateCertificates,
certificate: certificate
certificate: certificate,
withContent: detached ? nil : bytes
)
}
static func generateInvalidSignedTestDataWithSignedAttrs<Bytes: DataProtocol>(
Expand Down Expand Up @@ -1097,7 +1239,8 @@ extension CMS {
signatureAlgorithm: signatureAlgorithm,
additionalIntermediateCertificates: additionalIntermediateCertificates,
certificate: certificate,
signedAttrs: signedAttrs
signedAttrs: signedAttrs,
withContent: nil as Data?
)
}
}
Loading