Esempio n. 1
0
def test_x448(backend, wycheproof):
    assert set(wycheproof.testgroup.items()) == {
        ("curve", "curve448"),
        ("type", "XdhComp"),
    }

    private_key = X448PrivateKey.from_private_bytes(
        binascii.unhexlify(wycheproof.testcase["private"]))
    public_key_bytes = binascii.unhexlify(wycheproof.testcase["public"])
    if len(public_key_bytes) == 57:
        assert wycheproof.acceptable
        assert wycheproof.has_flag("NonCanonicalPublic")
        with pytest.raises(ValueError):
            X448PublicKey.from_public_bytes(public_key_bytes)
        return

    public_key = X448PublicKey.from_public_bytes(public_key_bytes)

    assert wycheproof.valid or wycheproof.acceptable

    expected = binascii.unhexlify(wycheproof.testcase["shared"])
    if expected == b"\x00" * 56:
        assert wycheproof.acceptable
        # OpenSSL returns an error on all zeros shared key
        with pytest.raises(ValueError):
            private_key.exchange(public_key)
    else:
        assert private_key.exchange(public_key) == expected
Esempio n. 2
0
def test_x448_unsupported(backend):
    with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM):
        X448PublicKey.from_public_bytes(b"0" * 56)

    with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM):
        X448PrivateKey.from_private_bytes(b"0" * 56)

    with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM):
        X448PrivateKey.generate()
Esempio n. 3
0
def test_x448_unsupported(backend):
    with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM):
        X448PublicKey.from_public_bytes(b"0" * 56)

    with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM):
        X448PrivateKey.from_private_bytes(b"0" * 56)

    with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM):
        X448PrivateKey.generate()
Esempio n. 4
0
def update_url_token(url_token_private_key: 'X448PrivateKey',
                     ut_pubkey_hex: str, cached_pk: str, onion_pub_key: bytes,
                     queues: 'QueueDict') -> Tuple[str, str]:
    """Update URL token for contact.

    When contact's URL token public key changes, update URL token.
    """
    if ut_pubkey_hex == cached_pk:
        raise SoftError("URL token public key has not changed.", output=False)

    try:
        public_key = bytes.fromhex(ut_pubkey_hex)

        if len(public_key) != TFC_PUBLIC_KEY_LENGTH or public_key == bytes(
                TFC_PUBLIC_KEY_LENGTH):
            raise ValueError

        shared_secret = url_token_private_key.exchange(
            X448PublicKey.from_public_bytes(public_key))
        url_token = hashlib.blake2b(shared_secret,
                                    digest_size=URL_TOKEN_LENGTH).hexdigest()

        queues[URL_TOKEN_QUEUE].put(
            (onion_pub_key,
             url_token))  # Update Flask server's URL token for contact

        return url_token, ut_pubkey_hex

    except (TypeError, ValueError):
        raise SoftError("URL token derivation failed.", output=False)
Esempio n. 5
0
    def from_bytes(cls, pk_bytes):
        if not isinstance(pk_bytes, bytes):
            raise TypeError("pk_bytes must be of type: bytes")
        if not len(pk_bytes) == DHPublicKey.KEY_LEN:
            raise ValueError("pk_bytes must be 56 bytes")

        return cls(X448PublicKey.from_public_bytes(pk_bytes))
Esempio n. 6
0
    def deserialize(cls, serialized_pk):
        if not isinstance(serialized_pk, dict):
            raise TypeError("serialized_pk must be of type: dict")

        public_key = X448PublicKey.from_public_bytes(
            bytes.fromhex(serialized_pk["public_key"]))
        return cls(public_key)
