ASN1Codable

ASN1Codable 是一个 Swift 框架,用于 ASN.1 编码和解码 Swift Codable 类型。

在大多数情况下,ASN.1 类型系统与 Swift 的类型系统是同构的,例如

类型可以直接在 Swift 中定义,也可以使用转换器从 ASN.1 生成,该转换器读取 Heimdalasn1_compile 工具的输出。

ASN1Codable 支持的功能包括

架构

ASN.1 编码和解码目前由 ASN1Kit 库提供,但这应被视为实现细节。

ASN.1 类型具有额外的元数据,最重要的是标签号和标签环境(例如 IMPLICITEXPLICIT),这些在 Swift 中没有自然的表示形式。目前,ASN1Codable 使用各种语言特性将 ASN.1 元数据与 Swift 类型关联起来。

通常,这涉及使用自定义的 CodingKey 类型,该类型可以直接编码标签号(对于具有统一标签的类型),或提供将键映射到元数据的函数。在某些情况下,例如类型别名和具有非统一标签的枚举类型,则使用泛型包装器代替。对于诸如整数和字符串之类的通用类型,ASN1Codable 使用 Swift 类型元数据。

用法

您可以按如下方式使用编码器和解码器

let encodedValue = try ASN1Encoder().encode(1234)
let decodedValue = try ASN1Decoder().decode(Int.self, from: encodedValue)

在 Tests 目录中还有许多其他示例。

asn1json2swift

asn1json2swift 工具读取 asn1_compile 发出的 JSON AST 表示形式,并发出 Swift 源代码。请注意,如果您希望重新编译 ASN.1,则需要使用 Heimdal 的 master 分支,因为该转换器依赖于 ASN.1 编译器的一些新功能。

功能包括

存在一些限制,例如不支持匿名嵌套类型,并且在某些情况下,属性包装器初始化程序无法正确发出。 这些很容易解决。

用法

应使用 Heimdal asn1_compile 工具的 JSON 输出调用 asn1json2swift 工具。

asn1json2swift translate --input [file] --output [file]
    --map-type [ASN1Type]:[@class|@objc|@external|SwiftType]
    --conform-type [*|ASN1Type]:[SwiftProtocol]

--map-type 选项允许将 ASN.1 类型作为 Swift 或 Objective-C 类发出,或者将其排除。 如果指定了 @external,则应提供具有该名称的 Swift 类型别名或类型。 如果指定了 Swift 类型,则转换器将发出类型别名,但不会发出类型定义。

--conform-type 允许为给定的 ASN.1 类型发出其他协议一致性。 默认情况下,所有类型都是 Codable; 如果它们包含在 SET 中,则它们也是 HashableEquatable。 由 ASN.1 编译器标记为保留原始编码值的类型也符合 ASN1PreserveBinary。 本身被标记的类型符合 ASN1TaggedType。 从 SET 转换而来的 Swift struct 符合市场协议 ASN1SetCodable。 其他协议一致性用于指示可扩展和开放类型。

certutil

此存储库包含 Certificate.framework,这是一个模仿 macOS Security.framework 的 C API,作为概念证明。还包括一个 certutil 工具,用于读取 PEM 编码的证书并输出 JSON 表示形式,以及 SAN 和重新编码的 DER。Swift 类型是在构建时从 rfc2459.json 生成的,而 rfc2459.json 又(如果存在 /usr/local/heimdal/bin/asn1_compile)是从 rfc2459.asn1 生成的。

用法

certutil parse --file [file]|--string [string] --json --reencode --san

例子

SEQUENCE

下面的 SEQUENCE 可以使用 ASN1ContextTagged 属性包装器表示。

TBSCertificate  ::=  SEQUENCE  {
     version         [0]  Version OPTIONAL, -- EXPLICIT DEFAULT 1,
     serialNumber         CertificateSerialNumber,
     signature            AlgorithmIdentifier,
     issuer               Name,
     validity             Validity,
     subject              Name,
     subjectPublicKeyInfo SubjectPublicKeyInfo,
     issuerUniqueID  [1]  IMPLICIT BIT STRING -- UniqueIdentifier -- OPTIONAL,
                          -- If present, version shall be v2 or v3
     subjectUniqueID [2]  IMPLICIT BIT STRING -- UniqueIdentifier -- OPTIONAL,
                          -- If present, version shall be v2 or v3
     extensions      [3]  EXPLICIT Extensions OPTIONAL
                          -- If present, version shall be v3
}

变为

class TBSCertificate: Codable {
    @ASN1ContextTagged<ASN1TagNumber$0, ASN1ExplicitTagging, Version?>
    var version: Version? = nil
    var serialNumber: CertificateSerialNumber
    var signature: AlgorithmIdentifier
    var issuer: Name
    var validity: Validity
    var subject: Name
    var subjectPublicKeyInfo: SubjectPublicKeyInfo
    @ASN1ContextTagged<ASN1TagNumber$1, ASN1ImplicitTagging, BitString?>
    var issuerUniqueID: BitString? = nil
    @ASN1ContextTagged<ASN1TagNumber$2, ASN1ImplicitTagging, BitString?>
    var subjectUniqueID: BitString? = nil
    @ASN1ContextTagged<ASN1TagNumber$3, ASN1ExplicitTagging, Extensions?>
    var extensions: Extensions? = nil
}

