This repository was archived by the owner on Jun 23, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathQRCode.swift
148 lines (116 loc) · 4.07 KB
/
QRCode.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//
// Copyright © 2020 NHSX. All rights reserved.
//
import CryptoKit
import Foundation
#warning("Rework this type")
// We should be able to inject the minimum we need for our JWT / security checking functionality,
// without having to expose our payload type (`Venue`) here.
// The name is also very old since this is more of a parser now.
public struct QRCode {
private var prefix = "UKC19TRACING"
private var minimumVersionNumber = 1
var keyId: String
var key: P256.Signing.PublicKey
public init(
keyId: String,
key: P256.Signing.PublicKey
) {
self.keyId = keyId
self.key = key
}
enum InitializationError: Error {
case invalidFormat
case invalidPrefix
case invalidVersionNumber
case invalidJSONData
case invalidKey
case invalidSignature
}
func parse(_ payload: String) throws -> Venue {
let parts = payload.split(separator: ":").map { String($0) }
guard parts.count == 3 else {
throw InitializationError.invalidFormat
}
guard parts[0] == prefix else {
throw InitializationError.invalidPrefix
}
guard let versionNumber = Int(parts[1]), versionNumber >= minimumVersionNumber else {
throw InitializationError.invalidVersionNumber
}
guard let jwt = JWT(string: parts[2]) else {
throw InitializationError.invalidJSONData
}
guard jwt.keyId == keyId else {
throw InitializationError.invalidKey
}
guard let signature = try? P256.Signing.ECDSASignature(rawRepresentation: jwt.signature) else {
throw InitializationError.invalidSignature
}
let digest = SHA256.hash(data: jwt.signed)
guard key.isValidSignature(signature, for: digest) else {
throw InitializationError.invalidSignature
}
let decoder = JSONDecoder()
return try decoder.decode(Venue.self, from: jwt.payload)
}
}
private extension Data {
init?(jwt: String) {
let components = jwt.split(separator: ".").map { String($0) }
switch components.count {
case 3:
self.init(jwtPart: components[1])
default:
return nil
}
}
}
private struct JWT {
private struct Header: Codable {
var kid: String
}
private struct Raw {
var header: Data
var payload: Data
var signature: Data
init?(string: String) {
let components = string.split(separator: ".")
.map { String($0) }
guard components.count == 3,
let header = Data(jwtPart: components[0]),
let payload = Data(jwtPart: components[1]),
let signature = Data(jwtPart: components[2]) else { return nil }
self.header = header
self.payload = payload
self.signature = signature
}
}
var keyId: String
var payload: Data
var signed: Data
var signature: Data
init?(string: String) {
guard let raw = Raw(string: string) else { return nil }
let decoder = JSONDecoder()
guard let header = try? decoder.decode(Header.self, from: raw.header) else { return nil }
keyId = header.kid
payload = raw.payload
signature = raw.signature
signed = string.split(separator: ".")
.map { String($0) }
.dropLast()
.joined(separator: ".")
.data(using: .ascii)!
}
}
private extension Data {
init?(jwtPart: String) {
var jwtPart = jwtPart
.replacingOccurrences(of: "-", with: "+")
.replacingOccurrences(of: "_", with: "/")
let paddingLength = jwtPart.count.quotientAndRemainder(dividingBy: 4).remainder
jwtPart.append(contentsOf: repeatElement("=", count: paddingLength))
self.init(base64Encoded: jwtPart)
}
}