Esempio n. 7
0
    def shared_secret(private_key: 'CK', public_key: 'CK') -> bytes:
        """ Compute the shared secret. """

        if public_key.crv == X25519:
            d = X25519PrivateKey.from_private_bytes(private_key.d)
            x = X25519PublicKey.from_public_bytes(public_key.x)
            secret = d.exchange(x)
        elif public_key.crv == X448:
            d = X448PrivateKey.from_private_bytes(private_key.d)

            x = X448PublicKey.from_public_bytes(public_key.x)
            secret = d.exchange(x)
        elif public_key.crv == P256:
            d = ec.derive_private_key(int(hexlify(private_key.d), 16),
                                      SECP256R1(), default_backend())

            x = ec.EllipticCurvePublicNumbers(int(hexlify(public_key.x), 16),
                                              int(hexlify(public_key.y), 16),
                                              SECP256R1())
            x = x.public_key()
            secret = d.exchange(ec.ECDH(), x)
        else:
            raise CoseIllegalCurve(f"{public_key.crv} is unsupported")

        return secret
 def get_shared_secret(client_private_key, server_public_key,
                       cryptographic_group):
     # x25519 (x00 x1d)
     if cryptographic_group == b"\x00\x1d":
         private_key = X25519PrivateKey.from_private_bytes(
             client_private_key)
         public_key = X25519PublicKey.from_public_bytes(server_public_key)
         return private_key.exchange(public_key)
     # x448 (x00 x1e)
     elif cryptographic_group == b"\x00\x1e":
         private_key = X448PrivateKey.from_private_bytes(client_private_key)
         public_key = X448PublicKey.from_public_bytes(server_public_key)
         return private_key.exchange(public_key)
     # secp256r1 (x00 x17)
     elif cryptographic_group == b"\x00\x17":
         return Crypto_Helper.get_shared_secret_secpr1(
             client_private_key, server_public_key, SECP256R1)
     # secp384r1 (x00 x18)
     elif cryptographic_group == b"\x00\x18":
         return Crypto_Helper.get_shared_secret_secpr1(
             client_private_key, server_public_key, SECP384R1)
     # secp521r1 (x00 x19)
     elif cryptographic_group == b"\x00\x19":
         return Crypto_Helper.get_shared_secret_secpr1(
             client_private_key, server_public_key, SECP521R1)
Esempio n. 9
0
 def test_rfc7748(self, vector, backend):
     private = binascii.unhexlify(vector["input_scalar"])
     public = binascii.unhexlify(vector["input_u"])
     shared_key = binascii.unhexlify(vector["output_u"])
     private_key = X448PrivateKey.from_private_bytes(private)
     public_key = X448PublicKey.from_public_bytes(public)
     computed_shared_key = private_key.exchange(public_key)
     assert computed_shared_key == shared_key
Esempio n. 10
0
 def test_rfc7748(self, vector, backend):
     private = binascii.unhexlify(vector["input_scalar"])
     public = binascii.unhexlify(vector["input_u"])
     shared_key = binascii.unhexlify(vector["output_u"])
     private_key = X448PrivateKey.from_private_bytes(private)
     public_key = X448PublicKey.from_public_bytes(public)
     computed_shared_key = private_key.exchange(public_key)
     assert computed_shared_key == shared_key
Esempio n. 11
0
    def test_rfc7748_1000_iteration(self, backend):
        old_private = private = public = binascii.unhexlify(
            b"05000000000000000000000000000000000000000000000000000000"
            b"00000000000000000000000000000000000000000000000000000000")
        shared_key = binascii.unhexlify(
            b"aa3b4749d55b9daf1e5b00288826c467274ce3ebbdd5c17b975e09d4"
            b"af6c67cf10d087202db88286e2b79fceea3ec353ef54faa26e219f38")
        private_key = X448PrivateKey.from_private_bytes(private)
        public_key = X448PublicKey.from_public_bytes(public)
        for _ in range(1000):
            computed_shared_key = private_key.exchange(public_key)
            private_key = X448PrivateKey.from_private_bytes(
                computed_shared_key)
            public_key = X448PublicKey.from_public_bytes(old_private)
            old_private = computed_shared_key

        assert computed_shared_key == shared_key
