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|
|
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' }
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -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"]),
|
||||||
|
|
|
@ -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] {
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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])
|
||||||
|
|
Loading…
Reference in New Issue