add GlobalPlatform
This commit is contained in:
parent
75c956d64a
commit
15b7469d99
|
@ -33,10 +33,52 @@ class Crypto {
|
||||||
|
|
||||||
func aes256CMac(data: [UInt8], key: [UInt8]) -> [UInt8] {
|
func aes256CMac(data: [UInt8], key: [UInt8]) -> [UInt8] {
|
||||||
let result = aes256Enc(data: data, iv: [UInt8](repeating: 0, count: SecureChannel.blockLength), key: key).suffix(16)
|
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)
|
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] {
|
func iso7816_4Pad(data: [UInt8], blockSize: Int) -> [UInt8] {
|
||||||
var padded = Array(data)
|
var padded = Array(data)
|
||||||
padded.append(0x80)
|
padded.append(0x80)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,20 @@
|
||||||
enum CLA: UInt8 {
|
public enum CLA: UInt8 {
|
||||||
case iso7816 = 0x00
|
case iso7816 = 0x00
|
||||||
case proprietary = 0x80
|
case proprietary = 0x80
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ISO7816INS: UInt8 {
|
public enum ISO7816INS: UInt8 {
|
||||||
case select = 0xa4
|
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 initialize = 0xfe
|
||||||
case getStatus = 0xf2
|
case getStatus = 0xf2
|
||||||
case verifyPIN = 0x20
|
case verifyPIN = 0x20
|
||||||
|
@ -82,7 +89,7 @@ public enum ExportKeyP2: UInt8 {
|
||||||
case publicOnly = 0x01
|
case publicOnly = 0x01
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SecureChannelINS: UInt8 {
|
public enum SecureChannelINS: UInt8 {
|
||||||
case openSecureChannel = 0x10
|
case openSecureChannel = 0x10
|
||||||
case mutuallyAuthenticate = 0x11
|
case mutuallyAuthenticate = 0x11
|
||||||
case pair = 0x12
|
case pair = 0x12
|
||||||
|
@ -117,3 +124,12 @@ public enum Identifier: String {
|
||||||
return keycardAID.val + [instanceId]
|
return keycardAID.val + [instanceId]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum GlobalPlatformKeys: String {
|
||||||
|
case defaultKeys = "404142434445464748494a4b4c4d4e4f"
|
||||||
|
case statusKeys = "c212e073ff8b4bbfaff4de8ab655221f"
|
||||||
|
|
||||||
|
public var val: [UInt8] {
|
||||||
|
return rawValue.hexToBytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue