OpenSSL-Swift

用于Gematik特定加密操作的Swift扩展包装器,内嵌OpenSSL

概述

此Xcode项目下载、编译并将OpenSSL 3.1.0版本嵌入到一个Swift框架中,该框架可以包含在MacOS/iOS框架和应用中。

该项目主要包含三个部分。

  1. PreBuildPhase脚本“Install OpenSSL”在OpenSSL目标构建之前运行。执行此脚本(例如,首次运行时)需要一段时间,因为它会为MacOS x86_64和iOS arm64架构进行编译和链接。连续运行该脚本将检查所需的目标版本是否已存在,并跳过多余的步骤。
  2. COpenSSL模块(映射)桥接并将OpenSSL C头文件暴露给Swift。
  3. OpenSSL目标和生成的框架,可以包含在外部项目中。在此目标中,只有有限的OpenSSL操作可用并被暴露出来。一般来说,这些操作是在任何Apple平台包含的框架中都不可用的:Security.framework、CommonCrypto 甚至 CryptoKit。
  4. Makefile和脚本,用于支持设置Xcodeproj和构建OpenSSL库

动机

如概述中所述,某些加密和安全操作在Apple平台加密框架中缺失。对于Gematik在iOS上的用例,最重要的是缺少对brainpool椭圆曲线的支持。这些曲线并非TLS标准的一部分,因此对于正常的HTTPS实现不是强制性的。但是,Gematik专门使用brainpool曲线作为其“gem”标准,如BSI建议 - 第6章。这导致了Apple平台包含的内容和Gematik的用例之间的差距。因此,我们决定围绕一个成熟且维护良好的开源加密库构建一个包装器,我们可以直接从Swift源代码访问它。直接从Swift访问OpenSSL加密操作允许我们不必手动释放由底层OpenSSL操作分配的内存,而是将这个复杂的代码隐藏在可能需要使用该框架提供的一些功能的普通用户/开发人员面前。

开始使用

集成设置

我们完全不支持CocoaPods,但是如果设置正确,它也可能有效

SPM: 通过XCode或Package.swift添加

.package(url: "https://github.com/gematik/OpenSSL-Swift", from: "4.0.0"),

Carthage: 将其放入您的Cartfile

github "gematik/OpenSSL-Swift" ~> 4.0

开发设置

$ make setup

打开OpenSSL-Swift.xcodeproj并构建/测试一个方案将执行脚本scripts/install_openssl。该脚本将从OpenSSL下载并编译用于多个平台/架构组合的框架。

支持的操作

如本文档的概述部分所述,只有OpenSSL的一小部分操作可用。本节介绍支持的操作。

X.509证书

该框架的API提供了一些关于基于X.509的证书的基本操作。由于它由OpenSSL支持,因此支持更多的签名算法。

您可以以原始DER表示形式或PEM表示形式(源自通常以-----BEGIN CERTIFICATE-----开头的字符串)通过Data实例化X509对象。

let derEncodedX509Data = Data(base64Encoded: "MIICsTC...")
let x509 = try X509(der: derEncodedX509Data)   
// or
let x509 = try X509(pem: "-----BEGIN CERTIFICATE-----.....".data(using: .ascii))

let issuerOneLine = try x509.issuerOneLine() // "/C=DE/O=gematik GmbH NOT-VALID/OU=Komponenten-CA der Telematikinfrastruktur/CN=GEM.KOMP-CA10 TEST-ONLY"
let sha256Fingerprint = try x509.sha256Fingerprint()
let validatedWithTrustStore = x509.validateWithTrustStore([rootCaCert, otherCaCert])

// If the certificate holder is using a BrainpoolP256r1 key for signing, you can retrieve the public counterpart conveniently
let brainpoolP256r1PublicKey = x509.brainpoolP256r1PublicKey() // `BrainpoolP256r1.Verify.PublicKey?`

OCSP响应

使用OSCP响应来尝试验证给定X509证书的状态。

您可以通过原始DER表示形式的Data实例化OCSPResponse对象。

