add GlobalPlatform

This commit is contained in:
Michele Balistreri 2021-04-29 15:43:22 +03:00
parent 75c956d64a
commit 15b7469d99
4 changed files with 252 additions and 6 deletions

View File

@ -33,10 +33,52 @@ class Crypto {
func aes256CMac(data: [UInt8], key: [UInt8]) -> [UInt8] {
let result = aes256Enc(data: data, iv: [UInt8](repeating: 0, count: SecureChannel.blockLength), key: key).suffix(16)
assert(result.count == 16, "CMac must be 16 bytes long but it is \(result.count)")
return Array(result)
}
func desEnc(data: [UInt8], iv: [UInt8], key: [UInt8]) -> [UInt8] {
var out: [UInt8] = [UInt8](repeating: 0, count: data.count)
var encrypted: Int = 0
var tmpKey = key
var tmpData = data
var tmpIV = iv
CCCrypt(CCOperation(kCCEncrypt), CCAlgorithm(kCCAlgorithmDES), CCOptions(0), &tmpKey, key.count, &tmpIV, &tmpData, data.count, &out, out.count, &encrypted)
return out
}
func des3Enc(data: [UInt8], iv: [UInt8], key: [UInt8]) -> [UInt8] {
var out: [UInt8] = [UInt8](repeating: 0, count: data.count)
var encrypted: Int = 0
var tmpKey = key
var tmpData = data
var tmpIV = iv
CCCrypt(CCOperation(kCCEncrypt), CCAlgorithm(kCCAlgorithm3DES), CCOptions(0), &tmpKey, key.count, &tmpIV, &tmpData, data.count, &out, out.count, &encrypted)
return out
}
func des3Mac(data: [UInt8], iv: [UInt8], key: [UInt8]) -> [UInt8] {
let enc: [UInt8] = des3Enc(data: data, iv: iv, key: key)
return Array(enc.suffix(8))
}
func des3FullMac(data: [UInt8], iv: [UInt8], key: [UInt8]) -> [UInt8] {
let des3IV : [UInt8]
if (data.count > 8) {
des3IV = desEnc(data: Array(data[0..<(data.count - 8)]), iv: iv, key: resizeDESKey8(key))
} else {
des3IV = iv
}
return des3Mac(data: Array(data.suffix(8)), iv: des3IV, key: key)
}
func resizeDESKey8(_ key: [UInt8]) -> [UInt8] {
return Array(key[0..<8])
}
func iso7816_4Pad(data: [UInt8], blockSize: Int) -> [UInt8] {
var padded = Array(data)
padded.append(0x80)

View File

@ -0,0 +1,94 @@
import Foundation
public class GlobalPlatformCommandSet {
let cardChannel: CardChannel
let secureChannel: SCP02
public init(cardChannel: CardChannel) {
self.cardChannel = cardChannel
self.secureChannel = SCP02(channel: cardChannel)
}
public func select() throws -> APDUResponse {
let selectApplet: APDUCommand = APDUCommand(cla: CLA.iso7816.rawValue, ins: ISO7816INS.select.rawValue, p1: 0x04, p2: 0x00, data: [])
return try cardChannel.send(selectApplet)
}
public func initializeUpdate(hostChallenge: [UInt8]) throws -> APDUResponse {
let initUpdate: APDUCommand = APDUCommand(cla: CLA.proprietary.rawValue, ins: GlobalPlatformINS.initializeUpdate.rawValue, p1: 0, p2: 0, data: hostChallenge)
let resp: APDUResponse = try cardChannel.send(initUpdate)
if (resp.sw == StatusWord.ok.rawValue) {
if !secureChannel.verifyChallenge(hostChallenge: hostChallenge, key: GlobalPlatformKeys.statusKeys.val, cardResponse: resp.data) {
if !secureChannel.verifyChallenge(hostChallenge: hostChallenge, key: GlobalPlatformKeys.defaultKeys.val, cardResponse: resp.data) {
throw CardError.invalidAuthData
}
}
}
return resp
}
public func externalUpdate(hostChallenge: [UInt8]) throws -> APDUResponse {
let hostCryptogram = secureChannel.generateHostCryptogram(hostChallenge: hostChallenge)
let externalAuth: APDUCommand = APDUCommand(cla: CLA.proprietary.rawValue, ins: GlobalPlatformINS.externalAuthenticate.rawValue, p1: 0x01, p2: 0, data: hostCryptogram)
return try secureChannel.send(externalAuth)
}
public func openSecureChannel() throws {
let hostChallenge: [UInt8] = Crypto.shared.random(count: 8)
try initializeUpdate(hostChallenge: hostChallenge).checkOK()
try externalUpdate(hostChallenge: hostChallenge).checkOK()
}
public func deleteKeycardInstance() throws -> APDUResponse {
return try delete(aid: Identifier.getKeycardInstanceAID())
}
public func deleteCashInstance() throws -> APDUResponse {
return try delete(aid: Identifier.keycardCashInstanceAID.val)
}
public func deleteNDEFInstance() throws -> APDUResponse {
return try delete(aid: Identifier.ndefInstanceAID.val)
}
public func delete(aid: [UInt8]) throws -> APDUResponse {
var data: [UInt8] = [0x4f]
data.append(UInt8(aid.count))
data.append(contentsOf: aid)
let delete: APDUCommand = APDUCommand(cla: CLA.proprietary.rawValue, ins: GlobalPlatformINS.delete.rawValue, p1: 0, p2: 0, data: data)
return try secureChannel.send(delete)
}
public func installKeycardInstance() throws -> APDUResponse {
return try installForInstall(packageAID: Identifier.packageAID.val, appletAID: Identifier.keycardAID.val, instanceAID: Identifier.getKeycardInstanceAID(), params: [])
}
public func installCashInstance(cashData: [UInt8]) throws -> APDUResponse {
return try installForInstall(packageAID: Identifier.packageAID.val, appletAID: Identifier.keycardCashAID.val, instanceAID: Identifier.keycardCashInstanceAID.val, params: cashData)
}
public func installNDEFInstance(ndefRecord: [UInt8]) throws -> APDUResponse {
return try installForInstall(packageAID: Identifier.packageAID.val, appletAID: Identifier.ndefAID.val, instanceAID: Identifier.ndefInstanceAID.val, params: ndefRecord)
}
public func installForInstall(packageAID: [UInt8], appletAID: [UInt8], instanceAID: [UInt8], params: [UInt8]) throws -> APDUResponse {
var data: [UInt8] = [UInt8(packageAID.count)]
data.append(contentsOf: packageAID)
data.append(UInt8(appletAID.count))
data.append(contentsOf: appletAID)
data.append(UInt8(instanceAID.count))
data.append(contentsOf: instanceAID)
data.append(0x01)
data.append(0x00)
data.append(UInt8(params.count + 2))
data.append(0xc9)
data.append(UInt8(params.count))
data.append(contentsOf: params)
data.append(0x00)
let installForInstall: APDUCommand = APDUCommand(cla: CLA.proprietary.rawValue, ins: GlobalPlatformINS.install.rawValue, p1: 0x0c, p2: 0, data: data)
return try secureChannel.send(installForInstall)
}
}

View File

@ -1,13 +1,20 @@
enum CLA: UInt8 {
public enum CLA: UInt8 {
case iso7816 = 0x00
case proprietary = 0x80
}
enum ISO7816INS: UInt8 {
public enum ISO7816INS: UInt8 {
case select = 0xa4
}
enum KeycardINS: UInt8 {
public enum GlobalPlatformINS: UInt8 {
case initializeUpdate = 0x50
case externalAuthenticate = 0x82
case delete = 0xe4
case install = 0xe6
}
public enum KeycardINS: UInt8 {
case initialize = 0xfe
case getStatus = 0xf2
case verifyPIN = 0x20
@ -82,7 +89,7 @@ public enum ExportKeyP2: UInt8 {
case publicOnly = 0x01
}
enum SecureChannelINS: UInt8 {
public enum SecureChannelINS: UInt8 {
case openSecureChannel = 0x10
case mutuallyAuthenticate = 0x11
case pair = 0x12
@ -117,3 +124,12 @@ public enum Identifier: String {
return keycardAID.val + [instanceId]
}
}
public enum GlobalPlatformKeys: String {
case defaultKeys = "404142434445464748494a4b4c4d4e4f"
case statusKeys = "c212e073ff8b4bbfaff4de8ab655221f"
public var val: [UInt8] {
return rawValue.hexToBytes
}
}

View File

@ -0,0 +1,94 @@
import Foundation
public class SCP02 {
public static let zeroIV: [UInt8] = [UInt8](repeating: 0, count: 8)
static let derivationPurposeEnc: [UInt8] = [0x01, 0x82]
static let derivationPurposeMac: [UInt8] = [0x01, 0x01]
static let derivationPurposeDek: [UInt8] = [0x01, 0x81]
var cardChallenge: [UInt8]
var encKey: [UInt8]
var macKey: [UInt8]
var dataKey: [UInt8]
var icv: [UInt8]
let cardChannel: CardChannel
public init(channel: CardChannel) {
cardChannel = channel
cardChallenge = []
encKey = []
macKey = []
dataKey = []
icv = SCP02.zeroIV
}
public func send(_ cmd: APDUCommand) throws -> APDUResponse {
return try cardChannel.send(wrap(cmd))
}
public func verifyChallenge(hostChallenge: [UInt8], key: [UInt8], cardResponse: [UInt8]) -> Bool {
if (cardResponse.count != 28) {
return false
}
cardChallenge = Array(cardResponse[12..<20])
let seq: [UInt8] = Array(cardResponse[12..<14])
let cardCryptogram: [UInt8] = Array(cardResponse[20..<28])
encKey = deriveSessionKey(key: key, seq: seq, purpose: SCP02.derivationPurposeEnc)
macKey = deriveSessionKey(key: key, seq: seq, purpose: SCP02.derivationPurposeMac)
dataKey = deriveSessionKey(key: key, seq: seq, purpose: SCP02.derivationPurposeDek)
return verifyCryptogram(hostChallenge: hostChallenge, cardCryptogram: cardCryptogram)
}
public func generateHostCryptogram(hostChallenge: [UInt8]) -> [UInt8] {
return generateCryptogram(challenge1: cardChallenge, challenge2: hostChallenge)
}
public func generateCryptogram(challenge1: [UInt8], challenge2: [UInt8]) -> [UInt8] {
var data: [UInt8] = []
data.append(contentsOf: challenge1)
data.append(contentsOf: challenge2)
let paddedData = Crypto.shared.iso7816_4Pad(data: data, blockSize: 8)
return Crypto.shared.des3Mac(data: paddedData, iv: SCP02.zeroIV, key: encKey)
}
func deriveSessionKey(key: [UInt8], seq: [UInt8], purpose: [UInt8]) -> [UInt8] {
var derivationData: [UInt8] = [UInt8](repeating: 0, count: 16)
derivationData[0] = purpose[0]
derivationData[1] = purpose[1]
derivationData[2] = seq[0]
derivationData[3] = seq[1]
var derivedKey: [UInt8] = Crypto.shared.des3Enc(data: derivationData, iv: SCP02.zeroIV, key: key)
derivedKey.append(contentsOf: Crypto.shared.resizeDESKey8(derivedKey))
return derivedKey
}
func verifyCryptogram(hostChallenge: [UInt8], cardCryptogram: [UInt8]) -> Bool {
let calculated: [UInt8] = generateCryptogram(challenge1: hostChallenge, challenge2: cardChallenge)
return calculated == cardCryptogram
}
func wrap(_ cmd: APDUCommand) -> APDUCommand {
let cla: UInt8 = cmd.cla | 0x04
var macData: [UInt8] = [cla, cmd.ins, cmd.p1, cmd.p2, UInt8(cmd.data.count + 8)]
macData.append(contentsOf: cmd.data)
if (icv != SCP02.zeroIV) {
icv = Crypto.shared.desEnc(data: icv, iv: SCP02.zeroIV, key: Crypto.shared.resizeDESKey8(macKey))
}
let mac: [UInt8] = Crypto.shared.des3FullMac(data: Crypto.shared.iso7816_4Pad(data: macData, blockSize: 8), iv: icv, key: macKey)
var wrappedData: [UInt8] = []
wrappedData.append(contentsOf: cmd.data)
wrappedData.append(contentsOf: mac)
icv = mac
return APDUCommand(cla: cla, ins: cmd.ins, p1: cmd.p1, p2: cmd.p2, data: wrappedData, needsLE: cmd.needsLE)
}
}