Esempio n. 12
0
    def test_rfc7748_1000_iteration(self, backend):
        old_private = private = public = binascii.unhexlify(
            b"05000000000000000000000000000000000000000000000000000000"
            b"00000000000000000000000000000000000000000000000000000000"
        )
        shared_key = binascii.unhexlify(
            b"aa3b4749d55b9daf1e5b00288826c467274ce3ebbdd5c17b975e09d4"
            b"af6c67cf10d087202db88286e2b79fceea3ec353ef54faa26e219f38"
        )
        private_key = X448PrivateKey.from_private_bytes(private)
        public_key = X448PublicKey.from_public_bytes(public)
        for _ in range(1000):
            computed_shared_key = private_key.exchange(public_key)
            private_key = X448PrivateKey.from_private_bytes(
                computed_shared_key
            )
            public_key = X448PublicKey.from_public_bytes(old_private)
            old_private = computed_shared_key

        assert computed_shared_key == shared_key
Esempio n. 13
0
 def test_pub_priv_bytes_raw(self, private_bytes, public_bytes, backend):
     private_key = X448PrivateKey.from_private_bytes(private_bytes)
     assert private_key.private_bytes(
         serialization.Encoding.Raw, serialization.PrivateFormat.Raw,
         serialization.NoEncryption()) == private_bytes
     assert private_key.public_key().public_bytes(
         serialization.Encoding.Raw,
         serialization.PublicFormat.Raw) == public_bytes
     public_key = X448PublicKey.from_public_bytes(public_bytes)
     assert public_key.public_bytes(
         serialization.Encoding.Raw,
         serialization.PublicFormat.Raw) == public_bytes
Esempio n. 14
0
 def test_pub_priv_bytes_raw(self, private_bytes, public_bytes, backend):
     private_key = X448PrivateKey.from_private_bytes(private_bytes)
     assert private_key.private_bytes(
         serialization.Encoding.Raw,
         serialization.PrivateFormat.Raw,
         serialization.NoEncryption()
     ) == private_bytes
     assert private_key.public_key().public_bytes(
         serialization.Encoding.Raw, serialization.PublicFormat.Raw
     ) == public_bytes
     public_key = X448PublicKey.from_public_bytes(public_bytes)
     assert public_key.public_bytes(
         serialization.Encoding.Raw, serialization.PublicFormat.Raw
     ) == public_bytes
Esempio n. 15
0
File: crypto.py Progetto: todun/tfc
    def shared_key(private_key: 'X448PrivateKey', public_key: bytes) -> bytes:
        """Derive the X448 shared key.

        The pyca/cryptography library validates the length of the public
        key and verifies that the shared secret is not zero.

        The X448 shared secret is not a random byte string, but a random
        point on the curve. Thus, the raw bits of the shared secret
        might not be uniformly distributed in the keyspace, but have
        bias towards 0 or 1.
            To get rid of the bias, the raw shared secret is passed
        through a computational extractor (BLAKE2b CSPRF) to ensure
        a uniformly random shared key.

        While `shared secret` and `shared key` are used synonymously, in
        TFC we choose to distinguish between the raw shared secret and
        the BLAKE2b compressed shared secret by calling only the latter
        the `shared key`.

        Note that the shared key won't be used directly as a session
        key. Instead, it will be used as the key parameter in separate
        BLAKE2b instances where the hash function is used as a KDF to
        extract unidirectional message/header keys and fingerprints.
        """
        try:
            shared_secret = private_key.exchange(
                X448PublicKey.from_public_bytes(public_key))  # type: bytes
        except ValueError as e:
            raise CriticalError(str(e))

        if not isinstance(shared_secret, bytes):  # pragma: no cover
            raise CriticalError(
                f"Derived an invalid type ({type(shared_secret)}) shared secret."
            )

        if len(shared_secret) != X448_SHARED_SECRET_LENGTH:  # pragma: no cover
            raise CriticalError(
                f"Generated an invalid size shared secret ({len(shared_secret)} bytes)."
            )

        return blake2b(shared_secret, digest_size=SYMMETRIC_KEY_LENGTH)
