Summary:
At the core of the TLSNotary protocol is dividing the TLS session keys between two parties (Client and Notary) and then using secure two-party computation (2PC) to generate an encrypted and authenticated request which Client sends to a TLS-enabled webserver.
During the run of the protocol neither Client nor Notary know none of the full TLS session keys, they only know their shares of those keys. This preserves the security assumptions of TLS while at the same time allowing Client to prove to Notary the provenance of the TLS transcript.
Below is an in-detail description of each step of the TLSNotary protocol:
Table of Contents
- Computing shares of the pre-master secret
- Using garbled circuit to derive shares of TLS session keys from shares of PMS
- Encrypting the request
- Computing MAC of the request using Oblivious Transfer
- Receiving the webserver response
- Contents of the notarization document
- Verifying the notarization document
- Extending the protocol to hide sensitive data
1. Computing shares of the pre-master secret.
In TLS, the first step towards obtaining TLS session keys is to compute a shared secret between the client and the server by running the ECDH protocol (https://en.wikipedia.org/wiki/Elliptic-curve_Diffie–Hellman). The resulting shared secret in TLS terms is called the pre-master secret (PMS).
Using the notation from Wikipedia, below is the 3-party ECDH protocol between the server (S) the client (C) and the notary (N), enabling the client and the notary to arrive at shares of PMS.
-
S sends its public key Qb to C and C passes it to N
-
C picks a random private key share dc and computes a public key share Qc=dc∗G
-
N picks a random private key share dn and computes a public key share Qn=dn∗G
-
N sends Qn to C who computes Qa=Qc+Qn and sends Qa to S
-
C computes an EC point (xp,yp)=dc∗Qb
-
N computes an EC point (xq,yq)=dn∗Qb
-
Addition of points (xp,yp) and (xq,yq) results in the coordinate xr, which is PMS. (The coordinate yr is not used in TLS)
(Using the notation from https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Point_addition)
Our goal is to compute
xr=(xq−xpyq−yp)2−xp−xq in such a way that a) neither party learns the other party's x value, and b) neither party learns xr. The parties must only learn their shares of xr.
Let's start out by simplifying the equation
xr=(yq2−2yqyp+yp2)(xq−xp)−2−xp−xqmodp
Since this is finite field arithmetic, if the result xr is larger than p, we must "reduce xr modulo p", i.e assign to xr the value xrmodp. The trailing modp is always implied from here on out but may be omitted for brevity.
(If you're curious, p of the most common EC curve P-256 is a prime number and its value is 2256−2224+2192+296−1)
Based on Fermat's little theorem (https://en.wikipedia.org/wiki/Fermat's_little_theorem), a−2modp=ap−3modp. Replacing the negative power, we get:
xr=(yq2−2yqyp+yp2)(xq−xp)p−3−xp−xq
- let A=(yq2−2yqyp+yp2)
- let B=(xq−xp)p−3
- let C=−xp−xq
We will compute xr=A∗B+C using the Paillier cryptosystem (https://en.wikipedia.org/wiki/Paillier_cryptosystem) which has some neat homomorphic properties which allow us to:
- add two encrypted values
- multiply an encrypted value by a cleartext value
In the beginning, Notary generates a Paillier keypair and sends the public key to Client. E() denotes an encrypted value. The parties proceed to compute:
1.1. Computing A=(yq2−2yqyp+yp2)
Notary:
- sends E(yq2) and E(−2yq)
Client:
-
computes E(yp2)
-
computes E(A)=E(yq2)+E(−2yq)∗yp+E(yp2)
-
picks random masks MA and NA and computes E(A∗MA+NA)
-
sends E(A∗MA+NA) and (NAmodp)
(Note that here NA (as well as Nb and NB below) is crucial, as without it Notary would be able to factorize A∗MA and learn A.)
Notary:
-
decrypts and gets (A∗MA+NA)
-
reduces (A∗MA+NA)modp
-
computes (A∗MA)modp=(A∗MA+NA)modp−NAmodp
1.2. Computing B=(xq−xp)p−3
Notary:
- sends E(xq)
Client:
-
lets b=xq−xp
-
computes E(−xp)
-
computes E(b)=E(xq)+E(−xp)
-
picks random masks Mb and Nb and computes E(b∗Mb+Nb)
-
sends E(b∗Mb+Nb) and (Nbmodp)
Notary:
-
decrypts and gets (b∗Mb+Nb)
-
reduces (b∗Mb+Nb)modp
-
computes (b∗Mb)modp=(b∗Mb+Nb)modp−Nbmodp
-
sends E((b∗Mb)p−3modp)
Client:
-
computes multiplicative inverse inv=(Mbp−3)−1modp
-
computes E((b∗Mb)p−3modp)∗inv=E(bp−3∗Mbp−3∗(Mbp−3)−1)=E(bp−3)=E(B)
-
picks random masks MB and NB and computes E(B∗MB+NB)
-
sends E(B∗MB+NB) and NBmodp
Notary:
-
decrypts and gets (B∗MB+NB)
-
reduces (B∗MB+NB)modp
-
computes (B∗MB)modp=(B∗MB+NB)modp−NBmodp
1.3. Computing A*B+C
Notary
- sends E(A∗MA∗B∗MB) and E(−xq)
Client:
-
computes E(A∗B)=E(A∗MA∗B∗MB)∗(MA∗MB)−1
-
computes E(−xp)
-
computes E(A∗B+C)=E(A∗B)+E(−xq)+E(−xp)
-
applies a random mask S_q and sends E(A∗B+C+Sq)
-
computes his additive PMS share: sq=(Sqmodp)
Notary:
-
decrypts and gets A∗B+C+Sq
-
computes his additive PMS share: sp=(A∗B+C+Sq)modp
The protocol described above is secure against Notary sending malicious inputs. Indeed, because Client only sends back masked values, Notary cannot learn anything about those values.
2. Using garbled circuit to derive shares of TLS session keys from shares of PMS.
Using Garbled Circuit (GC) (https://en.wikipedia.org/wiki/Garbled_circuit), each party provides their PMS share as an input to the circuit. Notary acts as the garbler. Client acts as the evaluator. The circuit combines the PMS shares, computes TLS's PRF function and outputs XOR shares of TLS session keys to each party. Also the circuit builds the Client Finished message and checks the Server Finished message.
In order to prevent malicious garbling, a dual execution protocol is used where Notary and Client take turns garbling/evaluating the same circuit. This reduces the bandwidth requirement by a factor of 5 compared to the state-of-the-art Authenticated Garbling (AG) (https://eprint.iacr.org/2017/030). (For reference: a standard 2KB request using AG would require Client to download 320MB of data from Notary).
The dual execution protocol is as follows:
- N acts as the garbler and C acts as the evaluator of a circuit. C evaluates the circuit, gets encoded output OUTc. He decodes it and gets Plaintextc. Now C knows what the N's encoded output OUTn should be.
- C acts as the garbler and N acts as the evaluator of the same circuit. N evaluates the circuit, gets encoded output OUTn. He decodes it and gets Plaintextn. Now N knows what the C's encoded output OUTc should be.
- C computes Checkc =H(OUTc ∣OUTn ). C does not reveal Checkc yet but sends a commitment Comm(Checkc ).
- N computes Checkn =H(OUTc ∣OUTn ) and sends it to C.
- C checks that Checkc == Checkn and decommits Comm(Checkc ) by sending Checkc to N.
- N checks the decommitment and then checks that Checkn == Checkc.
3. Encrypting the client request.
With each party having their shares of client_write_key and client_write_IV as inputs to the garbled circuit, we use the same technique as in 2. to compute the AES-ECB-encrypted counter blocks. The circuit outputs this value only to Client who xors the request's plaintext with the encrypted counter blocks to get the ciphertext. Client passes the ciphertext to Notary in order to jointly compute a MAC (see below 4.). After MAC is computed, Client sends the encrypted request with MAC to the webserver.
4. Computing MAC of the request using Oblivious Transfer.
In AES-GCM, MACs are computed using the GHASH function described in the NIST publication https://nvlpubs.nist.gov/nistpubs/legacy/sp/nistspecialpublication800-38d.pdf in section 6.4: "In effect, the GHASH function calculates X1•Hm⊕X2•Hm−1⊕...⊕Xm−1•H2⊕Xm•H."
Here Xn is the n-th 16-byte ciphertext block and Hn are powers of H (AES-ECB-encrypted zero), ⊕ is XOR, and • is block multiplication as defined in section 6.3.
Our goal is to compute block multiplication of each block Xn with the corresponding power Hm−n.
The pseudocode of x•y (as per section 6.3) is:
1. let res = 0;
2. let R = 0xE1000000000000000000000000000000;
3. for (let i=127; i >= 0; i--)
4. res = res ^ (x * ((y >> i) & 1));
5. x = (x >> 1) ^ ((x & 1) * R);
6. return res;
Suppose, Client's value is y and Notary's value is x. As can be seen in line 4, depending on whether a bit of y is 1 or 0, a modified x value is either xored to the result or not. The modified x values in line 5 can be pre-computed by Notary in advance: a total of 128 values x0,x1,...,x127
Notary picks 128 distinct xor masks and appllies them to each value xn. Using the 1-out-of-2 Oblivious Transfer (OT)(https://en.wikipedia.org/wiki/Oblivious_transfer#1–2_oblivious_transfer), for every bit of y, Client asks Notary to obliviously send to him either a masked xn if the bit is 1 or the mask itself if the bit is 0. In the end, Notary's sum of all xor masks and Client's sum of all values received will be their shares of the product.
This technique is sufficient to compute shares of any power of H, e.g. let's see how the parties compute H3, where Hn and Hc are Notary's and Client's shares of H.
H3=(Hc+Hn)3=Hc3+3Hc2Hn+3Hn2Hc+Hn3
Since addition is defined as xor, then 3Hc2Hn=Hc2Hn⊕Hc2Hn⊕Hc2Hn=Hc2Hn, thus
H3=Hc3+Hc2Hn+Hn2Hc+Hn3
The parties will compute locally Hc3 and Hn3 respectively. Then, using OT they will compute shares of Hc2Hn and Hn2Hc. H3 is the xor-sum of locally computed values and shares computed with OT.
Additional optimizations:
A) Free squaring.
Suppose the parties start with shares of Hx, then the shares Hx2,Hx3,Hx4,... can be computed locally without OT. To demonstrate, if parties start with shares of H2 and want to compute shares of H4, it can be seen that the middle terms are all equal to 0, because xoring a value to itself an even amount of times results in 0:
H4=(Hc2+Hn2)2=Hc4+4(Hc3Hn)+6(Hc2Hn2)+4(Hn3Hc)+Hn4=Hc4+Hn4
B) Block aggregation.
With parties having Hn and Hc and having computed shares locally using free squaring, they proceed to compute, e.g. H3X3+H5X5, where X3 and X5 are ciphertext blocks corresponding to the powers of H for the GHASH function. (Hn2 means Notary's share of H2)
Expanding:
H3X3=(Hn2+Hc2)(Hn+Hc)X3=Hn2HnX3+Hn2HcX3+Hc2HnX3+Hc2HcX3
and
H5X5=(Hn4+Hc4)(Hn+Hc)X5=Hn4HnX5+Hn4HcX5+Hc4HnX5+Hc4HcX5
Let K1n,K2n,... be values which Notary can compute locally
Let K1c,K2c,... be values which Client can compute locally
Given that all values of Xn are known to both parties, we rewrite the expanded sum of H3X3+H5X5:
K1n+K2nHc+K2cHn+K1c+K3n+K4nHc+K4cHn+K3c
The parties only need to do two block multiplication using OT (the rest can be computed locally):
K2nHc+K2cHn+K4nHc+K4cHn=(K2n+K4n)Hc+(K2c+K4c)Hn
The parties could have aggregated more X values, e.g. H3X3+H5X5+H9X9+H17X17 would still require only 2 block multiplication using OT.
The best strategy (which involves the least amount of OT) is for the parties to first compute shares of powers 3,5,7,9,11 ..., then apply "free squaring" to those shares and then run block aggregation.
5. Receiving the server response.
When Client receives a response from the webserver, he sends to Notary a hash commitment to the response. Notary reveals to Client Notary's TLS session keys' shares. Client combines his key shares with Notary's key shares and gets full TLS session keys. Client proceeds to authenticate the response and to decrypt it.
6. Contents of the notarization document.
The notarization document includes the following elements signed by Notary:
- Client's hash commitment to the server response
- Client's hash commitment to the Client's shares of TLS session keys
- Client's hash commitment to the Client's share of the PMS
- GHASH inputs used when computing MAC for the request
- Webserver's ephemeral pubkey used when computing shares of PMS
- Notary's PMS share
- Notary's TLS session keys
- Notarization timestamp
In addition to the elements signed by Notary, Client also includes in the notarization document the following:
- Notary's signature over the above 8 elements
- x509 certificate chain from the webserver's "Certificate" TLS message
- client_random and server_random values of the TLS session
- Webserver's signature over ephemeral pubkey and random values from the "Server Key Exchange" TLS message
- Client's TLS session keys
- Client's share of the PMS
- Webserver response with MAC
7. Verifying the notarization document.
Any third party who trusts the Notary's pubkey and has a list of trusted root CA certificates can verify the notarization document by performing the following:
- Check the Notary's signature(9)
- Check x509 certificate chain(10) validity at the time in timestamp(8)
- Verify that signature(12) was made using the public key from the leaf certificate over the random values(11) and over the ephemeral pubkey(5)
- Check that commitment(3) matches Client's PMS share(14)
- Combine PMS shares(6&14) and derive full TLS session keys
- Check that commitment(2) matches Client's session keys(13)
- Check that the full TLS session keys match the sum of Notary's keys(7) and Client's keys(13)
- Decrypt the request contained in GHASH inputs(4) and rule out domain fronting by making sure that HTTP header "Host:" matches the Common Name from the leaf certificate
- Check that the commitment(1) matches the webserver response(15)
- Check MAC of the response(15) and decrypt the response
8. Extending the protocol to hide sensitive data.
The protocol can be extended to enable Client to keep any sensitive data of the TLS transcript hidden from a verifier (a third party) while still proving the authenticity of the TLS transcript.
The previous assumption that Notary was a malicious garbler is not valid anymore in this extended protocol. Now, any malicious garbling will be immediately detected by the Client by checking the evaluated circuit's output against the correct output. This allows to use a less expensive semi-honest model where Notary acts exclusively as the garbler and Client as the evaluator.
For simplicity of explanation, the below protocol described hiding sensitive data in the webserver response but it can just as easily be applied to hide sensitive data in the Client's request.
The protocol runs exactly as the base protocol up until and including Step 4 after which the protocol changes:
-
Parties use GC 2PC to compute encrypted counter blocks for the webserver response. Each party inputs their xor share of the server_write_key (swk) and server_write_iv (siv) into the circuit. The circuit's plaintext output is encrypted counter blocks. The circuit's encoded outputs go to Client.
-
Notary must ensure that Client uses the same swk/siv share as the input to the circuit which was used when checking the Server Finished message.
-
After Client evaluates the circuit, he obtains encoded outputs for each bit of the encrypted counter blocks. Client selects only those encoded outputs which correspond to the part of the webserver response which Client wants to make public and sends to Notary a commitment to those encoded outputs. (Optionally, Client may also send to Notary a commitment to the secret part of the webserver response if the Client intends to prove in zero knowledge the content of the secret part.)
-
Notary sends back the decoding table and his TLS key shares.
-
Client gets full TLS keys and decrypts the webserver response. Client uses the decoding table to decode his encoded output from the circuit and checks that the decoded values from the circuit match the decrypted webserver response. If not, the protocol aborts because Notary was cheating.
-
Notary signs the decoding table and Client's commitment to the encoded outputs.
(Note that the format of the notarization document in Section 6 must be modified to reflect this extended protocol).
- When the verifier obtains the notarization document, he decodes Client's encoded outputs (for public data) using the Notary's decoding table and obtains encrypted counter blocks (ECB). The verifier xors ECB with the webserver response to get the plaintext which Clients wants to reveal. The remaining part of the webserver response for which Client did not provide encoded outputs remains redacted from the verifier's point of view.