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 _compute_kek(self, target_alg: '_EncAlg', ops: 'str') -> bytes: if self.key is None: # try to derive from this recipients' recipient list if not len(self.recipients): raise CoseException(f"No key found to {ops} the CEK") else: r_types = CoseRecipient.verify_recipients(self.recipients) if ops == 'encrypt': if DirectKeyAgreement in r_types: self.key = self.recipients[0].compute_cek(target_alg) elif KeyWrap in r_types or KeyAgreementWithKeyWrap in r_types: key_bytes = os.urandom(self.get_attr( headers.Algorithm)) for r in self.recipients: r.payload = key_bytes self.key = SymmetricKey(k=key_bytes) else: raise CoseException('Unsupported COSE recipient class') else: if DirectKeyAgreement in r_types or KeyWrap in r_types or KeyAgreementWithKeyWrap in r_types: self.key = self.recipients[0].decrypt( self.get_attr(headers.Algorithm)) else: raise CoseException('Unsupported COSE recipient class') if self.key is None: raise CoseException("No key found to decrypt the CEK") return self.key.k
def test_symmetric_key_wrap(kid, alg, key_ops, base_iv, k, pl, algo, ct): key = SymmetricKey(kid=kid, alg=alg, key_ops=key_ops, base_iv=base_iv, k=k) assert ct == key.key_wrap(pl, algo) # switch to another key operation key.key_ops = KeyOps.UNWRAP assert pl == key.key_unwrap(ct, algo)
def test_symmetric_key_derivation(kid, alg, key_ops, base_iv, k, salt, algo, ctx_alg, u, v, pub, priv, context, cek): key = SymmetricKey(kid=kid, alg=alg, key_ops=key_ops, base_iv=base_iv, k=k) ctx = CoseKDFContext(ctx_alg, u, v, pub, priv) assert ctx.encode() == context assert key.hmac_key_derivation(ctx, algo, salt) == cek
def test_symmetric_mac(kid, alg, key_ops, base_iv, k, pl, algo, ct): key = SymmetricKey(kid=kid, alg=alg, key_ops=key_ops, base_iv=base_iv, k=k) assert ct == key.compute_tag(pl, algo) # switch to another key operation key.key_ops = KeyOps.MAC_VERIFY assert key.verify_tag(ct, pl, algo)
def test_encrypt_hkdf_hmac_direct_decode( setup_encrypt_hkdf_hmac_direct_tests: tuple) -> None: _, test_input, test_output, test_intermediate, fail = setup_encrypt_hkdf_hmac_direct_tests # parse message and test for headers md: EncMessage = CoseMessage.decode(unhexlify(test_output)) assert md.phdr == extract_phdr(test_input, 'enveloped') assert md.uhdr == extract_uhdr(test_input, 'enveloped', 0) # check for external data and verify internal _enc_structure md.external_aad = unhexlify(test_input['enveloped'].get('external', b'')) assert md._enc_structure == unhexlify(test_intermediate['AAD_hex']) recipient = test_input['enveloped']['recipients'][0] assert md.recipients[0].phdr == recipient.get('protected', {}) # create HKDF contect v = PartyInfo( identity=md.recipients[0].uhdr.get(CoseHeaderKeys.PARTY_V_IDENTITY), nonce=md.recipients[0].uhdr.get(CoseHeaderKeys.PARTY_V_NONCE), other=md.recipients[0].uhdr.get(CoseHeaderKeys.PARTY_V_OTHER)) u = PartyInfo( identity=md.recipients[0].uhdr.get(CoseHeaderKeys.PARTY_U_IDENTITY), nonce=md.recipients[0].uhdr.get(CoseHeaderKeys.PARTY_U_NONCE), other=md.recipients[0].uhdr.get(CoseHeaderKeys.PARTY_U_OTHER)) public_data = test_input['enveloped']['recipients'][0].get( 'unsent', {}).get('pub_other') s = SuppPubInfo( len(test_intermediate['CEK_hex']) * 4, md.recipients[0].encode_phdr(), public_data.encode('utf-8') if public_data is not None else public_data) priv_data = test_input['enveloped']['recipients'][0].get('unsent', {}).get( 'priv_other', b'') hkdf_context = CoseKDFContext( md.phdr[CoseHeaderKeys.ALG], u, v, s, priv_data.encode('utf-8') if priv_data != b'' else priv_data) assert hkdf_context.encode() == unhexlify( test_intermediate["recipients"][0]['Context_hex']) # set shared secret key shared_secret = SymmetricKey( k=CoseKey.base64decode(test_input['enveloped']['recipients'][0]['key'][ SymmetricKey.SymPrm.K])) kek = md.recipients[0].derive_kek( shared_secret, alg=md.recipients[0].phdr[CoseHeaderKeys.ALG], context=hkdf_context, salt=md.recipients[0].uhdr.get(CoseHeaderKeys.SALT)) assert kek == unhexlify(test_intermediate["CEK_hex"]) cek = SymmetricKey(k=kek, alg=extract_alg(test_input['enveloped'])) assert md.decrypt(key=cek, nonce=extract_nonce( test_input, 0)) == test_input['plaintext'].encode('utf-8')
def test_symmetric_key_aeads(kid, alg, key_ops, base_iv, k, pl, aad, nonce, algo, ct): key = SymmetricKey(kid=kid, alg=alg, key_ops=key_ops, base_iv=base_iv, k=k) assert ct == key.encrypt(pl, aad, nonce, algo) # switch to another key operation key.key_ops = KeyOps.DECRYPT assert pl == key.decrypt(ct, aad, nonce, algo)
def decrypt(self, target_alg: '_EncAlg') -> bytes: alg = self.get_attr(headers.Algorithm) kek = SymmetricKey(k=self._compute_kek(target_alg, 'decrypt'), optional_params={ KpAlg: alg, KpKeyOps: [DecryptOp, UnwrapOp] }) kek.verify(SymmetricKey, alg, [UnwrapOp, DecryptOp]) return alg.key_unwrap(kek, self.payload)
def decrypt(self, key: SymmetricKey, alg: Optional['CoseAlgorithms'] = None) -> bytes: """ Key unwrapping. """ if key is None: raise CoseIllegalKeyType("COSE Key cannot be None") return key.key_unwrap(self.payload, alg=alg)
def compute_tag(self, *args, **kwargs) -> bytes: target_algorithm = self.get_attr(headers.Algorithm) r_types = CoseRecipient.verify_recipients(self.recipients) if DirectEncryption in r_types: # key should already be known payload = super(MacMessage, self).compute_tag() elif DirectKeyAgreement in r_types: self.key = self.recipients[0].compute_cek(target_algorithm, "encrypt") payload = super(MacMessage, self).compute_tag() elif KeyWrap in r_types or KeyAgreementWithKeyWrap in r_types: key_bytes = os.urandom( self.get_attr(headers.Algorithm).get_key_length()) for r in self.recipients: if r.payload == b'': r.payload = key_bytes else: key_bytes = r.payload r.encrypt(target_algorithm) self.key = SymmetricKey(k=key_bytes, alg=target_algorithm, key_ops=[MacCreateOp]) payload = super(MacMessage, self).compute_tag() else: raise CoseException('Unsupported COSE recipient class') return payload
def encrypt(self, nonce: bytes, key: SymmetricKey, alg: Optional[CoseAlgorithms] = None) -> bytes: """ Encrypts the payload. :param nonce: Nonce for decryption. Length tof the nonce depends on the AEAD. Nonce cannot be empty or None. :param key: A Symmetric COSE key object containing the symmetric key bytes and a optionally an AEAD algorithm. :param alg: If the 'alg' parameter is unset in the COSE key object, this parameter cannot be None. :raises ValueError: When the nonce is empty or None :raises CoseIllegalKeyType: When the key is not of type 'SymmetricKey'. :returns: ciphertext as bytes """ if nonce == b"" or nonce is None: raise ValueError(f"{nonce} is not a valid nonce value") if not isinstance(key, SymmetricKey): raise CoseIllegalKeyType( "COSE key should be of type 'SymmetricKey', got {}".format( type(key))) return key.encrypt(plaintext=self.payload, aad=self._enc_structure, nonce=nonce, alg=alg)
def create_cose_key( key_type: Type[CoseKey], input_data: dict, alg: Optional[CoseAlgorithms] = None, usage: Optional[KeyOps] = None) -> Union[EC2, SymmetricKey, OKP]: if key_type == EC2: key = EC2( kid=input_data.get(CoseKey.Common.KID), key_ops=usage, alg=alg, crv=input_data.get(EC2.EC2Prm.CRV), x=CoseKey.base64decode(input_data.get(EC2.EC2Prm.X)), y=CoseKey.base64decode(input_data.get(EC2.EC2Prm.Y)), d=CoseKey.base64decode(input_data.get(EC2.EC2Prm.D)), ) elif key_type == SymmetricKey: key = SymmetricKey(kid=input_data.get(CoseKey.Common.KID), alg=alg, key_ops=usage, k=CoseKey.base64decode( input_data.get(SymmetricKey.SymPrm.K))) elif key_type == OKP: key = OKP( kid=input_data.get(CoseKey.Common.KID), alg=alg, key_ops=usage, crv=input_data.get(OKP.OKPPrm.CRV), x=unhexlify(input_data.get(OKP.OKPPrm.X)), d=unhexlify(input_data.get(OKP.OKPPrm.D)), ) else: raise Exception return key
def compute_cek(self, target_alg: '_EncAlg', ops: str) -> Optional['SK']: if ops == "encrypt": if self.payload == b'': return None else: return SymmetricKey(k=self.payload, optional_params={ KpAlg: target_alg, KpKeyOps: [EncryptOp] }) else: return SymmetricKey(k=self.decrypt(target_alg), optional_params={ KpAlg: target_alg, KpKeyOps: [DecryptOp] })
def test_mac_direct_decoding(setup_mac_tests: tuple) -> None: _, test_input, test_output, test_intermediate, fail = setup_mac_tests if fail: skip("invalid test input") msg: MacMessage = CoseMessage.decode(unhexlify(test_output)) assert msg.phdr == test_input['mac'].get('protected', {}) assert msg.uhdr == test_input['mac'].get('unprotected', {}) assert msg.payload == test_input['plaintext'].encode('utf-8') # set up potential external data msg.external_aad = unhexlify(test_input['mac'].get("external", b'')) assert msg._mac_structure == unhexlify(test_intermediate['ToMac_hex']) alg = extract_alg(test_input['mac']) cek = create_cose_key(SymmetricKey, test_input['mac']["recipients"][0]["key"], usage=KeyOps.MAC_VERIFY, alg=alg) assert cek.k == unhexlify(test_intermediate['CEK_hex']) # verify recipients for r1, r2 in zip(msg.recipients, test_input['mac']['recipients']): assert r1.phdr == r2.get('protected', {}) assert r1.uhdr == r2.get('unprotected', {}) assert msg.verify_tag(cek) # re-encode and verify we are back where we started kek = SymmetricKey(key_ops=KeyOps.WRAP, alg=CoseAlgorithms.DIRECT.id) cek.key_ops = KeyOps.MAC_CREATE assert msg.encode(key=cek, mac_params=[RcptParams(key=kek)]) == unhexlify(test_output)
def verify_tag(self, key: SymmetricKey, alg: Optional[CoseAlgorithms] = None) -> bool: """ Verifies the authentication tag of a received message. """ if not isinstance(key, SymmetricKey): raise CoseIllegalKeyType( "COSE key should be of type 'SymmetricKey', got {}".format( type(key))) return key.verify_tag(self.auth_tag, self._mac_structure, alg)
def compute_tag(self, key: SymmetricKey, alg: Optional[CoseAlgorithms] = None) -> bytes: """ Computes the authentication tag of a COSE_Mac or COSE_Mac0 message. """ if not isinstance(key, SymmetricKey): raise CoseIllegalKeyType( "COSE key should be of type 'SymmetricKey', got {}".format( type(key))) self.auth_tag = key.compute_tag(self._mac_structure, alg) return self.auth_tag
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 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 test_encrypt_decoding(setup_encrypt_tests: tuple) -> None: _, test_input, test_output, test_intermediate, fail = setup_encrypt_tests if fail: skip("invalid test input") # parse initial message msg: EncMessage = CoseMessage.decode(unhexlify(test_output)) # verify parsed protected header assert msg.phdr == extract_phdr(test_input, 'enveloped') assert msg.uhdr == extract_uhdr(test_input, 'enveloped') nonce = extract_nonce( test_input, 0) if extract_nonce(test_input, 0) != b'' else extract_unsent_nonce( test_input, "enveloped") alg = extract_alg(test_input['enveloped']) cek = create_cose_key(SymmetricKey, test_input['enveloped']["recipients"][0]["key"], usage=KeyOps.DECRYPT, alg=alg) assert cek.k == unhexlify(test_intermediate['CEK_hex']) # look for external data and verify internal enc_structure msg.external_aad = unhexlify(test_input['enveloped'].get('external', b'')) assert msg._enc_structure == unhexlify(test_intermediate['AAD_hex']) # verify recipients assert len(msg.recipients) == 1 assert msg.recipients[0].phdr == test_input['enveloped']['recipients'][ 0].get('protected', {}) assert msg.recipients[0].uhdr == test_input['enveloped']['recipients'][ 0].get('unprotected', {}) # (1) verify decryption assert msg.decrypt(nonce=nonce, key=cek) == test_input['plaintext'].encode('utf-8') # re-encode and verify we are back where we started kek = SymmetricKey(key_ops=KeyOps.WRAP, alg=CoseAlgorithms.DIRECT.id) assert msg.encode(encrypt=False, nonce=nonce, key=cek, enc_params=[RcptParams(key=kek) ]) == unhexlify(test_output)
def test_encrypt_x25519_wrap_decode( setup_encrypt_x25519_direct_tests: tuple) -> None: _, test_input, test_output, test_intermediate, fail = setup_encrypt_x25519_direct_tests # DECODING # parse message and test for headers md: EncMessage = CoseMessage.decode(unhexlify(test_output)) assert md.phdr == extract_phdr(test_input, 'enveloped') assert md.uhdr == extract_uhdr(test_input, 'enveloped', 1) # check for external data and verify internal _enc_structure md.external_aad = unhexlify(test_input['enveloped'].get('external', b'')) assert md._enc_structure == unhexlify(test_intermediate['AAD_hex']) recipient = test_input['enveloped']['recipients'][0] assert md.recipients[0].phdr == recipient.get('protected', {}) # do not verify unprotected header since it contains the ephemeral public key of the sender # assert m.recipients[0].uhdr == rcpt.get('unprotected', {}) rcvr_skey, sender_key = setup_okp_receiver_keys( recipient, md.recipients[0].uhdr.get(CoseHeaderKeys.EPHEMERAL_KEY)) # create context KDF u = PartyInfo(nonce=unhexlify(test_input['rng_stream'][0]) ) if "sender_key" in recipient else PartyInfo() s = SuppPubInfo( len(test_intermediate['CEK_hex']) * 4, md.recipients[0].encode_phdr()) kdf_ctx = CoseKDFContext(md.phdr[CoseHeaderKeys.ALG], u, PartyInfo(), s) assert kdf_ctx.encode() == unhexlify( test_intermediate['recipients'][0]['Context_hex']) secret, kek_bytes = CoseRecipient.derive_kek(rcvr_skey, sender_key, context=kdf_ctx, expose_secret=True) assert secret == unhexlify( test_intermediate['recipients'][0]['Secret_hex']) assert kek_bytes == unhexlify(test_intermediate['CEK_hex']) alg = extract_alg(test_input['enveloped']) cek = SymmetricKey(k=kek_bytes) nonce = extract_nonce(test_input, 1) assert md.decrypt(nonce=nonce, alg=alg, key=cek) == test_input['plaintext'].encode('utf-8')
def _(cls, private_key: SymmetricKey, public_key=None, alg: Optional['CoseAlgorithms'] = None, context: 'CoseKDFContext' = None, curve=None, salt: bytes = b'', expose_secret: bool = False): _ = public_key _ = curve kek = private_key.hmac_key_derivation(context, alg, salt) if expose_secret: return private_key.private_bytes, kek else: return kek
def encrypt(self, target_alg) -> bytes: # static receiver key _ = target_alg peer_key: 'EC2Key' = self.local_attrs.get(headers.StaticKey) if peer_key is None: raise CoseException( "Static receiver key cannot be None. Should be configured in 'local_attrs' of the msg." ) alg = self.get_attr(headers.Algorithm) if alg is None: raise CoseException( "The algorithm parameter should at least be included in the unprotected header" ) # if ephemeral and not set, generate ephemeral key pair if self.key is None: if alg in {EcdhEsA128KW, EcdhEsA192KW, EcdhEsA256KW}: self._setup_ephemeral_key(peer_key) else: # alg uses a static sender raise CoseException("Static sender key cannot be None.") key_bytes = self._compute_kek( (self.get_attr(headers.Algorithm)).get_key_wrap_func(), peer_key, self.key, alg) wrap_func = alg.get_key_wrap_func() return wrap_func.key_wrap( SymmetricKey(k=key_bytes, optional_params={ KpAlg: alg, KpKeyOps: [DeriveKeyOp] }), self.payload)
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})
def decrypt(self, target_alg: '_EncAlg') -> bytes: alg = self.get_attr(headers.Algorithm) key_ops = [DecryptOp, UnwrapOp] if alg in {A128KW, A192KW, A256KW}: kek = SymmetricKey(k=self._compute_kek(target_alg, 'decrypt'), optional_params={ KpAlg: alg, KpKeyOps: key_ops }) kek.verify(SymmetricKey, alg, [UnwrapOp, DecryptOp]) elif alg in {RsaesOaepSha512, RsaesOaepSha256, RsaesOaepSha1}: kek = self.key kek.verify(RSAKey, alg, [UnwrapOp, DecryptOp]) else: raise CoseException( f"Unsupported algorithm for key unwrapping: {alg}") return alg.key_unwrap(kek, self.payload)
class KeyWrap(CoseRecipient): @classmethod def from_cose_obj(cls, cose_obj: list, *args, **kwargs) -> 'KeyWrap': msg = super().from_cose_obj(cose_obj) msg.context = kwargs.get('context') # only AE algorithms supported thus the protected header must be empty alg = msg.get_attr(headers.Algorithm) if alg in {A128KW, A192KW, A256KW} and len(msg.phdr): raise CoseMalformedMessage( f"Recipient class KEY_WRAP with alg {alg} must have a zero-length protected header" ) if msg.payload == b'': raise CoseMalformedMessage( f'Recipient class KEY_WRAP must carry the encrypted CEK in its payload' ) msg.recipients = [ CoseRecipient.create_recipient(r, context='Rec_Recipient') for r in msg.recipients ] return msg @property def context(self): return self._context @context.setter def context(self, context: str): self._context = context def encode(self, *args, **kwargs) -> list: recipient = [ self.phdr_encoded, self.uhdr_encoded, self.encrypt(kwargs.get('target_alg')) ] if len(self.recipients): recipient.append( [r.encode(*args, **kwargs) for r in self.recipients]) return recipient def _compute_kek(self, target_alg: '_EncAlg', ops: 'str') -> bytes: if self.key is None: # try to derive from this recipients' recipient list if not len(self.recipients): raise CoseException(f"No key found to {ops} the CEK") else: r_types = CoseRecipient.verify_recipients(self.recipients) if ops == 'encrypt': if DirectKeyAgreement in r_types: self.key = self.recipients[0].compute_cek(target_alg) elif KeyWrap in r_types or KeyAgreementWithKeyWrap in r_types: key_bytes = os.urandom(self.get_attr( headers.Algorithm)) for r in self.recipients: r.payload = key_bytes self.key = SymmetricKey(k=key_bytes) else: raise CoseException('Unsupported COSE recipient class') else: if DirectKeyAgreement in r_types or KeyWrap in r_types or KeyAgreementWithKeyWrap in r_types: self.key = self.recipients[0].decrypt( self.get_attr(headers.Algorithm)) else: raise CoseException('Unsupported COSE recipient class') if self.key is None: raise CoseException("No key found to decrypt the CEK") return self.key.k def compute_cek(self, target_alg: '_EncAlg', ops: str) -> Optional['SK']: if ops == "encrypt": if self.payload == b'': return None else: return SymmetricKey(k=self.payload, optional_params={ KpAlg: target_alg, KpKeyOps: [EncryptOp] }) else: return SymmetricKey(k=self.decrypt(target_alg), optional_params={ KpAlg: target_alg, KpKeyOps: [DecryptOp] }) 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 decrypt(self, target_alg: '_EncAlg') -> bytes: alg = self.get_attr(headers.Algorithm) kek = SymmetricKey(k=self._compute_kek(target_alg, 'decrypt'), optional_params={ KpAlg: alg, KpKeyOps: [DecryptOp, UnwrapOp] }) kek.verify(SymmetricKey, alg, [UnwrapOp, DecryptOp]) return alg.key_unwrap(kek, self.payload) def __repr__(self) -> str: phdr, uhdr = self._hdr_repr() return f'<COSE_Recipient: [{phdr}, {uhdr}, {utils.truncate(self._payload)}, {str(self.recipients)}]>'
def test_symmetric_key_generation(): key = SymmetricKey.generate_key(CoseAlgorithms.A128GCM, KeyOps.ENCRYPT, 16) assert isinstance(key, SymmetricKey)
def test_encrypt_ecdh_direct_decode_encode( setup_encrypt_ecdh_direct_tests: tuple) -> None: title, test_input, test_output, test_intermediate, fail = setup_encrypt_ecdh_direct_tests # DECODING # parse message and test for headers md: EncMessage = CoseMessage.decode(unhexlify(test_output)) assert md.phdr == extract_phdr(test_input, 'enveloped') assert md.uhdr == extract_uhdr(test_input, 'enveloped', 1) # check for external data and verify internal _enc_structure md.external_aad = unhexlify(test_input['enveloped'].get('external', b'')) assert md._enc_structure == unhexlify(test_intermediate['AAD_hex']) # verify the receiver and set up the keying material recipient = test_input['enveloped']['recipients'][0] assert md.recipients[0].phdr == recipient.get('protected', {}) # do not verify unprotected header since it contains the ephemeral public key of the sender # assert m.recipients[0].uhdr == rcpt.get('unprotected', {}) rcvr_skey, sender_key = setup_ec_receiver_keys( recipient, md.recipients[0].uhdr.get(CoseHeaderKeys.EPHEMERAL_KEY)) # create context KDF v = PartyInfo() u = PartyInfo(nonce=unhexlify(test_input['rng_stream'][0]) ) if "sender_key" in recipient else PartyInfo() s = SuppPubInfo( len(test_intermediate['CEK_hex']) * 4, md.recipients[0].encode_phdr()) kdf_ctx = CoseKDFContext(md.phdr[CoseHeaderKeys.ALG], u, v, s) assert kdf_ctx.encode() == unhexlify( test_intermediate['recipients'][0]['Context_hex']) secret, kek_bytes = CoseRecipient.derive_kek(rcvr_skey, sender_key, context=kdf_ctx, expose_secret=True) assert secret == unhexlify( test_intermediate['recipients'][0]['Secret_hex']) assert kek_bytes == unhexlify(test_intermediate['CEK_hex']) alg = extract_alg(test_input['enveloped']) cek = SymmetricKey(k=kek_bytes) nonce = extract_nonce(test_input, 1) assert md.decrypt(nonce=nonce, alg=alg, key=cek) == test_input['plaintext'].encode('utf-8') # ENCODING me = EncMessage(phdr=test_input['enveloped'].get("protected", {}), uhdr=test_input['enveloped'].get("unprotected", {}), payload=test_input['plaintext'].encode('utf-8')) if 'rng_stream' in test_input: me.uhdr_update( {CoseHeaderKeys.IV: unhexlify(test_input['rng_stream'][1])}) # Set up recipients and keys recipient = test_input['enveloped']['recipients'][0] if 'sender_key' in recipient: r1 = CoseRecipient(phdr=recipient.get('protected', {})) r1.uhdr_update( {CoseHeaderKeys.STATIC_KEY: sender_key.encode('crv', 'x', 'y')}) r1.uhdr_update(recipient.get('unprotected', {})) r1.uhdr_update({ CoseHeaderKeys.PARTY_U_NONCE: unhexlify(test_input['rng_stream'][0]) }) else: r1 = CoseRecipient(phdr=recipient.get('protected', {})) r1.uhdr_update( {CoseHeaderKeys.EPHEMERAL_KEY: sender_key.encode('crv', 'x', 'y')}) r1.uhdr_update(recipient.get('unprotected', {})) # append the first and only recipient me.recipients.append(r1) # set up cek cek = SymmetricKey(k=kek_bytes, alg=alg) kek = SymmetricKey(k=kek_bytes, alg=CoseAlgorithms.DIRECT.id) # without sorting probably does not match because the order of the recipient elements is not the same assert sorted( me.encode(key=cek, nonce=nonce, enc_params=[RcptParams(key=kek) ])) == sorted(unhexlify(test_output))
def test_encrypt_ecdh_wrap_decode(setup_encrypt_ecdh_wrap_tests: tuple): _, test_input, test_output, test_intermediate, fail = setup_encrypt_ecdh_wrap_tests # DECODING # parse message and test for headers md: EncMessage = CoseMessage.decode(unhexlify(test_output)) assert md.phdr == extract_phdr(test_input, 'enveloped') assert md.uhdr == extract_uhdr(test_input, 'enveloped', 1) # check for external data and verify internal _enc_structure md.external_aad = unhexlify(test_input['enveloped'].get('external', b'')) assert md._enc_structure == unhexlify(test_intermediate['AAD_hex']) recipient = test_input['enveloped']['recipients'][0] assert md.recipients[0].phdr == recipient.get('protected', {}) # do not verify unprotected header since it contains the ephemeral public key of the sender # assert m.recipients[0].uhdr == rcpt.get('unprotected', {}) rcvr_skey, sender_key = setup_ec_receiver_keys( recipient, md.recipients[0].uhdr.get(CoseHeaderKeys.EPHEMERAL_KEY)) # create context KDF s = SuppPubInfo( len(test_intermediate['recipients'][0]['KEK_hex']) * 4, md.recipients[0].encode_phdr()) if md.recipients[0].phdr[CoseHeaderKeys.ALG] in { CoseAlgorithms.ECDH_ES_A192KW, CoseAlgorithms.ECDH_SS_A192KW }: kdf_ctx = CoseKDFContext(CoseAlgorithms.A192KW, PartyInfo(), PartyInfo(), s) elif md.recipients[0].phdr[CoseHeaderKeys.ALG] in { CoseAlgorithms.ECDH_ES_A128KW, CoseAlgorithms.ECDH_SS_A128KW }: kdf_ctx = CoseKDFContext(CoseAlgorithms.A128KW, PartyInfo(), PartyInfo(), s) elif md.recipients[0].phdr[CoseHeaderKeys.ALG] in { CoseAlgorithms.ECDH_ES_A256KW, CoseAlgorithms.ECDH_SS_A256KW }: kdf_ctx = CoseKDFContext(CoseAlgorithms.A256KW, PartyInfo(), PartyInfo(), s) else: raise ValueError("Missed an algorithm?") assert kdf_ctx.encode() == unhexlify( test_intermediate['recipients'][0]['Context_hex']) secret, kek = CoseRecipient.derive_kek(rcvr_skey, sender_key, context=kdf_ctx, expose_secret=True) assert secret == unhexlify( test_intermediate['recipients'][0]['Secret_hex']) assert kek == unhexlify(test_intermediate['recipients'][0]['KEK_hex']) r1 = md.recipients[0] cek = r1.decrypt(key=SymmetricKey(k=kek, alg=r1.phdr[CoseHeaderKeys.ALG])) assert cek == unhexlify(test_intermediate['CEK_hex']) cek = SymmetricKey(k=cek, alg=extract_alg(test_input["enveloped"])) pld = md.decrypt(key=cek, nonce=extract_nonce(test_input, 1)) assert pld == test_input['plaintext'].encode('utf-8')
msg.external_aad = unhexlify(test_input['encrypted'].get('external', b'')) assert msg._enc_structure == unhexlify(test_intermediate['AAD_hex']) # verify decryption assert msg.decrypt(nonce=nonce, key=key) == test_input['plaintext'].encode('utf-8') # re-encode and verify we are back where we started assert msg.encode(encrypt=False, key=key, nonce=nonce) == unhexlify(test_output) @mark.parametrize("phdr, uhdr, payload, key", [ ({CoseHeaderKeys.ALG: CoseAlgorithms.A128GCM}, {CoseHeaderKeys.IV: unhexlify(b'89F52F65A1C580933B5261A72F')}, b'', SymmetricKey(kid=b'you_know', k=os.urandom(16), alg=CoseAlgorithms.A128GCM)), ({CoseHeaderKeys.ALG: CoseAlgorithms.A192GCM}, {CoseHeaderKeys.IV: unhexlify(b'89F52F65A1C580933B5261A72F')}, os.urandom(50), SymmetricKey(kid=b'you_know', k=os.urandom(16), alg=CoseAlgorithms.A192GCM)), ({CoseHeaderKeys.ALG: CoseAlgorithms.A256GCM}, {CoseHeaderKeys.IV: unhexlify(b'89F52F65A1C580933B5261A72F')}, os.urandom(100), SymmetricKey(kid=b'you_know', k=os.urandom(16), alg=CoseAlgorithms.A256GCM)) ], ids=['test_encode_decode_1', 'test_encode_decode_2', 'test_encode_decode_3']) def test_encode_decode_encrypt0(phdr, uhdr, payload, key): # create and encode a message original: Enc0Message = Enc0Message(phdr, uhdr, payload) encoded = original.encode(key=key, nonce=original.uhdr[CoseHeaderKeys.IV]) # decode the message