通过避免属性包装器并使用专门的 CodingKeys,可以稍微改善人体工程学。 此格式暴露了 ASN1Kit 内部类型(特别是 ASN1DecodedTag),因此不能保证在各个版本之间保持稳定; 而是仅供 asn1json2swift 转换器使用。

struct TBSCertificate: Codable {
    enum CodingKeys: ASN1MetadataCodingKey {
        case version
        case serialNumber
        case signature
        case issuer
        case validity
        case subject
        case subjectPublicKeyInfo
        case issuerUniqueID
        case subjectUniqueID
        case extensions

        static func metadata(forKey key: Self) -> ASN1Metadata? {
            let metadata: ASN1Metadata?

            switch key {
            case version:
                metadata = ASN1Metadata(tag: .taggedTag(0), tagging: .explicit)
            case issuerUniqueID:
                metadata = ASN1Metadata(tag: .taggedTag(1), tagging: .implicit)
            case subjectUniqueID:
                metadata = ASN1Metadata(tag: .taggedTag(2), tagging: .implicit)
            case extensions:
                metadata = ASN1Metadata(tag: .taggedTag(3), tagging: .explicit)
            default:
                metadata = nil
            }

            return metadata
        }
    }

    var version: Version?
    var serialNumber: CertificateSerialNumber
    var signature: SignatureAlgorithmIdentifier
    var issuer: Name
    var validity: Validity
    var subject: Name
    var subjectPublicKeyInfo: SubjectPublicKeyInfo
    var issuerUniqueID: BitString?
    var subjectUniqueID: BitString?
    var extensions: Extensions?
}

CHOICE

GeneralName ::= CHOICE {
        otherName                       [0]     IMPLICIT OtherName,
        rfc822Name                      [1]     IMPLICIT IA5String,
        dNSName                         [2]     IMPLICIT IA5String,
--      x400Address                     [3]     IMPLICIT ORAddress,--
        directoryName                   [4]     IMPLICIT Name,
--      ediPartyName                    [5]     IMPLICIT EDIPartyName, --
        uniformResourceIdentifier       [6]     IMPLICIT IA5String,
        iPAddress                       [7]     IMPLICIT OCTET STRING,
        registeredID                    [8]     IMPLICIT OBJECT IDENTIFIER
}

变为

enum GeneralName: Codable {
        enum CodingKeys: Int, ASN1ImplicitTagCodingKey {
                case otherName = 0
                case rfc822Name = 1
                case dNSName = 2
                case directoryName = 4
                case uniformResourceIdentifier = 6
                case iPAddress = 7
                case registeredID = 8
        }

        case otherName(OtherName)
        case rfc822Name(IA5String<String>)
        case dNSName(IA5String<String>)
        case directoryName(Name)
        case uniformResourceIdentifier(IA5String<String>)
        case iPAddress(Data)
        case registeredID(ObjectIdentifier)
}

或者

enum GeneralName: Codable {
    enum CodingKeys: CaseIterable, ASN1MetadataCodingKey {
        case otherName
        case rfc822Name
        case dNSName
        case directoryName
        case uniformResourceIdentifier
        case iPAddress
        case registeredID

        static func metadata(forKey key: Self) -> ASN1Metadata? {
            let metadata: ASN1Metadata?

            switch key {
            case otherName:
                metadata = ASN1Metadata(tag: .taggedTag(0), tagging: .implicit)
            case rfc822Name:
                metadata = ASN1Metadata(tag: .taggedTag(1), tagging: .implicit)
            case dNSName:
                metadata = ASN1Metadata(tag: .taggedTag(2), tagging: .implicit)
            case directoryName:
                metadata = ASN1Metadata(tag: .taggedTag(4), tagging: .explicit)
            case uniformResourceIdentifier:
                metadata = ASN1Metadata(tag: .taggedTag(6), tagging: .implicit)
            case iPAddress:
                metadata = ASN1Metadata(tag: .taggedTag(7), tagging: .implicit)
            case registeredID:
                metadata = ASN1Metadata(tag: .taggedTag(8), tagging: .implicit)
            }

            return metadata
        }
    }

    case otherName(OtherName)
    case rfc822Name(IA5String<String>)
    case dNSName(IA5String<String>)
    case directoryName(Name)
    case uniformResourceIdentifier(IA5String<String>)
    case iPAddress(Data)
    case registeredID(ObjectIdentifier)
}

前者是更紧凑的表示形式,可以与统一的标记环境一起使用(请注意,directoryName 在 ASN.1 中定义为 IMPLICIT,但由于它是 CHOICE,因此被提升为 EXPLICIT;这在运行时处理)。它还具有 API 稳定性保证,而后者格式不具有。