def encrypt(self, target_alg: '_EncAlg') -> bytes: alg = self.get_attr(headers.Algorithm) if len(self.phdr): raise CoseException( f"Protected header must be empty when using an AE algorithm: {alg}" ) if alg is None: raise CoseException( "The algorithm parameter should at least be included in the unprotected header" ) elif alg in {A128KW, A192KW, A256KW}: key_ops = [WrapOp, EncryptOp] kek = SymmetricKey(k=self._compute_kek(target_alg, ops='encrypt'), optional_params={ KpAlg: alg, KpKeyOps: key_ops }) kek.verify(SymmetricKey, alg, [WrapOp, EncryptOp]) elif alg in {RsaesOaepSha512, RsaesOaepSha256, RsaesOaepSha1}: kek = self.key kek.verify(RSAKey, alg, [WrapOp, EncryptOp]) else: raise CoseIllegalAlgorithm(f"Algorithm {alg} for {self.__name__}") return alg.key_wrap(kek, self.payload)
def x25519_key_derivation( self, public_key: 'OKP', context: CoseKDFContext = b'', alg: Optional[CoseAlgorithms] = None, curve: Optional[CoseEllipticCurves] = None) -> Tuple[bytes, bytes]: self._check_key_conf(alg, KeyOps.DERIVE_KEY, public_key, curve) try: alg_cfg = config(CoseAlgorithms(self.alg)) except KeyError as err: raise CoseIllegalAlgorithm(err) p = X25519PublicKey.from_public_bytes(public_key.x) d = X25519PrivateKey.from_private_bytes(self.d) shared_secret = d.exchange(p) derived_key = alg_cfg.kdf( algorithm=alg_cfg.hash(), length=int(context.supp_pub_info.key_data_length / 8), salt=None, info=context.encode(), backend=default_backend()).derive(shared_secret) return shared_secret, derived_key
def hmac_key_derivation(self, context: CoseKDFContext, alg: Optional[CoseAlgorithms] = None, salt: bytes = b'') -> bytes: """ HMAC-based key derivation based on secret bytes and a CoseKDFContext object. :param context: A CoseKDFContext object which contains necessary input for the KDF algorithm. :param alg: An optional CoseAlgorithm parameter. :param salt: And optional salt parameter. :return: A cryptographic key with a length specified in the CoseKDFContext object. """ self._check_key_conf(alg, KeyOps.DERIVE_KEY) try: alg_cfg = config(CoseAlgorithms(self.alg)) except KeyError as err: raise CoseIllegalAlgorithm(err) derived_key = alg_cfg.kdf( algorithm=alg_cfg.hash(), length=int(context.supp_pub_info.key_data_length / 8), salt=salt, info=context.encode(), backend=default_backend()).derive(self.k) return derived_key
def encrypt(self, target_alg: '_EncAlg') -> bytes: alg = self.get_attr(headers.Algorithm) if A128KW.identifier >= alg.identifier >= A256KW.identifier: if len(self.phdr): raise CoseException( f"Protected header must be empty when using an AE algorithm: {alg}" ) if alg is None: raise CoseException( "The algorithm parameter should at least be included in the unprotected header" ) self.key = SymmetricKey(k=self._compute_kek(target_alg, ops='encrypt'), optional_params={ KpAlg: alg, KpKeyOps: [WrapOp, EncryptOp] }) self.key.verify(SymmetricKey, alg, [WrapOp, EncryptOp]) return alg.key_wrap(self.key, self.payload) else: raise CoseIllegalAlgorithm(f"Algorithm {alg} for {self.__name__}")
def key_unwrap(self, wrapped_key: bytes, alg: Optional[CoseAlgorithms] = None) -> bytes: self._check_key_conf(alg, KeyOps.UNWRAP) try: alg_cfg = config(CoseAlgorithms(self.alg)) except KeyError as err: raise CoseIllegalAlgorithm(err) return alg_cfg.primitive.aes_key_unwrap(self.k, wrapped_key, default_backend())
def encrypt(self, plaintext: bytes, aad: bytes, nonce: bytes, alg: Optional[CoseAlgorithms]) -> bytes: self._check_key_conf(alg, KeyOps.ENCRYPT) try: cipher = self._prepare_cipher() ciphertext = cipher.encrypt(nonce=nonce, data=plaintext, associated_data=aad) except KeyError as err: raise CoseIllegalAlgorithm(err) return ciphertext
def compute_tag(self, to_be_maced: bytes, alg: Optional[CoseAlgorithms] = None) -> bytes: """ Compute the MAC over the payload. :param to_be_maced: The payload over which the authentication tag will be calculated. :param alg: An optional CoseAlgorithm for computing the authentication tag. :return: The authentication tag. """ self._check_key_conf(alg, KeyOps.MAC_CREATE) iv = unhexlify(b"".join([b"00"] * 16)) try: alg_cfg = config(CoseAlgorithms(self.alg)) except KeyError as err: raise CoseIllegalAlgorithm(err) if self.alg in { CoseAlgorithms.AES_MAC_128_128, CoseAlgorithms.AES_MAC_128_64, CoseAlgorithms.AES_MAC_256_64, CoseAlgorithms.AES_MAC_256_128 }: encryptor = Cipher(alg_cfg.primitive(self.k), modes.CBC(iv), backend=default_backend()).encryptor() while len(to_be_maced) % 16 != 0: to_be_maced += unhexlify(b"00") ciphertext = encryptor.update(to_be_maced) + encryptor.finalize() if alg_cfg.tag_length is not None: # truncate the result to the first 64 bits ciphertext = ciphertext[:-8] digest = ciphertext[-8:] else: digest = ciphertext[-16:] else: h = alg_cfg.primitive(self.k, alg_cfg.hash(), backend=default_backend()) h.update(to_be_maced) digest = h.finalize() if CoseAlgorithms(self.alg) == CoseAlgorithms.HMAC_256_64: # truncate the result to the first 64 bits digest = digest[:8] return digest
def compute_hash(self) -> bytes: try: alg_cfg = config(CoseAlgorithms(self.alg)) except KeyError as err: raise CoseIllegalAlgorithm(err) h = Hash(algorithm=alg_cfg.hash(), backend=openssl.backend) h.update(self.certificate) digest = h.finalize() if alg_cfg.tag_length is not None: digest = digest[:8] return digest
def key_wrap(self, plaintext_key: bytes, alg: Optional[CoseAlgorithms] = None) -> bytes: self._check_key_conf(alg, KeyOps.WRAP) try: alg_cfg = config(CoseAlgorithms(self.alg)) except KeyError as err: raise CoseIllegalAlgorithm(err) if self.alg in { CoseAlgorithms.A128KW, CoseAlgorithms.A192KW, CoseAlgorithms.A256KW }: return alg_cfg.primitive.aes_key_wrap(self.k, plaintext_key, default_backend()) elif self.alg == CoseAlgorithms.DIRECT: return b'' else: raise CoseIllegalAlgorithm( f"Key wrap requires one of the following algorithms: \ {(CoseAlgorithms.A256KW, CoseAlgorithms.A192KW, CoseAlgorithms.A128KW, CoseAlgorithms.DIRECT)}" )
def decrypt(self, target_alg: '_EncAlg') -> bytes: alg = self.get_attr(headers.Algorithm) _ = target_alg if alg in {EcdhEsA256KW, EcdhEsA192KW, EcdhEsA128KW}: peer_key = self.get_attr(headers.EphemeralKey) elif alg in {EcdhSsA256KW, EcdhSsA192KW, EcdhSsA128KW}: peer_key = self.get_attr(headers.StaticKey) else: raise CoseIllegalAlgorithm( f"Algorithm {alg} unsupported for {self.__name__}") kek = SymmetricKey(k=self._compute_kek(alg.get_key_wrap_func(), peer_key, self.key, alg), optional_params={ KpAlg: alg, KpKeyOps: [UnwrapOp, DecryptOp] }) kek.verify(SymmetricKey, alg, [UnwrapOp, DecryptOp]) return alg.get_key_wrap_func().key_unwrap(kek, self.payload)
def verify(self, key_type: Type['CK'], algorithm: Type['CoseAlg'], key_ops: List[Type['KEYOPS']]): """ Verify attributes of the COSE_key object.""" if not isinstance(self, key_type): raise CoseException("Wrong key type") if self.alg is not None and self.alg.identifier != algorithm.identifier: raise CoseIllegalAlgorithm( "Conflicting algorithms in key and COSE headers") if len(self.key_ops): match_key_ops = False for k in key_ops: if k in self.key_ops: match_key_ops = True if not match_key_ops: raise CoseIllegalKeyOps( f"Illegal key operations specified. Allowed: {key_ops}, found: {self.key_ops}" )
def compute_cek(self, target_alg: '_EncAlg', ops: str) -> 'SK': alg = self.get_attr(headers.Algorithm) if alg in {EcdhSsHKDF256, EcdhSsHKDF512, EcdhEsHKDF256, EcdhEsHKDF512}: if ops == "encrypt": peer_key = self.local_attrs.get(headers.StaticKey) else: if alg in {EcdhSsHKDF256, EcdhSsHKDF512}: peer_key = self.get_attr(headers.StaticKey) else: peer_key = self.get_attr(headers.EphemeralKey) else: raise CoseIllegalAlgorithm( f"Algorithm {alg} unsupported for {self.__name__}") if peer_key is None: raise CoseException("Unknown static receiver public key") peer_key.verify(EC2Key, alg, [DeriveKeyOp, DeriveBitsOp]) self.key.verify(EC2Key, alg, [DeriveKeyOp, DeriveBitsOp]) return SymmetricKey(k=self._compute_kek(target_alg, peer_key, self.key, alg), optional_params={KpAlg: target_alg})