Add support for certificates (#31)

* add ident command

* add Certificate struct

* fix ident command
This commit is contained in:
Michele Balistreri 2024-09-23 11:23:29 +02:00 committed by GitHub
parent e75e6b8da7
commit afb19836d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 125 additions and 17 deletions

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |spec| Pod::Spec.new do |spec|
spec.name = 'Keycard' spec.name = 'Keycard'
spec.version = '3.0.7' spec.version = '3.1.0'
spec.authors = {'Bitgamma' => 'opensource@bitgamma.com'} spec.authors = {'Bitgamma' => 'opensource@bitgamma.com'}
spec.homepage = 'https://github.com/status-im/Keycard.swift' spec.homepage = 'https://github.com/status-im/Keycard.swift'
spec.license = { :type => 'Apache' } spec.license = { :type => 'Apache' }

View File

@ -24,7 +24,7 @@
"repositoryURL": "https://github.com/status-im/secp256k1.swift.git", "repositoryURL": "https://github.com/status-im/secp256k1.swift.git",
"state": { "state": {
"branch": "master", "branch": "master",
"revision": "d2c49786e9245d77f4eba6ce78a87f87506623c5", "revision": "4ab977cc2b2d7319be858bcb30a5d189bb149884",
"version": null "version": null
} }
}, },

View File

@ -25,7 +25,8 @@ let package = Package(
// Targets can depend on other targets in this package, and on products in packages which this package depends on. // Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target( .target(
name: "Keycard", name: "Keycard",
dependencies: ["secp256k1", "CryptoSwift", "ZipArchive", "BigInt"]), dependencies: ["secp256k1", "CryptoSwift", "ZipArchive", "BigInt"],
swiftSettings: [.define("USE_SPM")]),
.testTarget( .testTarget(
name: "KeycardTests", name: "KeycardTests",
dependencies: ["Keycard"]), dependencies: ["Keycard"]),

View File

@ -0,0 +1,35 @@
enum CertificateTag: UInt8 {
case certificate = 0x8A
}
public struct Certificate {
let identPub: [UInt8]
let caSignature: RecoverableSignature
public static func fromTLV(certData: [UInt8]) -> Certificate {
let pub = Array(certData[0..<33])
let r = Array(certData[33..<65])
let s = Array(certData[65..<97])
let recId = certData[97]
let hash = Crypto.shared.sha256(pub)
let caPub = Crypto.shared.secp256k1RecoverPublic(r: r, s: s, recId: recId, hash: hash, compressed: true)
let caSig = RecoverableSignature(r: r, s: s, recId: recId, publicKey: caPub, compressed: true)
return Certificate(identPub: pub, caSignature: caSig);
}
public static func verifyIdentity(hash: [UInt8], tlvData: [UInt8]) throws -> [UInt8]? {
let tlv = TinyBERTLV(tlvData)
_ = try tlv.enterConstructed(tag: ECDSASignatureTag.signatureTemplate.rawValue)
let certData = try tlv.readPrimitive(tag: CertificateTag.certificate.rawValue)
let cert = Certificate.fromTLV(certData: certData)
let signature = tlv.peekUnread()
if (!Crypto.shared.secp256k1Verify(signature: signature, hash: hash, pubKey: cert.identPub)) {
return nil
}
return cert.caSignature.publicKey
}
}

View File

