Skip to content

Commit

Permalink
Add RSA support.
Browse files Browse the repository at this point in the history
  • Loading branch information
yspreen committed Apr 16, 2021
1 parent 2d3337b commit 7225860
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 45 deletions.
12 changes: 8 additions & 4 deletions PatientScannerDemo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
CE1BDF99262A4CD600766F97 /* X509.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1BDF98262A4CD600766F97 /* X509.swift */; };
CE3CC93C2628A7820079FB78 /* ASN1.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3CC93B2628A7820079FB78 /* ASN1.swift */; };
CE3CC9442628C2130079FB78 /* CBOR.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3CC9432628C2130079FB78 /* CBOR.swift */; };
CE7DE7FA2625EF18007E6694 /* SwiftCBOR in Frameworks */ = {isa = PBXBuildFile; productRef = CE7DE7F92625EF18007E6694 /* SwiftCBOR */; };
Expand All @@ -21,7 +22,7 @@
CEC2C4C22625ED030056E406 /* ZLib.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC2C4BF2625ED030056E406 /* ZLib.swift */; };
CEC2C4C32625ED030056E406 /* JWK.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC2C4C02625ED030056E406 /* JWK.swift */; };
CEC2C4C42625ED030056E406 /* Base45.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC2C4C12625ED030056E406 /* Base45.swift */; };
CEFAD86D2625F164009AFEF9 /* EC256.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFAD86C2625F164009AFEF9 /* EC256.swift */; };
CEFAD86D2625F164009AFEF9 /* Signature.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFAD86C2625F164009AFEF9 /* Signature.swift */; };
CEFAD8722625F29E009AFEF9 /* String+JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFAD8712625F29E009AFEF9 /* String+JSON.swift */; };
CEFAD87A26271414009AFEF9 /* COSE.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFAD87926271414009AFEF9 /* COSE.swift */; };
CEFAD87F262714C4009AFEF9 /* EHNTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFAD87E262714C4009AFEF9 /* EHNTests.swift */; };
Expand All @@ -46,6 +47,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
CE1BDF98262A4CD600766F97 /* X509.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = X509.swift; sourceTree = "<group>"; };
CE3CC93B2628A7820079FB78 /* ASN1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASN1.swift; sourceTree = "<group>"; };
CE3CC9432628C2130079FB78 /* CBOR.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBOR.swift; sourceTree = "<group>"; };
CEA6D6E8261F8D2700715333 /* PatientScannerDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PatientScannerDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
Expand All @@ -65,7 +67,7 @@
CEC2C4BF2625ED030056E406 /* ZLib.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZLib.swift; sourceTree = "<group>"; };
CEC2C4C02625ED030056E406 /* JWK.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JWK.swift; sourceTree = "<group>"; };
CEC2C4C12625ED030056E406 /* Base45.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Base45.swift; sourceTree = "<group>"; };
CEFAD86C2625F164009AFEF9 /* EC256.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EC256.swift; sourceTree = "<group>"; };
CEFAD86C2625F164009AFEF9 /* Signature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signature.swift; sourceTree = "<group>"; };
CEFAD8712625F29E009AFEF9 /* String+JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+JSON.swift"; sourceTree = "<group>"; };
CEFAD87926271414009AFEF9 /* COSE.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = COSE.swift; sourceTree = "<group>"; };
CEFAD87E262714C4009AFEF9 /* EHNTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EHNTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -132,11 +134,12 @@
CEA6D6F4261F8D2900715333 /* Assets.xcassets */,
CEA6D6F6261F8D2900715333 /* LaunchScreen.storyboard */,
CEA6D6F9261F8D2900715333 /* Info.plist */,
CEFAD86C2625F164009AFEF9 /* EC256.swift */,
CEFAD86C2625F164009AFEF9 /* Signature.swift */,
CE3CC93B2628A7820079FB78 /* ASN1.swift */,
CEFAD87926271414009AFEF9 /* COSE.swift */,
CEFAD88626271B9A009AFEF9 /* Data+hexString.swift */,
CE3CC9432628C2130079FB78 /* CBOR.swift */,
CE1BDF98262A4CD600766F97 /* X509.swift */,
);
path = PatientScannerDemo;
sourceTree = "<group>";
Expand Down Expand Up @@ -300,10 +303,11 @@
CEC2C4C32625ED030056E406 /* JWK.swift in Sources */,
CEC2C4C42625ED030056E406 /* Base45.swift in Sources */,
CE3CC9442628C2130079FB78 /* CBOR.swift in Sources */,
CE1BDF99262A4CD600766F97 /* X509.swift in Sources */,
CEA6D6F0261F8D2700715333 /* ViewController.swift in Sources */,
CE3CC93C2628A7820079FB78 /* ASN1.swift in Sources */,
CEFAD87A26271414009AFEF9 /* COSE.swift in Sources */,
CEFAD86D2625F164009AFEF9 /* EC256.swift in Sources */,
CEFAD86D2625F164009AFEF9 /* Signature.swift in Sources */,
CEA6D6EC261F8D2700715333 /* AppDelegate.swift in Sources */,
CEFAD8722625F29E009AFEF9 /* String+JSON.swift in Sources */,
CEC2C4C22625ED030056E406 /* ZLib.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion PatientScannerDemo/CBOR.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ struct CBOR {
#else
return nil
#endif
return Data(hexString: str)?.uint ?? str.encode()
return Data(hexString: str)?.uint ?? str.data(using: .utf8)?.uint
case let .byteString(uint):
return uint
default:
Expand Down
37 changes: 36 additions & 1 deletion PatientScannerDemo/COSE.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ struct COSE {
}
return verify(cbor, with: xHex, and: yHex)
}
public static func verify(_ cborData: Data, with rsa: String) -> Bool {
let decoder = SwiftCBOR.CBORDecoder(input: cborData.uint)

guard let cbor = try? decoder.decodeItem() else {
return false
}
return verify(cbor, with: rsa)
}
public static func verify(_ cbor: SwiftCBOR.CBOR, with xHex: String, and yHex: String) -> Bool {
let COSE_TAG = UInt64(18)

Expand All @@ -42,6 +50,33 @@ struct COSE {
guard let key = JWK.ecFrom(x: xHex, y: yHex) else {
return false
}
return EC256.verify(signature: s, for: d, with: key)
return Signature.verify(s, for: d, with: key)
}
public static func verify(_ cbor: SwiftCBOR.CBOR, with rsa: String) -> Bool {
let COSE_TAG = UInt64(18)

guard
case let SwiftCBOR.CBOR.tagged(tag, cborElement) = cbor,
tag.rawValue == COSE_TAG, // SIGN1
case let SwiftCBOR.CBOR.array(array) = cborElement,
case let SwiftCBOR.CBOR.byteString(signature) = array[3]
else {
return false
}

let signedPayload: [UInt8] = SwiftCBOR.CBOR.encode(
[
"Signature1",
array[0],
SwiftCBOR.CBOR.byteString([]),
array[2]
]
)
let d = Data(signedPayload)
let s = Data(signature)
guard let key = X509.rsa(from: rsa) else {
return false
}
return Signature.verify(s, for: d, with: key)
}
}
37 changes: 0 additions & 37 deletions PatientScannerDemo/EC256.swift

This file was deleted.

60 changes: 60 additions & 0 deletions PatientScannerDemo/Signature.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// EC256.swift
// PatientScannerDemo
//
// Created by Yannick Spreen on 4/13/21.
//
// https://developer.apple.com/forums/thread/83136
//

import Foundation

struct Signature {
public static func verify(_ signature: Data, for data: Data, with publicKey: SecKey) -> Bool {
if SecKeyIsAlgorithmSupported(publicKey, .verify, .ecdsaSignatureMessageX962SHA256) {
return verifyEC(signature, for: data, with: publicKey)
}
if SecKeyIsAlgorithmSupported(publicKey, .verify, .rsaSignatureMessagePSSSHA256) {
return verifyRSA(signature, for: data, with: publicKey)
}
return false
}

static func verifyEC(_ signature: Data, for data: Data, with publicKey: SecKey) -> Bool {
let sig = ASN1.signature(from: signature)

var error: Unmanaged<CFError>?
let result = SecKeyVerifySignature(
publicKey,
.ecdsaSignatureMessageX962SHA256,
data as NSData,
sig as NSData,
&error
)
if let err = error?.takeUnretainedValue().localizedDescription {
print(err)
}
error?.release()

return result
}

static func verifyRSA(_ signature: Data, for data: Data, with publicKey: SecKey) -> Bool {
let sig = signature

var error: Unmanaged<CFError>?
let result = SecKeyVerifySignature(
publicKey,
.rsaSignatureMessagePSSSHA256,
data as NSData,
sig as NSData,
&error
)
if let err = error?.takeUnretainedValue().localizedDescription {
print(err)
}
error?.release()

return result
}
}
21 changes: 21 additions & 0 deletions PatientScannerDemo/X509.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// X509.swift
// PatientScannerDemo
//
// Created by Yannick Spreen on 4/17/21.
//

import Foundation

struct X509 {
public static func rsa(from encodedCert: String) -> SecKey? {
guard
let encodedCertData = Data(base64Encoded: encodedCert),
let cert = SecCertificateCreateWithData(nil, encodedCertData as CFData),
let publicKey = SecCertificateCopyKey(cert)
else {
return nil
}
return publicKey
}
}
93 changes: 91 additions & 2 deletions PatientScannerDemoTests/EHNTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import XCTest


class EHNTests: XCTestCase {
func test_cose() throws {

func testCoseEcdsa() throws {
var barcode = "HC1NCFY70R30FFWTWGSLKC 4O992$V M63TMF2V*D9LPC.3EHPCGEC27B72VF/347O4-M6Y9M6FOYG4ILDEI8GR3ZI$15MABL:E9CVBGEEWRMLE C39S0/ANZ52T82Z-73D63P1U 1$PKC 72H2XX09WDH889V5"

let trustJson = """
Expand Down Expand Up @@ -74,6 +73,96 @@ class EHNTests: XCTestCase {
print("Nope - all failed - sadness all around")
XCTAssert(false)
}
func testCoseATCose() throws {
var barcode = "HC1NCFI.L3B6AP2YQ2%MNBCHC51/CAOZDL+OL7S99U60SVOGSHD%24R4PCTH7Y9KL6EVJBIMBL364D X1KKLZ*I9K4RLDR7B011EROZS6022WR5VX4QG9W72HWCC12B:TWZG.%N8GWBCOV0O%5F9U7H 8P4L.%2/:6TXB/-J6ZJ.*QYIBYSD6/T.6RDMMK0PX6HTPO1REO*CWMFG5CJ38+FBS+8DB9*2FFP9Z8HEM137CRBQ.893$I.R7 DRFOVMUQRVDP4IU613/G-0LZ:43+MM:QO.CQPJU21-7PJ*L/*DVLO:Q5G-7%WF4ZO*JJR:KH+O1707M6.73VXG0+0050+IMIFH:3V+DL3JK/21/LEI$T5TUT8P:40P10AXGBRS%-GZS8TGF-IT3-HU2SVJUAVG%AGO98R00Y.QT23DVP0TB*-J6YJP 8TU9U/OJSQGGMK+K3MH IR U7H%9/UU07AGEWMVTOBAYIV4SPGC2Y5FJJ67I4/J4FXM3GP:SAOZQJPL%WGP4BT59Q8K5MB*M44%K9JTWB9K+LS-2I9ST+3J%0X%1YGN0E0KL3/FHTZHEF9WIC4Q1HVK74L186BWT9+TJR7C2M%9JXN8S%0PVU8GFBET%0PG RR1KD2F11G-CW8A4124N9VN-T-:292AX*B%ER*$12-O:6K:/N$ 7IYFFXQ/5F NC49M/W5501B2OKU7E:NHMM8SETWUK*I$JR"

// Remove HC1 header if any
if (barcode.hasPrefix("HC1")) {
barcode = String(barcode.suffix(barcode.count-3))
}

guard
let compressed = try? barcode.fromBase45()
else {
XCTAssert(false)
return
}

let data = decompress(compressed)

guard
let kidBytes = CBOR.kid(from: data),
let kid = String(data: Data(kidBytes), encoding: .utf8),
let url = URL(string: "https://dev.a-sit.at/certservice/cert/\(kid)")
else {
XCTAssert(false)
return
}
let expectation = XCTestExpectation(description: "Download PubKey")
URLSession.shared.dataTask(with: URLRequest(url: url)) { body, response, error in
guard
error == nil,
let status = (response as? HTTPURLResponse)?.statusCode,
200 == status,
let body = body
else {
XCTAssert(false)
return
}
let encodedCert = body.base64EncodedString()
if COSE.verify(data, with: encodedCert) {
expectation.fulfill()
} else {
XCTAssert(false)
}
}.resume()
wait(for: [expectation], timeout: 15)
}
func testCoseATRsa() throws {
var barcode = "HC1NCFI.LUZB.P2YQ2E R002$DLTLIYXM/+0JNEAFUP.CV5GKCL /A0SQT/O8ZA85D0IJ$5D4.J+MR8-EGK6DM1R58V1ERUCWM4.E4ZM4$WG O E6KX9$KHCQMFE0B$KPKA*A2+ELQ*5ZQE/*1ZMSM5S.:KGQ33LQOWN67QW68LXM867D6837QCWEU*E*OKZCJPSCPIM1:Q83FBODLRD/W8K$E4 SG4P2JM6GCMU9FFSZAKNJH6ZNZVSWU78MHB4TO5J0N51U2F:17AB8O0*N0CQSA-9VIN904*STT$6R1LLXDT8J1ZN4I4368L02N72ZMGV496.0NO3OQCPFVF+C$BKQH1$+8L:AAEGRIB548B68D/3-ZTZ.JNM84E0F59$N6DY2*6FACA448900U42ZEMMO7-LO.45*IMXYIODF:82RN4L8Q%20HDJ-YCHIA1YT87A2/C1NO1F6DPCLCK9+BDXEEK23JH0/I8XJQEQ1T8BQ5GNDRFPQKKV6WT$A+98TIOUSAFA0J8OMY87$P-LESPS1KO+:O%HOY*C8XH4ZE7D16QR WNN$EP:U7E2HTLS023X0GJBW887LCKVHDGJ+Q88I0A73$H6R30XYH-LDC$4+24*JSE*H98A471BJ3:R0LU3MZA+ 4/GMZ9J D4E7L.TI$L8L:TN4G2S552748V$%D0CPH9RJ-7Z4AXFN*SS/ZC44O3P5%%AE936OUSR1H7WP%CP8KQUCY9I%A5FDBV+0A%HPVP5SKP9TKAFKT9FYROAABDBEHVQ9317O0D7MS5Z.Q2+K2.1RGGS3B5OTO2ROD60Z6LE98E94TV09JXM395VA0N.98EP3*4E5SHA7I96Q70UF74SPSIJI.BUHQJ$:K$9M51H9Z6RPHR.K-XT855UJJZC8C*MSITGMRE%O+:JQXV$LS7SJ1:DE LCX7RP7T JH4RR-NK.PASIWKA8J5Z1NA*BE7KFTJXO4BM5QVF.S0-5CG8KQWB8RCA1VP*1Q.ELZ1Q21G.JEGLOFH1:FR09$SCY/OGBU1QIE/2 9SK3"

// Remove HC1 header if any
if (barcode.hasPrefix("HC1")) {
barcode = String(barcode.suffix(barcode.count-3))
}

guard
let compressed = try? barcode.fromBase45()
else {
XCTAssert(false)
return
}

let data = decompress(compressed)

guard
let kidBytes = CBOR.kid(from: data),
let kid = String(data: Data(kidBytes), encoding: .utf8),
let url = URL(string: "https://dev.a-sit.at/certservice/cert/\(kid)")
else {
XCTAssert(false)
return
}
let expectation = XCTestExpectation(description: "Download PubKey")
URLSession.shared.dataTask(with: URLRequest(url: url)) { body, response, error in
guard
error == nil,
let status = (response as? HTTPURLResponse)?.statusCode,
200 == status,
let body = body
else {
XCTAssert(false)
return
}
let encodedCert = body.base64EncodedString()
if COSE.verify(data, with: encodedCert) {
expectation.fulfill()
} else {
XCTAssert(false)
}
}.resume()
wait(for: [expectation], timeout: 15)
}
}

/**
Expand Down

0 comments on commit 7225860

Please sign in to comment.