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 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 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 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_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 __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_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_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 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 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_invalid_type_exchange(self, backend): key = X448PrivateKey.generate() with pytest.raises(TypeError): key.exchange(object()) # type: ignore[arg-type]
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())
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 generate_dh(cls): return cls(X448PrivateKey.generate())
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 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_zero_public_key_raises_critical_error(self): with self.assertRaises(SystemExit): X448.shared_key(X448PrivateKey.generate(), bytes(TFC_PUBLIC_KEY_LENGTH))