Description
CMSSignerInfo serializes unsignedAttrs as [1] IMPLICIT SET OF Attribute
(per RFC 5652 §5.3), but its parser reads the [1]-tagged node and then asks
DER.set/BER.set for an inner identifier of [0] instead of [1]. As a
result, any SignerInfo that actually contains unsignedAttrs (e.g. a trusted
timestamp) fails to parse with:
ASN1Error.unexpectedFieldType: ASN1Identifier(tagNumber: 1, tagClass: contextSpecific)
The bug is latent today because nothing in the library emits unsignedAttrs,
but it means a SignerInfo produced with unsigned attributes cannot be
round-tripped or verified by swift-certificates itself. The serialized wire
format is correct, so third-party parsers (OpenSSL, etc.) read it fine.
Location
Sources/X509/CryptographicMessageSyntax/CMSSignerInfo.swift
- DER init: the
unsignedAttrs block passes identifier: .init(tagWithNumber: 0, ...)
- BER init: same
serialize(into:withIdentifier:) correctly writes tagWithNumber: 1
Fix
Change the inner DER.set / BER.set identifier for unsignedAttrs from
tagWithNumber: 0 to tagWithNumber: 1, matching the serializer and the
signedAttrs pattern.
Reproduction
A CMSSignerInfo with any unsignedAttrs fails assertRoundTrips:
import XCTest
import SwiftASN1
@testable @_spi(CMS) import X509
final class CMSSignerInfoUnsignedAttributesRoundTripTest: XCTestCase
{
private func assertRoundTrips<ASN1Object: DERParseable & DERSerializable & Equatable>(_ value: ASN1Object)
throws
{
var serializer = DER.Serializer()
try serializer.serialize(value)
let parsed = try ASN1Object(derEncoded: serializer.serializedBytes)
XCTAssertEqual(parsed, value)
}
func testCMSSignerInfoWithUnsignedAttrsRoundTrips()
throws
{
// A SignerInfo carrying unsignedAttrs must survive a serialize/parse round trip.
// This exercises the `[1] IMPLICIT` unsignedAttrs path.
let unsignedAttr = CMSAttribute(
attrType: .contentType,
attrValues: [try ASN1Any(erasing: ASN1OctetString(contentBytes: [0xDE, 0xAD, 0xBE, 0xEF]))]
)
try assertRoundTrips(
CMSSignerInfo(
version: .v1,
signerIdentifier: .issuerAndSerialNumber(
.init(
issuer: .init {
CountryName("US")
OrganizationName("Apple Inc.")
CommonName("Apple Public EV Server ECC CA 1 - G1")
},
serialNumber: .init(bytes: [20, 30, 40, 50])
)
),
digestAlgorithm: .sha256WithRSAEncryptionUsingNil,
signatureAlgorithm: .ecdsaWithSHA256,
signature: .init(contentBytes: [100, 110, 120, 130, 140]),
unsignedAttrs: [unsignedAttr]
)
)
}
}
Description
CMSSignerInfoserializesunsignedAttrsas[1] IMPLICIT SET OF Attribute(per RFC 5652 §5.3), but its parser reads the
[1]-tagged node and then asksDER.set/BER.setfor an inner identifier of[0]instead of[1]. As aresult, any
SignerInfothat actually containsunsignedAttrs(e.g. a trustedtimestamp) fails to parse with:
The bug is latent today because nothing in the library emits
unsignedAttrs,but it means a
SignerInfoproduced with unsigned attributes cannot beround-tripped or verified by swift-certificates itself. The serialized wire
format is correct, so third-party parsers (OpenSSL, etc.) read it fine.
Location
Sources/X509/CryptographicMessageSyntax/CMSSignerInfo.swiftunsignedAttrsblock passesidentifier: .init(tagWithNumber: 0, ...)serialize(into:withIdentifier:)correctly writestagWithNumber: 1Fix
Change the inner
DER.set/BER.setidentifier forunsignedAttrsfromtagWithNumber: 0totagWithNumber: 1, matching the serializer and thesignedAttrspattern.Reproduction
A
CMSSignerInfowith anyunsignedAttrsfailsassertRoundTrips: