Пример #1
0
    def create_message_two(self, message_one: bytes) -> bytes:
        """
        Decodes an incoming EDHOC message 1 and creates and EDHOC message 2 or error message based on the content
        of message 1.

        :param message_one: Bytes representing an EDHOC message 1.
        :returns: Bytes of an EDHOC message 2 or an EDHOC error message.
        """

        self.msg_1 = MessageOne.decode(message_one)

        self._internal_state = EdhocState.MSG_1_RCVD

        if not self._verify_cipher_selection(self.msg_1.selected_cipher,
                                             self.msg_1.cipher_suites):
            self._internal_state = EdhocState.EDHOC_FAIL

            return MessageError(err_msg="").encode()

        if self.aad1_cb is not None:
            self.aad1_cb(self.msg_1.aad1)

        self._generate_ephemeral_key()

        self.msg_2 = MessageTwo(self.g_y, self.conn_idr, self.ciphertext_2,
                                self.conn_idi)

        self._internal_state = EdhocState.MSG_2_SENT
        return self.msg_2.encode(self.corr)
Пример #2
0
    def create_message_three(self, message_two: bytes):

        self.msg_2 = MessageTwo.decode(message_two)

        self._internal_state = EdhocState.MSG_2_RCVD

        decoded = EdhocMessage.decode(self._decrypt(self.msg_2.ciphertext))

        self.cred_idr = decoded[0]

        if not self._verify_signature_or_mac2(signature_or_mac2=decoded[1]):
            self._internal_state = EdhocState.EDHOC_FAIL
            return MessageError(
                err_msg='Signature verification failed').encode()

        try:
            ad_2 = decoded[2]
            if self.aad2_cb is not None:
                self.aad2_cb(ad_2)
        except IndexError:
            pass

        self.msg_3 = MessageThree(self.ciphertext_3, self.conn_idr)

        self._internal_state = EdhocState.MSG_3_SENT

        return self.msg_3.encode(self.corr)
Пример #3
0
def test_responder_finalize(responder, test_vectors):
    responder.msg_1 = MessageOne.decode(test_vectors['S']['message_1'])
    responder.msg_2 = MessageTwo.decode(
        responder.create_message_two(test_vectors['S']['message_1']))
    responder.msg_3 = MessageThree.decode(test_vectors['S']['message_3'])

    decoded = EdhocMessage.decode(
        responder._decrypt(responder.msg_3.ciphertext))
    if KID.identifier in cbor2.loads(test_vectors['I']['cred_id']):
        assert decoded[0] == EdhocMessage.encode_bstr_id(
            cbor2.loads(test_vectors['I']['cred_id'])[KID.identifier])
    else:
        assert decoded[0] == cbor2.loads(test_vectors['I']['cred_id'])
    assert decoded[1] == test_vectors['S']['signature_3']

    if getattr(responder, 'remote_authkey', None) is None:
        warnings.warn(NoRemoteKey())
        return
    c_i, c_r, app_aead, app_hash = responder.finalize(
        test_vectors['S']['message_3'])

    assert c_i == test_vectors['I']['conn_id']
    assert c_r == test_vectors['R']['conn_id']
    assert app_aead == CipherSuite.from_id(
        test_vectors['I']['selected']).app_aead.identifier
    assert app_hash == CipherSuite.from_id(
        test_vectors['I']['selected']).app_hash.identifier
Пример #4
0
def test_initiator_finalize(initiator, test_vectors):
    initiator.msg_1 = MessageOne.decode(initiator.create_message_one())
    initiator.msg_2 = MessageTwo.decode(test_vectors['R']['message_2'])
    initiator.msg_3 = MessageThree.decode(
        initiator.create_message_three(test_vectors['R']['message_2']))

    c_i, c_r, app_aead, app_hash = initiator.finalize()

    assert c_i == test_vectors['I']['conn_id']
    assert c_r == test_vectors['R']['conn_id']
    assert app_aead == CipherSuite(test_vectors['I']['selected']).app_aead.id
    assert app_hash == CipherSuite(test_vectors['I']['selected']).app_hash.id
