ASN1Codable 是一个 Swift 框架,用于 ASN.1 编码和解码 Swift Codable 类型。
在大多数情况下,ASN.1 类型系统与 Swift 的类型系统是同构的,例如
SEQUENCE
或 SET
映射到一个 struct
CHOICE
或 ENUMERATED
类型映射到一个 enum
SEQUENCE OF
映射到 Array
SET OF
映射到 SET
OPTIONAL
映射到 Optional
类型可以直接在 Swift 中定义,也可以使用转换器从 ASN.1 生成,该转换器读取 Heimdal 的 asn1_compile
工具的输出。
ASN1Codable 支持的功能包括
asn1json2swift
转换工具与 Heimdal ASN.1 编译器 asn1_compile
集成Encodable
或 Decodable
(分别)的任何 Swift 类型AUTOMATIC
标签_save
中保留解码时的编码值(例如,用于验证签名)ASN.1 编码和解码目前由 ASN1Kit 库提供,但这应被视为实现细节。
ASN.1 类型具有额外的元数据,最重要的是标签号和标签环境(例如 IMPLICIT
或 EXPLICIT
),这些在 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
工具读取 asn1_compile
发出的 JSON AST 表示形式,并发出 Swift 源代码。请注意,如果您希望重新编译 ASN.1,则需要使用 Heimdal 的 master 分支,因为该转换器依赖于 ASN.1 编译器的一些新功能。
功能包括
struct
、class
或 @objc class
发出(默认值为 struct
,除非类型被保留或自引用)_save
) 和装饰选项DEFAULT
值存在一些限制,例如不支持匿名嵌套类型,并且在某些情况下,属性包装器初始化程序无法正确发出。 这些很容易解决。
应使用 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
中,则它们也是 Hashable
和 Equatable
。 由 ASN.1 编译器标记为保留原始编码值的类型也符合 ASN1PreserveBinary
。 本身被标记的类型符合 ASN1TaggedType
。 从 SET
转换而来的 Swift struct
符合市场协议 ASN1SetCodable
。 其他协议一致性用于指示可扩展和开放类型。
此存储库包含 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
可以使用 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?
}
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 稳定性保证,而后者格式不具有。