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()
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()
def test_x448_with_the_official_test_vectors(self) -> None: sk_alice_ = X448PrivateKey.from_private_bytes(TestX448.sk_alice) sk_bob_ = X448PrivateKey.from_private_bytes(TestX448.sk_bob) self.assertEqual(X448.derive_public_key(sk_alice_), TestX448.pk_alice) self.assertEqual(X448.derive_public_key(sk_bob_), TestX448.pk_bob) shared_secret1 = X448.shared_key(sk_alice_, TestX448.pk_bob) shared_secret2 = X448.shared_key(sk_bob_, TestX448.pk_alice) self.assertEqual(shared_secret1, blake2b(TestX448.shared_secret)) self.assertEqual(shared_secret2, blake2b(TestX448.shared_secret))
def pack_x448(self) -> bytes: private_key: X448PrivateKey = X448PrivateKey.generate() public_key: X448PublicKey = private_key.public_key() self.private = private_key self.public = public_key.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) return self.public
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 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
def generate_key( algorithm: CoseAlgorithms, key_ops: KeyOps, curve_type: CoseEllipticCurves = CoseEllipticCurves.X25519 ) -> 'OKP': """ Generate a random OKP COSE key object. :param algorithm: Specify the CoseAlgorithm to use. :param key_ops: Specify the key operation (KeyOps). :param curve_type: Specify curve, must be X25519 or X448. """ if curve_type == CoseEllipticCurves.X25519: private_key = X25519PrivateKey.generate() elif curve_type == CoseEllipticCurves.X448: private_key = X448PrivateKey.generate() else: raise CoseIllegalCurve( f"curve must be of type {CoseEllipticCurves.X25519} or {CoseEllipticCurves.X448}" ) encoding = Encoding(serialization.Encoding.Raw) private_format = PrivateFormat(serialization.PrivateFormat.Raw) public_format = PublicFormat(serialization.PublicFormat.Raw) encryption = serialization.NoEncryption() return OKP(alg=CoseAlgorithms(algorithm), key_ops=KeyOps(key_ops), crv=CoseEllipticCurves(curve_type), x=private_key.public_key().public_bytes( encoding, public_format), d=private_key.private_bytes(encoding, private_format, encryption))
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)
def deserialize(cls, serialized_dh): if not isinstance(serialized_dh, dict): raise TypeError("serialized_dh must be of type: dict") private_key = X448PrivateKey.from_private_bytes( bytes.fromhex(serialized_dh["private_key"])) return cls(private_key)
def test_round_trip_private_serialization( self, encoding, fmt, encryption, passwd, load_func, backend ): key = X448PrivateKey.generate() serialized = key.private_bytes(encoding, fmt, encryption) loaded_key = load_func(serialized, passwd, backend) assert isinstance(loaded_key, X448PrivateKey)
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
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
def __init__(self, dh_pair=None): if dh_pair: if not isinstance(dh_pair, X448PrivateKey): raise TypeError("dh_pair must be of type: X448PrivateKey") self._private_key = dh_pair else: self._private_key = X448PrivateKey.generate() self._public_key = self._private_key.public_key()
def test_buffer_protocol(self, backend): private_bytes = binascii.unhexlify( b"9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28d" b"d9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b") key = X448PrivateKey.from_private_bytes(bytearray(private_bytes)) assert key.private_bytes(serialization.Encoding.Raw, serialization.PrivateFormat.Raw, serialization.NoEncryption()) == private_bytes
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
def test_incorrect_public_key_length_raises_critical_error(self): sk = X448PrivateKey.generate() for key in [ key_len * b'a' for key_len in range(1, 100) if key_len != TFC_PUBLIC_KEY_LENGTH ]: with self.assertRaises(SystemExit): X448.shared_key(sk, key)
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
def test_buffer_protocol(self, backend): private_bytes = binascii.unhexlify( b"9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28d" b"d9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b" ) key = X448PrivateKey.from_private_bytes(bytearray(private_bytes)) assert key.private_bytes( serialization.Encoding.Raw, serialization.PrivateFormat.Raw, serialization.NoEncryption() ) == private_bytes
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
def test_invalid_public_bytes(self, backend): key = X448PrivateKey.generate().public_key() with pytest.raises(ValueError): key.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.SubjectPublicKeyInfo) with pytest.raises(ValueError): key.public_bytes(serialization.Encoding.PEM, serialization.PublicFormat.PKCS1) with pytest.raises(ValueError): key.public_bytes(serialization.Encoding.PEM, serialization.PublicFormat.Raw)
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
def test_invalid_private_bytes(self, backend): key = X448PrivateKey.generate() with pytest.raises(ValueError): key.private_bytes(serialization.Encoding.Raw, serialization.PrivateFormat.Raw, None) with pytest.raises(ValueError): key.private_bytes(serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8, None) with pytest.raises(ValueError): key.private_bytes(serialization.Encoding.PEM, serialization.PrivateFormat.Raw, serialization.NoEncryption())
def derive_key( self, context: Union[List[Any], Dict[str, Any]], material: bytes = b"", public_key: Optional[COSEKeyInterface] = None, ) -> COSEKeyInterface: if self._public_key: raise ValueError("Public key cannot be used for key derivation.") if not public_key: raise ValueError("public_key should be set.") if not isinstance(public_key.key, X25519PublicKey) and not isinstance( public_key.key, X448PublicKey): raise ValueError("public_key should be x25519/x448 public key.") # if self._alg not in COSE_ALGORITHMS_CKDM_KEY_AGREEMENT.values(): # raise ValueError(f"Invalid alg for key derivation: {self._alg}.") # Validate context information. if isinstance(context, dict): context = to_cis(context, self._alg) else: self._validate_context(context) # Derive key. if self._private_key: self._key = self._private_key else: self._key = X25519PrivateKey.generate( ) if self._crv == 4 else X448PrivateKey.generate() shared_key = self._key.exchange(public_key.key) hkdf = HKDF( algorithm=self._hash_alg(), length=COSE_KEY_LEN[context[0]] // 8, salt=None, info=self._dumps(context), ) cose_key = { 1: 4, 3: context[0], -1: hkdf.derive(shared_key), } if cose_key[3] in [1, 2, 3]: return AESGCMKey(cose_key) if cose_key[3] in [4, 5, 6, 7]: return HMACKey(cose_key) if cose_key[3] in [10, 11, 12, 13, 30, 31, 32, 33]: return AESCCMKey(cose_key) # cose_key[3] == 24: return ChaCha20Key(cose_key)
def generate_x_curve_keys(x_curve_type): if x_curve_type == "x25519": private_key = X25519PrivateKey.generate() elif x_curve_type == "x448": private_key = X448PrivateKey.generate() else: raise Exception("Unknown x curve type {}".format(x_curve_type)) public_key = private_key.public_key() private_key_bytes = private_key.private_bytes( encoding=Encoding.Raw, format=PrivateFormat.Raw, encryption_algorithm=NoEncryption(), ) public_key_bytes = public_key.public_bytes(encoding=Encoding.Raw, format=PublicFormat.Raw) return private_key_bytes, public_key_bytes, public_key_bytes
def generate_private_key() -> 'X448PrivateKey': """Generate the X448 private key. The pyca/cryptography's key generation process is as follows: 1. When `X448PrivateKey.generate()` is called by this method, the `generate()` class method imports the OpenSSL backend[1]. 2. Importing the backend causes Python to call the Backend class[2] that runs the `__init__()` method of the class[3], which then calls the `activate_osrandom_engine()` instance method[4]. 3. Calling the `activate_osrandom_engine()` disables the default OpenSSL CSPRNG, and activates the pyca/cryptography "OS random engine".[5] 4. Unlike OpenSSL user-space CSPRNG that only seeds from /dev/urandom, the OS random engine uses GETRANDOM(0) syscall that sources all of its entropy directly from the ChaCha20 DRNG. The OS random engine does not suffer from the fork() weakness where forked process is not automatically reseeded, and it's also safe from issues with OpenSSL CSPRNG initialization.[6] 5. The fallback option (/dev/urandom) of OS random engine might be problematic on pre-3.17 kernels if the CSPRNG has not been properly seeded. However, TFC checks that the kernel version of the OS it's running on is at least 4.17. This means that the used source of entropy is always GETRANDOM(0).[7] This can be verified from the source code as well: The last parameter `0` of the GETRANDOM syscall[8] indicates GRND_NONBLOCK flag is not set. This means the ChaCha20 DRNG is used, and that it does not yield entropy until it has been fully seeded. This is the same case as with TFC's `csprng()` function. [1] https://github.com/pyca/cryptography/blob/2.7/src/cryptography/hazmat/primitives/asymmetric/x448.py#L38 [2] https://github.com/pyca/cryptography/blob/2.7/src/cryptography/hazmat/backends/openssl/backend.py#L2445 [3] https://github.com/pyca/cryptography/blob/2.7/src/cryptography/hazmat/backends/openssl/backend.py#L115 [4] https://github.com/pyca/cryptography/blob/2.7/src/cryptography/hazmat/backends/openssl/backend.py#L122 [5] https://cryptography.io/en/latest/hazmat/backends/openssl/#activate_osrandom_engine [6] https://cryptography.io/en/latest/hazmat/backends/openssl/#os-random-engine [7] https://cryptography.io/en/latest/hazmat/backends/openssl/#os-random-sources [8] https://github.com/pyca/cryptography/blob/master/src/_cffi_src/openssl/src/osrandom_engine.c#L391 """ return X448PrivateKey.generate()
def test_invalid_public_bytes(self, backend): key = X448PrivateKey.generate().public_key() with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.Raw, serialization.PublicFormat.SubjectPublicKeyInfo ) with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.PKCS1 ) with pytest.raises(ValueError): key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.Raw )
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
def test_invalid_private_bytes(self, backend): key = X448PrivateKey.generate() with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.Raw, serialization.PrivateFormat.Raw, None ) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8, None ) with pytest.raises(ValueError): key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.Raw, serialization.NoEncryption() )
def generate_key(crv: Union[Type['CoseCurve'], str, int], optional_params: dict = None) -> 'OKPKey': """ Generate a random OKPKey COSE key object. :param crv: Specify an elliptic curve. :param optional_params: Optional key attributes for the :class:`~cose.keys.okp.OKPKey` object, e.g., \ :class:`~cose.keys.keyparam.KpAlg` or :class:`~cose.keys.keyparam.KpKid`. :returns: A COSE `OKPKey` key. """ if type(crv) == str or type(crv) == int: crv = CoseCurve.from_id(crv) if crv == X25519: private_key = X25519PrivateKey.generate() elif crv == Ed25519: private_key = Ed25519PrivateKey.generate() elif crv == Ed448: private_key = Ed448PrivateKey.generate() elif crv == X448: private_key = X448PrivateKey.generate() else: raise CoseIllegalCurve( f"Curve must be of type {X25519}, {X448}, {Ed25519}, or {Ed448}" ) encoding = Encoding(serialization.Encoding.Raw) private_format = PrivateFormat(serialization.PrivateFormat.Raw) public_format = PublicFormat(serialization.PublicFormat.Raw) encryption = serialization.NoEncryption() return OKPKey(crv=crv, x=private_key.public_key().public_bytes( encoding, public_format), d=private_key.private_bytes(encoding, private_format, encryption), optional_params=optional_params)
def main() -> None: """Load persistent settings and launch the Relay Program. This function loads settings from the settings database and launches processes for the Relay Program. It then monitors the EXIT_QUEUE for EXIT/WIPE signals and each process in case one of them dies. If you're reading this code to get the big picture on how TFC works, start by looking at `tfc.py` for Transmitter Program functionality. After you have reviewed the Transmitter Program's code, revisit the code of this program. The Relay Program operates multiple processes to enable real time IO between multiple data sources and destinations. Symbols: process_name denotes the name of the process ─>, <─, ↑, ↓ denotes the direction of data passed from one process to another (Description) denotes the description of data passed from one process to another ┈, ┊ denotes the link between a description and path of data matching the description ▶|, |◀ denotes the gateways where the direction of data flow is enforced with hardware data diodes Relay Program (Networked Computer) ┏━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━┓ ┃ ┃ (Contact management commands) ┃ ┌─────────────────────────────┬─────────────────────┐ ┃ | | ↓ ┃ | ┌─────> relay_command ┌───> c_req_manager ┃ | │ │ | ┃ | │ (Onion Service┈│ |┈(Contact requests) ┃ | │ private key) │ | ┃ | │ ↓ | ┃ | │ onion_service ───────────────────────────> client on contact's ┃ | (Relay Program┈│ ↑ ┊ ┃ Networked Computer | commands) │ │┈(Outgoing msg/file/public key) ┃ | │ │ ┃ Source ───▶|─────(── gateway_loop ─> src_incoming ─> flask_server <─┐ Computer ┃ | | | ┃ | | | ┃ | (Local keys, commands, | | ┃ | and copies of messages)┄| | ┃ | ┊ ↓ | ┃ Destination <──|◀─────(────────────────────── dst_outgoing | Computer ┃ | ┊ ↑ | ┃ ├──> g_msg_manager ┊ │ | ┃ | ↑ ┊ │ | ┃ | (Group┈│ (Incoming┈│ (URL token)┈| ┃ | management │ messages) │ | ┃ │ messages) │ │ | ┃ ↓ │ │ | ┃ client_scheduler │ │ | ┃ └──> client ──────────┴─────────────────────┘ ┃ ↑ ┃ │ ┃ └─────────────────────────────────────────────────────────── flask_server on ┃ ┊ ┃ contact's Networked (Incoming message/file/public key/group management message) Computer ┃ ┃ ┗━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━ ━━┛ The diagram above gives a rough overview of the structure of the Relay Program. The Relay Program acts as a protocol converter that reads datagrams from the Source Computer. Outgoing message/file/public key datagrams are made available in the user's Tor v3 Onion Service. Copies of sent message datagrams as well as datagrams from contacts' Onion Services are forwarded to the Destination Computer. The Relay-to-Relay encrypted datagrams from contacts such as contact requests, public keys and group management messages are displayed by the Relay Program. Outgoing message datagrams are loaded by contacts from the user's Flask web server. To request messages intended for them, each contact uses a contact-specific URL token to load the messages. The URL token is the X448 shared secret derived from the per-session ephemeral X448 values of the two conversing parties. The private value stays on the Relay Program -- the public value is obtained by connecting to the root domain of contact's Onion Service. """ working_dir = f'{os.getenv("HOME")}/{DIR_TFC}' ensure_dir(working_dir) os.chdir(working_dir) _, local_test, data_diode_sockets = process_arguments() gateway = Gateway(NC, local_test, data_diode_sockets) print_title(NC) url_token_private_key = X448PrivateKey.generate() url_token_public_key = url_token_private_key.public_key().public_bytes( encoding=Encoding.Raw, format=PublicFormat.Raw).hex() # type: str queues = \ {GATEWAY_QUEUE: Queue(), # All datagrams from `gateway_loop` to `src_incoming` DST_MESSAGE_QUEUE: Queue(), # Message datagrams from `src_incoming`/`client` to `dst_outgoing` M_TO_FLASK_QUEUE: Queue(), # Message/pubkey datagrams from `src_incoming` to `flask_server` F_TO_FLASK_QUEUE: Queue(), # File datagrams from `src_incoming` to `flask_server` SRC_TO_RELAY_QUEUE: Queue(), # Command datagrams from `src_incoming` to `relay_command` DST_COMMAND_QUEUE: Queue(), # Command datagrams from `src_incoming` to `dst_outgoing` CONTACT_MGMT_QUEUE: Queue(), # Contact management commands from `relay_command` to `client_scheduler` C_REQ_STATE_QUEUE: Queue(), # Contact req. notify setting from `relay_command` to `c_req_manager` URL_TOKEN_QUEUE: Queue(), # URL tokens from `client` to `flask_server` GROUP_MSG_QUEUE: Queue(), # Group management messages from `client` to `g_msg_manager` CONTACT_REQ_QUEUE: Queue(), # Contact requests from `flask_server` to `c_req_manager` C_REQ_MGMT_QUEUE: Queue(), # Contact list management from `relay_command` to `c_req_manager` GROUP_MGMT_QUEUE: Queue(), # Contact list management from `relay_command` to `g_msg_manager` ONION_CLOSE_QUEUE: Queue(), # Onion Service close command from `relay_command` to `onion_service` ONION_KEY_QUEUE: Queue(), # Onion Service private key from `relay_command` to `onion_service` TOR_DATA_QUEUE: Queue(), # Open port for Tor from `onion_service` to `client_scheduler` EXIT_QUEUE: Queue() # EXIT/WIPE signal from `relay_command` to `main` } # type: Dict[bytes, Queue[Any]] process_list = [ Process(target=gateway_loop, args=(queues, gateway)), Process(target=src_incoming, args=(queues, gateway)), Process(target=dst_outgoing, args=(queues, gateway)), Process(target=client_scheduler, args=(queues, gateway, url_token_private_key)), Process(target=g_msg_manager, args=(queues, )), Process(target=c_req_manager, args=(queues, )), Process(target=flask_server, args=(queues, url_token_public_key)), Process(target=onion_service, args=(queues, )), Process(target=relay_command, args=(queues, gateway, sys.stdin.fileno())) ] for p in process_list: p.start() monitor_processes(process_list, NC, queues)
def test_invalid_length_from_private_bytes(self, backend): with pytest.raises(ValueError): X448PrivateKey.from_private_bytes(b"a" * 55) with pytest.raises(ValueError): X448PrivateKey.from_private_bytes(b"a" * 57)
def test_generate(self, backend): key = X448PrivateKey.generate() assert key assert key.public_key()
def test_generate(self, backend): key = X448PrivateKey.generate() assert key assert key.public_key()
def test_invalid_type_exchange(self, backend): key = X448PrivateKey.generate() with pytest.raises(TypeError): key.exchange(object()) # type: ignore[arg-type]
def data(): mensaje= b"89392679556575597635196565386081702718512260749406173579320076828061414488077821523722981888265393212848183094" #X25519 bench start_time= time() private_key = X25519PrivateKey.generate() peer_public_key = X25519PrivateKey.generate().public_key() k255=time()-start_time #print("Generación llave X25519 en: " + str(k255) + " sec con tamaño: "+ str(sys.getsizeof(private_key))+" bytes") private_key=0 #25519 sign start_time= time() private_key = Ed25519PrivateKey.generate() signature = private_key.sign(mensaje) s255=time()-start_time #print("Generación firma X25519 en: " + str(s255) + " sec") start_time= time() public_key = private_key.public_key() public_key.verify(signature, mensaje) ver255=time()-start_time #print("Verificación firma X25519 en: " + str(ver255) + " sec") private_key=0 #448 Key Exchange start_time=time() private_key = X448PrivateKey.generate() peer_public_key = X448PrivateKey.generate().public_key() k448=time()-start_time #print("Generación 448 llave en: " + str(k448) + " sec con tamaño: "+ str(sys.getsizeof(private_key))+" bytes") private_key=0 #448 sign private_key = Ed448PrivateKey.generate() start_time= time() signature = private_key.sign(mensaje) s448=time()-start_time #print("Generación 448 firma en: " + str(s448) + " sec") start_time=time() public_key = private_key.public_key() public_key.verify(signature, mensaje) ver448=time()-start_time #print("Verificación 448 firma en: " + str(ver448) + " sec") private_key=0 #RSA GENERATION start_time=time() private_key = rsa.generate_private_key( public_exponent=65537, key_size=2048, ) krsa=time()-start_time #print("Generación RSA llave en: " + str(krsa) + " sec con tamaño: "+ str(sys.getsizeof(private_key))+" bytes") #RSA SINGING start_time=time() signature = private_key.sign( mensaje, padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH ), hashes.SHA256() ) srsa=time()-start_time #print("Generación RSA firma en: " + str(srsa) + " sec") private_key=0 #DSA SINGING start_time=time() private_key = dsa.generate_private_key( key_size=1024, ) kdsa=time()-start_time #print("Generación DSA llave en: " + str(kdsa) + " sec con tamaño: "+ str(sys.getsizeof(private_key))+" bytes") start_time =time() signature = private_key.sign( mensaje, hashes.SHA256() ) sdsa=time()-start_time #print("Generación DSA firma en: " + str(sdsa) + " sec") return [k255,s255,ver255,k448,s448,ver448,krsa,srsa,kdsa,sdsa]
def test_invalid_type_exchange(self, backend): key = X448PrivateKey.generate() with pytest.raises(TypeError): key.exchange(object())
def test_invalid_length_from_private_bytes(self, backend): with pytest.raises(ValueError): X448PrivateKey.from_private_bytes(b"a" * 55) with pytest.raises(ValueError): X448PrivateKey.from_private_bytes(b"a" * 57)
def generate_dh(cls): return cls(X448PrivateKey.generate())
def test_round_trip_private_serialization(self, encoding, fmt, encryption, passwd, load_func, backend): key = X448PrivateKey.generate() serialized = key.private_bytes(encoding, fmt, encryption) loaded_key = load_func(serialized, passwd, backend) assert isinstance(loaded_key, X448PrivateKey)
def generate_private_key() -> 'X448PrivateKey': """Generate the X448 private key. The size of the private key is 56 bytes (448 bits). """ return X448PrivateKey.generate()
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