def sign_certificate_builder( session: PivSession, slot: SLOT, key_type: KEY_TYPE, builder: x509.CertificateBuilder, ) -> x509.Certificate: """Sign a Certificate.""" dummy_key = _dummy_key(key_type) cert = builder.sign(dummy_key, hashes.SHA256(), default_backend()) sig = session.sign( slot, key_type, cert.tbs_certificate_bytes, hashes.SHA256(), padding.PKCS1v15(), # Only used for RSA ) seq = Tlv.parse_list(Tlv.unpack(0x30, cert.public_bytes(Encoding.DER))) # Replace signature, add unused bits = 0 seq[2] = Tlv(seq[2].tag, b"\0" + sig) # Re-assemble sequence der = Tlv(0x30, b"".join(seq)) return x509.load_der_x509_certificate(der, default_backend())
def generate_ec_key(self, key_slot, curve_name, timestamp=None): """Requires Admin PIN verification.""" if timestamp is None: timestamp = int(time.time()) attributes = _format_ec_attributes(key_slot, curve_name) self._put_data(key_slot.key_id, attributes) resp = self._app.send_apdu(0, INS.GENERATE_ASYM, 0x80, 0x00, key_slot.crt) data = Tlv.parse_dict(Tlv.unwrap(0x7F49, resp)) pubkey_enc = data[0x86] self._put_data(key_slot.gen_time, struct.pack(">I", timestamp)) # TODO: Calculate and write fingerprint if curve_name == "x25519": # Added in 2.0 from cryptography.hazmat.primitives.asymmetric import x25519 return x25519.X25519PublicKey.from_public_bytes(pubkey_enc) if curve_name == "ed25519": # Added in 2.6 from cryptography.hazmat.primitives.asymmetric import ed25519 return ed25519.Ed25519PublicKey.from_public_bytes(pubkey_enc) curve = getattr(ec, curve_name.upper()) try: # Added in cryptography 2.5 return ec.EllipticCurvePublicKey.from_encoded_point(curve(), pubkey_enc) except AttributeError: return ec.EllipticCurvePublicNumbers.from_encoded_point( curve(), pubkey_enc ).public_key(default_backend())
class KEY_SLOT(_KeySlot, Enum): # noqa: N801 SIG = _KeySlot("SIGNATURE", 1, 0xC1, 0xC7, 0xCE, 0xD6, Tlv(0xB6)) ENC = _KeySlot("ENCRYPTION", 2, 0xC2, 0xC8, 0xCF, 0xD7, Tlv(0xB8)) AUT = _KeySlot("AUTHENTICATION", 3, 0xC3, 0xC9, 0xD0, 0xD8, Tlv(0xA4)) ATT = _KeySlot( "ATTESTATION", 4, 0xDA, 0xDB, 0xDD, 0xD9, Tlv(0xB6, Tlv(0x84, b"\x81")) )
def generate_rsa_key(self, key_slot, key_size, timestamp=None): """Requires Admin PIN verification.""" if (4, 2, 0) <= self.version < (4, 3, 5): raise NotSupportedError( "RSA key generation not supported on this YubiKey") if timestamp is None: timestamp = int(time.time()) neo = self.version < (4, 0, 0) if not neo: attributes = _format_rsa_attributes(key_size) self._put_data(key_slot.key_id, attributes) elif key_size != 2048: raise ValueError("Unsupported key size!") resp = self._app.send_apdu(0, INS.GENERATE_ASYM, 0x80, 0x00, key_slot.crt) data = Tlv.parse_dict(Tlv.unpack(0x7F49, resp)) numbers = rsa.RSAPublicNumbers(bytes2int(data[0x82]), bytes2int(data[0x81])) self._put_data(key_slot.gen_time, struct.pack(">I", timestamp)) # TODO: Calculate and write fingerprint return numbers.public_key(default_backend())
def _pack_tlvs(tlvs): header = b"" body = b"" for tlv in tlvs: header += tlv[: -tlv.length] body += tlv.value return Tlv(0x7F48, header) + Tlv(0x5F48, body)
def sign_csr_builder( session: PivSession, slot: SLOT, public_key: Union[rsa.RSAPublicKey, ec.EllipticCurvePublicKey], builder: x509.CertificateSigningRequestBuilder, ) -> x509.CertificateSigningRequest: """Sign a CSR.""" key_type = KEY_TYPE.from_public_key(public_key) dummy_key = _dummy_key(key_type) csr = builder.sign(dummy_key, hashes.SHA256(), default_backend()) seq = Tlv.parse_list(Tlv.unpack(0x30, csr.public_bytes(Encoding.DER))) # Replace public key pub_format = (PublicFormat.PKCS1 if key_type.algorithm == ALGORITHM.RSA else PublicFormat.SubjectPublicKeyInfo) dummy_bytes = dummy_key.public_key().public_bytes(Encoding.DER, pub_format) pub_bytes = public_key.public_bytes(Encoding.DER, pub_format) seq[0] = Tlv(seq[0].replace(dummy_bytes, pub_bytes)) sig = session.sign( slot, key_type, seq[0], hashes.SHA256(), padding.PKCS1v15(), # Only used for RSA ) # Replace signature, add unused bits = 0 seq[2] = Tlv(seq[2].tag, b"\0" + sig) # Re-assemble sequence der = Tlv(0x30, b"".join(seq)) return x509.load_der_x509_csr(der, default_backend())
def __init__(self, raw_data=Tlv(0x80)): data = Tlv.parse_dict(Tlv(raw_data).value) self._flags = struct.unpack(">B", data[0x81])[0] if 0x81 in data else None self.salt = data.get(0x82) self.pin_timestamp = struct.unpack( ">I", data[0x83]) if 0x83 in data else None
def _select_certificate(self, key_slot): self._app.send_apdu( 0, INS.SELECT_DATA, 3 - key_slot.index, 0x04, Tlv(0, Tlv(0x60, Tlv(0x5C, b"\x7f\x21")))[1:], )
def get_bytes(self) -> bytes: data = b"" if self._flags is not None: data += Tlv(0x81, struct.pack(">B", self._flags)) if self.salt is not None: data += Tlv(0x82, self.salt) if self.pin_timestamp is not None: data += Tlv(0x83, struct.pack(">I", self.pin_timestamp)) return Tlv(0x80, data)
def is_pkcs12(data): """ Tries to identify a PKCS12 container. The PFX PDU version is assumed to be v3. See: https://tools.ietf.org/html/rfc7292. """ try: header = Tlv.parse_list(Tlv.unpack(0x30, data))[0] return header.tag == 0x02 and header.value == b"\x03" except ValueError: return False
def generate_chuid() -> bytes: """Generates a CHUID (Cardholder Unique Identifier).""" # Non-Federal Issuer FASC-N # [9999-9999-999999-0-1-0000000000300001] FASC_N = (b"\xd4\xe7\x39\xda\x73\x9c\xed\x39\xce\x73\x9d\x83\x68" + b"\x58\x21\x08\x42\x10\x84\x21\xc8\x42\x10\xc3\xeb") # Expires on: 2030-01-01 EXPIRY = b"\x32\x30\x33\x30\x30\x31\x30\x31" return (Tlv(0x30, FASC_N) + Tlv(0x34, os.urandom(16)) + Tlv(0x35, EXPIRY) + Tlv(0x3E) + Tlv(TAG_LRC))
def _select_certificate(self, key_slot): data = Tlv(0x60, Tlv(0x5C, b"\x7f\x21")) if self.version <= ( 5, 4, 3): # These use a non-standard byte in the command. data = b"\x06" + data # 6 is the length of the data. self._app.send_apdu( 0, INS.SELECT_DATA, 3 - key_slot.index, 0x04, data, )
def is_pkcs12(data): """ Tries to identify a PKCS12 container. The PFX PDU version is assumed to be v3. See: https://tools.ietf.org/html/rfc7292. """ try: header = Tlv.parse_from(Tlv.unpack(0x30, data))[0] return header.tag == 0x02 and header.value == b"\x03" except ValueError as e: logger.debug("Unable to parse TLV", exc_info=e) return False
def test_tlv(self): self.assertEqual(Tlv(b"\xfe\6foobar"), Tlv(0xFE, b"foobar")) tlv1 = Tlv(b"\0\5hello") tlv2 = Tlv(0xFE, b"") tlv3 = Tlv(0x12, b"hi" * 200) self.assertEqual(b"\0\5hello", tlv1) self.assertEqual(b"\xfe\0", tlv2) self.assertEqual(b"\x12\x82\x01\x90" + b"hi" * 200, tlv3) self.assertEqual(b"\0\5hello\xfe\0\x12\x82\x01\x90" + b"hi" * 200, tlv1 + tlv2 + tlv3)
def _select_certificate(self, key_slot): try: require_version(self.version, (5, 2, 0)) data: bytes = Tlv(0x60, Tlv(0x5C, b"\x7f\x21")) if self.version <= (5, 4, 3): # These use a non-standard byte in the command. data = b"\x06" + data # 6 is the length of the data. self._app.send_apdu( 0, INS.SELECT_DATA, 3 - key_slot.indx, 0x04, data, ) except NotSupportedError: if key_slot == KEY_SLOT.AUT: return # Older version still support AUT, which is the default slot. raise
def parse(cls, data: bytes) -> "KdfData": fields = Tlv.parse_dict(data) return cls( _parse_int(fields, 0x81, KdfAlgorithm, KdfAlgorithm.NONE), _parse_int(fields, 0x82, HashAlgorithm), _parse_int(fields, 0x83), fields.get(0x84), fields.get(0x85), fields.get(0x86), fields.get(0x87), fields.get(0x88), )
def _get_key_template(key, key_slot, crt=False): def _pack_tlvs(tlvs): header = b"" body = b"" for tlv in tlvs: header += tlv[: -tlv.length] body += tlv.value return Tlv(0x7F48, header) + Tlv(0x5F48, body) values: Tuple[Tlv, ...] if isinstance(key, rsa.RSAPrivateKeyWithSerialization): rsa_numbers = key.private_numbers() ln = (key.key_size // 8) // 2 e = Tlv(0x91, b"\x01\x00\x01") # e=65537 p = Tlv(0x92, int2bytes(rsa_numbers.p, ln)) q = Tlv(0x93, int2bytes(rsa_numbers.q, ln)) values = (e, p, q) if crt: dp = Tlv(0x94, int2bytes(rsa_numbers.dmp1, ln)) dq = Tlv(0x95, int2bytes(rsa_numbers.dmq1, ln)) qinv = Tlv(0x96, int2bytes(rsa_numbers.iqmp, ln)) n = Tlv(0x97, int2bytes(rsa_numbers.public_numbers.n, 2 * ln)) values += (dp, dq, qinv, n) elif isinstance(key, ec.EllipticCurvePrivateKeyWithSerialization): ec_numbers = key.private_numbers() ln = key.key_size // 8 privkey = Tlv(0x92, int2bytes(ec_numbers.private_value, ln)) values = (privkey,) elif _get_curve_name(key) in ("ed25519", "x25519"): privkey = Tlv( 0x92, key.private_bytes(Encoding.Raw, PrivateFormat.Raw, NoEncryption()) ) values = (privkey,) return Tlv(0x4D, key_slot.crt + _pack_tlvs(values))
def test_read_write_certificate_as_object(self, ykman_cli): with pytest.raises(SystemExit): ykman_cli("piv", "objects", "export", hex(OBJECT_ID.AUTHENTICATION), "-") cert = generate_self_signed_certificate() cert_bytes_der = cert.public_bytes(encoding=serialization.Encoding.DER) input_tlv = Tlv(0x70, cert_bytes_der) + Tlv(0x71, b"\0") + Tlv( 0xFE, b"") ykman_cli( "piv", "objects", "import", hex(OBJECT_ID.AUTHENTICATION), "-", "-m", DEFAULT_MANAGEMENT_KEY, input=input_tlv, ) output1 = ykman_cli("piv", "objects", "export", hex(OBJECT_ID.AUTHENTICATION), "-").stdout_bytes output_cert_bytes = Tlv.parse_dict(output1)[0x70] assert output_cert_bytes == cert_bytes_der output2 = ykman_cli( "piv", "certificates", "export", hex(SLOT.AUTHENTICATION), "-", "--format", "DER", ).stdout_bytes assert output2 == cert_bytes_der
def generate_rsa_key(self, key_slot, key_size, timestamp=None): """Requires Admin PIN verification.""" ensure_not_cve201715361_vulnerable_firmware_version(self.version) if timestamp is None: timestamp = int(time.time()) neo = self.version < (4, 0, 0) if not neo: attributes = _format_rsa_attributes(key_size) self._put_data(key_slot.key_id, attributes) elif key_size != 2048: raise ValueError("Unsupported key size!") resp = self._app.send_apdu(0, INS.GENERATE_ASYM, 0x80, 0x00, key_slot.crt) data = Tlv.parse_dict(Tlv.unwrap(0x7F49, resp)) numbers = rsa.RSAPublicNumbers( int_from_bytes(data[0x82], "big"), int_from_bytes(data[0x81], "big") ) self._put_data(key_slot.gen_time, struct.pack(">I", timestamp)) # TODO: Calculate and write fingerprint return numbers.public_key(default_backend())
def test_parse_tlvs(self): tlvs = Tlv.parse_list(b"\x00\x02\xd0\x0d\xa1\x00\xfe\x04\xfe\xed\xfa\xce") self.assertEqual(3, len(tlvs)) self.assertEqual(0, tlvs[0].tag) self.assertEqual(2, tlvs[0].length) self.assertEqual(b"\xd0\x0d", tlvs[0].value) self.assertEqual(0xA1, tlvs[1].tag) self.assertEqual(0, tlvs[1].length) self.assertEqual(b"", tlvs[1].value) self.assertEqual(0xFE, tlvs[2].tag) self.assertEqual(4, tlvs[2].length) self.assertEqual(b"\xfe\xed\xfa\xce", tlvs[2].value)
def generate_ccc() -> bytes: """Generates a CCC (Card Capability Container).""" return (Tlv(0xF0, b"\xa0\x00\x00\x01\x16\xff\x02" + os.urandom(14)) + Tlv(0xF1, b"\x21") + Tlv(0xF2, b"\x21") + Tlv(0xF3) + Tlv(0xF4, b"\x00") + Tlv(0xF5, b"\x10") + Tlv(0xF6) + Tlv(0xF7) + Tlv(0xFA) + Tlv(0xFB) + Tlv(0xFC) + Tlv(0xFD) + Tlv(TAG_LRC))
def __init__(self, raw_data: bytes = Tlv(0x88)): data = Tlv.parse_dict(Tlv(raw_data).value) self.key = data.get(0x89)
def get_bytes(self) -> bytes: data = b"" if self.key is not None: data += Tlv(0x89, self.key) return Tlv(0x88, data)