mirror of
https://github.com/selfxyz/self.git
synced 2026-01-09 14:48:06 -05:00
615 lines
28 KiB
Swift
615 lines
28 KiB
Swift
import Foundation
|
|
import OpenSSL
|
|
import CryptoTokenKit
|
|
|
|
#if !os(macOS)
|
|
import CoreNFC
|
|
import CryptoKit
|
|
|
|
@available(iOS 15, *)
|
|
private enum PACEHandlerError {
|
|
case DHKeyAgreementError(String)
|
|
case ECDHKeyAgreementError(String)
|
|
|
|
var value: String {
|
|
switch self {
|
|
case .DHKeyAgreementError(let errMsg): return errMsg
|
|
case .ECDHKeyAgreementError(let errMsg): return errMsg
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
@available(iOS 15, *)
|
|
extension PACEHandlerError: LocalizedError {
|
|
public var errorDescription: String? {
|
|
return NSLocalizedString(value, comment: "PACEHandlerError")
|
|
}
|
|
}
|
|
|
|
@available(iOS 15, *)
|
|
public class PACEHandler {
|
|
|
|
|
|
private static let MRZ_PACE_KEY_REFERENCE : UInt8 = 0x01
|
|
private static let CAN_PACE_KEY_REFERENCE : UInt8 = 0x02 // Not currently supported
|
|
private static let PIN_PACE_KEY_REFERENCE : UInt8 = 0x03 // Not currently supported
|
|
private static let CUK_PACE_KEY_REFERENCE : UInt8 = 0x04 // Not currently supported
|
|
|
|
var tagReader : TagReader
|
|
var paceInfo : PACEInfo
|
|
|
|
var isPACESupported : Bool = false
|
|
var paceError : String = ""
|
|
|
|
// Params used
|
|
private var paceKey : [UInt8] = []
|
|
private var paceKeyType : UInt8 = 0
|
|
private var paceOID : String = ""
|
|
private var parameterSpec : Int32 = -1
|
|
private var mappingType : PACEMappingType!
|
|
private var agreementAlg : String = ""
|
|
private var cipherAlg : String = ""
|
|
private var digestAlg : String = ""
|
|
private var keyLength : Int = -1
|
|
|
|
public init(cardAccess : CardAccess, tagReader: TagReader) throws {
|
|
self.tagReader = tagReader
|
|
|
|
guard let pi = cardAccess.paceInfo else {
|
|
throw NFCPassportReaderError.NotYetSupported( "PACE not supported" )
|
|
}
|
|
|
|
self.paceInfo = pi
|
|
isPACESupported = true
|
|
}
|
|
|
|
public func doPACE( mrzKey : String ) async throws {
|
|
guard isPACESupported else {
|
|
throw NFCPassportReaderError.NotYetSupported( "PACE not supported" )
|
|
}
|
|
|
|
Log.info( "Performing PACE with \(paceInfo.getProtocolOIDString())" )
|
|
|
|
paceOID = paceInfo.getObjectIdentifier()
|
|
parameterSpec = try paceInfo.getParameterSpec()
|
|
|
|
mappingType = try paceInfo.getMappingType() // Either GM, CAM, or IM.
|
|
agreementAlg = try paceInfo.getKeyAgreementAlgorithm() // Either DH or ECDH.
|
|
cipherAlg = try paceInfo.getCipherAlgorithm() // Either DESede or AES.
|
|
digestAlg = try paceInfo.getDigestAlgorithm() // Either SHA-1 or SHA-256.
|
|
keyLength = try paceInfo.getKeyLength() // Get key length the enc cipher. Either 128, 192, or 256.
|
|
|
|
paceKeyType = PACEHandler.MRZ_PACE_KEY_REFERENCE
|
|
paceKey = try createPaceKey( from: mrzKey )
|
|
|
|
// Temporary logging
|
|
Log.verbose("doPace - inpit parameters" )
|
|
Log.verbose("paceOID - \(paceOID)" )
|
|
Log.verbose("parameterSpec - \(parameterSpec)" )
|
|
Log.verbose("mappingType - \(mappingType!)" )
|
|
Log.verbose("agreementAlg - \(agreementAlg)" )
|
|
Log.verbose("cipherAlg - \(cipherAlg)" )
|
|
Log.verbose("digestAlg - \(digestAlg)" )
|
|
Log.verbose("keyLength - \(keyLength)" )
|
|
Log.verbose("keyLength - \(mrzKey)" )
|
|
Log.verbose("paceKey - \(binToHexRep(paceKey, asArray:true))" )
|
|
|
|
// First start the initial auth call
|
|
_ = try await tagReader.sendMSESetATMutualAuth(oid: paceOID, keyType: paceKeyType)
|
|
|
|
let decryptedNonce = try await self.doStep1()
|
|
let ephemeralParams = try await self.doStep2(passportNonce: decryptedNonce)
|
|
let (ephemeralKeyPair, passportPublicKey) = try await self.doStep3KeyExchange(ephemeralParams: ephemeralParams)
|
|
let (encKey, macKey) = try await self.doStep4KeyAgreement( pcdKeyPair: ephemeralKeyPair, passportPublicKey: passportPublicKey)
|
|
try self.paceCompleted( ksEnc: encKey, ksMac: macKey )
|
|
Log.debug("PACE SUCCESSFUL" )
|
|
}
|
|
|
|
/// Handles an error during the PACE process
|
|
/// Logs and stoes the error and returns false to the caller
|
|
/// - Parameters:
|
|
/// - stage: Where in the PACE process the error occurred
|
|
/// - error: The error message
|
|
func handleError( _ stage: String, _ error: String, needToTerminateGA: Bool = false ) {
|
|
Log.error( "PACEHandler: \(stage) - \(error)" )
|
|
Log.error( " OpenSSLError: \(OpenSSLUtils.getOpenSSLError())" )
|
|
self.paceError = "\(stage) - \(error)"
|
|
//self.completedHandler?( false )
|
|
|
|
/*
|
|
if needToTerminateGA {
|
|
// This is to fix some passports that don't automatically terminate command chaining!
|
|
// No idea if this is the correct way to do it but testing.....
|
|
let terminateGA = wrapDO(b:0x83, arr:[0x00])
|
|
tagReader.sendGeneralAuthenticate(data:terminateGA, isLast:true, completed: { [weak self] response, error in
|
|
self?.completedHandler?( false )
|
|
})
|
|
} else {
|
|
self.completedHandler?( false )
|
|
}
|
|
*/
|
|
}
|
|
|
|
/// Performs PACE Step 1- receives an encrypted nonce from the passport and decypts it with the PACE key - derived from MRZ, CAN (not yet supported)
|
|
func doStep1() async throws -> [UInt8] {
|
|
Log.debug("Doing PACE Step1...")
|
|
let response = try await tagReader.sendGeneralAuthenticate(data: [], isLast: false)
|
|
|
|
let data = response.data
|
|
let encryptedNonce = try unwrapDO(tag: 0x80, wrappedData: data)
|
|
Log.verbose( "Encrypted nonce - \(binToHexRep(encryptedNonce, asArray:true))" )
|
|
|
|
let decryptedNonce: [UInt8]
|
|
if self.cipherAlg == "DESede" {
|
|
let iv = [UInt8](repeating:0, count: 8)
|
|
decryptedNonce = tripleDESDecrypt(key: self.paceKey, message: encryptedNonce, iv: iv)
|
|
} else if self.cipherAlg == "AES" {
|
|
let iv = [UInt8](repeating:0, count: 16)
|
|
decryptedNonce = AESDecrypt(key: self.paceKey, message: encryptedNonce, iv: iv)
|
|
} else {
|
|
throw NFCPassportReaderError.UnsupportedCipherAlgorithm
|
|
}
|
|
|
|
Log.verbose( "Decrypted nonce - \(binToHexRep(decryptedNonce, asArray:true) )" )
|
|
return decryptedNonce
|
|
}
|
|
|
|
|
|
/// Performs PACE Step 2 - computes ephemeral parameters by mapping the nonce received from the passport
|
|
/// (and if IM used the nonce generated by us)
|
|
///
|
|
/// Using the supported
|
|
/// - Parameters:
|
|
/// - passportNonce: The decrypted nonce received from the passport
|
|
func doStep2( passportNonce: [UInt8]) async throws -> OpaquePointer {
|
|
Log.debug( "Doing PACE Step2...")
|
|
switch(mappingType) {
|
|
case .CAM, .GM:
|
|
Log.debug( " Using General Mapping (GM)...")
|
|
return try await doPACEStep2GM(passportNonce: passportNonce)
|
|
case .IM:
|
|
Log.debug( " Using Integrated Mapping (IM)...")
|
|
return try await doPACEStep2IM(passportNonce: passportNonce)
|
|
default:
|
|
throw NFCPassportReaderError.PACEError( "Step2GM", "Unsupported Mapping Type" )
|
|
}
|
|
|
|
}
|
|
|
|
/// Performs PACEStep 2 using Generic Mapping
|
|
///
|
|
/// Using the supported
|
|
/// - Parameters:
|
|
/// - passportNonce: The decrypted nonce received from the passport
|
|
func doPACEStep2GM(passportNonce : [UInt8]) async throws -> OpaquePointer {
|
|
|
|
let mappingKey : OpaquePointer
|
|
mappingKey = try self.paceInfo.createMappingKey( )
|
|
|
|
guard let pcdMappingEncodedPublicKey = OpenSSLUtils.getPublicKeyData(from: mappingKey) else {
|
|
throw NFCPassportReaderError.PACEError( "Step2GM", "Unable to get public key from mapping key")
|
|
}
|
|
Log.verbose( "public mapping key - \(binToHexRep(pcdMappingEncodedPublicKey, asArray:true))")
|
|
|
|
Log.debug( "Sending public mapping key to passport..")
|
|
let step2Data = wrapDO(b:0x81, arr:pcdMappingEncodedPublicKey)
|
|
let response = try await tagReader.sendGeneralAuthenticate(data:step2Data, isLast:false)
|
|
|
|
let piccMappingEncodedPublicKey = try unwrapDO(tag: 0x82, wrappedData: response.data)
|
|
|
|
Log.debug( "Received passports public mapping key")
|
|
Log.verbose( " public mapping key - \(binToHexRep(piccMappingEncodedPublicKey, asArray: true))")
|
|
|
|
// Do mapping agreement
|
|
|
|
// First, Convert nonce to BIGNUM
|
|
guard let bn_nonce = BN_bin2bn(passportNonce, Int32(passportNonce.count), nil) else {
|
|
throw NFCPassportReaderError.PACEError( "Step2GM", "Unable to convert picc nonce to bignum" )
|
|
}
|
|
defer { BN_free(bn_nonce) }
|
|
|
|
// ephmeralParams are free'd in stage 3
|
|
let ephemeralParams : OpaquePointer
|
|
if self.agreementAlg == "DH" {
|
|
Log.debug( "Doing DH Mapping agreement")
|
|
ephemeralParams = try self.doDHMappingAgreement(mappingKey: mappingKey, passportPublicKeyData: piccMappingEncodedPublicKey, nonce: bn_nonce )
|
|
} else if self.agreementAlg == "ECDH" {
|
|
Log.debug( "Doing ECDH Mapping agreement")
|
|
ephemeralParams = try self.doECDHMappingAgreement(mappingKey: mappingKey, passportPublicKeyData: piccMappingEncodedPublicKey, nonce: bn_nonce )
|
|
} else {
|
|
throw NFCPassportReaderError.PACEError( "Step2GM", "Unsupported agreement algorithm" )
|
|
}
|
|
|
|
// Need to free the mapping key we created now
|
|
EVP_PKEY_free(mappingKey)
|
|
return ephemeralParams
|
|
}
|
|
|
|
func doPACEStep2IM( passportNonce: [UInt8] ) async throws -> OpaquePointer {
|
|
// Not implemented yet
|
|
throw NFCPassportReaderError.PACEError( "Step2IM", "IM not yet implemented" )
|
|
}
|
|
|
|
/// Generates an ephemeral public/private key pair based on mapping parameters from step 2, and then sends
|
|
/// the public key to the passport and receives its ephmeral public key in exchange
|
|
/// - Parameters:
|
|
/// - ephemeralParams: The ehpemeral mapping keys generated by step2
|
|
/// - Returns:
|
|
/// - Tuple of Generated Ephemeral KeyPair and the Passport's public key
|
|
func doStep3KeyExchange(ephemeralParams: OpaquePointer) async throws -> (OpaquePointer, OpaquePointer) {
|
|
Log.debug( "Doing PACE Step3 - Key Exchange")
|
|
|
|
// Generate ephemeral keypair from ephemeralParams
|
|
var ephKeyPair : OpaquePointer? = nil
|
|
let pctx = EVP_PKEY_CTX_new(ephemeralParams, nil)
|
|
EVP_PKEY_keygen_init(pctx)
|
|
EVP_PKEY_keygen(pctx, &ephKeyPair)
|
|
EVP_PKEY_CTX_free(pctx)
|
|
|
|
guard let ephemeralKeyPair = ephKeyPair else {
|
|
throw NFCPassportReaderError.PACEError( "Step3 KeyEx", "Unable to get create ephermeral key pair" )
|
|
}
|
|
|
|
Log.debug( "Generated Ephemeral key pair")
|
|
|
|
// We've finished with the ephemeralParams now - we can now free it
|
|
EVP_PKEY_free( ephemeralParams )
|
|
|
|
guard let publicKey = OpenSSLUtils.getPublicKeyData( from: ephemeralKeyPair ) else {
|
|
throw NFCPassportReaderError.PACEError( "Step3 KeyEx", "Unable to get public key from ephermeral key pair" )
|
|
}
|
|
Log.verbose( "Ephemeral public key - \(binToHexRep(publicKey, asArray: true))")
|
|
|
|
// exchange public keys
|
|
Log.debug( "Sending ephemeral public key to passport")
|
|
let step3Data = wrapDO(b:0x83, arr:publicKey)
|
|
let response = try await tagReader.sendGeneralAuthenticate(data:step3Data, isLast:false)
|
|
let passportEncodedPublicKey = try? unwrapDO(tag: 0x84, wrappedData: response.data)
|
|
guard let passportPublicKey = OpenSSLUtils.decodePublicKeyFromBytes(pubKeyData: passportEncodedPublicKey!, params: ephemeralKeyPair) else {
|
|
throw NFCPassportReaderError.PACEError( "Step3 KeyEx", "Unable to decode passports ephemeral key" )
|
|
}
|
|
|
|
Log.verbose( "Received passports ephemeral public key - \(binToHexRep(passportEncodedPublicKey!, asArray: true))" )
|
|
return (ephemeralKeyPair, passportPublicKey)
|
|
}
|
|
|
|
/// This performs PACE Step 4 - Key Agreement.
|
|
/// Here the shared secret is computed from our ephemeral private key and the passports ephemeral public key
|
|
/// The new secure messaging (ksEnc and ksMac) keys are computed from the shared secret
|
|
/// An authentication token is generated from the passports public key and the computed ksMac key
|
|
/// Then, the authetication token is send to the passport, it returns its own computed authentication token
|
|
/// We then compute an expected authentication token from the ksMac key and our ephemeral public key
|
|
/// Finally we compare the recieved auth token to the expected token and if they are the same then PACE has succeeded!
|
|
/// - Parameters:
|
|
/// - pcdKeyPair: our ephemeral key pair
|
|
/// - passportPublicKey: passports ephemeral public key
|
|
/// - Returns:
|
|
/// - Tuple of KSEnc KSMac
|
|
func doStep4KeyAgreement( pcdKeyPair: OpaquePointer, passportPublicKey: OpaquePointer) async throws -> ([UInt8], [UInt8]) {
|
|
Log.debug( "Doing PACE Step4 Key Agreement...")
|
|
|
|
Log.debug( "Computing shared secret...")
|
|
let sharedSecret = OpenSSLUtils.computeSharedSecret(privateKeyPair: pcdKeyPair, publicKey: passportPublicKey)
|
|
Log.verbose( "Shared secret - \(binToHexRep(sharedSecret, asArray:true))")
|
|
|
|
Log.debug( "Deriving ksEnc and ksMac keys from shared secret")
|
|
let gen = SecureMessagingSessionKeyGenerator()
|
|
let encKey = try! gen.deriveKey(keySeed: sharedSecret, cipherAlgName: cipherAlg, keyLength: keyLength, mode: .ENC_MODE)
|
|
let macKey = try! gen.deriveKey(keySeed: sharedSecret, cipherAlgName: cipherAlg, keyLength: keyLength, mode: .MAC_MODE)
|
|
Log.verbose( "encKey - \(binToHexRep(encKey, asArray:true))")
|
|
Log.verbose( "macKey - \(binToHexRep(macKey, asArray:true))")
|
|
|
|
// Step 4 - generate authentication token
|
|
Log.debug( "Generating authentication token")
|
|
guard let pcdAuthToken = try? generateAuthenticationToken( publicKey: passportPublicKey, macKey: macKey) else {
|
|
throw NFCPassportReaderError.PACEError( "Step3 KeyAgreement", "Unable to generate authentication token using passports public key" )
|
|
}
|
|
Log.verbose( "authentication token - \(pcdAuthToken)")
|
|
|
|
Log.debug( "Sending auth token to passport")
|
|
let step4Data = wrapDO(b:0x85, arr:pcdAuthToken)
|
|
let response = try await tagReader.sendGeneralAuthenticate(data:step4Data, isLast:true)
|
|
|
|
let tvlResp = TKBERTLVRecord.sequenceOfRecords(from: Data(response.data))!
|
|
if tvlResp[0].tag != 0x86 {
|
|
Log.warning("Was expecting tag 0x86, found: \(binToHex(UInt8(tvlResp[0].tag)))")
|
|
}
|
|
// Calculate expected authentication token
|
|
let expectedPICCToken = try self.generateAuthenticationToken( publicKey: pcdKeyPair, macKey: macKey)
|
|
|
|
Log.verbose( "Expecting authentication token from passport - \(expectedPICCToken)")
|
|
|
|
let piccToken = [UInt8](tvlResp[0].value)
|
|
Log.verbose( "Received authentication token from passport - \(piccToken)")
|
|
|
|
guard piccToken == expectedPICCToken else {
|
|
Log.error( "Error PICC Token mismatch!\npicToken - \(piccToken)\nexpectedPICCToken - \(expectedPICCToken)" )
|
|
throw NFCPassportReaderError.PACEError( "Step3 KeyAgreement", "Error PICC Token mismatch!\npicToken - \(piccToken)\nexpectedPICCToken - \(expectedPICCToken)" )
|
|
}
|
|
|
|
Log.debug( "Auth token from passport matches expected token!" )
|
|
|
|
// This will be added for CAM when supported
|
|
// var encryptedChipAuthenticationData : [UInt8]? = nil
|
|
// if (sself.mappingType == PACEMappingType.CAM) {
|
|
// if tvlResp[1].tag != 0x8A {
|
|
// Log.warning("CAM: Was expecting tag 0x86, found: \(binToHex(UInt8(tvlResp[1].tag)))")
|
|
// }
|
|
// encryptedChipAuthenticationData = [UInt8](tvlResp[1].value)
|
|
// }
|
|
|
|
// We're done!
|
|
return (encKey, macKey)
|
|
}
|
|
|
|
/// Called once PACE has completed with the newly generated ksEnc and ksMac keys for restarting secure messaging
|
|
/// - Parameters:
|
|
/// - ksEnc: the computed encryption key derived from the key agreement
|
|
/// - ksMac: the computed mac key derived from the key agreement
|
|
func paceCompleted( ksEnc: [UInt8], ksMac: [UInt8] ) throws {
|
|
// Restart secure messaging
|
|
let ssc = withUnsafeBytes(of: 0.bigEndian, Array.init)
|
|
if (cipherAlg.hasPrefix("DESede")) {
|
|
Log.info( "Restarting secure messaging using DESede encryption")
|
|
let sm = SecureMessaging(encryptionAlgorithm: .DES, ksenc: ksEnc, ksmac: ksMac, ssc: ssc)
|
|
tagReader.secureMessaging = sm
|
|
} else if (cipherAlg.hasPrefix("AES")) {
|
|
Log.info( "Restarting secure messaging using AES encryption")
|
|
let sm = SecureMessaging(encryptionAlgorithm: .AES, ksenc: ksEnc, ksmac: ksMac, ssc: ssc)
|
|
tagReader.secureMessaging = sm
|
|
} else {
|
|
throw NFCPassportReaderError.PACEError( "PACECompleted", "Not restarting secure messaging as unsupported cipher algorithm requested - \(cipherAlg)" )
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK - PACEHandler Utility functions
|
|
@available(iOS 15, *)
|
|
extension PACEHandler {
|
|
|
|
/// Does the DH key Mapping agreement
|
|
/// - Parameter mappingKey - Pointer to an EVP_PKEY structure containing the mapping key
|
|
/// - Parameter passportPublicKeyData - byte array containing the publick key read from the passport
|
|
/// - Parameter nonce - Pointer to an BIGNUM structure containing the unencrypted nonce
|
|
/// - Returns the EVP_PKEY containing the mapped ephemeral parameters
|
|
func doDHMappingAgreement( mappingKey : OpaquePointer, passportPublicKeyData: [UInt8], nonce: OpaquePointer ) throws -> OpaquePointer {
|
|
guard let dh_mapping_key = EVP_PKEY_get1_DH(mappingKey) else {
|
|
// Error
|
|
throw PACEHandlerError.DHKeyAgreementError( "Unable to get DH mapping key" )
|
|
}
|
|
|
|
// Compute the shared secret using the mapping key and the passports public mapping key
|
|
let bn = BN_bin2bn(passportPublicKeyData, Int32(passportPublicKeyData.count), nil)
|
|
defer { BN_free( bn ) }
|
|
|
|
var secret = [UInt8](repeating: 0, count: Int(DH_size(dh_mapping_key)))
|
|
DH_compute_key( &secret, bn, dh_mapping_key)
|
|
|
|
// Convert the secret to a bignum
|
|
let bn_h = BN_bin2bn(secret, Int32(secret.count), nil)
|
|
defer { BN_clear_free(bn_h) }
|
|
|
|
// Initialize ephemeral parameters with parameters from the mapping key
|
|
guard let ephemeral_key = DHparams_dup(dh_mapping_key) else {
|
|
// Error
|
|
throw PACEHandlerError.DHKeyAgreementError("Unable to get initialise ephemeral parameters from DH mapping key")
|
|
}
|
|
defer{ DH_free(ephemeral_key) }
|
|
|
|
var p : OpaquePointer? = nil
|
|
var q : OpaquePointer? = nil
|
|
var g : OpaquePointer? = nil
|
|
DH_get0_pqg(dh_mapping_key, &p, &q, &g)
|
|
|
|
// map to new generator
|
|
guard let bn_g = BN_new() else {
|
|
throw PACEHandlerError.DHKeyAgreementError( "Unable to create bn_g" )
|
|
}
|
|
defer{ BN_free(bn_g) }
|
|
guard let new_g = BN_new() else {
|
|
throw PACEHandlerError.DHKeyAgreementError( "Unable to create new_g" )
|
|
}
|
|
defer{ BN_free(new_g) }
|
|
|
|
// bn_g = g^nonce mod p
|
|
// ephemeral_key->g = bn_g mod p * h => (g^nonce mod p) * h mod p
|
|
let bn_ctx = BN_CTX_new()
|
|
guard BN_mod_exp(bn_g, g, nonce, p, bn_ctx) == 1,
|
|
BN_mod_mul(new_g, bn_g, bn_h, p, bn_ctx) == 1 else {
|
|
// Error
|
|
throw PACEHandlerError.DHKeyAgreementError( "Failed to generate new parameters" )
|
|
}
|
|
|
|
guard DH_set0_pqg(ephemeral_key, BN_dup(p), BN_dup(q), BN_dup(new_g)) == 1 else {
|
|
// Error
|
|
throw PACEHandlerError.DHKeyAgreementError( "Unable to set DH pqg paramerters" )
|
|
}
|
|
|
|
// Set the ephemeral params
|
|
guard let ephemeralParams = EVP_PKEY_new() else {
|
|
throw PACEHandlerError.ECDHKeyAgreementError( "Unable to create ephemeral params" )
|
|
}
|
|
|
|
guard EVP_PKEY_set1_DH(ephemeralParams, ephemeral_key) == 1 else {
|
|
// Error
|
|
EVP_PKEY_free( ephemeralParams )
|
|
throw PACEHandlerError.DHKeyAgreementError( "Unable to set ephemeral parameters" )
|
|
}
|
|
return ephemeralParams
|
|
}
|
|
|
|
/// Does the ECDH key Mapping agreement
|
|
/// - Parameter mappingKey - Pointer to an EVP_PKEY structure containing the mapping key
|
|
/// - Parameter passportPublicKeyData - byte array containing the publick key read from the passport
|
|
/// - Parameter nonce - Pointer to an BIGNUM structure containing the unencrypted nonce
|
|
/// - Returns the EVP_PKEY containing the mapped ephemeral parameters
|
|
func doECDHMappingAgreement( mappingKey : OpaquePointer, passportPublicKeyData: [UInt8], nonce: OpaquePointer ) throws -> OpaquePointer {
|
|
|
|
let ec_mapping_key = EVP_PKEY_get1_EC_KEY(mappingKey)
|
|
|
|
guard let group = EC_GROUP_dup(EC_KEY_get0_group(ec_mapping_key)) else {
|
|
// Error
|
|
throw PACEHandlerError.ECDHKeyAgreementError( "Unable to get EC group" )
|
|
}
|
|
defer { EC_GROUP_free(group) }
|
|
|
|
guard let order = BN_new() else {
|
|
// Error
|
|
throw PACEHandlerError.ECDHKeyAgreementError( "Unable to create order bignum" )
|
|
}
|
|
defer { BN_free( order ) }
|
|
|
|
guard let cofactor = BN_new() else {
|
|
// error
|
|
throw PACEHandlerError.ECDHKeyAgreementError( "Unable to create cofactor bignum" )
|
|
}
|
|
defer { BN_free( cofactor ) }
|
|
|
|
guard EC_GROUP_get_order(group, order, nil) == 1 ||
|
|
EC_GROUP_get_cofactor(group, cofactor, nil) == 1 else {
|
|
// Handle error
|
|
throw PACEHandlerError.ECDHKeyAgreementError( "Unable to get order or cofactor from group" )
|
|
}
|
|
|
|
// Create the shared secret in the form of a ECPoint
|
|
|
|
// Ideally I'd use OpenSSLUtls.computeSharedSecret for this but for reasons as yet unknown, it only returns the first 32 bytes
|
|
// NOT the full 64 bytes (would then convert to 65 with e header of 4 for uncompressed)
|
|
guard let sharedSecretMappingPoint = self.computeECDHMappingKeyPoint(privateKey: mappingKey, inputKey: passportPublicKeyData) else {
|
|
// Error
|
|
throw PACEHandlerError.ECDHKeyAgreementError( "Failed to compute new shared secret mapping point from mapping key and passport public mapping key" )
|
|
}
|
|
defer { EC_POINT_free( sharedSecretMappingPoint ) }
|
|
|
|
// Map the nonce using Generic mapping to get the new parameters (inc a new generator)
|
|
guard let newGenerater = EC_POINT_new(group) else {
|
|
throw PACEHandlerError.ECDHKeyAgreementError( "Unable to create new mapping generator point" )
|
|
}
|
|
defer{ EC_POINT_free(newGenerater) }
|
|
|
|
// g = (generator * nonce) + (sharedSecretMappingPoint * 1)
|
|
guard EC_POINT_mul(group, newGenerater, nonce, sharedSecretMappingPoint, BN_value_one(), nil) == 1 else {
|
|
throw PACEHandlerError.ECDHKeyAgreementError( "Failed to map nonce to get new generator params" )
|
|
}
|
|
|
|
// Initialize ephemeral parameters with parameters from the mapping key
|
|
guard let ephemeralParams = EVP_PKEY_new() else {
|
|
throw PACEHandlerError.ECDHKeyAgreementError( "Unable to create ephemeral params" )
|
|
}
|
|
|
|
let ephemeral_key = EC_KEY_dup(ec_mapping_key)
|
|
defer{ EC_KEY_free(ephemeral_key) }
|
|
|
|
// configure the new EC_KEY
|
|
guard EVP_PKEY_set1_EC_KEY(ephemeralParams, ephemeral_key) == 1,
|
|
EC_GROUP_set_generator(group, newGenerater, order, cofactor) == 1,
|
|
EC_GROUP_check(group, nil) == 1,
|
|
EC_KEY_set_group(ephemeral_key, group) == 1 else {
|
|
// Error
|
|
|
|
EVP_PKEY_free( ephemeralParams )
|
|
throw PACEHandlerError.ECDHKeyAgreementError( "Unable to configure new ephemeral params" )
|
|
}
|
|
return ephemeralParams
|
|
}
|
|
|
|
/// Generate Authentication token from a publicKey and and a mac key
|
|
/// - Parameters:
|
|
/// - publicKey: An EVP_PKEY structure containing a public key data which will be used to generate the auth code
|
|
/// - macKey: The mac key derived from the key agreement
|
|
/// - Throws: An error if we are unable to encode the public key data
|
|
/// - Returns: The authentication token (8 bytes)
|
|
func generateAuthenticationToken( publicKey: OpaquePointer, macKey: [UInt8] ) throws -> [UInt8] {
|
|
var encodedPublicKeyData = try encodePublicKey(oid:self.paceOID, key:publicKey)
|
|
|
|
if cipherAlg == "DESede" {
|
|
// If DESede (3DES), we need to pad the data
|
|
encodedPublicKeyData = pad(encodedPublicKeyData, blockSize: 8)
|
|
}
|
|
|
|
Log.verbose( "Generating Authentication Token" )
|
|
Log.verbose( "EncodedPubKey = \(binToHexRep(encodedPublicKeyData, asArray: true))" )
|
|
Log.verbose( "macKey = \(binToHexRep(macKey, asArray: true))" )
|
|
|
|
let maccedPublicKeyDataObject = mac(algoName: cipherAlg == "DESede" ? .DES : .AES, key: macKey, msg: encodedPublicKeyData)
|
|
|
|
// Take 8 bytes for auth token
|
|
let authToken = [UInt8](maccedPublicKeyDataObject[0..<8])
|
|
Log.verbose( "Generated authToken = \(binToHexRep(authToken, asArray: true))" )
|
|
return authToken
|
|
}
|
|
|
|
/// Encodes a PublicKey as an TLV strucuture based on TR-SAC 1.01 4.5.1 and 4.5.2
|
|
/// - Parameters:
|
|
/// - oid: The object identifier specifying the key type
|
|
/// - key: The ECP_PKEY public key to encode
|
|
/// - Throws: Error if unable to encode
|
|
/// - Returns: the encoded public key in tlv format
|
|
func encodePublicKey( oid : String, key : OpaquePointer ) throws -> [UInt8] {
|
|
let encodedOid = oidToBytes(oid:oid, replaceTag: false)
|
|
guard let pubKeyData = OpenSSLUtils.getPublicKeyData(from: key) else {
|
|
Log.error( "PACEHandler: encodePublicKey() - Unable to get public key data" )
|
|
throw NFCPassportReaderError.InvalidDataPassed("Unable to get public key data")
|
|
}
|
|
|
|
let keyType = EVP_PKEY_base_id( key )
|
|
let tag : TKTLVTag
|
|
if keyType == EVP_PKEY_DH || keyType == EVP_PKEY_DHX {
|
|
tag = 0x84
|
|
} else {
|
|
tag = 0x86
|
|
}
|
|
|
|
guard let encOid = TKBERTLVRecord(from: Data(encodedOid)) else {
|
|
throw NFCPassportReaderError.InvalidASN1Value
|
|
}
|
|
let encPub = TKBERTLVRecord(tag:tag, value: Data(pubKeyData))
|
|
let record = TKBERTLVRecord(tag: 0x7F49, records:[encOid, encPub])
|
|
let data = record.data
|
|
|
|
return [UInt8](data)
|
|
}
|
|
|
|
/// Computes a key seed based on an MRZ key
|
|
/// - Parameter the mrz key
|
|
/// - Returns a encoded key based on the mrz key that can be used for PACE
|
|
func createPaceKey( from mrzKey: String ) throws -> [UInt8] {
|
|
let buf: [UInt8] = Array(mrzKey.utf8)
|
|
let hash = calcSHA1Hash(buf)
|
|
|
|
let smskg = SecureMessagingSessionKeyGenerator()
|
|
let key = try smskg.deriveKey(keySeed: hash, cipherAlgName: cipherAlg, keyLength: keyLength, nonce: nil, mode: .PACE_MODE, paceKeyReference: paceKeyType)
|
|
return key
|
|
}
|
|
|
|
/// Performs the ECDH PACE GM key agreement protocol by multiplying a private key with a public key
|
|
/// - Parameters:
|
|
/// - key: an EVP_PKEY structure containng a ECDH private key
|
|
/// - inputKey: a public key
|
|
/// - Returns: a new EC_POINT
|
|
func computeECDHMappingKeyPoint( privateKey : OpaquePointer, inputKey : [UInt8] ) -> OpaquePointer? {
|
|
|
|
let ecdh = EVP_PKEY_get1_EC_KEY(privateKey)
|
|
defer { EC_KEY_free(ecdh) }
|
|
|
|
let privateECKey = EC_KEY_get0_private_key(ecdh) // BIGNUM
|
|
|
|
// decode public key
|
|
guard let group = EC_KEY_get0_group(ecdh) else{ return nil }
|
|
guard let ecp = EC_POINT_new(group) else { return nil }
|
|
defer { EC_POINT_free(ecp) }
|
|
guard EC_POINT_oct2point(group, ecp, inputKey, inputKey.count,nil) != 0 else { return nil }
|
|
|
|
// create our output point
|
|
let output = EC_POINT_new(group)
|
|
|
|
// Multiply our private key with the passports public key to get a new point
|
|
EC_POINT_mul(group, output, nil, ecp, privateECKey, nil)
|
|
|
|
return output
|
|
}
|
|
}
|
|
|
|
#endif
|