Handshake
The handshake process in Fiber is crucial for establishing secure communication between nodes. It involves several steps to ensure that both parties can trust each other and exchange symmetric keys securely.
Steps in the Handshake Process
-
Public Key Retrieval: The client retrieves the server’s public encryption key. This key is used to encrypt the symmetric key that will be shared with the server.
async def get_public_encryption_key(httpx_client: httpx.AsyncClient, server_address: str, timeout: int = 3) -> rsa.RSAPublicKey:response = await httpx_client.get(url=f"{server_address}/{bcst.PUBLIC_ENCRYPTION_KEY_ENDPOINT}", timeout=timeout)response.raise_for_status()data = encryption.PublicKeyResponse(**response.json())public_key_pem = data.public_key.encode()public_key_encryption_key = rust_openssl.keys.load_pem_public_key(public_key_pem, backend=default_backend())assert isinstance(public_key_encryption_key, rsa.RSAPublicKey), "Expected an RSA public key"return public_key_encryption_key -
Symmetric Key Generation: The client generates a symmetric key and a unique identifier (UUID) for this key. The symmetric key is used for encrypting subsequent communications.
symmetric_key: bytes = os.urandom(32)symmetric_key_uuid: str = os.urandom(32).hex() -
Symmetric Key Encryption: The symmetric key is encrypted using the server’s public key. This ensures that only the server can decrypt the symmetric key.
encrypted_symmetric_key = public_key_encryption_key.encrypt(symmetric_key,padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),algorithm=hashes.SHA256(),label=None,),) -
Symmetric Key Exchange: The client sends the encrypted symmetric key to the server along with the UUID, a timestamp, and a nonce to prevent replay attacks.
payload = SymmetricKeyExchange(encrypted_symmetric_key=base64.b64encode(encrypted_symmetric_key).decode(),symmetric_key_uuid=symmetric_key_uuid,ss58_address=keypair.ss58_address,timestamp=time.time(),nonce="unique_nonce",signature=signatures.sign_message(keypair, "message"),)response = await httpx_client.post(f"{server_address}/{bcst.EXCHANGE_SYMMETRIC_KEY_ENDPOINT}", json=payload.model_dump()) -
Server Verification: The server decrypts the symmetric key using its private key and verifies the client’s identity using the provided signature.
async def exchange_symmetric_key(payload: SymmetricKeyExchange,config: Config = Depends(get_config),_=Depends(blacklist_low_stake),__=Depends(verify_signature),):if config.encryption_keys_handler.nonce_manager.nonce_is_valid(payload.nonce):raise HTTPException(status_code=401,detail="Oi, I've seen that nonce before. Don't send me the nonce more than once",)base64_symmetric_key = get_symmetric_key_b64_from_payload(payload, config.encryption_keys_handler.private_key)fernet = Fernet(base64_symmetric_key)config.encryption_keys_handler.add_symmetric_key(uuid=payload.symmetric_key_uuid,hotkey_ss58_address=payload.ss58_address,fernet=fernet,)return {"status": "Symmetric key exchanged successfully"}