|
1 | 1 | # This file should only be needed when generating certificates.
|
2 | 2 |
|
| 3 | +import binascii |
3 | 4 | import hashlib
|
4 | 5 |
|
5 | 6 | from . import tlv
|
|
8 | 9 | import ecdsa
|
9 | 10 | from ecdsa import der
|
10 | 11 |
|
11 |
| -import pathlib |
| 12 | +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" |
12 | 13 |
|
13 | 14 |
|
14 | 15 | class CertificationType(Enum8):
|
@@ -41,39 +42,39 @@ def encode_set(*encoded_pieces):
|
41 | 42 | return b"\x31" + der.encode_length(total_len) + b"".join(encoded_pieces)
|
42 | 43 |
|
43 | 44 |
|
| 45 | +def encode_utf8_string(s): |
| 46 | + encoded = s.encode("utf-8") |
| 47 | + return b"\x0c" + der.encode_length(len(encoded)) + encoded |
| 48 | + |
| 49 | + |
44 | 50 | def generate_certificates(
|
45 | 51 | vendor_id=0xFFF1, product_id=0x8000, device_type=22, prefix=None
|
46 | 52 | ):
|
47 | 53 | declaration = CertificationDeclaration()
|
48 | 54 | declaration.format_version = 1 # Always 1
|
49 | 55 | declaration.vendor_id = vendor_id
|
50 | 56 | declaration.product_id_array = [product_id]
|
51 |
| - declaration.device_type_id = 0x1234 # device_type |
52 |
| - declaration.certificate_id = "ZIG20141ZB330001-24" # "CSA00000SWC00000-00" |
| 57 | + declaration.device_type_id = device_type |
| 58 | + declaration.certificate_id = "CSA00000SWC00000-00" |
53 | 59 | declaration.security_level = 0 # Always 0
|
54 | 60 | declaration.security_information = 0 # Always 0
|
55 |
| - declaration.version_number = 0x2694 # 1 # Always 1 |
| 61 | + declaration.version_number = 1 # Always 1 |
56 | 62 | declaration.certification_type = CertificationType.DEVELOPMENT_AND_TEST
|
57 | 63 | declaration = declaration.encode()
|
58 | 64 |
|
59 |
| - for i in range(0, len(declaration), 16): |
60 |
| - print(f"{i:08x}", declaration[i : i + 16].hex(" ")) |
61 |
| - |
62 | 65 | # From: https://github.com/project-chip/matter.js/blob/main/packages/protocol/src/certificate/CertificationDeclarationManager.ts
|
63 | 66 | # NIST256p is the same as secp256r1
|
64 | 67 | private_key = ecdsa.keys.SigningKey.from_string(
|
65 | 68 | b"\xae\xf3\x48\x41\x16\xe9\x48\x1e\xc5\x7b\xe0\x47\x2d\xf4\x1b\xf4\x99\x06\x4e\x50\x24\xad\x86\x9e\xca\x5e\x88\x98\x02\xd4\x80\x75",
|
66 | 69 | curve=ecdsa.curves.NIST256p,
|
67 | 70 | hashfunc=hashlib.sha256,
|
68 | 71 | )
|
69 |
| - print(private_key.to_string().hex().upper()) |
70 | 72 | subject_key_identifier = b"\x62\xfa\x82\x33\x59\xac\xfa\xa9\x96\x3e\x1c\xfa\x14\x0a\xdd\xf5\x04\xf3\x71\x60"
|
71 | 73 | signature = private_key.sign_deterministic(
|
72 | 74 | declaration,
|
73 | 75 | hashfunc=hashlib.sha256,
|
74 | 76 | sigencode=ecdsa.util.sigencode_der_canonize,
|
75 | 77 | )
|
76 |
| - print("signature", signature.hex(" ")) |
77 | 78 |
|
78 | 79 | certification_declaration = []
|
79 | 80 | # version
|
@@ -120,9 +121,118 @@ def generate_certificates(
|
120 | 121 | return cms_signed
|
121 | 122 |
|
122 | 123 |
|
| 124 | +def generate_dac( |
| 125 | + vendor_id, product_id, product_name, random_source |
| 126 | +) -> tuple[bytes, bytes]: |
| 127 | + dac_key = ecdsa.keys.SigningKey.generate( |
| 128 | + curve=ecdsa.NIST256p, hashfunc=hashlib.sha256, entropy=random_source.urandom |
| 129 | + ) |
| 130 | + |
| 131 | + version = der.encode_constructed(0, der.encode_integer(2)) |
| 132 | + serial_number = der.encode_integer(1) |
| 133 | + signature_algorithm = der.encode_sequence(der.encode_oid(1, 2, 840, 10045, 4, 3, 2)) |
| 134 | + # CircuitMatter PAI for vendor ID 0xfff4 |
| 135 | + issuer = b"\x30\x32\x31\x1a\x30\x18\x06\x03\x55\x04\x03\x0c\x11\x43\x69\x72\x63\x75\x69\x74\x4d\x61\x74\x74\x65\x72\x20\x50\x41\x49\x31\x14\x30\x12\x06\x0a\x2b\x06\x01\x04\x01\x82\xa2\x7c\x02\x01\x0c\x04\x46\x46\x46\x34" |
| 136 | + |
| 137 | + # Starting 10/17/2024 and never expiring |
| 138 | + validity = b"\x30\x20\x17\x0d\x32\x34\x31\x30\x31\x37\x30\x30\x30\x30\x30\x30\x5a\x18\x0f\x39\x39\x39\x39\x31\x32\x33\x31\x32\x33\x35\x39\x35\x39\x5a" |
| 139 | + |
| 140 | + common_name = encode_set( |
| 141 | + der.encode_sequence( |
| 142 | + der.encode_oid(2, 5, 4, 3), encode_utf8_string(product_name) |
| 143 | + ) |
| 144 | + ) |
| 145 | + encoded_vendor_id = encode_set( |
| 146 | + der.encode_sequence( |
| 147 | + der.encode_oid(1, 3, 6, 1, 4, 1, 37244, 2, 1), |
| 148 | + encode_utf8_string(f"{vendor_id:04X}"), |
| 149 | + ) |
| 150 | + ) |
| 151 | + encoded_product_id = encode_set( |
| 152 | + der.encode_sequence( |
| 153 | + der.encode_oid(1, 3, 6, 1, 4, 1, 37244, 2, 2), |
| 154 | + encode_utf8_string(f"{product_id:04X}"), |
| 155 | + ) |
| 156 | + ) |
| 157 | + subject = der.encode_sequence(common_name, encoded_vendor_id, encoded_product_id) |
| 158 | + |
| 159 | + algorithm_id = b"\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07" |
| 160 | + |
| 161 | + public_key = dac_key.verifying_key.to_string(encoding="uncompressed") |
| 162 | + public_key_info = der.encode_sequence( |
| 163 | + algorithm_id, der.encode_bitstring(public_key, unused=0) |
| 164 | + ) |
| 165 | + |
| 166 | + basic_constraints = b"\x30\x0c\x06\x03\x55\x1d\x13\x01\x01\xff\x04\x02\x30\x00" |
| 167 | + key_usage = b"\x30\x0e\x06\x03\x55\x1d\x0f\x01\x01\xff\x04\x04\x03\x02\x07\x80" |
| 168 | + key_id = der.encode_sequence( |
| 169 | + der.encode_oid(2, 5, 29, 14), |
| 170 | + der.encode_octet_string( |
| 171 | + der.encode_octet_string(hashlib.sha1(public_key).digest()) |
| 172 | + ), |
| 173 | + ) |
| 174 | + 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 | + extensions = der.encode_constructed( |
| 176 | + 3, der.encode_sequence(basic_constraints, key_usage, key_id, authority_key_id) |
| 177 | + ) |
| 178 | + |
| 179 | + certificate = der.encode_sequence( |
| 180 | + version, |
| 181 | + serial_number, |
| 182 | + signature_algorithm, |
| 183 | + issuer, |
| 184 | + validity, |
| 185 | + subject, |
| 186 | + public_key_info, |
| 187 | + extensions, |
| 188 | + ) |
| 189 | + |
| 190 | + pai_key = ecdsa.keys.SigningKey.from_der(PAI_KEY_DER, hashfunc=hashlib.sha256) |
| 191 | + signature = pai_key.sign_deterministic( |
| 192 | + certificate, |
| 193 | + hashfunc=hashlib.sha256, |
| 194 | + sigencode=ecdsa.util.sigencode_der_canonize, |
| 195 | + ) |
| 196 | + |
| 197 | + dac_cert = der.encode_sequence( |
| 198 | + certificate, signature_algorithm, der.encode_bitstring(signature, unused=0) |
| 199 | + ) |
| 200 | + dac_key = dac_key.to_der() |
| 201 | + return dac_cert, dac_key |
| 202 | + |
| 203 | + |
| 204 | +def generate_initial_state(vendor_id, product_id, product_name, random_source): |
| 205 | + if vendor_id != 0xFFF4 or product_id != 0x1234: |
| 206 | + raise ValueError("Invalid vendor_id or product_id") |
| 207 | + |
| 208 | + cd = generate_certificates(vendor_id=vendor_id, product_id=product_id) |
| 209 | + |
| 210 | + dac_cert, dac_key = generate_dac(vendor_id, product_id, product_name, random_source) |
| 211 | + initial_state = { |
| 212 | + "discriminator": 3840, |
| 213 | + "passcode": 67202583, |
| 214 | + "iteration-count": 10000, |
| 215 | + "salt": "5uCP0ITHYzI9qBEe6hfU4HfY3y7VopSk0qNvhvznhiQ=", |
| 216 | + "verifier": "0xGqxJFBr/ViQt3lv1Yw5F0GcPBAtFFvXB+EcIIjH5cEsjkPZHDQyFWjA6Ide+2gafYnZgIy6gJBgdJOlD8htAZKe0i6nIhT/ADsBWH4CvZcl37n/ofEEECWSEBV4vy/0A==", |
| 217 | + "devices": { |
| 218 | + "root": { |
| 219 | + "0x3e": { |
| 220 | + "cd": binascii.b2a_base64(cd, newline=False).decode("utf-8"), |
| 221 | + "dac_cert": binascii.b2a_base64(dac_cert, newline=False).decode( |
| 222 | + "utf-8" |
| 223 | + ), |
| 224 | + "dac_key": binascii.b2a_base64(dac_key, newline=False).decode( |
| 225 | + "utf-8" |
| 226 | + ), |
| 227 | + } |
| 228 | + }, |
| 229 | + }, |
| 230 | + } |
| 231 | + return initial_state |
| 232 | + |
| 233 | + |
123 | 234 | if __name__ == "__main__":
|
124 |
| - cd = generate_certificates() |
125 |
| - pathlib.Path("certification_declaration.der").write_bytes(cd) |
126 |
| - for i in range(0, len(cd), 16): |
127 |
| - print(f"{i:08x}", cd[i : i + 16].hex(" ")) |
128 |
| - print(cd.hex(" ")) |
| 235 | + from circuitmatter.utility import random |
| 236 | + |
| 237 | + initial_state = generate_initial_state(0xFFF4, 0x1234, "CircuitMatter", random) |
| 238 | + print(initial_state) |
0 commit comments