Turnkey does not trust anything running outside of secure enclaves. See our approach for more details. When enclaves and end-users need to exchange private information, we’ve rely on a protocol based on HPKE to establish a secure channel.
This channel is a short-lived, one-way communication channel used in the following case:
Neither the client or the server should reused keys to send or receive more than one message. We want to avoid the recipient target key being used more then once in order to ensure forward secrecy; see security details section for important details and caveats.
This protocol builds on top of the HPKE standard (RFC 9180) by adding recipient pre-flight authentication so the client can verify it is sending ciphertext to a turnkey controlled enclave and the enclave can verify its sending ciphertext to the correct client. See the security profile section more details.
KEM_P256_HKDF_SHA256
KDF_HKDF_SHA256
AEAD_AES256GCM
turnkey_hpke
(raw bytes)EncappedPublicKey||ReceiverPublicKey
clientTargetPub
key to server.ciphertext, serverEncappedPub = ENCRYPT(plaintext, clientTargetPub)
and clears clientTargetPub
from memory.serverEncappedPub_sig_enclaveAuthPriv = SIGN(serverEncappedPub, enclaveAuthPriv)
.(ciphertext, serverEncappedPub, serverEncappedPub_sig_enclaveAuthPriv)
to client.VERIFY(serverEncappedPub, serverEncappedPub_sig_enclaveAuthPriv)
.DECRYPT(ciphertext, serverEncappedPub, clientTargetPriv)
and the client target pair is cleared from memory. If the target pair is used multiple times we increase the count of messages that an attacker with the compromised target private key can decrypt. There is no hard mechanism to prevent a faulty client from resubmitting the same target public key.serverTargetPub_sig_enclaveAuthPriv = SIGN(serverTargetPub, enclaveAuthPriv)
.(serverTargetPub, serverTargetPub_sig_enclaveAuthPriv)
to client.VERIFY(serverTargetPub, serverTargetPub_sig_enclaveAuthPriv)
.ciphertext, clientEncappedPub = ENCRYPT(plaintext, serverTargetPub)
and clears serverTargetPub from memory.(ciphertext, clientEncappedPub)
to server and the client is cleared from memory.DECRYPT(ciphertext, clientEncappedPub, clientTargetPriv)
and server target pair is cleared from memory. If the target pair is used multiple times we increase the count of messages that an attacker with the compromised target private key can decrypt.Turnkey’s import functionality works by anchoring import in a target encryption key (TEK). This target encryption key is a standard P-256 key pair and is generated in the Turnkey secure enclave via a INIT_IMPORT_WALLET
or INIT_IMPORT_PRIVATE_KEY
activity. This TEK is encrypted to the enclave’s quorum key and the TEK public key is returned in the activity response, following the protocol above.
The following diagram summarizes the flow:
The client-side iframe plays the role of the client and encrypts the wallet’s mnemonic or private key to the secure enclave using the protocol described above. The encrypted key material is then passed as a parameter inside of a signed IMPORT_WALLET
or IMPORT_PRIVATE_KEY
activity. During this activity, the Turnkey enclave uses its key pair to decrypt and import the encrypted key material.
Turnkey’s export functionality works by anchoring export in a target encryption key (TEK). This target encryption key is a standard P-256 key pair and can be created in many ways: completely offline, or online inside of script using the web crypto APIs.
The following diagram summarizes the flow:
The client-side iframe plays the role of the server: the public portion of this key pair is passed as a parameter inside of a signed EXPORT_WALLET
, EXPORT_PRIVATE_KEY
, or EXPORT_WALLET_ACCOUNT
activity.
Turnkey’s enclave encrypts the private key material (wallet mnemonic or private key) to the end-user’s TEK using the protocol described in the previous section.
Once the activity succeeds, the encrypted mnemonic or private key can be decrypted by the end-user only.
Unlike typical auth and recovery flows in the industry, Turnkey doesn’t send unencrypted tokens. We use the protocol above to send credentials to end-users with no man-in-the-middle risk. This ensures that even if the content of an auth email is leaked, an attacker cannot decrypt and use the underlying credential. The following diagram summarizes the email auth flow:
Our email auth flow works by anchoring on a target encryption key (TEK). This target encryption key is a standard P-256 key pair and can be created in many ways: completely offline, or online inside of script using the web crypto APIs.
The public part of this key pair is passed as a parameter inside of a signed INIT_OTP_AUTH
or EMAIL_AUTH
activity.
Our enclave creates a fresh P256 key pair (“credential”) and encrypts its private key to the recovering user’s TEK using the protocol above.
Once the encrypted credential is received via email, it’s decrypted where the target public key was originally created. The credential is then ready to be used to sign Turnkey activity requests.
Our OTP flows work similarly, except the bundle is not emailed to the user directly. Instead, it is returned as part of the OTP_AUTH
activity results.
We achieve recipient authentication for both the server and client:
The underlying HPKE spec does not provide forward secrecy on the recipient side since the target key can be long lived. To improve forward secrecy we specify that the target key should only be used once by the sender and receiver. We cannot enforce this strictly on the client-side because a client may choose to reuse their key.
We use OpMod Base
because the sender’s KEM private key is not long lived and thus does not need HPKE authentication. In order for this to be exploited one side’s private key data would have to be leaked or an attacker would need to spoof a message from the sender. Turnkey mitigates this attack by layering a signature from an authentication key over payloads that contain ciphertext + encappedPub. Note that in the case of client to server the authentication signature is verified by the our policy engine. Read more about HPKE asymmetric authentication here.
Turnkey does not trust anything running outside of secure enclaves. See our approach for more details. When enclaves and end-users need to exchange private information, we’ve rely on a protocol based on HPKE to establish a secure channel.
This channel is a short-lived, one-way communication channel used in the following case:
Neither the client or the server should reused keys to send or receive more than one message. We want to avoid the recipient target key being used more then once in order to ensure forward secrecy; see security details section for important details and caveats.
This protocol builds on top of the HPKE standard (RFC 9180) by adding recipient pre-flight authentication so the client can verify it is sending ciphertext to a turnkey controlled enclave and the enclave can verify its sending ciphertext to the correct client. See the security profile section more details.
KEM_P256_HKDF_SHA256
KDF_HKDF_SHA256
AEAD_AES256GCM
turnkey_hpke
(raw bytes)EncappedPublicKey||ReceiverPublicKey
clientTargetPub
key to server.ciphertext, serverEncappedPub = ENCRYPT(plaintext, clientTargetPub)
and clears clientTargetPub
from memory.serverEncappedPub_sig_enclaveAuthPriv = SIGN(serverEncappedPub, enclaveAuthPriv)
.(ciphertext, serverEncappedPub, serverEncappedPub_sig_enclaveAuthPriv)
to client.VERIFY(serverEncappedPub, serverEncappedPub_sig_enclaveAuthPriv)
.DECRYPT(ciphertext, serverEncappedPub, clientTargetPriv)
and the client target pair is cleared from memory. If the target pair is used multiple times we increase the count of messages that an attacker with the compromised target private key can decrypt. There is no hard mechanism to prevent a faulty client from resubmitting the same target public key.serverTargetPub_sig_enclaveAuthPriv = SIGN(serverTargetPub, enclaveAuthPriv)
.(serverTargetPub, serverTargetPub_sig_enclaveAuthPriv)
to client.VERIFY(serverTargetPub, serverTargetPub_sig_enclaveAuthPriv)
.ciphertext, clientEncappedPub = ENCRYPT(plaintext, serverTargetPub)
and clears serverTargetPub from memory.(ciphertext, clientEncappedPub)
to server and the client is cleared from memory.DECRYPT(ciphertext, clientEncappedPub, clientTargetPriv)
and server target pair is cleared from memory. If the target pair is used multiple times we increase the count of messages that an attacker with the compromised target private key can decrypt.Turnkey’s import functionality works by anchoring import in a target encryption key (TEK). This target encryption key is a standard P-256 key pair and is generated in the Turnkey secure enclave via a INIT_IMPORT_WALLET
or INIT_IMPORT_PRIVATE_KEY
activity. This TEK is encrypted to the enclave’s quorum key and the TEK public key is returned in the activity response, following the protocol above.
The following diagram summarizes the flow:
The client-side iframe plays the role of the client and encrypts the wallet’s mnemonic or private key to the secure enclave using the protocol described above. The encrypted key material is then passed as a parameter inside of a signed IMPORT_WALLET
or IMPORT_PRIVATE_KEY
activity. During this activity, the Turnkey enclave uses its key pair to decrypt and import the encrypted key material.
Turnkey’s export functionality works by anchoring export in a target encryption key (TEK). This target encryption key is a standard P-256 key pair and can be created in many ways: completely offline, or online inside of script using the web crypto APIs.
The following diagram summarizes the flow:
The client-side iframe plays the role of the server: the public portion of this key pair is passed as a parameter inside of a signed EXPORT_WALLET
, EXPORT_PRIVATE_KEY
, or EXPORT_WALLET_ACCOUNT
activity.
Turnkey’s enclave encrypts the private key material (wallet mnemonic or private key) to the end-user’s TEK using the protocol described in the previous section.
Once the activity succeeds, the encrypted mnemonic or private key can be decrypted by the end-user only.
Unlike typical auth and recovery flows in the industry, Turnkey doesn’t send unencrypted tokens. We use the protocol above to send credentials to end-users with no man-in-the-middle risk. This ensures that even if the content of an auth email is leaked, an attacker cannot decrypt and use the underlying credential. The following diagram summarizes the email auth flow:
Our email auth flow works by anchoring on a target encryption key (TEK). This target encryption key is a standard P-256 key pair and can be created in many ways: completely offline, or online inside of script using the web crypto APIs.
The public part of this key pair is passed as a parameter inside of a signed INIT_OTP_AUTH
or EMAIL_AUTH
activity.
Our enclave creates a fresh P256 key pair (“credential”) and encrypts its private key to the recovering user’s TEK using the protocol above.
Once the encrypted credential is received via email, it’s decrypted where the target public key was originally created. The credential is then ready to be used to sign Turnkey activity requests.
Our OTP flows work similarly, except the bundle is not emailed to the user directly. Instead, it is returned as part of the OTP_AUTH
activity results.
We achieve recipient authentication for both the server and client:
The underlying HPKE spec does not provide forward secrecy on the recipient side since the target key can be long lived. To improve forward secrecy we specify that the target key should only be used once by the sender and receiver. We cannot enforce this strictly on the client-side because a client may choose to reuse their key.
We use OpMod Base
because the sender’s KEM private key is not long lived and thus does not need HPKE authentication. In order for this to be exploited one side’s private key data would have to be leaked or an attacker would need to spoof a message from the sender. Turnkey mitigates this attack by layering a signature from an authentication key over payloads that contain ciphertext + encappedPub. Note that in the case of client to server the authentication signature is verified by the our policy engine. Read more about HPKE asymmetric authentication here.