|
4 | 4 | import hashlib
|
5 | 5 |
|
6 | 6 | from . import tlv
|
| 7 | +from . import pase |
7 | 8 | from .data_model import Enum8
|
8 | 9 |
|
9 | 10 | import ecdsa
|
| 11 | +from ecdsa.curves import NIST256p |
10 | 12 | from ecdsa import der
|
11 | 13 |
|
12 | 14 | PAI_KEY_DER = b"\x30\x77\x02\x01\x01\x04\x20\xbb\x76\xa5\x80\x5f\x97\x26\x49\xaf\x1e\x8a\x87\xdc\x45\x57\xe6\x2c\x09\x00\xe5\x07\x09\xe8\x5c\x79\xc6\x44\xdf\x78\x90\xe5\x96\xa0\x0a\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\xa1\x44\x03\x42\x00\x04\x37\x5d\x2b\xc8\xc6\x15\x27\x5b\xfd\x84\x8b\x52\xfe\x21\x96\xe2\xa1\x4e\xf3\xcc\x91\xae\xf0\x5d\xff\x85\x1c\xbc\x19\xb1\xa9\x35\x45\x8c\xfe\x04\xaa\x42\x4e\x01\x6d\xe3\xd6\x74\xdc\x5b\x73\x29\xbd\x77\x57\xfd\xdb\x32\x38\xd6\x26\x73\x62\x9b\x3c\x79\x08\x45"
|
13 | 15 |
|
| 16 | +INVALID_PASSCODES = [ |
| 17 | + 0, |
| 18 | + 11111111, |
| 19 | + 22222222, |
| 20 | + 33333333, |
| 21 | + 44444444, |
| 22 | + 55555555, |
| 23 | + 66666666, |
| 24 | + 77777777, |
| 25 | + 88888888, |
| 26 | + 12345678, |
| 27 | + 87654321, |
| 28 | +] |
| 29 | + |
14 | 30 |
|
15 | 31 | class CertificationType(Enum8):
|
16 | 32 | DEVELOPMENT_AND_TEST = 0
|
@@ -171,6 +187,7 @@ def generate_dac(
|
171 | 187 | der.encode_octet_string(hashlib.sha1(public_key).digest())
|
172 | 188 | ),
|
173 | 189 | )
|
| 190 | + # ID of the CircuitMatter 0xFFF4 PAI |
174 | 191 | authority_key_id = b"\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x07\xf8\x38\x0a\x5f\x01\x36\xfc\xe2\x36\xbd\x45\xf2\x88\xff\x22\xdc\xa6\xf4\xa7"
|
175 | 192 | extensions = der.encode_constructed(
|
176 | 193 | 3, der.encode_sequence(basic_constraints, key_usage, key_id, authority_key_id)
|
@@ -201,19 +218,103 @@ def generate_dac(
|
201 | 218 | return dac_cert, dac_key
|
202 | 219 |
|
203 | 220 |
|
| 221 | +def compute_verifier(passcode: int, salt: bytes, iterations: int) -> bytes: |
| 222 | + w0, w1 = pase._pbkdf2(passcode, salt, iterations) |
| 223 | + L = NIST256p.generator * w1 |
| 224 | + |
| 225 | + return w0.to_bytes(NIST256p.baselen, byteorder="big") + L.to_bytes("uncompressed") |
| 226 | + |
| 227 | + |
| 228 | +# Look up tables for Verhoeff Algorithm |
| 229 | +# From: https://en.wikipedia.org/wiki/Verhoeff_algorithm#Table-based_algorithm |
| 230 | +D_TABLE = ( |
| 231 | + b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09" |
| 232 | + + b"\x01\x02\x03\x04\x00\x06\x07\x08\x09\x05" |
| 233 | + + b"\x02\x03\x04\x00\x01\x07\x08\x09\x05\x06" |
| 234 | + + b"\x03\x04\x00\x01\x02\x08\x09\x05\x06\x07" |
| 235 | + + b"\x04\x00\x01\x02\x03\x09\x05\x06\x07\x08" |
| 236 | + + b"\x05\x09\x08\x07\x06\x00\x04\x03\x02\x01" |
| 237 | + + b"\x06\x05\x09\x08\x07\x01\x00\x04\x03\x02" |
| 238 | + + b"\x07\x06\x05\x09\x08\x02\x01\x00\x04\x03" |
| 239 | + + b"\x08\x07\x06\x05\x09\x03\x02\x01\x00\x04" |
| 240 | + + b"\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00" |
| 241 | +) |
| 242 | + |
| 243 | +INV_TABLE = b"\x00\x04\x03\x02\x01\x05\x06\x07\x08\x09" |
| 244 | + |
| 245 | +P_TABLE = ( |
| 246 | + b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09" |
| 247 | + + b"\x01\x05\x07\x06\x02\x08\x03\x00\x09\x04" |
| 248 | + + b"\x05\x08\x00\x03\x07\x09\x06\x01\x04\x02" |
| 249 | + b"\x08\x09\x01\x06\x00\x04\x03\x05\x02\x07" |
| 250 | + + b"\x09\x04\x05\x03\x01\x02\x06\x08\x07\x00" |
| 251 | + + b"\x04\x02\x08\x06\x05\x07\x03\x09\x00\x01" |
| 252 | + + b"\x02\x07\x09\x03\x08\x00\x06\x04\x01\x05" |
| 253 | + + b"\x07\x00\x04\x06\x09\x01\x03\x02\x05\x08" |
| 254 | +) |
| 255 | + |
| 256 | + |
| 257 | +def _bcd(buf, n): |
| 258 | + div = 10 ** (len(buf) - 1) |
| 259 | + for i in range(len(buf)): |
| 260 | + buf[i] = (n // div) % 10 |
| 261 | + div //= 10 |
| 262 | + |
| 263 | + |
| 264 | +def compute_manual_code( |
| 265 | + discriminator, passcode, vendor_id=None, product_id=None |
| 266 | +) -> str: |
| 267 | + vid_pid_present = 0 |
| 268 | + if vendor_id is not None and product_id is not None: |
| 269 | + vid_pid_present = 1 |
| 270 | + |
| 271 | + digits = memoryview(bytearray(11)) |
| 272 | + digits[0] = (vid_pid_present << 2) | (discriminator >> 10) |
| 273 | + d2_6 = ((discriminator & 0x300) << 6) | (passcode & 0x3FFF) |
| 274 | + _bcd(digits[1:6], d2_6) |
| 275 | + d7_10 = passcode >> 14 |
| 276 | + _bcd(digits[6:10], d7_10) |
| 277 | + |
| 278 | + # Checksum of zero. We'll overwrite it. |
| 279 | + digits[10] = 0 |
| 280 | + |
| 281 | + c = 0 |
| 282 | + for i, n in enumerate(reversed(digits)): |
| 283 | + c = D_TABLE[c * 10 + P_TABLE[(i % 8) * 10 + n]] |
| 284 | + |
| 285 | + digits[10] = INV_TABLE[c] |
| 286 | + digits = [str(x) for x in digits] |
| 287 | + digits.insert(4, "-") |
| 288 | + digits.insert(8, "-") |
| 289 | + |
| 290 | + return "".join(digits) |
| 291 | + |
| 292 | + |
204 | 293 | def generate_initial_state(vendor_id, product_id, product_name, random_source):
|
205 | 294 | if vendor_id != 0xFFF4 or product_id != 0x1234:
|
206 | 295 | raise ValueError("Invalid vendor_id or product_id")
|
207 | 296 |
|
208 | 297 | cd = generate_certificates(vendor_id=vendor_id, product_id=product_id)
|
209 | 298 |
|
210 | 299 | dac_cert, dac_key = generate_dac(vendor_id, product_id, product_name, random_source)
|
| 300 | + |
| 301 | + passcode = 0 |
| 302 | + while passcode in INVALID_PASSCODES: |
| 303 | + passcode = random_source.randbelow(99999999) |
| 304 | + discriminator = random_source.randbelow(1 << 12) # A 12-bit random number |
| 305 | + iteration_count = 10000 |
| 306 | + salt = random_source.urandom(32) |
| 307 | + verifier = compute_verifier(passcode, salt, iteration_count) |
| 308 | + # This does *NOT* follow the spec because the passcode is stored alongside the verifier. |
| 309 | + # The spec wants the passcode stored physically on the box and package of the device in |
| 310 | + # the setup code and QR Code. The verifier is in the device only. |
211 | 311 | initial_state = {
|
212 |
| - "discriminator": 3840, |
213 |
| - "passcode": 67202583, |
214 |
| - "iteration-count": 10000, |
215 |
| - "salt": "5uCP0ITHYzI9qBEe6hfU4HfY3y7VopSk0qNvhvznhiQ=", |
216 |
| - "verifier": "0xGqxJFBr/ViQt3lv1Yw5F0GcPBAtFFvXB+EcIIjH5cEsjkPZHDQyFWjA6Ide+2gafYnZgIy6gJBgdJOlD8htAZKe0i6nIhT/ADsBWH4CvZcl37n/ofEEECWSEBV4vy/0A==", |
| 312 | + "discriminator": discriminator, |
| 313 | + "passcode": passcode, |
| 314 | + "manual_code": compute_manual_code(discriminator, passcode), |
| 315 | + "iteration-count": iteration_count, |
| 316 | + "salt": binascii.b2a_base64(salt, newline=False).decode("utf-8"), |
| 317 | + "verifier": binascii.b2a_base64(verifier, newline=False).decode("utf-8"), |
217 | 318 | "devices": {
|
218 | 319 | "root": {
|
219 | 320 | "0x3e": {
|
|
0 commit comments