From 664d0af6012ce785056496c360454af299844129 Mon Sep 17 00:00:00 2001 From: Nicolas Bachschmidt Date: Tue, 17 Dec 2024 13:02:57 +0100 Subject: [PATCH] Support certificates with invalid RDN attribute values Some certificate authorities issue certificates with invalid RDN attribute values. Certificates must use UTF8String instead of PrintableString to represent strings that contains `*` or `@`, but mistakes happen. When a RDN attribute value is a PrintableString or UTF8String with an invalid content, rather than rejecting the certificate, fall back to storing the value as an Any. --- Sources/X509/RDNAttribute.swift | 31 ++++++++------------ Tests/X509Tests/DistinguishedNameTests.swift | 14 +++++++++ 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/Sources/X509/RDNAttribute.swift b/Sources/X509/RDNAttribute.swift index c83c081f..84442e61 100644 --- a/Sources/X509/RDNAttribute.swift +++ b/Sources/X509/RDNAttribute.swift @@ -119,12 +119,16 @@ extension RelativeDistinguishedName.Attribute.Value { extension RelativeDistinguishedName.Attribute.Value.Storage: DERParseable, DERSerializable { @inlinable init(derEncoded node: SwiftASN1.ASN1Node) throws { - switch node.identifier { - case ASN1UTF8String.defaultIdentifier: - self = .utf8(String(try ASN1UTF8String(derEncoded: node))) - case ASN1PrintableString.defaultIdentifier: - self = .printable(String(try ASN1PrintableString(derEncoded: node))) - default: + do { + switch node.identifier { + case ASN1UTF8String.defaultIdentifier: + self = .utf8(String(try ASN1UTF8String(derEncoded: node))) + case ASN1PrintableString.defaultIdentifier: + self = .printable(String(try ASN1PrintableString(derEncoded: node))) + default: + self = .any(ASN1Any(derEncoded: node)) + } + } catch { self = .any(ASN1Any(derEncoded: node)) } } @@ -149,19 +153,10 @@ extension RelativeDistinguishedName.Attribute.Value: CustomStringConvertible { @inlinable public var description: String { let text: String - switch storage { - case .printable(let string), .utf8(let string): + if let string = String(self) { text = string - case .any(let any): - do { - text = try String(ASN1PrintableString(asn1Any: any)) - } catch { - do { - text = try String(ASN1UTF8String(asn1Any: any)) - } catch { - text = String(describing: any) - } - } + } else { + text = String(describing: ASN1Any(self)) } // This is a very slow way to do this, but until we have any evidence that diff --git a/Tests/X509Tests/DistinguishedNameTests.swift b/Tests/X509Tests/DistinguishedNameTests.swift index a049035f..76b9efe5 100644 --- a/Tests/X509Tests/DistinguishedNameTests.swift +++ b/Tests/X509Tests/DistinguishedNameTests.swift @@ -445,4 +445,18 @@ final class DistinguishedNameTests: XCTestCase { XCTAssertEqual(String(example.value), result) } } + + func testRDNAttributeValuesCanBeParsedWhenPrintableStringIsInvalid() throws { + // '&' is not allowed in PrintableString. + let value = try ASN1Any(erasing: ASN1UTF8String("Wells Fargo & Company"), withIdentifier: .printableString) + + let attribute = try RelativeDistinguishedName.Attribute(derEncoded: [ + 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x15, 0x57, 0x65, 0x6c, 0x6c, 0x73, 0x20, + 0x46, 0x61, 0x72, 0x67, 0x6f, 0x20, 0x26, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, + ]) + + XCTAssertEqual(attribute.type, .RDNAttributeType.organizationName) + XCTAssertEqual(attribute.value, RelativeDistinguishedName.Attribute.Value(asn1Any: value)) + XCTAssertNil(String(attribute.value)) + } }