let ocspResponse = try! OCSPResponse(der: Base64.decode(data: "MIIHBQoBAKCCBv4wggb6Bgkr..."))

print(ocspResponse.status()) // the `OCSPResponse.Status` of the response itself, e.g. .successful, .tryLater ...
print(try vauOcspResponseNoKnownSignerCa.producedAt()) // the `Date` held in the `producedAt` field of the response 

// Check the revocation status `OCSPResponse.CertStatus` for a given certificate with a ocspResponse.
let certificate, certificateIssuer: X509!
print(try ocspResponse.certificateStatus(for: certificate, issuer: certificateIssuer)) // .good, .revoked ...

// Verify the signature on a ocspResponse against a given trust store
let ocspSignerCa, rootCa: X509!
print(try ocspResponse.basicVerifyWith(trustedStore: [ocspSignerCa, rootCa])) // true, false
// Use `OCSPResponse.BasicVerifyOptions` to set certain basic-verify check flags 
// refer to: https://www.openssl.org/docs/man1.1.0/man3/OCSP_resp_get0.html -> OCSP_basic_verify()
// After successful path validation the function returns success if the OCSP_NOCHECKS flag is set.
let options: OCSPResponse.BasicVerifyOptions = [.noChecks]
try vauOcspResponse.basicVerifyWith(trustedStore: [ocspSignerCa, rootCa], options: options)

密码消息语法

支持为(多个)X509接收者证书进行消息的CMS加密(目前仅支持RSA!)。将创建使用AES 256 GCMAuthenticated-Enveloped-Data Content Type结构。

let x509rsa = try X509(pem: x509rsaPem.data(using: .utf8)!)
let x509ecc = try X509(pem: x509eccPem.data(using: .utf8)!)
let recipients = [x509rsa, x509ecc]
let data = message.data(using: .utf8)!
let cms = try CMSContentInfo.encryptPartial(data: data)
try cms.addRecipientsRSAOnly(recipients)
try cms.final(data: data)
print(cms.derBytes?.hexString())

密钥管理

密码操作通常基于公钥/私钥,在此处由ECPublicKeyPrivateKey协议表示。您可以根据您的用例,通过选择实现类来实例化它们,例如 BrainpoolP256r1.KeyExchange.PublicKeyBrainpoolP256r1.KeyExchange.PrivateKey等,并从那里继续。公钥可以通过传入椭圆曲线点( 0x4 || x || y) [ANSI X9.62格式] 初始化,私钥也可以(0x4 || x || y || k) 。其中k是私钥。私钥也可以仅通过将k作为原始参数来初始化。私钥也可以随机生成。

生成私钥(密钥对)

let key = try BrainpoolP256r1.KeyExchange.generateKey()

ECDH共享密钥计算

可以使用BrainpoolP256r1.KeyExchange.PublicKey及其对应的BrainpoolP256r1.KeyExchange.PrivateKey以及调用所述私钥上的.sharedSecret(with: peerKey)方法,并将公钥作为peer传入来导出BrainpoolP256r1共享密钥。

let pubKeyx962 = try Data(hex: "048634212830DAD457CA05305E6687134166B9C21A65FFEBF555F4E75DFB04888866E4B6843624CBDA43C97EA89968BC41FD53576F82C03EFA7D601B9FACAC2B29")
let privateKeyRaw = try Data(hex: "83456D98DEA3435C166385A4E644EBCA588E8A0AA7C811F51FCC736368630206")
let pubKey = try BrainpoolP256r1.KeyExchange.PublicKey(x962: pubKeyx962)
let privateKey = try BrainpoolP256r1.KeyExchange.PrivateKey(raw: privateKeyRaw)
let sharedSecretData = try privateKey.sharedSecret(with: pubKey)

验证BrainpoolP256r1签名

let pubkeyraw = Data[...]
let pubKey = try BrainpoolP256r1.Verify.PublicKey(x962: pubkeyraw)
let derSignature = Data[...]
let signature = try BrainpoolP256r1.Verify.Signature(derRepresentation: derSignature)
let message = "A signed message"