Пример #5
0
def test_initiator_finalize(initiator, test_vectors):
    initiator.msg_1 = MessageOne.decode(initiator.create_message_one())
    initiator.msg_2 = MessageTwo.decode(test_vectors['S']['message_2'])
    if getattr(initiator, 'remote_authkey', None) is None:
        warnings.warn(NoRemoteKey())
        return

    initiator.msg_3 = MessageThree.decode(initiator.create_message_three(test_vectors['S']['message_2']))

    c_i, c_r, app_aead, app_hash = initiator.finalize()

    assert c_i == test_vectors['I']['conn_id']
    assert c_r == test_vectors['R']['conn_id']
    assert app_aead == CipherSuite.from_id(test_vectors['I']['selected']).app_aead.identifier
    assert app_hash == CipherSuite.from_id(test_vectors['I']['selected']).app_hash.identifier
Пример #6
0
def test_initiator_message3(initiator, test_vectors):
    initiator.msg_1 = MessageOne.decode(test_vectors['I']['message_1'])
    initiator.msg_2 = MessageTwo.decode(test_vectors['R']['message_2'])

    crv = CoseEllipticCurves(CipherSuite(initiator._selected_cipher).dh_curve)
    hash_func = config_cose(CipherSuite(initiator._selected_cipher).hash).hash

    assert initiator.data_2 == test_vectors['R']['data_2']
    assert initiator._th2_input == test_vectors['R']['input_th_2']
    assert initiator._prk2e == test_vectors['R']['prk_2e']
    assert initiator._prk3e2m == test_vectors['R']['prk_3e2m']
    assert initiator.transcript(
        hash_func, initiator._th2_input) == test_vectors['R']['th_2']

    assert initiator._decrypt(
        initiator.msg_2.ciphertext) == test_vectors['R']['p_2e']

    assert initiator.shared_secret(initiator.ephemeral_key,
                                   OKP(x=initiator.g_y,
                                       crv=crv)) == test_vectors['S']['g_xy']
    assert initiator.data_3 == test_vectors['I']['data_3']
    assert initiator._th3_input == test_vectors['I']['input_th_3']
    assert initiator.transcript(
        hash_func, initiator._th3_input) == test_vectors['I']['th_3']
    assert initiator.cred_id == test_vectors['I']['id_cred']
    assert initiator._prk4x3m == test_vectors['I']['prk_4x3m']
    assert initiator._external_aad(
        initiator._th3_input,
        initiator.aad3_cb) == test_vectors['I']['eaad_3m']
    assert initiator._hkdf3(16, 'K_3m',
                            initiator._prk4x3m) == test_vectors['I']['k_3m']
    assert initiator._hkdf3(13, 'IV_3m',
                            initiator._prk4x3m) == test_vectors['I']['iv_3m']
    assert initiator._mac(initiator._hkdf3, 'K_3m', 16, 'IV_3m', 13,
                          initiator._th3_input, initiator._prk4x3m,
                          initiator.aad2_cb) == test_vectors['I']['mac3']
    assert initiator.signature_or_mac3(
        test_vectors['I']['mac3']) == test_vectors['I']['sign_or_mac3']
    assert initiator._p_3ae == test_vectors['I']['p_3ae']
    assert initiator._hkdf3(16, 'K_3ae',
                            initiator._prk3e2m) == test_vectors['I']['k_3ae']
    assert initiator._hkdf3(13, 'IV_3ae',
                            initiator._prk3e2m) == test_vectors['I']['iv_3ae']
    assert initiator.ciphertext_3 == test_vectors['I']['ciphertext_3']

    assert initiator.create_message_three(
        test_vectors['R']['message_2']) == test_vectors['I']['message_3']
