mirror of
https://github.com/selfxyz/self.git
synced 2026-01-23 05:28:02 -05:00
@@ -1,185 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
#if !os(macOS)
|
||||
import CoreNFC
|
||||
|
||||
@available(iOS 15, *)
|
||||
public class BACHandler {
|
||||
let KENC : [UInt8] = [0,0,0,1]
|
||||
let KMAC : [UInt8] = [0,0,0,2]
|
||||
|
||||
public var ksenc : [UInt8] = []
|
||||
public var ksmac : [UInt8] = []
|
||||
|
||||
var rnd_icc : [UInt8] = []
|
||||
var rnd_ifd : [UInt8] = []
|
||||
public var kifd : [UInt8] = []
|
||||
|
||||
var tagReader : TagReader?
|
||||
|
||||
public init() {
|
||||
// For testing only
|
||||
}
|
||||
|
||||
public init(tagReader: TagReader) {
|
||||
self.tagReader = tagReader
|
||||
}
|
||||
|
||||
public func performBACAndGetSessionKeys( mrzKey : String ) async throws {
|
||||
guard let tagReader = self.tagReader else {
|
||||
throw NFCPassportReaderError.NoConnectedTag
|
||||
}
|
||||
|
||||
Log.debug( "BACHandler - deriving Document Basic Access Keys" )
|
||||
_ = try self.deriveDocumentBasicAccessKeys(mrz: mrzKey)
|
||||
|
||||
// Make sure we clear secure messaging (could happen if we read an invalid DG or we hit a secure error
|
||||
tagReader.secureMessaging = nil
|
||||
|
||||
// get Challenge
|
||||
Log.debug( "BACHandler - Getting initial challenge" )
|
||||
let response = try await tagReader.getChallenge()
|
||||
|
||||
Log.verbose( "DATA - \(response.data)" )
|
||||
|
||||
Log.debug( "BACHandler - Doing mutual authentication" )
|
||||
let cmd_data = self.authentication(rnd_icc: [UInt8](response.data))
|
||||
let maResponse = try await tagReader.doMutualAuthentication(cmdData: Data(cmd_data))
|
||||
Log.debug( "DATA - \(maResponse.data)" )
|
||||
guard maResponse.data.count > 0 else {
|
||||
throw NFCPassportReaderError.InvalidMRZKey
|
||||
}
|
||||
|
||||
let (KSenc, KSmac, ssc) = try self.sessionKeys(data: [UInt8](maResponse.data))
|
||||
tagReader.secureMessaging = SecureMessaging(ksenc: KSenc, ksmac: KSmac, ssc: ssc)
|
||||
Log.debug( "BACHandler - complete" )
|
||||
}
|
||||
|
||||
|
||||
func deriveDocumentBasicAccessKeys(mrz: String) throws -> ([UInt8], [UInt8]) {
|
||||
let kseed = generateInitialKseed(kmrz:mrz)
|
||||
|
||||
Log.verbose("Calculate the Basic Access Keys (Kenc and Kmac) using TR-SAC 1.01, 4.2")
|
||||
let smskg = SecureMessagingSessionKeyGenerator()
|
||||
self.ksenc = try smskg.deriveKey(keySeed: kseed, mode: .ENC_MODE)
|
||||
self.ksmac = try smskg.deriveKey(keySeed: kseed, mode: .MAC_MODE)
|
||||
|
||||
return (ksenc, ksmac)
|
||||
}
|
||||
|
||||
///
|
||||
/// Calculate the kseed from the kmrz:
|
||||
/// - Calculate a SHA-1 hash of the kmrz
|
||||
/// - Take the most significant 16 bytes to form the Kseed.
|
||||
/// @param kmrz: The MRZ information
|
||||
/// @type kmrz: a string
|
||||
/// @return: a 16 bytes string
|
||||
///
|
||||
/// - Parameter kmrz: mrz key
|
||||
/// - Returns: first 16 bytes of the mrz SHA1 hash
|
||||
///
|
||||
func generateInitialKseed(kmrz : String ) -> [UInt8] {
|
||||
|
||||
Log.verbose("Calculate the SHA-1 hash of MRZ_information")
|
||||
Log.verbose("\tMRZ KEY - \(kmrz)")
|
||||
let hash = calcSHA1Hash( [UInt8](kmrz.data(using:.utf8)!) )
|
||||
|
||||
Log.verbose("\tsha1(MRZ_information): \(binToHexRep(hash))")
|
||||
|
||||
let subHash = Array(hash[0..<16])
|
||||
Log.verbose("Take the most significant 16 bytes to form the Kseed")
|
||||
Log.verbose("\tKseed: \(binToHexRep(subHash))" )
|
||||
|
||||
return Array(subHash)
|
||||
}
|
||||
|
||||
|
||||
/// Construct the command data for the mutual authentication.
|
||||
/// - Request an 8 byte random number from the MRTD's chip (rnd.icc)
|
||||
/// - Generate an 8 byte random (rnd.ifd) and a 16 byte random (kifd)
|
||||
/// - Concatenate rnd.ifd, rnd.icc and kifd (s = rnd.ifd + rnd.icc + kifd)
|
||||
/// - Encrypt it with TDES and the Kenc key (eifd = TDES(s, Kenc))
|
||||
/// - Compute the MAC over eifd with TDES and the Kmax key (mifd = mac(pad(eifd))
|
||||
/// - Construct the APDU data for the mutualAuthenticate command (cmd_data = eifd + mifd)
|
||||
///
|
||||
/// @param rnd_icc: The challenge received from the ICC.
|
||||
/// @type rnd_icc: A 8 bytes binary string
|
||||
/// @return: The APDU binary data for the mutual authenticate command
|
||||
func authentication( rnd_icc : [UInt8]) -> [UInt8] {
|
||||
self.rnd_icc = rnd_icc
|
||||
|
||||
Log.verbose("Request an 8 byte random number from the MRTD's chip")
|
||||
Log.verbose("\tRND.ICC: " + binToHexRep(self.rnd_icc))
|
||||
|
||||
self.rnd_icc = rnd_icc
|
||||
|
||||
let rnd_ifd = generateRandomUInt8Array(8)
|
||||
let kifd = generateRandomUInt8Array(16)
|
||||
|
||||
Log.verbose("Generate an 8 byte random and a 16 byte random")
|
||||
Log.verbose("\tRND.IFD: \(binToHexRep(rnd_ifd))" )
|
||||
Log.verbose("\tRND.Kifd: \(binToHexRep(kifd))")
|
||||
|
||||
let s = rnd_ifd + rnd_icc + kifd
|
||||
|
||||
Log.verbose("Concatenate RND.IFD, RND.ICC and Kifd")
|
||||
Log.verbose("\tS: \(binToHexRep(s))")
|
||||
|
||||
let iv : [UInt8] = [0, 0, 0, 0, 0, 0, 0, 0]
|
||||
let eifd = tripleDESEncrypt(key: ksenc,message: s, iv: iv)
|
||||
|
||||
Log.verbose("Encrypt S with TDES key Kenc as calculated in Appendix 5.2")
|
||||
Log.verbose("\tEifd: \(binToHexRep(eifd))")
|
||||
|
||||
let mifd = mac(algoName: .DES, key: ksmac, msg: pad(eifd, blockSize:8))
|
||||
|
||||
Log.verbose("Compute MAC over eifd with TDES key Kmac as calculated in-Appendix 5.2")
|
||||
Log.verbose("\tMifd: \(binToHexRep(mifd))")
|
||||
// Construct APDU
|
||||
|
||||
let cmd_data = eifd + mifd
|
||||
Log.verbose("Construct command data for MUTUAL AUTHENTICATE")
|
||||
Log.verbose("\tcmd_data: \(binToHexRep(cmd_data))")
|
||||
|
||||
self.rnd_ifd = rnd_ifd
|
||||
self.kifd = kifd
|
||||
|
||||
return cmd_data
|
||||
}
|
||||
|
||||
/// Calculate the session keys (KSenc, KSmac) and the SSC from the data
|
||||
/// received by the mutual authenticate command.
|
||||
|
||||
/// @param data: the data received from the mutual authenticate command send to the chip.
|
||||
/// @type data: a binary string
|
||||
/// @return: A set of two 16 bytes keys (KSenc, KSmac) and the SSC
|
||||
public func sessionKeys(data : [UInt8] ) throws -> ([UInt8], [UInt8], [UInt8]) {
|
||||
Log.verbose("Decrypt and verify received data and compare received RND.IFD with generated RND.IFD \(binToHexRep(self.ksmac))" )
|
||||
|
||||
let response = tripleDESDecrypt(key: self.ksenc, message: [UInt8](data[0..<32]), iv: [0,0,0,0,0,0,0,0] )
|
||||
|
||||
let response_kicc = [UInt8](response[16..<32])
|
||||
let Kseed = xor(self.kifd, response_kicc)
|
||||
Log.verbose("Calculate XOR of Kifd and Kicc")
|
||||
Log.verbose("\tKseed: \(binToHexRep(Kseed))" )
|
||||
|
||||
let smskg = SecureMessagingSessionKeyGenerator()
|
||||
let KSenc = try smskg.deriveKey(keySeed: Kseed, mode: .ENC_MODE)
|
||||
let KSmac = try smskg.deriveKey(keySeed: Kseed, mode: .MAC_MODE)
|
||||
|
||||
// let KSenc = self.keyDerivation(kseed: Kseed,c: KENC)
|
||||
// let KSmac = self.keyDerivation(kseed: Kseed,c: KMAC)
|
||||
|
||||
Log.verbose("Calculate Session Keys (KSenc and KSmac) using Appendix 5.1")
|
||||
Log.verbose("\tKSenc: \(binToHexRep(KSenc))" )
|
||||
Log.verbose("\tKSmac: \(binToHexRep(KSmac))" )
|
||||
|
||||
|
||||
let ssc = [UInt8](self.rnd_icc.suffix(4) + self.rnd_ifd.suffix(4))
|
||||
Log.verbose("Calculate Send Sequence Counter")
|
||||
Log.verbose("\tSSC: \(binToHexRep(ssc))" )
|
||||
return (KSenc, KSmac, ssc)
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
@@ -1,216 +0,0 @@
|
||||
import Foundation
|
||||
import OpenSSL
|
||||
|
||||
#if !os(macOS)
|
||||
import CoreNFC
|
||||
import CryptoKit
|
||||
|
||||
@available(iOS 15, *)
|
||||
class ChipAuthenticationHandler {
|
||||
|
||||
private static let NO_PACE_KEY_REFERENCE : UInt8 = 0x00
|
||||
private static let ENC_MODE : UInt8 = 0x1
|
||||
private static let MAC_MODE : UInt8 = 0x2
|
||||
private static let PACE_MODE : UInt8 = 0x3
|
||||
|
||||
private static let COMMAND_CHAINING_CHUNK_SIZE = 224
|
||||
|
||||
var tagReader : TagReader?
|
||||
var gaSegments = [[UInt8]]()
|
||||
|
||||
var chipAuthInfos = [Int:ChipAuthenticationInfo]()
|
||||
var chipAuthPublicKeyInfos = [ChipAuthenticationPublicKeyInfo]()
|
||||
|
||||
var isChipAuthenticationSupported : Bool = false
|
||||
|
||||
public init(dg14 : DataGroup14, tagReader: TagReader) {
|
||||
self.tagReader = tagReader
|
||||
|
||||
for secInfo in dg14.securityInfos {
|
||||
if let cai = secInfo as? ChipAuthenticationInfo {
|
||||
let keyId = cai.getKeyId()
|
||||
chipAuthInfos[keyId] = cai
|
||||
} else if let capki = secInfo as? ChipAuthenticationPublicKeyInfo {
|
||||
chipAuthPublicKeyInfos.append(capki)
|
||||
}
|
||||
}
|
||||
|
||||
if chipAuthPublicKeyInfos.count > 0 {
|
||||
isChipAuthenticationSupported = true
|
||||
}
|
||||
}
|
||||
|
||||
public func doChipAuthentication() async throws {
|
||||
|
||||
Log.info( "Performing Chip Authentication - number of public keys found - \(chipAuthPublicKeyInfos.count)" )
|
||||
guard isChipAuthenticationSupported else {
|
||||
throw NFCPassportReaderError.NotYetSupported( "ChipAuthentication not supported" )
|
||||
}
|
||||
|
||||
var success = false
|
||||
for pubKey in chipAuthPublicKeyInfos {
|
||||
do {
|
||||
success = try await self.doChipAuthentication( with: pubKey)
|
||||
if success {
|
||||
break
|
||||
}
|
||||
} catch {
|
||||
// try next key
|
||||
}
|
||||
}
|
||||
|
||||
if !success {
|
||||
throw NFCPassportReaderError.ChipAuthenticationFailed
|
||||
}
|
||||
}
|
||||
|
||||
private func doChipAuthentication( with chipAuthPublicKeyInfo : ChipAuthenticationPublicKeyInfo ) async throws -> Bool {
|
||||
|
||||
// So it turns out that some passports don't have ChipAuthInfo items.
|
||||
// So if we do have a ChipAuthInfo the we take the keyId (if present) and OID from there,
|
||||
// BUT if we don't then we will try to infer the OID from the public key
|
||||
let keyId = chipAuthPublicKeyInfo.keyId
|
||||
let chipAuthInfoOID : String
|
||||
if let chipAuthInfo = chipAuthInfos[keyId ?? 0] {
|
||||
chipAuthInfoOID = chipAuthInfo.oid
|
||||
} else {
|
||||
if let oid = inferOID( fromPublicKeyOID:chipAuthPublicKeyInfo.oid) {
|
||||
chipAuthInfoOID = oid
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
try await self.doCA( keyId: keyId, encryptionDetailsOID: chipAuthInfoOID, publicKey: chipAuthPublicKeyInfo.pubKey )
|
||||
return true
|
||||
}
|
||||
|
||||
/// Infer OID from public key type - Best guess seems to be to use 3DES_CBC_CBC for both ECDH and DH keys
|
||||
/// Apparently works for French passports
|
||||
private func inferOID(fromPublicKeyOID: String ) -> String? {
|
||||
if fromPublicKeyOID == SecurityInfo.ID_PK_ECDH_OID {
|
||||
Log.warning("No ChipAuthenticationInfo - guessing its id-CA-ECDH-3DES-CBC-CBC");
|
||||
return SecurityInfo.ID_CA_ECDH_3DES_CBC_CBC_OID
|
||||
} else if fromPublicKeyOID == SecurityInfo.ID_PK_DH_OID {
|
||||
Log.warning("No ChipAuthenticationInfo - guessing its id-CA-DH-3DES-CBC-CBC");
|
||||
return SecurityInfo.ID_CA_DH_3DES_CBC_CBC_OID
|
||||
}
|
||||
|
||||
Log.warning("No ChipAuthenticationInfo and unsupported ChipAuthenticationPublicKeyInfo public key OID \(fromPublicKeyOID)")
|
||||
return nil;
|
||||
}
|
||||
|
||||
private func doCA( keyId: Int?, encryptionDetailsOID oid: String, publicKey: OpaquePointer) async throws {
|
||||
|
||||
// Generate Ephemeral Keypair from parameters from DG14 Public key
|
||||
// This should work for both EC and DH keys
|
||||
var ephemeralKeyPair : OpaquePointer? = nil
|
||||
let pctx = EVP_PKEY_CTX_new(publicKey, nil)
|
||||
EVP_PKEY_keygen_init(pctx)
|
||||
EVP_PKEY_keygen(pctx, &ephemeralKeyPair)
|
||||
EVP_PKEY_CTX_free(pctx)
|
||||
|
||||
// Send the public key to the passport
|
||||
try await sendPublicKey(oid: oid, keyId: keyId, pcdPublicKey: ephemeralKeyPair!)
|
||||
|
||||
Log.debug( "Public Key successfully sent to passport!" )
|
||||
|
||||
// Use our ephemeral private key and the passports public key to generate a shared secret
|
||||
// (the passport with do the same thing with their private key and our public key)
|
||||
let sharedSecret = OpenSSLUtils.computeSharedSecret(privateKeyPair:ephemeralKeyPair!, publicKey:publicKey)
|
||||
|
||||
// Now try to restart Secure Messaging using the new shared secret and
|
||||
try restartSecureMessaging( oid : oid, sharedSecret : sharedSecret, maxTranceiveLength : 1, shouldCheckMAC : true)
|
||||
}
|
||||
|
||||
private func sendPublicKey(oid : String, keyId : Int?, pcdPublicKey : OpaquePointer) async throws {
|
||||
let cipherAlg = try ChipAuthenticationInfo.toCipherAlgorithm(oid: oid)
|
||||
guard let keyData = OpenSSLUtils.getPublicKeyData(from: pcdPublicKey) else {
|
||||
throw NFCPassportReaderError.InvalidDataPassed("Unable to get public key data from public key" )
|
||||
}
|
||||
|
||||
if cipherAlg.hasPrefix("DESede") {
|
||||
|
||||
var idData : [UInt8] = []
|
||||
if let keyId = keyId {
|
||||
idData = intToBytes( val:keyId, removePadding:true)
|
||||
idData = wrapDO( b:0x84, arr:idData)
|
||||
}
|
||||
let wrappedKeyData = wrapDO( b:0x91, arr:keyData)
|
||||
_ = try await self.tagReader?.sendMSEKAT(keyData: Data(wrappedKeyData), idData: Data(idData))
|
||||
} else if cipherAlg.hasPrefix("AES") {
|
||||
_ = try await self.tagReader?.sendMSESetATIntAuth(oid: oid, keyId: keyId)
|
||||
let data = wrapDO(b: 0x80, arr:keyData)
|
||||
gaSegments = self.chunk(data: data, segmentSize: ChipAuthenticationHandler.COMMAND_CHAINING_CHUNK_SIZE )
|
||||
try await self.handleGeneralAuthentication()
|
||||
} else {
|
||||
throw NFCPassportReaderError.InvalidDataPassed("Cipher Algorithm \(cipherAlg) not supported")
|
||||
}
|
||||
}
|
||||
|
||||
private func handleGeneralAuthentication() async throws {
|
||||
repeat {
|
||||
// Pull next segment from list
|
||||
let segment = gaSegments.removeFirst()
|
||||
let isLast = gaSegments.isEmpty
|
||||
|
||||
// send it
|
||||
_ = try await self.tagReader?.sendGeneralAuthenticate(data: segment, isLast: isLast)
|
||||
} while ( !gaSegments.isEmpty )
|
||||
}
|
||||
|
||||
private func restartSecureMessaging( oid : String, sharedSecret : [UInt8], maxTranceiveLength : Int, shouldCheckMAC : Bool) throws {
|
||||
let cipherAlg = try ChipAuthenticationInfo.toCipherAlgorithm(oid: oid)
|
||||
let keyLength = try ChipAuthenticationInfo.toKeyLength(oid: oid)
|
||||
|
||||
// Start secure messaging.
|
||||
let smskg = SecureMessagingSessionKeyGenerator()
|
||||
let ksEnc = try smskg.deriveKey(keySeed: sharedSecret, cipherAlgName: cipherAlg, keyLength: keyLength, mode: .ENC_MODE)
|
||||
let ksMac = try smskg.deriveKey(keySeed: sharedSecret, cipherAlgName: cipherAlg, keyLength: keyLength, mode: .MAC_MODE)
|
||||
|
||||
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 {
|
||||
Log.error( "Not restarting secure messaging as unsupported cipher algorithm requested - \(cipherAlg)")
|
||||
throw NFCPassportReaderError.InvalidDataPassed("Unsupported cipher algorithm \(cipherAlg)" )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func inferDigestAlgorithmFromCipherAlgorithmForKeyDerivation( cipherAlg : String, keyLength : Int) throws -> String {
|
||||
if cipherAlg == "DESede" || cipherAlg == "AES-128" {
|
||||
return "SHA1"
|
||||
}
|
||||
if cipherAlg == "AES" && keyLength == 128 {
|
||||
return "SHA1"
|
||||
}
|
||||
if cipherAlg == "AES-256" || cipherAlg == "AES-192" {
|
||||
return "SHA256"
|
||||
}
|
||||
if cipherAlg == "AES" && (keyLength == 192 || keyLength == 256) {
|
||||
return "SHA256"
|
||||
}
|
||||
|
||||
throw NFCPassportReaderError.InvalidDataPassed("Unsupported cipher algorithm or key length")
|
||||
}
|
||||
|
||||
/// Chunks up a byte array into a number of segments of the given size,
|
||||
/// and a final segment if there is a remainder.
|
||||
/// - Parameter segmentSize the number of bytes per segment
|
||||
/// - Parameter data the data to be partitioned
|
||||
/// - Parameter a list with the segments
|
||||
func chunk( data : [UInt8], segmentSize: Int ) -> [[UInt8]] {
|
||||
return stride(from: 0, to: data.count, by: segmentSize).map {
|
||||
Array(data[$0 ..< Swift.min($0 + segmentSize, data.count)])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user