Esempio n. 16
0
File: crypto.py Progetto: gtog/tfc
    def shared_key(private_key: 'X448PrivateKey', public_key: bytes) -> bytes:
        """Derive the X448 shared key.

        Since the shared secret is zero if contact's public key is zero,
        this function checks the public key is a valid non-zero
        bytestring.

        Because the raw bits of the X448 shared secret might not be
        uniformly distributed in the keyspace (i.e. bits might have bias
        towards 0 or 1), the raw shared secret is passed through BLAKE2b
        CSPRF to ensure uniformly random shared key.
        """
        if len(public_key) != TFC_PUBLIC_KEY_LENGTH:
            raise CriticalError("Invalid public key length.")

        if public_key == bytes(TFC_PUBLIC_KEY_LENGTH):
            raise CriticalError("Received zero public key.")

        shared_secret = private_key.exchange(
            X448PublicKey.from_public_bytes(public_key))
        return blake2b(shared_secret, digest_size=SYMMETRIC_KEY_LENGTH)
Esempio n. 17
0
    def shared_secret(private_key: Key, public_key: Key) -> bytes:
        """ Compute the shared secret. """

        if public_key.crv == CoseEllipticCurves.X25519:
            d = X25519PrivateKey.from_private_bytes(private_key.d)
            x = X25519PublicKey.from_public_bytes(public_key.x)
        elif public_key.crv == CoseEllipticCurves.X448:
            d = X448PrivateKey.from_private_bytes(private_key.d)
            x = X448PublicKey.from_public_bytes(public_key.x)
        elif public_key.crv == CoseEllipticCurves.P_256:
            d = ec.derive_private_key(int(hexlify(private_key.d), 16),
                                      config_cose(public_key.crv).curve[1](),
                                      default_backend())

            x = ec.EllipticCurvePublicNumbers(
                int(hexlify(public_key.x), 16), int(hexlify(public_key.y), 16),
                config_cose(public_key.crv).curve[1]())
        else:
            raise CoseIllegalCurve(f"{public_key.crv} is unsupported")

        secret = d.exchange(x)
        return secret
Esempio n. 18
0
    def test_invalid_length_from_public_bytes(self, backend):
        with pytest.raises(ValueError):
            X448PublicKey.from_public_bytes(b"a" * 55)

        with pytest.raises(ValueError):
            X448PublicKey.from_public_bytes(b"a" * 57)
Esempio n. 19
0
 def exchange_x448(self, public: bytes) -> bytes:
     assert len(public) == 56, f"x448 public key must be 56 bytes"
     public_key = X448PublicKey.from_public_bytes(public)
     return self.private.exchange(public_key)