Пример #7
0
def test_initiator_message3(initiator, test_vectors):
    initiator.msg_1 = MessageOne.decode(test_vectors['S']['message_1'])
    initiator.msg_2 = MessageTwo.decode(test_vectors['S']['message_2'])

    crv = CipherSuite.from_id(initiator._selected_cipher).dh_curve
    hash_func = CipherSuite.from_id(initiator._selected_cipher).hash.hash_cls

    assert initiator.data_2 == test_vectors['S']['data_2']
    assert initiator._th2_input == test_vectors['S']['input_th_2']
    assert initiator._prk2e == test_vectors['S']['prk_2e']
    assert initiator._prk3e2m == test_vectors['S']['prk_3e2m']
    assert initiator.transcript(hash_func, initiator._th2_input) == test_vectors['S']['th_2']

    assert initiator._decrypt(initiator.msg_2.ciphertext) == test_vectors['S']['p_2e']

    assert initiator.shared_secret(initiator.ephemeral_key, OKPKey(x=initiator.g_y, crv=crv)) == test_vectors['S'][
        'g_xy']
    assert initiator.data_3 == test_vectors['S']['data_3']
    assert initiator._th3_input == test_vectors['S']['input_th_3']
    assert initiator.transcript(hash_func, initiator._th3_input) == test_vectors['S']['th_3']
    assert initiator.cred_id == cbor2.loads(test_vectors['I']['cred_id'])
    assert initiator._prk4x3m == test_vectors['S']['prk_4x3m']
    assert initiator._hkdf3(16, 'K_3m', initiator._prk4x3m) == test_vectors['S']['k_3m']
    assert initiator._hkdf3(13, 'IV_3m', initiator._prk4x3m) == test_vectors['S']['iv_3m']
    assert initiator._mac(
        initiator.cred_idi,
        initiator.cred,
        initiator._hkdf3,
        'K_3m',
        16,
        'IV_3m',
        13,
        initiator._th3_input,
        initiator._prk4x3m,
        initiator.aad2_cb) == test_vectors['S']['mac_3']
    assert initiator.signature_or_mac3(test_vectors['S']['mac_3']) == test_vectors['S']['signature_3']
    assert initiator._p_3ae == test_vectors['S']['p_3ae']
    assert initiator._hkdf3(16, 'K_3ae', initiator._prk3e2m) == test_vectors['S']['k_3ae']
    assert initiator._hkdf3(13, 'IV_3ae', initiator._prk3e2m) == test_vectors['S']['iv_3ae']
    assert initiator.ciphertext_3 == test_vectors['S']['ciphertext_3']

    if initiator.remote_authkey is None:
        warnings.warn(NoRemoteKey())
        return
    assert initiator.create_message_three(test_vectors['S']['message_2']) == test_vectors['S']['message_3']
Пример #8
0
def test_responder_finalize(responder, test_vectors):
    responder.msg_1 = MessageOne.decode(test_vectors['I']['message_1'])
    responder.msg_2 = MessageTwo.decode(
        responder.create_message_two(test_vectors['I']['message_1']))
    responder.msg_3 = MessageThree.decode(test_vectors['I']['message_3'])

    decoded = EdhocMessage.decode(
        responder._decrypt(responder.msg_3.ciphertext))
    if CoseHeaderKeys.KID in test_vectors['I']['id_cred']:
        assert decoded[0] == EdhocMessage.encode_bstr_id(
            test_vectors['I']['id_cred'][CoseHeaderKeys.KID])
    else:
        assert decoded[0] == test_vectors['I']['id_cred']
    assert decoded[1] == test_vectors['I']['sign_or_mac3']

    c_i, c_r, app_aead, app_hash = responder.finalize(
        test_vectors['I']['message_3'])

    assert c_i == test_vectors['I']['conn_id']
    assert c_r == test_vectors['R']['conn_id']
    assert app_aead == CipherSuite(test_vectors['I']['selected']).app_aead.id
    assert app_hash == CipherSuite(test_vectors['I']['selected']).app_hash.id
