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