CVE-2025-0343
Description
Swift ASN.1 can be caused to crash when parsing certain BER/DER constructions. This crash is caused by a confusion in the ASN.1 library itself which assumes that certain objects can only be provided in either constructed or primitive forms, and will trigger a preconditionFailure if that constraint isn't met.
Importantly, these constraints are actually required to be true in DER, but that correctness wasn't enforced on the early node parser side so it was incorrect to rely on it later on in decoding, which is what the library did.
These crashes can be triggered when parsing any DER/BER format object. There is no memory-safety issue here: the crash is a graceful one from the Swift runtime. The impact of this is that it can be used as a denial-of-service vector when parsing BER/DER data from unknown sources, e.g. when parsing TLS certificates.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/apple/swift-asn1SwiftURL | < 1.3.1 | 1.3.1 |
Patches
1ae33e5941bb8Merge commit from fork
8 files changed · +75 −25
Sources/SwiftASN1/Basic ASN1 Types/ASN1BitString.swift+1 −1 modified@@ -57,7 +57,7 @@ public struct ASN1BitString: DERImplicitlyTaggable, BERImplicitlyTaggable { } guard case .primitive(let content) = node.content else { - preconditionFailure("ASN.1 parser generated primitive node with constructed content") + throw ASN1Error.invalidASN1Object(reason: "ASN1BitString encoded with constructed encoding") } // The initial octet explains how many of the bits in the _final_ octet are not part of the bitstring.
Sources/SwiftASN1/Basic ASN1 Types/ASN1Integer.swift+2 −2 modified@@ -53,7 +53,7 @@ extension ASN1IntegerRepresentable { } guard case .primitive(var dataBytes) = node.content else { - preconditionFailure("ASN.1 parser generated primitive node with constructed content") + throw ASN1Error.invalidASN1Object(reason: "INTEGER encoded with constructed encoding") } // Zero bytes of integer is not an acceptable encoding. @@ -93,7 +93,7 @@ extension ASN1IntegerRepresentable { } guard case .primitive(var dataBytes) = node.content else { - preconditionFailure("ASN.1 parser generated primitive node with constructed content") + throw ASN1Error.invalidASN1Object(reason: "INTEGER encoded with constructed encoding") } // Zero bytes of integer is not an acceptable encoding.
Sources/SwiftASN1/Basic ASN1 Types/ASN1OctetString.swift+1 −1 modified@@ -30,7 +30,7 @@ public struct ASN1OctetString: DERImplicitlyTaggable, BERImplicitlyTaggable { } guard case .primitive(let content) = node.content else { - preconditionFailure("ASN.1 parser generated primitive node with constructed content") + throw ASN1Error.invalidASN1Object(reason: "ASN1OctetString encoded with constructed encoding") } self.bytes = content
Sources/SwiftASN1/Basic ASN1 Types/GeneralizedTime.swift+3 −9 modified@@ -206,21 +206,15 @@ public struct GeneralizedTime: DERImplicitlyTaggable, BERImplicitlyTaggable, Has @inlinable public init(derEncoded node: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { - guard node.identifier == identifier else { - throw ASN1Error.unexpectedFieldType(node.identifier) - } - - guard case .primitive(let content) = node.content else { - preconditionFailure("ASN.1 parser generated primitive node with constructed content") - } - + let content = try ASN1OctetString(derEncoded: node, withIdentifier: identifier).bytes self = try TimeUtilities.generalizedTimeFromBytes(content) } @inlinable public init(berEncoded node: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { // TODO: BER supports relaxed timestamp parsing, which is not yet supported - self = try .init(derEncoded: node, withIdentifier: identifier) + let content = try ASN1OctetString(berEncoded: node, withIdentifier: identifier).bytes + self = try TimeUtilities.generalizedTimeFromBytes(content) } @inlinable
Sources/SwiftASN1/Basic ASN1 Types/ObjectIdentifier.swift+1 −1 modified@@ -42,7 +42,7 @@ public struct ASN1ObjectIdentifier: DERImplicitlyTaggable, BERImplicitlyTaggable } guard case .primitive(let content) = node.content else { - preconditionFailure("ASN.1 parser generated primitive node with constructed content") + throw ASN1Error.invalidASN1Object(reason: "OID encoded with constructed encoding") } try Self.validateObjectIdentifierInEncodedForm(content)
Sources/SwiftASN1/Basic ASN1 Types/UTCTime.swift+3 −9 modified@@ -128,20 +128,14 @@ public struct UTCTime: DERImplicitlyTaggable, BERImplicitlyTaggable, Hashable, S @inlinable public init(derEncoded node: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { - guard node.identifier == identifier else { - throw ASN1Error.unexpectedFieldType(node.identifier) - } - - guard case .primitive(let content) = node.content else { - preconditionFailure("ASN.1 parser generated primitive node with constructed content") - } - + let content = try ASN1OctetString(derEncoded: node, withIdentifier: identifier).bytes self = try TimeUtilities.utcTimeFromBytes(content) } @inlinable public init(berEncoded node: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { - self = try .init(derEncoded: node, withIdentifier: identifier) + let content = try ASN1OctetString(berEncoded: node, withIdentifier: identifier).bytes + self = try TimeUtilities.utcTimeFromBytes(content) } @inlinable
Sources/SwiftASN1/DER.swift+3 −2 modified@@ -233,8 +233,9 @@ extension DER { // We expect a single child. guard case .constructed(let nodes) = node.content else { - // This error is an internal parser error: the tag above is always constructed. - preconditionFailure("Explicit tags are always constructed") + throw ASN1Error.invalidASN1Object( + reason: "Explicit tags should always be constructed, got \(node.identifier) which is not." + ) } var nodeIterator = nodes.makeIterator()
Tests/SwiftASN1Tests/ASN1Tests.swift+61 −0 modified@@ -967,6 +967,23 @@ class ASN1Tests: XCTestCase { } } + func testPrimitiveTaggedObject() throws { + // We should error if primitive encoding is used for an explicitly tagged object. + let weirdASN1: [UInt8] = [ + 0x30, 0x05, // Sequence, containing... + 0x82, 0x03, // Context specific tag 2, 3 byte body, containing... + 0x02, 0x01, 0x00, // Integer 0 + ] + let parsed = try DER.parse(weirdASN1) + try DER.sequence(parsed, identifier: .sequence) { nodes in + XCTAssertThrowsError( + try DER.optionalExplicitlyTagged(&nodes, tagNumber: 2, tagClass: .contextSpecific, { _ in }) + ) { error in + XCTAssertEqual((error as? ASN1Error)?.code, .invalidASN1Object) + } + } + } + func testSPKIWithUnexpectedKeyTypeOID() throws { // This is an SPKI object for RSA instead of EC. This is a 1024-bit RSA key, so hopefully no-one will think to use it. let rsaSPKI = @@ -1366,4 +1383,48 @@ class ASN1Tests: XCTestCase { XCTAssertEqual(asn1OctetString.bytes, [0xFE, 0xED, 0xFA, 0xCE]) XCTAssertThrowsError(try DER.parse(berOctetString)) } + + func testConstructedBoolean() throws { + let weirdASN1: [UInt8] = [0x21, 0x00] + let node = try DER.parse(weirdASN1) + XCTAssertThrowsError(try Bool(berEncoded: node)) + XCTAssertThrowsError(try Bool(derEncoded: node)) + } + + func testConstructedInteger() throws { + let weirdASN1: [UInt8] = [0x22, 0x00] + let node = try DER.parse(weirdASN1) + XCTAssertThrowsError(try Int(berEncoded: node)) + XCTAssertThrowsError(try Int(derEncoded: node)) + } + + func testConstructedBitString() throws { + let weirdASN1: [UInt8] = [0x23, 0x08, 0x03, 0x02, 0x00, 0xAB, 0x03, 0x02, 0x04, 0xC] + let node = try DER.parse(weirdASN1) + // Not yet supported + // XCTAssertEqual(try ASN1BitString(berEncoded: node), ASN1BitString(bytes: [0xAB, 0xC], paddingBits: 4)) + XCTAssertThrowsError(try ASN1BitString(berEncoded: node)) + XCTAssertThrowsError(try ASN1BitString(derEncoded: node)) + } + + func testConstructedOctetString() throws { + let weirdASN1: [UInt8] = [0x24, 0x06, 0x04, 0x01, 0xAB, 0x04, 0x01, 0xCD] + let node = try DER.parse(weirdASN1) + XCTAssertEqual(try ASN1OctetString(berEncoded: node), ASN1OctetString(contentBytes: [0xAB, 0xCD])) + XCTAssertThrowsError(try ASN1OctetString(derEncoded: node)) + } + + func testConstructedNull() throws { + let weirdASN1: [UInt8] = [0x25, 0x00] + let node = try DER.parse(weirdASN1) + XCTAssertThrowsError(try ASN1Null(berEncoded: node)) + XCTAssertThrowsError(try ASN1Null(derEncoded: node)) + } + + func testConstructedOID() throws { + let weirdASN1: [UInt8] = [0x26, 0x03, 0x02, 0x01, 0x00] + let node = try DER.parse(weirdASN1) + XCTAssertThrowsError(try ASN1ObjectIdentifier(berEncoded: node)) + XCTAssertThrowsError(try ASN1ObjectIdentifier(derEncoded: node)) + } }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.