@ -195,16 +195,16 @@ class Crypto {
func secp256k1PublicFromPrivate(_ privKey: [UInt8]) -> [UInt8] { func secp256k1PublicFromPrivate(_ privKey: [UInt8]) -> [UInt8] {
var pubKey = secp256k1_pubkey() var pubKey = secp256k1_pubkey()
_ = secp256k1_ec_pubkey_create(secp256k1Ctx, &pubKey, privKey) _ = secp256k1_ec_pubkey_create(secp256k1Ctx, &pubKey, privKey)
return _secp256k1PubToBytes(&pubKey) return _secp256k1PubToBytes(&pubKey, false)
} }
func secp256k1RecoverPublic(r: [UInt8], s: [UInt8], recId: UInt8, hash: [UInt8]) -> [UInt8] { func secp256k1RecoverPublic(r: [UInt8], s: [UInt8], recId: UInt8, hash: [UInt8], compressed: Bool) -> [UInt8] {
var sig = secp256k1_ecdsa_recoverable_signature() var sig = secp256k1_ecdsa_recoverable_signature()
_ = secp256k1_ecdsa_recoverable_signature_parse_compact(secp256k1Ctx, &sig, r + s, Int32(recId)) _ = secp256k1_ecdsa_recoverable_signature_parse_compact(secp256k1Ctx, &sig, r + s, Int32(recId))
var pubKey = secp256k1_pubkey() var pubKey = secp256k1_pubkey()
_ = secp256k1_ecdsa_recover(secp256k1Ctx, &pubKey, &sig, hash) _ = secp256k1_ecdsa_recover(secp256k1Ctx, &pubKey, &sig, hash)
return _secp256k1PubToBytes(&pubKey) return _secp256k1PubToBytes(&pubKey, compressed)
} }
func secp256k1Sign(hash: [UInt8], privKey: [UInt8]) -> [UInt8] { func secp256k1Sign(hash: [UInt8], privKey: [UInt8]) -> [UInt8] {
@ -217,11 +217,33 @@ class Crypto {
secp256k1_ecdsa_signature_serialize_der(secp256k1Ctx, &derSig, &derOutLen, &sig) secp256k1_ecdsa_signature_serialize_der(secp256k1Ctx, &derSig, &derOutLen, &sig)
return Array(derSig[0..<derOutLen]) return Array(derSig[0..<derOutLen])
} }
func secp256k1Verify(signature: [UInt8], hash: [UInt8], pubKey: [UInt8]) -> Bool {
var sig = secp256k1_ecdsa_signature()
_ = secp256k1_ecdsa_signature_parse_der(secp256k1Ctx, &sig, signature, signature.count)
var signorm = secp256k1_ecdsa_signature()
_ = secp256k1_ecdsa_signature_normalize(secp256k1Ctx, &signorm, &sig)
var pkey = secp256k1_pubkey();
_ = secp256k1_ec_pubkey_parse(secp256k1Ctx, &pkey, pubKey, pubKey.count)
return secp256k1_ecdsa_verify(secp256k1Ctx, &signorm, hash, &pkey) != 0
}
private func _secp256k1PubToBytes(_ pubKey: inout secp256k1_pubkey) -> [UInt8] { private func _secp256k1PubToBytes(_ pubKey: inout secp256k1_pubkey, _ compressed: Bool) -> [UInt8] {
var pubKeyBytes = [UInt8](repeating: 0, count: 65) var outputLen: Int
var outputLen = 65 var compressedFlag: UInt32
_ = secp256k1_ec_pubkey_serialize(secp256k1Ctx, &pubKeyBytes, &outputLen, &pubKey, UInt32(SECP256K1_EC_UNCOMPRESSED))
if (compressed) {
outputLen = 33
compressedFlag = UInt32(SECP256K1_EC_COMPRESSED)
} else {
outputLen = 65
compressedFlag = UInt32(SECP256K1_EC_UNCOMPRESSED)
}
var pubKeyBytes = [UInt8](repeating: 0, count: outputLen)
_ = secp256k1_ec_pubkey_serialize(secp256k1Ctx, &pubKeyBytes, &outputLen, &pubKey, compressedFlag)
return pubKeyBytes return pubKeyBytes
} }

View File

@ -1,5 +1,10 @@
import Foundation import Foundation
#if USE_SPM
import ZipArchive
#else
import SSZipArchive import SSZipArchive
#endif
struct FileLoader { struct FileLoader {
private static let blockSize = 247 // 255 - 8 bytes for MAC private static let blockSize = 247 // 255 - 8 bytes for MAC

View File

@ -29,6 +29,7 @@ public enum KeycardINS: UInt8 {
case initialize = 0xfe case initialize = 0xfe
case factoryReset = 0xfd case factoryReset = 0xfd
case getStatus = 0xf2 case getStatus = 0xf2
case identifyCard = 0x14
case verifyPIN = 0x20 case verifyPIN = 0x20
case changePIN = 0x21 case changePIN = 0x21
case unblockPIN = 0x22 case unblockPIN = 0x22

View File

@ -61,6 +61,11 @@ public class KeycardCommandSet {
public func unpairOthers() throws { public func unpairOthers() throws {
try secureChannel.unpairOthers(channel: cardChannel) try secureChannel.unpairOthers(channel: cardChannel)
} }
public func identifyCard(challenge: [UInt8]) throws -> APDUResponse {
let cmd = APDUCommand(cla: CLA.proprietary.rawValue, ins: KeycardINS.identifyCard.rawValue, p1: 0x00, p2: 0x00, data: challenge)
return try cardChannel.send(cmd)
}
public func openSecureChannel(index: UInt8, data: [UInt8]) throws -> APDUResponse { public func openSecureChannel(index: UInt8, data: [UInt8]) throws -> APDUResponse {
try secureChannel.openSecureChannel(channel: cardChannel, index: index, data: data) try secureChannel.openSecureChannel(channel: cardChannel, index: index, data: data)
@ -302,5 +307,5 @@ public class KeycardCommandSet {
public func factoryReset() throws -> APDUResponse { public func factoryReset() throws -> APDUResponse {
let cmd = APDUCommand(cla: CLA.proprietary.rawValue, ins: KeycardINS.factoryReset.rawValue, p1: FactoryResetP1.magic.rawValue, p2: FactoryResetP2.magic.rawValue, data: []) let cmd = APDUCommand(cla: CLA.proprietary.rawValue, ins: KeycardINS.factoryReset.rawValue, p1: FactoryResetP1.magic.rawValue, p2: FactoryResetP2.magic.rawValue, data: [])
return try cardChannel.send(cmd) return try cardChannel.send(cmd)
} }
} }

View File

@ -1,5 +1,6 @@
enum ECDSASignatureTag: UInt8 { enum ECDSASignatureTag: UInt8 {
case signatureTemplate = 0xA0 case signatureTemplate = 0xA0
case rawSignature = 0x80
case ecdsaTemplate = 0x30 case ecdsaTemplate = 0x30
} }
@ -8,29 +9,63 @@ public struct RecoverableSignature {
public let recId: UInt8 public let recId: UInt8
public let r: [UInt8] public let r: [UInt8]
public let s: [UInt8] public let s: [UInt8]
public let compressed: Bool
public init(r: [UInt8], s: [UInt8], recId: UInt8, publicKey: [UInt8], compressed: Bool) {
self.r = r
self.s = s
self.recId = recId
self.publicKey = publicKey
self.compressed = compressed
}
public init(hash: [UInt8], data: [UInt8]) throws { public init(hash: [UInt8], data: [UInt8]) throws {
let tlv = TinyBERTLV(data) let tlv = TinyBERTLV(data)
let tag = try tlv.readTag()
tlv.unreadLastTag()
if (tag == ECDSASignatureTag.rawSignature.rawValue) {
try self.init(hash: hash, signature: tlv.readPrimitive(tag: tag))
} else if (tag == ECDSASignatureTag.signatureTemplate.rawValue) {
try self.init(hash: hash, tlv: tlv)
} else {
throw TLVError.unexpectedTag(expected: ECDSASignatureTag.signatureTemplate.rawValue, actual: tag)
}
}
private init(hash: [UInt8], tlv: TinyBERTLV) throws {
_ = try tlv.enterConstructed(tag: ECDSASignatureTag.signatureTemplate.rawValue) _ = try tlv.enterConstructed(tag: ECDSASignatureTag.signatureTemplate.rawValue)
self.publicKey = try tlv.readPrimitive(tag: AppInfoTag.pubKey.rawValue) self.publicKey = try tlv.readPrimitive(tag: AppInfoTag.pubKey.rawValue)
_ = try tlv.enterConstructed(tag: ECDSASignatureTag.ecdsaTemplate.rawValue) _ = try tlv.enterConstructed(tag: ECDSASignatureTag.ecdsaTemplate.rawValue)
self.r = try Util.shared.dropZeroPrefix(uint8: tlv.readPrimitive(tag: TLVTag.int.rawValue)) self.r = try Util.shared.dropZeroPrefix(uint8: tlv.readPrimitive(tag: TLVTag.int.rawValue))
self.s = try Util.shared.dropZeroPrefix(uint8: tlv.readPrimitive(tag: TLVTag.int.rawValue)) self.s = try Util.shared.dropZeroPrefix(uint8: tlv.readPrimitive(tag: TLVTag.int.rawValue))
self.compressed = false
self.recId = try RecoverableSignature.calculateRecId(hash: hash, pubkey: self.publicKey, r: self.r, s: self.s, compressed: self.compressed)
}
private init(hash: [UInt8], signature: [UInt8]) throws {
self.r = Array(signature[0..<32])
self.s = Array(signature[32..<64])
self.recId = signature[64]
self.compressed = false
self.publicKey = Crypto.shared.secp256k1RecoverPublic(r: self.r, s: self.s, recId: self.recId, hash: hash, compressed: self.compressed)
}
public static func calculateRecId(hash: [UInt8], pubkey: [UInt8], r: [UInt8], s: [UInt8], compressed: Bool) throws -> UInt8 {
var foundID: UInt8 = UInt8.max var foundID: UInt8 = UInt8.max
for i: UInt8 in 0...3 { for i: UInt8 in 0...3 {
let pub = Crypto.shared.secp256k1RecoverPublic(r: r, s: s, recId: i, hash: hash) let pub = Crypto.shared.secp256k1RecoverPublic(r: r, s: s, recId: i, hash: hash, compressed: compressed)
if (pub == self.publicKey) { if (pub == pubkey) {
foundID = i foundID = i
break break
} }
} }
if (foundID != UInt8.max) { if (foundID == UInt8.max) {
self.recId = foundID
} else {
throw CardError.unrecoverableSignature throw CardError.unrecoverableSignature
} }
return foundID
} }
} }

View File

@ -113,6 +113,10 @@ class TinyBERTLV {
throw TLVError.unexpectedLength(length: val.count) throw TLVError.unexpectedLength(length: val.count)
} }
} }
func peekUnread() -> [UInt8] {
return Array(self.buf[self.pos..<self.buf.count])
}
func readLength() -> Int { func readLength() -> Int {
var len = Int(buf[pos]) var len = Int(buf[pos])