def test_ecdh_es_direct(): bob_key = Key.generate(KeyAlg.P256) bob_jwk = bob_key.get_jwk_public() ephem_key = Key.generate(KeyAlg.P256) ephem_jwk = ephem_key.get_jwk_public() message = b"Hello there" alg = "ECDH-ES" enc = "A256GCM" apu = "Alice" apv = "Bob" protected_b64 = b64_url(f'{{"alg":"{alg}",' f'"enc":"{enc}",' f'"apu":"{b64_url(apu)}",' f'"apv":"{b64_url(apv)}",' f'"epk":{ephem_jwk}}}').encode("ascii") encrypted_msg = EcdhEs(enc, apu, apv).encrypt_direct(KeyAlg.A256GCM, ephem_key, bob_jwk, message, aad=protected_b64) ciphertext, tag, nonce = encrypted_msg.parts # switch to receiver message_recv = EcdhEs(enc, apu, apv).decrypt_direct( KeyAlg.A256GCM, ephem_jwk, bob_key, ciphertext, nonce=nonce, tag=tag, aad=protected_b64, ) assert message_recv == message
async def test_es_encrypt_x(self, session: Session): alg = KeyAlg.X25519 bob_sk = Key.generate(alg) bob_pk = Key.from_jwk(bob_sk.get_jwk_public()) with pytest.raises( test_module.DidcommEnvelopeError, match="No message recipients" ): _ = test_module.ecdh_es_encrypt({}, MESSAGE) with async_mock.patch( "aries_askar.Key.generate", async_mock.MagicMock(side_effect=AskarError(99, "")), ): with pytest.raises( test_module.DidcommEnvelopeError, match="Error creating content encryption key", ): _ = test_module.ecdh_es_encrypt({BOB_KID: bob_pk}, MESSAGE) with async_mock.patch( "aries_askar.Key.aead_encrypt", async_mock.MagicMock(side_effect=AskarError(99, "")), ): with pytest.raises( test_module.DidcommEnvelopeError, match="Error encrypting", ): _ = test_module.ecdh_es_encrypt({BOB_KID: bob_pk}, MESSAGE)
def _create_keypair(key_type: KeyType, seed: Union[str, bytes] = None) -> Key: """Instantiate a new keypair with an optional seed value.""" if key_type == KeyType.ED25519: alg = KeyAlg.ED25519 method = None # elif key_type == KeyType.BLS12381G1: # alg = KeyAlg.BLS12_381_G1 elif key_type == KeyType.BLS12381G2: alg = KeyAlg.BLS12_381_G2 method = SeedMethod.BlsKeyGen # elif key_type == KeyType.BLS12381G1G2: # alg = KeyAlg.BLS12_381_G1G2 else: raise WalletError(f"Unsupported key algorithm: {key_type}") if seed: try: if key_type == KeyType.ED25519: # not a seed - it is the secret key seed = validate_seed(seed) return Key.from_secret_bytes(alg, seed) else: return Key.from_seed(alg, seed, method=method) except AskarError as err: if err.code == AskarErrorCode.INPUT: raise WalletError("Invalid seed for key generation") from None else: return Key.generate(alg)
def test_ed25519(): key = Key.generate(KeyAlg.ED25519) assert key.algorithm == KeyAlg.ED25519 message = b"test message" sig = key.sign_message(message) assert key.verify_signature(message, sig) x25519_key = key.convert_key(KeyAlg.X25519) x25519_key_2 = Key.generate(KeyAlg.X25519) _kex = x25519_key.key_exchange(KeyAlg.XC20P, x25519_key_2) key.get_jwk_public()
async def test_1pu_decrypt_x(self): alg = KeyAlg.X25519 alice_sk = Key.generate(alg) alice_pk = Key.from_jwk(alice_sk.get_jwk_public()) bob_sk = Key.generate(alg) message_unknown_alg = JweEnvelope( protected={"alg": "NOT-SUPPORTED"}, ) message_unknown_alg.add_recipient( JweRecipient(encrypted_key=b"0000", header={"kid": BOB_KID}) ) with pytest.raises( test_module.DidcommEnvelopeError, match="Unsupported ECDH-1PU algorithm", ): _ = test_module.ecdh_1pu_decrypt( message_unknown_alg, BOB_KID, bob_sk, alice_pk, ) message_unknown_enc = JweEnvelope( protected={"alg": "ECDH-1PU+A128KW", "enc": "UNKNOWN"}, ) message_unknown_enc.add_recipient( JweRecipient(encrypted_key=b"0000", header={"kid": BOB_KID}) ) with pytest.raises( test_module.DidcommEnvelopeError, match="Unsupported ECDH-1PU content encryption", ): _ = test_module.ecdh_1pu_decrypt( message_unknown_enc, BOB_KID, bob_sk, alice_pk ) message_invalid_epk = JweEnvelope( protected={"alg": "ECDH-1PU+A128KW", "enc": "A256CBC-HS512", "epk": {}}, ) message_invalid_epk.add_recipient( JweRecipient(encrypted_key=b"0000", header={"kid": BOB_KID}) ) with pytest.raises( test_module.DidcommEnvelopeError, match="Error loading ephemeral key", ): _ = test_module.ecdh_1pu_decrypt( message_invalid_epk, BOB_KID, bob_sk, alice_pk, )
def test_crypto_box_seal(): x25519_key = Key.generate(KeyAlg.X25519) msg = b"test message" sealed = crypto_box_seal(x25519_key, msg) opened = crypto_box_seal_open(x25519_key, sealed) assert msg == opened
def _pack_message(to_verkeys: Sequence[str], from_key: Optional[Key], message: bytes) -> bytes: """Encode a message using the DIDComm v1 'pack' algorithm.""" wrapper = JweEnvelope() cek = Key.generate(KeyAlg.C20P) # avoid converting to bytes object: this way the only copy is zeroed afterward cek_b = key_get_secret_bytes(cek._handle) sender_vk = (bytes_to_b58(from_key.get_public_bytes()).encode("utf-8") if from_key else None) sender_xk = from_key.convert_key(KeyAlg.X25519) if from_key else None for target_vk in to_verkeys: target_xk = Key.from_public_bytes(KeyAlg.ED25519, b58_to_bytes(target_vk)).convert_key( KeyAlg.X25519) if sender_vk: enc_sender = crypto_box.crypto_box_seal(target_xk, sender_vk) nonce = crypto_box.random_nonce() enc_cek = crypto_box.crypto_box(target_xk, sender_xk, cek_b, nonce) wrapper.add_recipient( JweRecipient( encrypted_key=enc_cek, header=OrderedDict([ ("kid", target_vk), ("sender", b64url(enc_sender)), ("iv", b64url(nonce)), ]), )) else: enc_sender = None nonce = None enc_cek = crypto_box.crypto_box_seal(target_xk, cek_b) wrapper.add_recipient( JweRecipient(encrypted_key=enc_cek, header={"kid": target_vk})) wrapper.set_protected( OrderedDict([ ("enc", "xchacha20poly1305_ietf"), ("typ", "JWM/1.0"), ("alg", "Authcrypt" if from_key else "Anoncrypt"), ]), auto_flatten=False, ) enc = cek.aead_encrypt(message, aad=wrapper.protected_bytes) ciphertext, tag, nonce = enc.parts wrapper.set_payload(ciphertext, nonce, tag) return wrapper.to_json().encode("utf-8")
def ecdh_es_encrypt(to_verkeys: Mapping[str, Key], message: bytes) -> bytes: """Encode a message using DIDComm v2 anonymous encryption.""" wrapper = JweEnvelope(with_flatten_recipients=False) alg_id = "ECDH-ES+A256KW" enc_id = "XC20P" enc_alg = KeyAlg.XC20P wrap_alg = KeyAlg.A256KW if not to_verkeys: raise DidcommEnvelopeError("No message recipients") try: cek = Key.generate(enc_alg) except AskarError: raise DidcommEnvelopeError("Error creating content encryption key") for (kid, recip_key) in to_verkeys.items(): try: epk = Key.generate(recip_key.algorithm, ephemeral=True) except AskarError: raise DidcommEnvelopeError("Error creating ephemeral key") enc_key = ecdh.EcdhEs(alg_id, None, None).sender_wrap_key(wrap_alg, epk, recip_key, cek) wrapper.add_recipient( JweRecipient( encrypted_key=enc_key.ciphertext, header={ "kid": kid, "epk": epk.get_jwk_public() }, )) wrapper.set_protected(OrderedDict([ ("alg", alg_id), ("enc", enc_id), ])) try: payload = cek.aead_encrypt(message, aad=wrapper.protected_bytes) except AskarError: raise DidcommEnvelopeError("Error encrypting message payload") wrapper.set_payload(payload.ciphertext, payload.nonce, payload.tag) return wrapper.to_json().encode("utf-8")
def ecdh_1pu_decrypt( wrapper: JweEnvelope, recip_kid: str, recip_key: Key, sender_key: Key, ) -> Tuple[str, str, str]: """Decode a message with DIDComm v2 authenticated encryption.""" alg_id = wrapper.protected.get("alg") if alg_id in ("ECDH-1PU+A128KW", "ECDH-1PU+A256KW"): wrap_alg = alg_id[9:] else: raise DidcommEnvelopeError(f"Unsupported ECDH-1PU algorithm: {alg_id}") enc_alg = wrapper.protected.get("enc") if enc_alg not in ("A128CBC-HS256", "A256CBC-HS512"): raise DidcommEnvelopeError( f"Unsupported ECDH-1PU content encryption: {enc_alg}") recip = wrapper.get_recipient(recip_kid) if not recip: raise DidcommEnvelopeError(f"Recipient header not found: {recip_kid}") try: epk = Key.from_jwk(wrapper.protected.get("epk")) except AskarError: raise DidcommEnvelopeError("Error loading ephemeral key") apu = wrapper.protected.get("apu") apv = wrapper.protected.get("apv") try: cek = ecdh.Ecdh1PU(alg_id, apu, apv).receiver_unwrap_key( wrap_alg, enc_alg, epk, sender_key, recip_key, recip.encrypted_key, cc_tag=wrapper.tag, ) except AskarError: raise DidcommEnvelopeError("Error decrypting content encryption key") try: plaintext = cek.aead_decrypt( wrapper.ciphertext, nonce=wrapper.iv, tag=wrapper.tag, aad=wrapper.combined_aad, ) except AskarError: raise DidcommEnvelopeError("Error decrypting message payload") return plaintext
def _extract_payload_key(sender_cek: dict, recip_secret: Key) -> Tuple[bytes, str]: """ Extract the payload key from pack recipient details. Returns: A tuple of the CEK and sender verkey """ recip_x = recip_secret.convert_key(KeyAlg.X25519) if sender_cek["nonce"] and sender_cek["sender"]: sender_vk = crypto_box.crypto_box_seal_open( recip_x, sender_cek["sender"]).decode("utf-8") sender_x = Key.from_public_bytes( KeyAlg.ED25519, b58_to_bytes(sender_vk)).convert_key(KeyAlg.X25519) cek = crypto_box.crypto_box_open(recip_x, sender_x, sender_cek["key"], sender_cek["nonce"]) else: sender_vk = None cek = crypto_box.crypto_box_seal_open(recip_x, sender_cek["key"]) return cek, sender_vk
async def test_es_round_trip(self, session: Session): alg = KeyAlg.X25519 bob_sk = Key.generate(alg) bob_pk = Key.from_jwk(bob_sk.get_jwk_public()) carol_sk = Key.generate(KeyAlg.P256) # testing mixed recipient key types carol_pk = Key.from_jwk(carol_sk.get_jwk_public()) enc_message = test_module.ecdh_es_encrypt( {BOB_KID: bob_pk, CAROL_KID: carol_pk}, MESSAGE ) # receiver must have the private keypair accessible await session.insert_key("my_sk", bob_sk, tags={"kid": BOB_KID}) plaintext, recip_kid, sender_kid = await test_module.unpack_message( session, enc_message ) assert recip_kid == BOB_KID assert sender_kid is None assert plaintext == MESSAGE
def test_aes_cbc_hmac(): key = Key.generate(KeyAlg.A128CBC_HS256) assert key.algorithm == KeyAlg.A128CBC_HS256 data = b"test message" nonce = key.aead_random_nonce() params = key.aead_params() assert params.nonce_length == 16 assert params.tag_length == 16 enc = key.aead_encrypt(data, nonce=nonce, aad=b"aad") dec = key.aead_decrypt(enc, nonce=nonce, aad=b"aad") assert data == bytes(dec)
async def test_1pu_round_trip(self, session: Session): alg = KeyAlg.X25519 alice_sk = Key.generate(alg) alice_pk = Key.from_jwk(alice_sk.get_jwk_public()) bob_sk = Key.generate(alg) bob_pk = Key.from_jwk(bob_sk.get_jwk_public()) enc_message = test_module.ecdh_1pu_encrypt( {BOB_KID: bob_pk}, ALICE_KID, alice_sk, MESSAGE ) # receiver must have the private keypair accessible await session.insert_key("my_sk", bob_sk, tags={"kid": BOB_KID}) # for now at least, insert the sender public key so it can be resolved await session.insert_key("alice_pk", alice_pk, tags={"kid": ALICE_KID}) plaintext, recip_kid, sender_kid = await test_module.unpack_message( session, enc_message ) assert recip_kid == BOB_KID assert sender_kid == ALICE_KID assert plaintext == MESSAGE
async def verify_message( self, message: Union[List[bytes], bytes], signature: bytes, from_verkey: str, key_type: KeyType, ) -> bool: """ Verify a signature against the public key of the signer. Args: message: The message to verify signature: The signature to verify from_verkey: Verkey to use in verification key_type: The key type to derive the signature verification algorithm from Returns: True if verified, else False Raises: WalletError: If the verkey is not provided WalletError: If the signature is not provided WalletError: If the message is not provided WalletError: If another backend error occurs """ if not from_verkey: raise WalletError("Verkey not provided") if not signature: raise WalletError("Signature not provided") if not message: raise WalletError("Message not provided") verkey = b58_to_bytes(from_verkey) if key_type == KeyType.ED25519: try: pk = Key.from_public_bytes(KeyAlg.ED25519, verkey) return pk.verify_signature(message, signature) except AskarError as err: raise WalletError( "Exception when verifying message signature") from err # other key types are currently verified outside of Askar return verify_signed_message( message=message, signature=signature, verkey=verkey, key_type=key_type, )
def test_ecdh_es_wrapped(): bob_key = Key.generate(KeyAlg.X25519) bob_jwk = bob_key.get_jwk_public() ephem_key = Key.generate(KeyAlg.X25519) ephem_jwk = ephem_key.get_jwk_public() message = b"Hello there" alg = "ECDH-ES+A128KW" enc = "A256GCM" apu = "Alice" apv = "Bob" protected_b64 = b64_url(f'{{"alg":"{alg}",' f'"enc":"{enc}",' f'"apu":"{b64_url(apu)}",' f'"apv":"{b64_url(apv)}",' f'"epk":{ephem_jwk}}}').encode("ascii") cek = Key.generate(KeyAlg.A256GCM) encrypted_msg = cek.aead_encrypt(message, aad=protected_b64) ciphertext, tag, nonce = encrypted_msg.parts encrypted_key = EcdhEs(alg, apu, apv).sender_wrap_key(KeyAlg.A128KW, ephem_key, bob_jwk, cek) encrypted_key = encrypted_key.ciphertext # switch to receiver cek_recv = EcdhEs(alg, apu, apv).receiver_unwrap_key( KeyAlg.A128KW, KeyAlg.A256GCM, ephem_jwk, bob_key, encrypted_key, ) message_recv = cek_recv.aead_decrypt(ciphertext, nonce=nonce, tag=tag, aad=protected_b64) assert message_recv == message
async def test_key_store(store: Store): # test key operations in a new session async with store as session: # Create a new keypair keypair = Key.generate(KeyAlg.ED25519) # Store keypair key_name = "testkey" await session.insert_key(key_name, keypair, metadata="metadata", tags={"a": "b"}) # Fetch keypair fetch_key = await session.fetch_key(key_name) assert fetch_key and fetch_key.name == key_name and fetch_key.tags == { "a": "b" } # Update keypair await session.update_key(key_name, metadata="updated metadata", tags={"a": "c"}) # Fetch keypair fetch_key = await session.fetch_key(key_name) assert fetch_key and fetch_key.name == key_name and fetch_key.tags == { "a": "c" } # Check key equality thumbprint = keypair.get_jwk_thumbprint() assert fetch_key.key.get_jwk_thumbprint() == thumbprint # Fetch with filters keys = await session.fetch_all_keys(alg=KeyAlg.ED25519, thumbprint=thumbprint, tag_filter={"a": "c"}, limit=1) assert len(keys) == 1 and keys[0].name == key_name # Remove await session.remove_key(key_name) assert await session.fetch_key(key_name) is None
def test_bls_keygen(): key = Key.from_seed( KeyAlg.BLS12_381_G1G2, b"testseed000000000000000000000001", method=SeedMethod.BlsKeyGen, ) assert key.get_jwk_public(KeyAlg.BLS12_381_G1) == ( '{"crv":"BLS12381_G1","kty":"EC","x":' '"h56eYI8Qkq5hitICb-ik8wRTzcn6Fd4iY8aDNVc9q1xoPS3lh4DB_B4wNtar1HrV"}') assert key.get_jwk_public(KeyAlg.BLS12_381_G2) == ( '{"crv":"BLS12381_G2","kty":"EC",' '"x":"iZIOsO6BgLV72zCrBE2ym3DEhDYcghnUMO4O8IVVD8yS-C_zu6OA3L-ny-AO4' 'rbkAo-WuApZEjn83LY98UtoKpTufn4PCUFVQZzJNH_gXWHR3oDspJaCbOajBfm5qj6d"}' ) assert key.get_jwk_public() == ( '{"crv":"BLS12381_G1G2","kty":"EC",' '"x":"h56eYI8Qkq5hitICb-ik8wRTzcn6Fd4iY8aDNVc9q1xoPS3lh4DB_B4wNtar1H' "rViZIOsO6BgLV72zCrBE2ym3DEhDYcghnUMO4O8IVVD8yS-C_zu6OA3L-ny-AO4rbk" 'Ao-WuApZEjn83LY98UtoKpTufn4PCUFVQZzJNH_gXWHR3oDspJaCbOajBfm5qj6d"}')
async def _unpack_message(session: Session, enc_message: bytes) -> Tuple[str, str, str]: """Decode a message using the DIDComm v1 'unpack' algorithm.""" try: wrapper = JweEnvelope.from_json(enc_message) except ValidationError: raise WalletError("Invalid packed message") alg = wrapper.protected.get("alg") is_authcrypt = alg == "Authcrypt" if not is_authcrypt and alg != "Anoncrypt": raise WalletError("Unsupported pack algorithm: {}".format(alg)) recips = extract_pack_recipients(wrapper.recipients()) payload_key, sender_vk = None, None for recip_vk in recips: recip_key_entry = await session.fetch_key(recip_vk) if recip_key_entry: payload_key, sender_vk = _extract_payload_key( recips[recip_vk], recip_key_entry.key) break if not payload_key: raise WalletError("No corresponding recipient key found in {}".format( tuple(recips))) if not sender_vk and is_authcrypt: raise WalletError( "Sender public key not provided for Authcrypt message") cek = Key.from_secret_bytes(KeyAlg.C20P, payload_key) message = cek.aead_decrypt( wrapper.ciphertext, nonce=wrapper.iv, tag=wrapper.tag, aad=wrapper.protected_bytes, ) return message, recip_vk, sender_vk
def ecdh_1pu_encrypt(to_verkeys: Mapping[str, Key], sender_kid: str, sender_key: Key, message: bytes) -> bytes: """Encode a message using DIDComm v2 authenticated encryption.""" wrapper = JweEnvelope(with_flatten_recipients=False) alg_id = "ECDH-1PU+A256KW" enc_id = "A256CBC-HS512" enc_alg = KeyAlg.A256CBC_HS512 wrap_alg = KeyAlg.A256KW agree_alg = sender_key.algorithm if not to_verkeys: raise DidcommEnvelopeError("No message recipients") try: cek = Key.generate(enc_alg) except AskarError: raise DidcommEnvelopeError("Error creating content encryption key") try: epk = Key.generate(agree_alg, ephemeral=True) except AskarError: raise DidcommEnvelopeError("Error creating ephemeral key") apu = b64url(sender_kid) apv = [] for (kid, recip_key) in to_verkeys.items(): if agree_alg: if agree_alg != recip_key.algorithm: raise DidcommEnvelopeError( "Recipient key types must be consistent") else: agree_alg = recip_key.algorithm apv.append(kid) apv.sort() apv = b64url(".".join(apv)) wrapper.set_protected( OrderedDict([ ("alg", alg_id), ("enc", enc_id), ("apu", apu), ("apv", apv), ("epk", json.loads(epk.get_jwk_public())), ("skid", sender_kid), ])) try: payload = cek.aead_encrypt(message, aad=wrapper.protected_bytes) except AskarError: raise DidcommEnvelopeError("Error encrypting message payload") wrapper.set_payload(payload.ciphertext, payload.nonce, payload.tag) for (kid, recip_key) in to_verkeys.items(): enc_key = ecdh.Ecdh1PU(alg_id, apu, apv).sender_wrap_key(wrap_alg, epk, sender_key, recip_key, cek, cc_tag=payload.tag) wrapper.add_recipient( JweRecipient(encrypted_key=enc_key.ciphertext, header={"kid": kid})) return wrapper.to_json().encode("utf-8")
async def test_unpack_message_1pu_x(self, session: Session): alg = KeyAlg.X25519 alice_sk = Key.generate(alg) alice_pk = Key.from_jwk(alice_sk.get_jwk_public()) bob_sk = Key.generate(alg) bob_pk = Key.from_jwk(bob_sk.get_jwk_public()) # receiver must have the private keypair accessible await session.insert_key("my_sk", bob_sk, tags={"kid": BOB_KID}) # for now at least, insert the sender public key so it can be resolved await session.insert_key("alice_pk", alice_pk, tags={"kid": ALICE_KID}) message_1pu_no_skid = json.dumps( { "protected": b64url(json.dumps({"alg": "ECDH-1PU+A128KW"})), "recipients": [{"header": {"kid": BOB_KID}, "encrypted_key": "MTIzNA"}], "iv": "MTIzNA", "ciphertext": "MTIzNA", "tag": "MTIzNA", } ) with pytest.raises( test_module.DidcommEnvelopeError, match="Sender key ID not provided", ): _ = await test_module.unpack_message(session, message_1pu_no_skid) message_1pu_unknown_skid = json.dumps( { "protected": b64url( json.dumps({"alg": "ECDH-1PU+A128KW", "skid": "UNKNOWN"}) ), "recipients": [{"header": {"kid": BOB_KID}, "encrypted_key": "MTIzNA"}], "iv": "MTIzNA", "ciphertext": "MTIzNA", "tag": "MTIzNA", } ) with pytest.raises( test_module.DidcommEnvelopeError, match="Sender public key not found", ): _ = await test_module.unpack_message(session, message_1pu_unknown_skid) message_1pu_apu_invalid = json.dumps( { "protected": b64url( json.dumps({"alg": "ECDH-1PU+A128KW", "skid": "A", "apu": "A"}) ), "recipients": [{"header": {"kid": BOB_KID}, "encrypted_key": "MTIzNA"}], "iv": "MTIzNA", "ciphertext": "MTIzNA", "tag": "MTIzNA", } ) with pytest.raises( test_module.DidcommEnvelopeError, match="Invalid apu value", ): _ = await test_module.unpack_message(session, message_1pu_apu_invalid) message_1pu_apu_mismatch = json.dumps( { "protected": b64url( json.dumps( { "alg": "ECDH-1PU+A128KW", "skid": ALICE_KID, "apu": b64url("UNKNOWN"), } ) ), "recipients": [{"header": {"kid": BOB_KID}, "encrypted_key": "MTIzNA"}], "iv": "MTIzNA", "ciphertext": "MTIzNA", "tag": "MTIzNA", } ) with pytest.raises( test_module.DidcommEnvelopeError, match="Mismatch between skid and apu", ): _ = await test_module.unpack_message(session, message_1pu_apu_mismatch)
def test_ecdh_1pu_wrapped_expected(): ephem = Key.from_jwk(""" {"kty": "OKP", "crv": "X25519", "x": "k9of_cpAajy0poW5gaixXGs9nHkwg1AFqUAFa39dyBc", "d": "x8EVZH4Fwk673_mUujnliJoSrLz0zYzzCWp5GUX2fc8"} """) alice = Key.from_jwk(""" {"kty": "OKP", "crv": "X25519", "x": "Knbm_BcdQr7WIoz-uqit9M0wbcfEr6y-9UfIZ8QnBD4", "d": "i9KuFhSzEBsiv3PKVL5115OCdsqQai5nj_Flzfkw5jU"} """) bob = Key.from_jwk(""" {"kty": "OKP", "crv": "X25519", "x": "BT7aR0ItXfeDAldeeOlXL_wXqp-j5FltT0vRSG16kRw", "d": "1gDirl_r_Y3-qUa3WXHgEXrrEHngWThU3c9zj9A2uBg"} """) alg = "ECDH-1PU+A128KW" enc = "A256CBC-HS512" apu = "Alice" apv = "Bob and Charlie" protected_b64 = b64_url( f'{{"alg":"{alg}",' f'"enc":"{enc}",' f'"apu":"{b64_url(apu)}",' f'"apv":"{b64_url(apv)}",' '"epk":' '{"kty":"OKP",' '"crv":"X25519",' '"x":"k9of_cpAajy0poW5gaixXGs9nHkwg1AFqUAFa39dyBc"}}').encode("ascii") protected = (f'{{"alg":"{alg}",' f'"enc":"{enc}",' f'"apu":"{b64_url(apu)}",' f'"apv":"{b64_url(apv)}",' '"epk":' '{"kty":"OKP",' '"crv":"X25519",' '"x":"k9of_cpAajy0poW5gaixXGs9nHkwg1AFqUAFa39dyBc"}}') assert protected == ( '{"alg":"ECDH-1PU+A128KW",' '"enc":"A256CBC-HS512",' '"apu":"QWxpY2U",' # Alice '"apv":"Qm9iIGFuZCBDaGFybGll",' # Bob and Charlie '"epk":' '{"kty":"OKP",' '"crv":"X25519",' '"x":"k9of_cpAajy0poW5gaixXGs9nHkwg1AFqUAFa39dyBc"}}') cek = Key.from_secret_bytes( KeyAlg.A256CBC_HS512, bytes.fromhex("fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0" "efeeedecebeae9e8e7e6e5e4e3e2e1e0" "dfdedddcdbdad9d8d7d6d5d4d3d2d1d0" "cfcecdcccbcac9c8c7c6c5c4c3c2c1c0"), ) iv = bytes.fromhex("000102030405060708090a0b0c0d0e0f") message = b"Three is a magic number." enc = cek.aead_encrypt(message, nonce=iv, aad=protected_b64) ciphertext, cc_tag = enc.ciphertext, enc.tag assert b64_url(ciphertext) == "Az2IWsISEMDJvyc5XRL-3-d-RgNBOGolCsxFFoUXFYw" assert b64_url(cc_tag) == "HLb4fTlm8spGmij3RyOs2gJ4DpHM4hhVRwdF_hGb3WQ" derived = Ecdh1PU(alg, apu, apv)._derive_key( KeyAlg.A128KW, ephem, sender_key=alice, receiver_key=bob, cc_tag=cc_tag, receive=False, ) assert derived.get_secret_bytes() == bytes.fromhex( "df4c37a0668306a11e3d6b0074b5d8df") encrypted_key = derived.wrap_key(cek).ciphertext_tag assert b64_url(encrypted_key) == ( "pOMVA9_PtoRe7xXW1139NzzN1UhiFoio8lGto9cf0t8PyU-" "sjNXH8-LIRLycq8CHJQbDwvQeU1cSl55cQ0hGezJu2N9IY0QN") # test sender_wrap_key encrypted_key_2 = Ecdh1PU(alg, apu, apv).sender_wrap_key( KeyAlg.A128KW, ephem, alice, bob, cek, cc_tag=cc_tag, ) assert encrypted_key_2.ciphertext_tag == encrypted_key # Skipping key derivation for Charlie. # Assemble encrypted_key, iv, cc_tag, ciphertext, and headers into a JWE envelope here. # Receiver disassembles envelope and.. derived_recv = Ecdh1PU(alg, apu, apv)._derive_key( KeyAlg.A128KW, ephem, sender_key=alice, receiver_key=bob, cc_tag=cc_tag, receive=True, ) cek_recv = derived_recv.unwrap_key(KeyAlg.A256CBC_HS512, encrypted_key) assert cek_recv.get_jwk_secret() == cek.get_jwk_secret() message_recv = cek_recv.aead_decrypt(ciphertext, nonce=iv, aad=protected_b64, tag=cc_tag) assert message_recv == message # test receiver_wrap_key cek_recv_2 = Ecdh1PU(alg, apu, apv).receiver_unwrap_key( KeyAlg.A128KW, KeyAlg.A256CBC_HS512, ephem, sender_key=alice, receiver_key=bob, ciphertext=encrypted_key, cc_tag=cc_tag, ) assert cek_recv_2.get_jwk_secret() == cek.get_jwk_secret()