Skip to content

Commit afb1983

Browse files
authored
Add support for certificates (#31)
* add ident command * add Certificate struct * fix ident command
1 parent e75e6b8 commit afb1983

10 files changed

+125
-17
lines changed

Diff for: Keycard.podspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |spec|
22
spec.name = 'Keycard'
3-
spec.version = '3.0.7'
3+
spec.version = '3.1.0'
44
spec.authors = {'Bitgamma' => '[email protected]'}
55
spec.homepage = 'https://github.com/status-im/Keycard.swift'
66
spec.license = { :type => 'Apache' }

Diff for: Package.resolved

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"repositoryURL": "https://github.com/status-im/secp256k1.swift.git",
2525
"state": {
2626
"branch": "master",
27-
"revision": "d2c49786e9245d77f4eba6ce78a87f87506623c5",
27+
"revision": "4ab977cc2b2d7319be858bcb30a5d189bb149884",
2828
"version": null
2929
}
3030
},

Diff for: Package.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ let package = Package(
2525
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
2626
.target(
2727
name: "Keycard",
28-
dependencies: ["secp256k1", "CryptoSwift", "ZipArchive", "BigInt"]),
28+
dependencies: ["secp256k1", "CryptoSwift", "ZipArchive", "BigInt"],
29+
swiftSettings: [.define("USE_SPM")]),
2930
.testTarget(
3031
name: "KeycardTests",
3132
dependencies: ["Keycard"]),

Diff for: Sources/Keycard/Certificate.swift

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
enum CertificateTag: UInt8 {
2+
case certificate = 0x8A
3+
}
4+
5+
public struct Certificate {
6+
let identPub: [UInt8]
7+
let caSignature: RecoverableSignature
8+
9+
public static func fromTLV(certData: [UInt8]) -> Certificate {
10+
let pub = Array(certData[0..<33])
11+
let r = Array(certData[33..<65])
12+
let s = Array(certData[65..<97])
13+
let recId = certData[97]
14+
15+
let hash = Crypto.shared.sha256(pub)
16+
let caPub = Crypto.shared.secp256k1RecoverPublic(r: r, s: s, recId: recId, hash: hash, compressed: true)
17+
let caSig = RecoverableSignature(r: r, s: s, recId: recId, publicKey: caPub, compressed: true)
18+
19+
return Certificate(identPub: pub, caSignature: caSig);
20+
}
21+
22+
public static func verifyIdentity(hash: [UInt8], tlvData: [UInt8]) throws -> [UInt8]? {
23+
let tlv = TinyBERTLV(tlvData)
24+
_ = try tlv.enterConstructed(tag: ECDSASignatureTag.signatureTemplate.rawValue)
25+
let certData = try tlv.readPrimitive(tag: CertificateTag.certificate.rawValue)
26+
let cert = Certificate.fromTLV(certData: certData)
27+
let signature = tlv.peekUnread()
28+
29+
if (!Crypto.shared.secp256k1Verify(signature: signature, hash: hash, pubKey: cert.identPub)) {
30+
return nil
31+
}
32+
33+
return cert.caSignature.publicKey
34+
}
35+
}

Diff for: Sources/Keycard/Crypto.swift