try pubKey.verify(signature: signature, message: message.data(using: .utf8)!)

PACE协议映射用于共享密钥导出的Nonce

PACE协议使用基于共享密码(可能具有低熵)的强会话密钥来建立安全消息传递通道。

参见:BSI TR-03110

此实现遵循gemSpec_COS_V3.11.0 (N085.064)中指定的PACE协议一致性,用于使用BrainpoolP256r1建立与德国健康卡 (eGK) 的安全通信通道。

该算法将返回一个要发送给peer的公钥和一个为内部使用而生成的keyPair2。实际的密钥必须从进一步的peer公钥和前面提到的keyPair2中导出。

可以使用BrainpoolP256r1.KeyExchange.PrivateKey,其peer密钥对应物BrainpoolP256r1.KeyExchange.PublicKey和普通的nonce密码(作为Data)通过调用所述私钥上的.paceMapNonce(nonce: nonce with: peerKey)方法,传入nonce和公钥作为peer来导出BrainpoolP256r1 PACE共享密钥。

let nonce = try Data(hex: "A44248628B8E8B94072EF3843C56E844")
let ownKeyPair1Raw = try Data(hex: "0D7DFFAC3558C4C3C075A0479F4C3A4864DBD8E686CDB154DD0BDD0BA7CE4D51")
let ownKeyPair1 = try ECPrivateKeyImpl<BrainpoolP256r1.Curve>(raw: ownKeyPair1Raw)
let peerKeyRaw = try Data(hex: "045CAC41779F548CBE714A08CBCEB40F616B5EFDD59DD3345802027DCB0C3FB02B20DC7A458B7744102DE98D350D4399FEC0F8CC5CCE50317A2CEE3CB418A4DA41")
let peerKey1 = try ECPublicKeyImpl<BrainpoolP256r1.Curve>(x962: peerKeyRaw)
let (ownPubKey2, ownKeyPair2) = try ownPrivateKey1.paceMapNonce(nonce: nonce, peerKey1: peerKey1)

// receive the peer's second publicKey peerPubKey2
let sharedSecret = try ownKeyPair2.sharedSecret(peerPubKey2)

MAC计算

支持通过RFC: The AES-CMAC Algorithm 中描述的128位AES-CMAC (CBC-MAC)算法生成MAC。

let key = try! Data(hex: "2b7e151628aed2a6abf7158809cf4f3c")
let message = try! Data(hex: "")
let cmac = CMAC.aes128cbc(key: key, data: message) // == hex: "bb1d6929e95937287fa37d129b756746"

OpenSSL版本更新

要更新嵌入到库中的OpenSSL版本,您只需在第18:19行的scripts/install_openssl中更新新的OpenSSL版本和适当的SHA256哈希值。

许可证

EUROPEAN UNION PUBLIC LICENCE v. 1.2

EUPL © the European Union 2007, 2016

以下条款适用

  1. 版权声明:每个发布的工作成果都附有明确的使用许可条件声明。这些通常是与开源或自由软件相关的典型条件。除非另有说明,否则此处描述/提供/链接的程序是自由软件。

  2. 许可声明:特此授予任何人免费获得本软件和相关文档文件(“软件”)的副本的许可,以不受限制地处理本软件,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或出售软件副本的权利,并允许向其提供软件的人员在以下条件下这样做:

    1. 版权声明(第1项)和许可声明(第2项)应包含在所有副本或软件的重要部分中。

    2. 该软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对特定用途的适用性、适销性和/或非侵权性的保证。作者或版权持有者对因软件或软件的使用或其他交易而引起或与之相关的任何损害或其他索赔概不负责,无论是在合同、侵权行为或其他诉讼中。

    3. 该软件是研发活动的结果,因此不一定经过质量保证,也没有有责任承担的产品特性。因此,gematik不提供任何支持或其他用户帮助(除非在个别情况下另有说明,并且不构成法律义务)。此外,不要求进一步开发并将结果适应更先进的技术水平。

  3. Gematik可以随时在不事先通知或说明理由的情况下,暂时或永久地从发布地点删除已发布的结果。