| # Copyright 2021-2022 Google LLC |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| # ----------------------------------------------------------------------------- |
| # Crypto support |
| # |
| # See Bluetooth spec Vol 3, Part H - 2.2 CRYPTOGRAPHIC TOOLBOX |
| # ----------------------------------------------------------------------------- |
| |
| # ----------------------------------------------------------------------------- |
| # Imports |
| # ----------------------------------------------------------------------------- |
| import logging |
| import operator |
| import platform |
| if platform.system() != 'Emscripten': |
| import secrets |
| from cryptography.hazmat.primitives.ciphers import ( |
| Cipher, |
| algorithms, |
| modes |
| ) |
| from cryptography.hazmat.primitives.asymmetric.ec import ( |
| generate_private_key, |
| ECDH, |
| EllipticCurvePublicNumbers, |
| EllipticCurvePrivateNumbers, |
| SECP256R1 |
| ) |
| from cryptography.hazmat.primitives import cmac |
| else: |
| # TODO: implement stubs |
| pass |
| |
| # ----------------------------------------------------------------------------- |
| # Logging |
| # ----------------------------------------------------------------------------- |
| logger = logging.getLogger(__name__) |
| |
| |
| # ----------------------------------------------------------------------------- |
| # Classes |
| # ----------------------------------------------------------------------------- |
| class EccKey: |
| def __init__(self, private_key): |
| self.private_key = private_key |
| |
| @classmethod |
| def generate(cls): |
| private_key = generate_private_key(SECP256R1()) |
| return cls(private_key) |
| |
| @classmethod |
| def from_private_key_bytes(cls, d_bytes, x_bytes, y_bytes): |
| d = int.from_bytes(d_bytes, byteorder='big', signed=False) |
| x = int.from_bytes(x_bytes, byteorder='big', signed=False) |
| y = int.from_bytes(y_bytes, byteorder='big', signed=False) |
| private_key = EllipticCurvePrivateNumbers(d, EllipticCurvePublicNumbers(x, y, SECP256R1())).private_key() |
| return cls(private_key) |
| |
| @property |
| def x(self): |
| return self.private_key.public_key().public_numbers().x.to_bytes(32, byteorder='big') |
| |
| @property |
| def y(self): |
| return self.private_key.public_key().public_numbers().y.to_bytes(32, byteorder='big') |
| |
| def dh(self, public_key_x, public_key_y): |
| x = int.from_bytes(public_key_x, byteorder='big', signed=False) |
| y = int.from_bytes(public_key_y, byteorder='big', signed=False) |
| public_key = EllipticCurvePublicNumbers(x, y, SECP256R1()).public_key() |
| shared_key = self.private_key.exchange(ECDH(), public_key) |
| |
| return shared_key |
| |
| |
| # ----------------------------------------------------------------------------- |
| # Functions |
| # ----------------------------------------------------------------------------- |
| |
| # ----------------------------------------------------------------------------- |
| def xor(x, y): |
| assert(len(x) == len(y)) |
| return bytes(map(operator.xor, x, y)) |
| |
| |
| # ----------------------------------------------------------------------------- |
| def r(): |
| ''' |
| Generate 16 bytes of random data |
| ''' |
| return secrets.token_bytes(16) |
| |
| |
| # ----------------------------------------------------------------------------- |
| def e(key, data): |
| ''' |
| AES-128 ECB, expecting byte-swapped inputs and producing a byte-swapped output. |
| |
| See Bluetooth spec Vol 3, Part H - 2.2.1 Security function e |
| ''' |
| |
| cipher = Cipher(algorithms.AES(bytes(reversed(key))), modes.ECB()) |
| encryptor = cipher.encryptor() |
| return bytes(reversed(encryptor.update(bytes(reversed(data))))) |
| |
| |
| # ----------------------------------------------------------------------------- |
| def ah(k, r): |
| ''' |
| See Bluetooth spec Vol 3, Part H - 2.2.2 Random Address Hash function ah |
| ''' |
| |
| padding = bytes(13) |
| r_prime = r + padding |
| return e(k, r_prime)[0:3] |
| |
| |
| # ----------------------------------------------------------------------------- |
| def c1(k, r, preq, pres, iat, rat, ia, ra): |
| ''' |
| See Bluetooth spec, Vol 3, Part H - 2.2.3 Confirm value generation function c1 for LE Legacy Pairing |
| ''' |
| |
| p1 = bytes([iat, rat]) + preq + pres |
| p2 = ra + ia + bytes([0, 0, 0, 0]) |
| return e(k, xor(e(k, xor(r, p1)), p2)) |
| |
| |
| # ----------------------------------------------------------------------------- |
| def s1(k, r1, r2): |
| ''' |
| See Bluetooth spec, Vol 3, Part H - 2.2.4 Key generation function s1 for LE Legacy Pairing |
| ''' |
| |
| return e(k, r2[0:8] + r1[0:8]) |
| |
| |
| # ----------------------------------------------------------------------------- |
| def aes_cmac(m, k): |
| ''' |
| See Bluetooth spec, Vol 3, Part H - 2.2.5 FunctionAES-CMAC |
| |
| NOTE: the input and output of this internal function are in big-endian byte order |
| ''' |
| mac = cmac.CMAC(algorithms.AES(k)) |
| mac.update(m) |
| return mac.finalize() |
| |
| |
| # ----------------------------------------------------------------------------- |
| def f4(u, v, x, z): |
| ''' |
| See Bluetooth spec, Vol 3, Part H - 2.2.6 LE Secure Connections Confirm Value Generation Function f4 |
| ''' |
| return bytes(reversed(aes_cmac(bytes(reversed(u)) + bytes(reversed(v)) + z, bytes(reversed(x))))) |
| |
| |
| # ----------------------------------------------------------------------------- |
| def f5(w, n1, n2, a1, a2): |
| ''' |
| See Bluetooth spec, Vol 3, Part H - 2.2.7 LE Secure Connections Key Generation Function f5 |
| |
| NOTE: this returns a tuple: (MacKey, LTK) in little-endian byte order |
| ''' |
| salt = bytes.fromhex('6C888391AAF5A53860370BDB5A6083BE') |
| t = aes_cmac(bytes(reversed(w)), salt) |
| key_id = bytes([0x62, 0x74, 0x6c, 0x65]) |
| return ( |
| bytes(reversed(aes_cmac( |
| bytes([0]) + |
| key_id + |
| bytes(reversed(n1)) + |
| bytes(reversed(n2)) + |
| bytes(reversed(a1)) + |
| bytes(reversed(a2)) + |
| bytes([1, 0]), |
| t |
| ))), |
| bytes(reversed(aes_cmac( |
| bytes([1]) + |
| key_id + |
| bytes(reversed(n1)) + |
| bytes(reversed(n2)) + |
| bytes(reversed(a1)) + |
| bytes(reversed(a2)) + |
| bytes([1, 0]), |
| t |
| ))) |
| ) |
| |
| |
| # ----------------------------------------------------------------------------- |
| def f6(w, n1, n2, r, io_cap, a1, a2): |
| ''' |
| See Bluetooth spec, Vol 3, Part H - 2.2.8 LE Secure Connections Check Value Generation Function f6 |
| ''' |
| return bytes(reversed(aes_cmac( |
| bytes(reversed(n1)) + |
| bytes(reversed(n2)) + |
| bytes(reversed(r)) + |
| bytes(reversed(io_cap)) + |
| bytes(reversed(a1)) + |
| bytes(reversed(a2)), |
| bytes(reversed(w)) |
| ))) |
| |
| |
| # ----------------------------------------------------------------------------- |
| def g2(u, v, x, y): |
| ''' |
| See Bluetooth spec, Vol 3, Part H - 2.2.9 LE Secure Connections Numeric Comparison Value Generation Function g2 |
| ''' |
| return int.from_bytes( |
| aes_cmac(bytes(reversed(u)) + bytes(reversed(v)) + bytes(reversed(y)), bytes(reversed(x)))[-4:], |
| byteorder='big' |
| ) |