Esempio n. 20
0
File: client.py Progetto: gtog/tfc
def client(onion_pub_key: bytes,
           queues: 'QueueDict',
           url_token_private_key: X448PrivateKey,
           tor_port: str,
           gateway: 'Gateway',
           onion_addr_user: str,
           unittest: bool = False) -> None:
    """Load packets from contact's Onion Service."""
    url_token = ''
    cached_pk = ''
    short_addr = pub_key_to_short_address(onion_pub_key)
    onion_addr = pub_key_to_onion_address(onion_pub_key)
    check_delay = RELAY_CLIENT_MIN_DELAY
    is_online = False

    session = requests.session()
    session.proxies = {
        'http': f'socks5h://127.0.0.1:{tor_port}',
        'https': f'socks5h://127.0.0.1:{tor_port}'
    }

    rp_print(f"Connecting to {short_addr}...", bold=True)

    # When Transmitter Program sends contact under UNENCRYPTED_ADD_EXISTING_CONTACT, this function
    # receives user's own Onion address: That way it knows to request the contact to add them:
    if onion_addr_user:
        while True:
            try:
                reply = session.get(
                    f'http://{onion_addr}.onion/contact_request/{onion_addr_user}',
                    timeout=45).text
                if reply == "OK":
                    break
            except requests.exceptions.RequestException:
                time.sleep(RELAY_CLIENT_MIN_DELAY)

    while True:
        with ignored(EOFError, KeyboardInterrupt):
            time.sleep(check_delay)

            # Obtain URL token
            # ----------------

            # Load URL token public key from contact's Onion Service root domain
            try:
                url_token_public_key_hex = session.get(
                    f'http://{onion_addr}.onion/', timeout=45).text
            except requests.exceptions.RequestException:
                url_token_public_key_hex = ''

            # Manage online status of contact based on availability of URL token's public key
            if url_token_public_key_hex == '':
                if check_delay < RELAY_CLIENT_MAX_DELAY:
                    check_delay *= 2
                if check_delay > CLIENT_OFFLINE_THRESHOLD and is_online:
                    is_online = False
                    rp_print(f"{short_addr} is now offline", bold=True)
                continue
            else:
                check_delay = RELAY_CLIENT_MIN_DELAY
                if not is_online:
                    is_online = True
                    rp_print(f"{short_addr} is now online", bold=True)

            # When contact's URL token public key changes, update URL token
            if url_token_public_key_hex != cached_pk:
                try:
                    public_key = bytes.fromhex(url_token_public_key_hex)

                    if len(public_key
                           ) != TFC_PUBLIC_KEY_LENGTH or public_key == bytes(
                               TFC_PUBLIC_KEY_LENGTH):
                        raise ValueError

                    shared_secret = url_token_private_key.exchange(
                        X448PublicKey.from_public_bytes(public_key))
                    url_token = hashlib.blake2b(
                        shared_secret,
                        digest_size=SYMMETRIC_KEY_LENGTH).hexdigest()
                except (TypeError, ValueError):
                    continue

                cached_pk = url_token_public_key_hex  # Update client's URL token public key
                queues[URL_TOKEN_QUEUE].put(
                    (onion_pub_key,
                     url_token))  # Update Flask server's URL token for contact

            # Load TFC data with URL token
            # ----------------------------

            get_data_loop(onion_addr, url_token, short_addr, onion_pub_key,
                          queues, session, gateway)

            if unittest:
                break
Esempio n. 21
0
 def test_public_bytes(self, private_bytes, public_bytes, backend):
     private_key = X448PrivateKey._from_private_bytes(private_bytes)
     assert private_key.public_key().public_bytes() == public_bytes
     public_key = X448PublicKey.from_public_bytes(public_bytes)
     assert public_key.public_bytes() == public_bytes
Esempio n. 22
0
    def test_invalid_length_from_public_bytes(self, backend):
        with pytest.raises(ValueError):
            X448PublicKey.from_public_bytes(b"a" * 55)

        with pytest.raises(ValueError):
            X448PublicKey.from_public_bytes(b"a" * 57)
