Add support for certificates (#31)
* add ident command * add Certificate struct * fix ident command
This commit is contained in:
parent
e75e6b8da7
commit
afb19836d6
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |spec|
|
||||
spec.name = 'Keycard'
|
||||
spec.version = '3.0.7'
|
||||
spec.version = '3.1.0'
|
||||
spec.authors = {'Bitgamma' => 'opensource@bitgamma.com'}
|
||||
spec.homepage = 'https://github.com/status-im/Keycard.swift'
|
||||
spec.license = { :type => 'Apache' }
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
"repositoryURL": "https://github.com/status-im/secp256k1.swift.git",
|
||||
"state": {
|
||||
"branch": "master",
|
||||
"revision": "d2c49786e9245d77f4eba6ce78a87f87506623c5",
|
||||
"revision": "4ab977cc2b2d7319be858bcb30a5d189bb149884",
|
||||
"version": null
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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.
|
||||
.target(
|
||||
name: "Keycard",
|
||||
dependencies: ["secp256k1", "CryptoSwift", "ZipArchive", "BigInt"]),
|
||||
dependencies: ["secp256k1", "CryptoSwift", "ZipArchive", "BigInt"],
|
||||
swiftSettings: [.define("USE_SPM")]),
|
||||
.testTarget(
|
||||
name: "KeycardTests",
|
||||
dependencies: ["Keycard"]),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -195,16 +195,16 @@ class Crypto {
|
|||
func secp256k1PublicFromPrivate(_ privKey: [UInt8]) -> [UInt8] {
|
||||
var pubKey = secp256k1_pubkey()
|
||||
_ = 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()
|
||||
_ = secp256k1_ecdsa_recoverable_signature_parse_compact(secp256k1Ctx, &sig, r + s, Int32(recId))
|
||||
|
||||
var pubKey = secp256k1_pubkey()
|
||||
_ = secp256k1_ecdsa_recover(secp256k1Ctx, &pubKey, &sig, hash)
|
||||
return _secp256k1PubToBytes(&pubKey)
|
||||
return _secp256k1PubToBytes(&pubKey, compressed)
|
||||
}
|
||||
|
||||
func secp256k1Sign(hash: [UInt8], privKey: [UInt8]) -> [UInt8] {
|
||||
|
@ -217,11 +217,33 @@ class Crypto {
|
|||
secp256k1_ecdsa_signature_serialize_der(secp256k1Ctx, &derSig, &derOutLen, &sig)
|
||||
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] {
|
||||
var pubKeyBytes = [UInt8](repeating: 0, count: 65)
|
||||
var outputLen = 65
|
||||
_ = secp256k1_ec_pubkey_serialize(secp256k1Ctx, &pubKeyBytes, &outputLen, &pubKey, UInt32(SECP256K1_EC_UNCOMPRESSED))
|
||||
private func _secp256k1PubToBytes(_ pubKey: inout secp256k1_pubkey, _ compressed: Bool) -> [UInt8] {
|
||||
var outputLen: Int
|
||||
var compressedFlag: UInt32
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import Foundation
|
||||
|
||||
#if USE_SPM
|
||||
import ZipArchive
|
||||
#else
|
||||
import SSZipArchive
|
||||
#endif
|
||||
|
||||
struct FileLoader {
|
||||
private static let blockSize = 247 // 255 - 8 bytes for MAC
|
||||
|
|
|
@ -29,6 +29,7 @@ public enum KeycardINS: UInt8 {
|
|||
case initialize = 0xfe
|
||||
case factoryReset = 0xfd
|
||||
case getStatus = 0xf2
|
||||
case identifyCard = 0x14
|
||||
case verifyPIN = 0x20
|
||||
case changePIN = 0x21
|
||||
case unblockPIN = 0x22
|
||||
|
|
|
@ -61,6 +61,11 @@ public class KeycardCommandSet {
|
|||
public func unpairOthers() throws {
|
||||
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 {
|
||||
try secureChannel.openSecureChannel(channel: cardChannel, index: index, data: data)
|
||||
|
@ -302,5 +307,5 @@ public class KeycardCommandSet {
|
|||
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: [])
|
||||
return try cardChannel.send(cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
enum ECDSASignatureTag: UInt8 {
|
||||
case signatureTemplate = 0xA0
|
||||
case rawSignature = 0x80
|
||||
case ecdsaTemplate = 0x30
|
||||
}
|
||||
|
||||
|
@ -8,29 +9,63 @@ public struct RecoverableSignature {
|
|||
public let recId: UInt8
|
||||
public let r: [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 {
|
||||
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)
|
||||
self.publicKey = try tlv.readPrimitive(tag: AppInfoTag.pubKey.rawValue)
|
||||
_ = try tlv.enterConstructed(tag: ECDSASignatureTag.ecdsaTemplate.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.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
|
||||
|
||||
for i: UInt8 in 0...3 {
|
||||
let pub = Crypto.shared.secp256k1RecoverPublic(r: r, s: s, recId: i, hash: hash)
|
||||
if (pub == self.publicKey) {
|
||||
let pub = Crypto.shared.secp256k1RecoverPublic(r: r, s: s, recId: i, hash: hash, compressed: compressed)
|
||||
if (pub == pubkey) {
|
||||
foundID = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (foundID != UInt8.max) {
|
||||
self.recId = foundID
|
||||
} else {
|
||||
if (foundID == UInt8.max) {
|
||||
throw CardError.unrecoverableSignature
|
||||
}
|
||||
|
||||
return foundID
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,6 +113,10 @@ class TinyBERTLV {
|
|||
throw TLVError.unexpectedLength(length: val.count)
|
||||
}
|
||||
}
|
||||
|
||||
func peekUnread() -> [UInt8] {
|
||||
return Array(self.buf[self.pos..<self.buf.count])
|
||||
}
|
||||
|
||||
func readLength() -> Int {
|
||||
var len = Int(buf[pos])
|
||||
|
|
Loading…
Reference in New Issue