+29-7
Original file line numberDiff line numberDiff line change
@@ -195,16 +195,16 @@ class Crypto {
195195
func secp256k1PublicFromPrivate(_ privKey: [UInt8]) -> [UInt8] {
196196
var pubKey = secp256k1_pubkey()
197197
_ = secp256k1_ec_pubkey_create(secp256k1Ctx, &pubKey, privKey)
198-
return _secp256k1PubToBytes(&pubKey)
198+
return _secp256k1PubToBytes(&pubKey, false)
199199
}
200200

201-
func secp256k1RecoverPublic(r: [UInt8], s: [UInt8], recId: UInt8, hash: [UInt8]) -> [UInt8] {
201+
func secp256k1RecoverPublic(r: [UInt8], s: [UInt8], recId: UInt8, hash: [UInt8], compressed: Bool) -> [UInt8] {
202202
var sig = secp256k1_ecdsa_recoverable_signature()
203203
_ = secp256k1_ecdsa_recoverable_signature_parse_compact(secp256k1Ctx, &sig, r + s, Int32(recId))
204204

205205
var pubKey = secp256k1_pubkey()
206206
_ = secp256k1_ecdsa_recover(secp256k1Ctx, &pubKey, &sig, hash)
207-
return _secp256k1PubToBytes(&pubKey)
207+
return _secp256k1PubToBytes(&pubKey, compressed)
208208
}
209209

210210
func secp256k1Sign(hash: [UInt8], privKey: [UInt8]) -> [UInt8] {
@@ -217,11 +217,33 @@ class Crypto {
217217
secp256k1_ecdsa_signature_serialize_der(secp256k1Ctx, &derSig, &derOutLen, &sig)
218218
return Array(derSig[0..<derOutLen])
219219
}
220+
221+
func secp256k1Verify(signature: [UInt8], hash: [UInt8], pubKey: [UInt8]) -> Bool {
222+
var sig = secp256k1_ecdsa_signature()
223+
_ = secp256k1_ecdsa_signature_parse_der(secp256k1Ctx, &sig, signature, signature.count)
224+
var signorm = secp256k1_ecdsa_signature()
225+
_ = secp256k1_ecdsa_signature_normalize(secp256k1Ctx, &signorm, &sig)
226+
227+
var pkey = secp256k1_pubkey();
228+
_ = secp256k1_ec_pubkey_parse(secp256k1Ctx, &pkey, pubKey, pubKey.count)
229+
230+
return secp256k1_ecdsa_verify(secp256k1Ctx, &signorm, hash, &pkey) != 0
231+
}
220232

221-
private func _secp256k1PubToBytes(_ pubKey: inout secp256k1_pubkey) -> [UInt8] {
222-
var pubKeyBytes = [UInt8](repeating: 0, count: 65)
223-
var outputLen = 65
224-
_ = secp256k1_ec_pubkey_serialize(secp256k1Ctx, &pubKeyBytes, &outputLen, &pubKey, UInt32(SECP256K1_EC_UNCOMPRESSED))
233+
private func _secp256k1PubToBytes(_ pubKey: inout secp256k1_pubkey, _ compressed: Bool) -> [UInt8] {
234+
var outputLen: Int
235+
var compressedFlag: UInt32
236+
237+
if (compressed) {
238+
outputLen = 33
239+
compressedFlag = UInt32(SECP256K1_EC_COMPRESSED)
240+
} else {
241+
outputLen = 65
242+
compressedFlag = UInt32(SECP256K1_EC_UNCOMPRESSED)
243+
}
244+
245+
var pubKeyBytes = [UInt8](repeating: 0, count: outputLen)
246+
_ = secp256k1_ec_pubkey_serialize(secp256k1Ctx, &pubKeyBytes, &outputLen, &pubKey, compressedFlag)
225247

226248
return pubKeyBytes
227249
}

Diff for: Sources/Keycard/FileLoader.swift

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import Foundation
2+
3+
#if USE_SPM
4+
import ZipArchive
5+
#else
26
import SSZipArchive
7+
#endif
38

49
struct FileLoader {
510
private static let blockSize = 247 // 255 - 8 bytes for MAC

Diff for: Sources/Keycard/Keycard.swift

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public enum KeycardINS: UInt8 {
2929
case initialize = 0xfe
3030
case factoryReset = 0xfd
3131
case getStatus = 0xf2
32+
case identifyCard = 0x14
3233
case verifyPIN = 0x20
3334
case changePIN = 0x21
3435
case unblockPIN = 0x22

Diff for: Sources/Keycard/KeycardCommandSet.swift

+6-1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ public class KeycardCommandSet {
6161
public func unpairOthers() throws {
6262
try secureChannel.unpairOthers(channel: cardChannel)
6363
}
64+
65+
public func identifyCard(challenge: [UInt8]) throws -> APDUResponse {
66+
let cmd = APDUCommand(cla: CLA.proprietary.rawValue, ins: KeycardINS.identifyCard.rawValue, p1: 0x00, p2: 0x00, data: challenge)
67+
return try cardChannel.send(cmd)
68+
}
6469

6570
public func openSecureChannel(index: UInt8, data: [UInt8]) throws -> APDUResponse {
6671
try secureChannel.openSecureChannel(channel: cardChannel, index: index, data: data)
@@ -302,5 +307,5 @@ public class KeycardCommandSet {
302307
public func factoryReset() throws -> APDUResponse {
303308
let cmd = APDUCommand(cla: CLA.proprietary.rawValue, ins: KeycardINS.factoryReset.rawValue, p1: FactoryResetP1.magic.rawValue, p2: FactoryResetP2.magic.rawValue, data: [])
304309
return try cardChannel.send(cmd)
305-
}
310+
}
306311
}

Diff for: Sources/Keycard/RecoverableSignature.swift

+41-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
enum ECDSASignatureTag: UInt8 {
22
case signatureTemplate = 0xA0
3+
case rawSignature = 0x80
34
case ecdsaTemplate = 0x30
45
}
56

@@ -8,29 +9,63 @@ public struct RecoverableSignature {
89
public let recId: UInt8
910
public let r: [UInt8]
1011
public let s: [UInt8]
12+
public let compressed: Bool
13+
14+
public init(r: [UInt8], s: [UInt8], recId: UInt8, publicKey: [UInt8], compressed: Bool) {
15+
self.r = r
16+
self.s = s
17+
self.recId = recId
18+
self.publicKey = publicKey
19+
self.compressed = compressed
20+
}
1121

1222
public init(hash: [UInt8], data: [UInt8]) throws {
1323
let tlv = TinyBERTLV(data)
24+
let tag = try tlv.readTag()
25+
tlv.unreadLastTag()
26+
27+
if (tag == ECDSASignatureTag.rawSignature.rawValue) {
28+
try self.init(hash: hash, signature: tlv.readPrimitive(tag: tag))
29+
} else if (tag == ECDSASignatureTag.signatureTemplate.rawValue) {
30+
try self.init(hash: hash, tlv: tlv)
31+
} else {
32+
throw TLVError.unexpectedTag(expected: ECDSASignatureTag.signatureTemplate.rawValue, actual: tag)
33+
}
34+
}
35+
36+
private init(hash: [UInt8], tlv: TinyBERTLV) throws {
1437
_ = try tlv.enterConstructed(tag: ECDSASignatureTag.signatureTemplate.rawValue)
1538
self.publicKey = try tlv.readPrimitive(tag: AppInfoTag.pubKey.rawValue)
1639
_ = try tlv.enterConstructed(tag: ECDSASignatureTag.ecdsaTemplate.rawValue)
1740
self.r = try Util.shared.dropZeroPrefix(uint8: tlv.readPrimitive(tag: TLVTag.int.rawValue))
1841
self.s = try Util.shared.dropZeroPrefix(uint8: tlv.readPrimitive(tag: TLVTag.int.rawValue))
19-
42+
self.compressed = false
43+
self.recId = try RecoverableSignature.calculateRecId(hash: hash, pubkey: self.publicKey, r: self.r, s: self.s, compressed: self.compressed)
44+
}
45+
46+
private init(hash: [UInt8], signature: [UInt8]) throws {
47+
self.r = Array(signature[0..<32])
48+
self.s = Array(signature[32..<64])
49+
self.recId = signature[64]
50+
self.compressed = false
51+
self.publicKey = Crypto.shared.secp256k1RecoverPublic(r: self.r, s: self.s, recId: self.recId, hash: hash, compressed: self.compressed)
52+
}
53+
54+
public static func calculateRecId(hash: [UInt8], pubkey: [UInt8], r: [UInt8], s: [UInt8], compressed: Bool) throws -> UInt8 {
2055
var foundID: UInt8 = UInt8.max
2156

2257
for i: UInt8 in 0...3 {
23-
let pub = Crypto.shared.secp256k1RecoverPublic(r: r, s: s, recId: i, hash: hash)
24-
if (pub == self.publicKey) {
58+
let pub = Crypto.shared.secp256k1RecoverPublic(r: r, s: s, recId: i, hash: hash, compressed: compressed)
59+
if (pub == pubkey) {
2560
foundID = i
2661
break
2762
}
2863
}
2964

30-
if (foundID != UInt8.max) {
31-
self.recId = foundID
32-
} else {
65+
if (foundID == UInt8.max) {
3366
throw CardError.unrecoverableSignature
3467
}
68+
69+
return foundID
3570
}
3671
}

Diff for: Sources/Keycard/TinyBERTLV.swift

+4
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ class TinyBERTLV {
113113
throw TLVError.unexpectedLength(length: val.count)
114114
}
115115
}
116+
117+
func peekUnread() -> [UInt8] {
118+
return Array(self.buf[self.pos..<self.buf.count])
119+
}
116120

117121
func readLength() -> Int {
118122
var len = Int(buf[pos])

0 commit comments

Comments
 (0)