Esempio n. 23
0
    def __init__(self, params: Dict[int, Any]):
        super().__init__(params)
        self._public_key: Any = None
        self._private_key: Any = None
        self._hash_alg: Any = None
        self._x = None
        self._d = None

        # Validate kty.
        if params[1] != 1:
            raise ValueError("kty(1) should be OKP(1).")

        # Validate crv.
        if -1 not in params:
            raise ValueError("crv(-1) not found.")
        self._crv = params[-1]
        if not isinstance(self._crv, int):
            raise ValueError("crv(-1) should be int.")
        if self._crv not in [4, 5, 6, 7]:
            raise ValueError(
                f"Unsupported or unknown crv(-1) for OKP: {self._crv}.")
        if self._crv in [4, 5]:
            if not self._alg:
                raise ValueError("X25519/X448 needs alg explicitly.")
            if self._alg in [-25, -27]:
                self._hash_alg = hashes.SHA256
            elif self._alg in [-26, -28]:
                self._hash_alg = hashes.SHA512
            else:
                raise ValueError(
                    f"Unsupported or unknown alg used with X25519/X448: {self._alg}."
                )

        # Validate alg and key_ops.
        if self._key_ops:
            if set(self._key_ops) & set([3, 4, 5, 6, 9, 10]):
                raise ValueError(
                    "Unknown or not permissible key_ops(4) for OKP.")
        else:
            if self._crv in [4, 5]:
                self._key_ops = [7, 8] if -4 in params else []
            else:  # self._crv in [6, 7]
                self._key_ops = [1, 2] if -4 in params else [2]
        if self._alg:
            if self._alg in COSE_ALGORITHMS_SIG_OKP.values():
                if -4 in params:
                    # private key for signing.
                    if not (set(self._key_ops) & set([1, 2])):
                        raise ValueError("Invalid key_ops for signing key.")
                    if set(self._key_ops) & set([7, 8]):
                        raise ValueError(
                            "Signing key should not be used for key derivation."
                        )
                else:
                    # public key for signing.
                    if 2 not in self._key_ops or len(self._key_ops) != 1:
                        raise ValueError("Invalid key_ops for public key.")
            elif self._alg in COSE_ALGORITHMS_CKDM_KEY_AGREEMENT.values():
                if -4 in params:
                    # private key for key derivation.
                    if not (set(self._key_ops) & set([7, 8])):
                        raise ValueError("Invalid key_ops for key derivation.")
                    if set(self._key_ops) & set([1, 2]):
                        raise ValueError(
                            "Private key for ECDHE should not be used for signing."
                        )
                else:
                    # public key for key derivation.
                    if self._key_ops:
                        raise ValueError(
                            "Public key for ECDHE should not have key_ops.")
            else:
                raise ValueError(
                    f"Unsupported or unknown alg(3) for OKP: {self._alg}.")
        else:
            if -4 in params:
                # private key.
                if set(self._key_ops) & set([1, 2]):
                    # private key for signing.
                    if set(self._key_ops) & set([7, 8]):
                        raise ValueError(
                            "OKP private key should not be used for both signing and key derivation."
                        )
                    self._alg = -8  # EdDSA
            else:
                # public key.
                if 2 in self._key_ops:
                    if len(self._key_ops) > 1:
                        raise ValueError("Invalid key_ops for public key.")
                else:
                    raise ValueError("Invalid key_ops for public key.")

        if self._alg in COSE_ALGORITHMS_CKDM_KEY_AGREEMENT_ES.values():
            if -2 not in params:
                return

        # Validate x.
        if -2 not in params:
            raise ValueError("x(-2) not found.")
        if not isinstance(params[-2], bytes):
            raise ValueError("x(-2) should be bytes(bstr).")
        self._x = params[-2]
        try:
            if -4 not in params:
                if self._crv == 4:  # X25519
                    self._public_key = X25519PublicKey.from_public_bytes(
                        self._x)
                elif self._crv == 5:  # X448
                    self._public_key = X448PublicKey.from_public_bytes(self._x)
                elif self._crv == 6:  # Ed25519
                    self._public_key = Ed25519PublicKey.from_public_bytes(
                        self._x)
                else:  # self._crv == 7 (Ed448)
                    self._public_key = Ed448PublicKey.from_public_bytes(
                        self._x)
                self._key = self._public_key
                return
        except ValueError as err:
            raise ValueError("Invalid key parameter.") from err

        if not isinstance(params[-4], bytes):
            raise ValueError("d(-4) should be bytes(bstr).")

        try:
            self._d = params[-4]
            if self._crv == 4:  # X25519
                self._private_key = X25519PrivateKey.from_private_bytes(
                    self._d)
            elif self._crv == 5:  # X448
                self._private_key = X448PrivateKey.from_private_bytes(self._d)
            elif self._crv == 6:  # Ed25519
                self._private_key = Ed25519PrivateKey.from_private_bytes(
                    self._d)
            else:  # self._crv == 7 (Ed448)
                self._private_key = Ed448PrivateKey.from_private_bytes(self._d)
            self._key = self._private_key
        except ValueError as err:
            raise ValueError("Invalid key parameter.") from err
        return