Пример #9
0
class Responder(EdhocRole):
    role = 'R'
    remote_role = 'I'

    def __init__(self,
                 cred: Union[RPK, Certificate],
                 cred_idr: CoseHeaderMap,
                 auth_key: RPK,
                 supported_ciphers: List[Type['CS']],
                 remote_cred_cb: Callable[[CoseHeaderMap], Union[Certificate,
                                                                 RPK]],
                 conn_idr: Optional[bytes] = None,
                 aad1_cb: Optional[Callable[..., bytes]] = None,
                 aad2_cb: Optional[Callable[..., bytes]] = None,
                 aad3_cb: Optional[Callable[..., bytes]] = None,
                 ephemeral_key: Optional['CK'] = None):
        """
        Create an EDHOC responder.

        :param cred: The public authentication credentials of the Responder.
        :param cred_idr: The Responder's credential identifier (a CBOR encoded COSE header map)
        :param auth_key: The private authentication key (CoseKey) of the Responder.
        :param supported_ciphers: A list of ciphers supported by the Responder.
        :param conn_idr: The connection identifier of the Responder.
        :param remote_cred_cb: A callback that fetches the remote credentials
        :param aad1_cb: A callback to pass received additional data to the application protocol.
        :param aad2_cb: A callback to pass additional data to the remote endpoint.
        :param aad3_cb: A callback to pass received additional data to the application protocol.
        :param ephemeral_key: Preload an (CoseKey) ephemeral key (if unset a random key will be generated).
        """

        if conn_idr is None:
            conn_idr = os.urandom(1)

        super().__init__(cred, cred_idr, auth_key, supported_ciphers, conn_idr,
                         remote_cred_cb, aad1_cb, aad2_cb, aad3_cb,
                         ephemeral_key)

    @property
    def cipher_suite(self) -> 'CS':
        if self.msg_1 is None:
            raise EdhocException(
                "Message 1 not received. Cannot derive selected cipher suite.")
        else:
            if not self._verify_cipher_selection(self.msg_1.selected_cipher,
                                                 self.msg_1.cipher_suites):
                raise EdhocException("Invalid cipher suite setup")
            return CipherSuite.from_id(self.msg_1.selected_cipher)

    @property
    def corr(self):
        """ Get the EDHOC correlation value for the method_corr parameter in EDHOC message 1. """

        if self.msg_1 is None:
            raise EdhocException(
                "Message 1 not received. Cannot derive selected cipher suite.")

        return self.msg_1.method_corr % 4

    @property
    def method(self):
        """ Get the EDHOC method value for the method_corr parameter in EDHOC message 1. """

        if self.msg_1 is None:
            raise EdhocException(
                "Message 1 not received. Cannot derive selected cipher suite.")

        return (self.msg_1.method_corr - self.corr) // 4

    @property
    def conn_idi(self):
        if self.corr in [Correlation.CORR_1, Correlation.CORR_3]:
            conn_idi = b''
        else:
            conn_idi = self.msg_1.conn_idi

        return conn_idi

    @property
    def conn_idr(self):
        if self.corr in [Correlation.CORR_2, Correlation.CORR_3]:
            conn_idr = b''
        else:
            conn_idr = self._conn_id
        return conn_idr

    @property
    def cred_idi(self) -> CoseHeaderMap:
        return self._cred_idi

    @cred_idi.setter
    def cred_idi(self, value):
        if isinstance(value, int):
            value = {4: EdhocMessage.decode_bstr_id(value)}
        elif isinstance(value, bytes):
            value = {4: value}

        self._cred_idi = value
        self._populate_remote_details(value)

    @property
    def cred_idr(self) -> CoseHeaderMap:
        return self.cred_id

    @property
    def ciphertext_2(self) -> bytes:
        """ Create the ciphertext_2 message part from EDHOC message 2. """

        length = len(self._p_2e)
        xord = int.from_bytes(self._p_2e, "big") ^ int.from_bytes(
            self._hkdf2(length, "KEYSTREAM_2", self._prk2e), "big")
        return xord.to_bytes((xord.bit_length() + 7) // 8, byteorder="big")

    @property
    def g_y(self) -> bytes:

        self._generate_ephemeral_key()

        return self.ephemeral_key.x

    @property
    def g_x(self) -> bytes:
        return self.msg_1.g_x

    @property
    def local_pubkey(self) -> RPK:
        """ Returns the local ephemeral public key. """

        # Is this a good criterion? (Possibly there doesn't need to be a
        # distinction; self.cipher_suite.dh_curve.keyclass(...) could do)
        if self.cipher_suite.dh_curve in [X448, X25519]:
            return OKPKey(x=self.g_y, crv=self.cipher_suite.dh_curve)
        else:
            return EC2Key(x=self.g_y, crv=self.cipher_suite.dh_curve)

    @property
    def remote_pubkey(self) -> RPK:
        """ Returns the remote ephemeral public key. """

        if self.cipher_suite.dh_curve in [X448, X25519]:
            return OKPKey(x=self.g_x, crv=self.cipher_suite.dh_curve)
        else:
            return EC2Key(x=self.g_x, crv=self.cipher_suite.dh_curve)

    @property
    def local_authkey(self) -> RPK:
        return self._local_authkey

    def signature_or_mac2(self, mac_2: bytes):
        return self._signature_or_mac(mac_2, self._th2_input, self.aad2_cb)

    def create_message_two(self, message_one: bytes) -> bytes:
        """
        Decodes an incoming EDHOC message 1 and creates and EDHOC message 2 or error message based on the content
        of message 1.

        :param message_one: Bytes representing an EDHOC message 1.
        :returns: Bytes of an EDHOC message 2 or an EDHOC error message.
        """

        self.msg_1 = MessageOne.decode(message_one)

        self._internal_state = EdhocState.MSG_1_RCVD

        if not self._verify_cipher_selection(self.msg_1.selected_cipher,
                                             self.msg_1.cipher_suites):
            self._internal_state = EdhocState.EDHOC_FAIL

            return MessageError(err_msg="").encode()

        if self.aad1_cb is not None:
            self.aad1_cb(self.msg_1.aad1)

        self._generate_ephemeral_key()

        self.msg_2 = MessageTwo(self.g_y, self.conn_idr, self.ciphertext_2,
                                self.conn_idi)

        self._internal_state = EdhocState.MSG_2_SENT
        return self.msg_2.encode(self.corr)

    def finalize(
            self, message_three: bytes
    ) -> Union[Tuple[bytes, bytes, int, int], bytes]:
        """
        Decodes an incoming EDHOC message 3 and finalizes the key exchange.

        :param message_three: An EDHOC message 3
        :return: An EDHOC error message in case the verification of the EDHOC message 3 fails or a 4-tuple containing
         the initiator and responder's connection identifiers and the application AEAD and hash algorithms.
        """

        self.msg_3 = MessageThree.decode(message_three)

        self._internal_state = EdhocState.MSG_3_RCVD

        decoded = EdhocMessage.decode(self._decrypt(self.msg_3.ciphertext))

        self.cred_idi = decoded[0]

        if not self._verify_signature_or_mac3(signature_or_mac3=decoded[1]):
            return MessageError(err_msg='').encode()

        try:
            ad_3 = decoded[2]
            if self.aad3_cb is not None:
                self.aad3_cb(ad_3)
        except IndexError:
            pass

        app_aead = self.cipher_suite.app_aead
        app_hash = self.cipher_suite.app_hash

        self._internal_state = EdhocState.EDHOC_SUCC

        return self.msg_1.conn_idi, self._conn_id, app_aead.identifier, app_hash.identifier

    def _verify_signature_or_mac3(self, signature_or_mac3: bytes) -> bool:
        mac_3 = self._mac(self.cred_idi, self.remote_cred, self._hkdf3, 'K_3m',
                          16, 'IV_3m', 13, self._th3_input, self._prk4x3m,
                          self.aad3_cb)

        if not self.is_static_dh(self.remote_role):
            external_aad = self._external_aad(self.remote_cred,
                                              self._th3_input, self.aad3_cb)
            cose_sign = Sign1Message(
                phdr=self.cred_idi,
                uhdr={headers.Algorithm: self.cipher_suite.sign_alg},
                payload=mac_3,
                external_aad=external_aad)
            # FIXME peeking into internals (probably best resolved at pycose level)
            cose_sign.key = self.remote_authkey
            cose_sign._signature = signature_or_mac3
            return cose_sign.verify_signature()
        else:
            return signature_or_mac3 == mac_3

    @property
    def _hkdf2(self) -> Callable:
        return functools.partial(super()._hkdf_expand,
                                 transcript=self._th2_input)

    @property
    def _hkdf3(self) -> Callable:
        return functools.partial(super()._hkdf_expand,
                                 transcript=self._th3_input)

    @property
    def _p_2e(self):
        # compute MAC_2
        # TODO: resolve magic key and IV lengths
        mac_2 = self._mac(self.cred_idr, self.cred, self._hkdf2, 'K_2m', 16,
                          'IV_2m', 13, self._th2_input, self._prk3e2m,
                          self.aad2_cb)

        # compute the signature_or_mac2
        signature = self.signature_or_mac2(mac_2)

        if KID.identifier in self.cred_id:
            cred_id = EdhocMessage.encode_bstr_id(self.cred_id[KID.identifier])
        else:
            cred_id = self.cred_id

        return b"".join([
            cbor2.dumps(cred_id, default=EdhocRole._custom_cbor_encoder),
            cbor2.dumps(signature)
        ])

    def _prk3e2m_static_dh(self, prk: bytes):
        return self._prk(self.auth_key, self.remote_pubkey, prk)

    def _prk4x3m_static_dh(self, prk: bytes):
        return self._prk(self.ephemeral_key, self.remote_authkey, prk)

    def _verify_cipher_selection(self, selected: CipherSuite,
                                 supported: List[CipherSuite]) -> bool:
        """
        Checks if the selected cipher suite is supported and that no prior cipher suites in the Initiator's list of
        supported ciphers is supported by the Responder.

        :param selected: the cipher suite selected by the Initiator
        :param supported: the list of cipher suites supported by the Initiator
        :return: True or False
        """

        if selected not in self.supported_ciphers:
            return False

        for sc in supported:
            if sc in self.supported_ciphers and sc != selected:
                return False
            elif sc in self.supported_ciphers and sc == selected:
                return True
            else:
                continue

        return True

    def _decrypt(self, ciphertext: bytes) -> bytes:
        # TODO: resolve magic key and IV lengths
        iv_bytes = self._hkdf3(13, 'IV_3ae', self._prk3e2m)

        # TODO: resolve magic key and IV lengths
        cose_key = self._create_cose_key(self._hkdf3, 16, 'K_3ae',
                                         self._prk3e2m, [DecryptOp])

        th_3 = self.transcript(self.cipher_suite.hash.hash_cls,
                               self._th3_input)

        return Enc0Message(uhdr={
            headers.IV: iv_bytes,
            headers.Algorithm: self.cipher_suite.aead
        },
                           key=cose_key,
                           payload=ciphertext,
                           external_aad=th_3).decrypt()
Пример #10
0
class Responder(EdhocRole):
    def __init__(self,
                 cred: CBOR,
                 cred_idr: CoseHeaderMap,
                 auth_key: Key,
                 supported_ciphers: List[CipherSuite],
                 peer_cred: Optional[Union[Callable[..., bytes], CBOR]],
                 conn_idr: Optional[bytes] = None,
                 aad1_cb: Optional[Callable[..., bytes]] = None,
                 aad2_cb: Optional[Callable[..., bytes]] = None,
                 aad3_cb: Optional[Callable[..., bytes]] = None,
                 ephemeral_key: Optional[Key] = None):
        """
        Create an EDHOC responder.

        :param cred: The public authentication credentials of the Responder.
        :param cred_idr: The Responder's credential identifier (a CBOR encoded COSE header map)
        :param auth_key: The private authentication key (CoseKey) of the Responder.
        :param supported_ciphers: A list of ciphers supported by the Responder.
        :param conn_idr: The connection identifier of the Responder.
        :param peer_cred: Provide the public authentication material for the remote peer, by a callback or directly.
        :param aad1_cb: A callback to pass received additional data to the application protocol.
        :param aad2_cb: A callback to pass additional data to the remote endpoint.
        :param aad3_cb: A callback to pass received additional data to the application protocol.
        :param ephemeral_key: Preload an (CoseKey) ephemeral key (if unset a random key will be generated).
        """

        if conn_idr is None:
            conn_idr = os.urandom(1)

        super().__init__(cred, cred_idr, auth_key, supported_ciphers, conn_idr, peer_cred, aad1_cb, aad2_cb, aad3_cb,
                         ephemeral_key)

        self._cred_idi = None

    @property
    def cipher_suite(self) -> CipherSuite:
        if self.msg_1 is None:
            raise EdhocException("Message 1 not received. Cannot derive selected cipher suite.")
        else:
            if not self._verify_cipher_selection(self.msg_1.selected_cipher, self.msg_1.cipher_suites):
                raise EdhocException("Invalid cipher suite setup")
            return CipherSuite(self.msg_1.selected_cipher)

    @property
    def corr(self):
        """ Get the EDHOC correlation value for the method_corr parameter in EDHOC message 1. """

        if self.msg_1 is None:
            raise EdhocException("Message 1 not received. Cannot derive selected cipher suite.")

        return self.msg_1.method_corr % 4

    @property
    def method(self):
        """ Get the EDHOC method value for the method_corr parameter in EDHOC message 1. """

        if self.msg_1 is None:
            raise EdhocException("Message 1 not received. Cannot derive selected cipher suite.")

        return (self.msg_1.method_corr - self.corr) // 4

    @property
    def conn_idi(self):
        if self.corr in [Correlation.CORR_1, Correlation.CORR_3]:
            conn_idi = b''
        else:
            conn_idi = self.msg_1.conn_idi

        return conn_idi

    @property
    def conn_idr(self):
        if self.corr in [Correlation.CORR_2, Correlation.CORR_3]:
            conn_idr = b''
        else:
            conn_idr = self._conn_id
        return conn_idr

    @property
    def cred_idi(self) -> CoseHeaderMap:
        return self._cred_idi

    @property
    def cred_idr(self) -> CoseHeaderMap:
        return self.cred_id

    @property
    def ciphertext_2(self) -> bytes:
        """ Create the ciphertext_2 message part from EDHOC message 2. """

        length = len(self._p_2e)
        xord = int.from_bytes(self._p_2e, "big") ^ int.from_bytes(self._hkdf2(length, "K_2e", self._prk2e), "big")
        return xord.to_bytes((xord.bit_length() + 7) // 8, byteorder="big")

    @property
    def g_y(self) -> bytes:

        self._generate_ephemeral_key()

        return self.ephemeral_key.x

    @property
    def g_x(self) -> bytes:
        return self.msg_1.g_x

    @property
    def local_pubkey(self) -> Key:
        """ Returns the local ephemeral public key. """

        if self.cipher_suite.dh_curve in [CoseEllipticCurves.X448, CoseEllipticCurves.X25519]:
            return OKP(x=self.g_y, crv=self.cipher_suite.dh_curve)
        else:
            # TODO: implement NIST curves
            pass

    @property
    def remote_pubkey(self) -> Key:
        """ Returns the remote ephemeral public key. """

        if self.cipher_suite.dh_curve in [CoseEllipticCurves.X448, CoseEllipticCurves.X25519]:
            return OKP(x=self.g_x, crv=self.cipher_suite.dh_curve)
        else:
            # TODO: implement NIST curves
            pass

    @property
    def local_authkey(self) -> Key:
        return self._local_authkey

    @property
    def remote_authkey(self) -> Key:
        if hasattr(self._remote_authkey, '__call__'):
            return self._remote_authkey(self.cred_idi)
        else:
            return self._remote_authkey

    @property
    def peer_cred(self):
        if hasattr(self._peer_cred, '__call__'):
            self._peer_cred(self.cred_idi)
        else:
            return self._peer_cred

    def signature_or_mac2(self, mac_2: bytes):
        return self._signature_or_mac(mac_2, self._th2_input, self.aad2_cb)

    def create_message_two(self, message_one: bytes) -> bytes:
        """
        Decodes an incoming EDHOC message 1 and creates and EDHOC message 2 or error message based on the content
        of message 1.

        :param message_one: Bytes representing an EDHOC message 1.
        :returns: Bytes of an EDHOC message 2 or an EDHOC error message.
        """

        self.msg_1 = MessageOne.decode(message_one)

        self._internal_state = EdhocState.MSG_1_RCVD

        if not self._verify_cipher_selection(self.msg_1.selected_cipher, self.msg_1.cipher_suites):
            self._internal_state = EdhocState.EDHOC_FAIL

            return MessageError(err_msg="").encode()

        if self.aad1_cb is not None:
            self.aad1_cb(self.msg_1.aad1)

        self._generate_ephemeral_key()

        self.msg_2 = MessageTwo(self.g_y, self.conn_idr, self.ciphertext_2, self.conn_idi)

        self._internal_state = EdhocState.MSG_2_SENT
        return self.msg_2.encode()

    def finalize(self, message_three: bytes) -> Union[Tuple[bytes, bytes, int, int], bytes]:
        """
        Decodes an incoming EDHOC message 3 and finalizes the key exchange.

        :param message_three: An EDHOC message 3
        :return: An EDHOC error message in case the verification of the EDHOC message 3 fails or a 4-tuple containing
         the initiator and responder's connection identifiers and the application AEAD and hash algorithms.
        """

        self.msg_3 = MessageThree.decode(message_three)

        self._internal_state = EdhocState.MSG_3_RCVD

        decoded = EdhocMessage.decode(self._decrypt(self.msg_3.ciphertext))

        self._cred_idi = decoded[0]

        if not self._verify_signature(signature=decoded[1]):
            return MessageError(err_msg='').encode()

        try:
            ad_3 = decoded[2]
            if self.aad3_cb is not None:
                self.aad3_cb(ad_3)
        except IndexError:
            pass

        app_aead = self.cipher_suite.app_aead
        app_hash = self.cipher_suite.app_hash

        self._internal_state = EdhocState.EDHOC_SUCC

        return self.msg_1.conn_idi, self._conn_id, app_aead.id, app_hash.id

    @property
    def _hkdf2(self) -> Callable:
        return functools.partial(super()._hkdf_expand, transcript=self._th2_input)

    @property
    def _hkdf3(self) -> Callable:
        return functools.partial(super()._hkdf_expand, transcript=self._th3_input)

    @property
    def _p_2e(self):
        # compute MAC_2
        # TODO: resolve magic key and IV lengths
        mac_2 = self._mac(self._hkdf2, 'K_2m', 16, 'IV_2m', 13, self._th2_input, self._prk3e2m, self.aad2_cb)

        # compute the signature_or_mac2
        signature = self.signature_or_mac2(mac_2)

        if CoseHeaderKeys.KID in self.cred_id:
            cred_id = EdhocMessage.encode_bstr_id(self.cred_id[CoseHeaderKeys.KID])
        else:
            cred_id = self.cred_id

        return b"".join([cbor2.dumps(cred_id), cbor2.dumps(signature)])

    def _prk3e2m_static_dh(self, prk: bytes):
        return self._prk(self.auth_key, self.remote_pubkey, prk)

    def _prk4x3m_static_dh(self, prk: bytes):
        return self._prk(self.ephemeral_key, self.remote_authkey, prk)

    def _verify_cipher_selection(self, selected: CipherSuite, supported: List[CipherSuite]) -> bool:
        """
        Checks if the selected cipher suite is supported and that no prior cipher suites in the Initiator's list of
        supported ciphers is supported by the Responder.

        :param selected: the cipher suite selected by the Initiator
        :param supported: the list of cipher suites supported by the Initiator
        :return: True or False
        """

        if selected not in self.supported_ciphers:
            return False

        for sc in supported:
            if sc in self.supported_ciphers and sc != selected:
                return False
            elif sc in self.supported_ciphers and sc == selected:
                return True
            else:
                continue

        return True

    def _decrypt(self, ciphertext: bytes) -> bytes:
        # TODO: resolve magic key and IV lengths
        iv_bytes = self._hkdf3(13, 'IV_3ae', self._prk3e2m)

        hash_func = config_cose(self.cipher_suite.hash).hash
        # TODO: resolve magic key and IV lengths
        cose_key = self._create_cose_key(self._hkdf3, 16, 'K_3ae', self._prk3e2m, KeyOps.DECRYPT)

        th_3 = self.transcript(hash_func, self._th3_input)

        return Enc0Message(payload=ciphertext, external_aad=th_3).decrypt(iv_bytes, cose_key)
Пример #11
0
    def data_2(self) -> CBOR:
        """ Create the data_2 message part from EDHOC message 2. """

        return MessageTwo.data_2(self.g_y, self.conn_idr, self.corr,
                                 self.conn_idi)