Skip to content

Commit

Permalink
Add support for certificates (#31)
Browse files Browse the repository at this point in the history
* add ident command

* add Certificate struct

* fix ident command
  • Loading branch information
bitgamma authored Sep 23, 2024
1 parent e75e6b8 commit afb1983
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 17 deletions.
2 changes: 1 addition & 1 deletion Keycard.podspec
Original file line number Diff line number Diff line change
@@ -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' }
Expand Down
2 changes: 1 addition & 1 deletion Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"repositoryURL": "https://github.com/status-im/secp256k1.swift.git",
"state": {
"branch": "master",
"revision": "d2c49786e9245d77f4eba6ce78a87f87506623c5",
"revision": "4ab977cc2b2d7319be858bcb30a5d189bb149884",
"version": null
}
},
Expand Down
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"]),
Expand Down
35 changes: 35 additions & 0 deletions Sources/Keycard/Certificate.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
36 changes: 29 additions & 7 deletions Sources/Keycard/Crypto.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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] {
Expand All @@ -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
}
Expand Down
5 changes: 5 additions & 0 deletions Sources/Keycard/FileLoader.swift
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions Sources/Keycard/Keycard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion Sources/Keycard/KeycardCommandSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
}
}
47 changes: 41 additions & 6 deletions Sources/Keycard/RecoverableSignature.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
enum ECDSASignatureTag: UInt8 {
case signatureTemplate = 0xA0
case rawSignature = 0x80
case ecdsaTemplate = 0x30
}

Expand All @@ -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
}
}
4 changes: 4 additions & 0 deletions Sources/Keycard/TinyBERTLV.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down

0 comments on commit afb1983

Please sign in to comment.