mirror of
https://github.com/tlsnotary/PageSigner.git
synced 2026-01-09 22:57:57 -05:00
653 lines
25 KiB
JavaScript
653 lines
25 KiB
JavaScript
async function send_and_recv(command, data, expected_response, uid) {
|
|
var url = "http://" + chosen_notary.IP + ":" + chosen_notary.port
|
|
var payload = JSON.stringify({'request':command, 'uid': uid, 'data':b64encode(data)})
|
|
try{
|
|
var req = await fetch(url, {method:'POST', body: payload, cache: 'no-store'})
|
|
}
|
|
catch (err){
|
|
throw('Could not connect to the PageSigner server. The error was:' + err)
|
|
}
|
|
var text_ab = await req.arrayBuffer()
|
|
var text = ba2str(ab2ba(text_ab))
|
|
console.log(text.length, text.slice(-100))
|
|
|
|
var response_json = JSON.parse(text);
|
|
if (response_json.response !== expected_response) {
|
|
reject('Unexpected response. Expected ' + expected_response + ' but got ' + response);
|
|
return;
|
|
}
|
|
var data = b64decode(response_json.data);
|
|
return data;
|
|
}
|
|
|
|
|
|
const start_audit = async function(server, port, headers){
|
|
var mhm = false //multiple handshake messages
|
|
|
|
var all_handshakes = []; //a concatenation of all handshake messages up to this point
|
|
var client_write_key = null;
|
|
var client_write_IV = null;
|
|
var cwkCryptoKey = null; // client_write_key in Crypto.Subtle format
|
|
var uid = Math.random().toString(36).slice(-10); //a new uid for each notarized page
|
|
|
|
let supported_groups_extension = []
|
|
supported_groups_extension.push(0x00, 0x0a) //Type supported_groups
|
|
supported_groups_extension.push(0x00, 0x04) //Length
|
|
supported_groups_extension.push(0x00, 0x02) //Supported Groups List Length
|
|
supported_groups_extension.push(0x00, 0x17) //Supported Group: secp256r1
|
|
|
|
let signature_algorithm_extension = []
|
|
supported_groups_extension.push(0x00, 0x0d) //Type signature_algorithms
|
|
signature_algorithm_extension.push(0x00, 0x04) //Length
|
|
signature_algorithm_extension.push(0x00, 0x02) //Signature Hash Algorithms Length
|
|
signature_algorithm_extension.push(0x04, 0x01) //Signature Algorithm: rsa_pkcs1_sha256 (0x0401)
|
|
|
|
let server_name_extension = []
|
|
let server_name = str2ba(server)
|
|
server_name_extension.push(0x00, 0x00) //Extension type: server_name
|
|
server_name_extension = server_name_extension.concat(bi2ba(server_name.length+5, {fixed:2})) //Length
|
|
server_name_extension = server_name_extension.concat(bi2ba(server_name.length+3, {fixed:2})) //Server Name List Length
|
|
server_name_extension.push(0x00) //Type: host name
|
|
server_name_extension = server_name_extension.concat(bi2ba(server_name.length, {fixed:2})) //Server Name Length
|
|
server_name_extension = server_name_extension.concat(server_name)
|
|
|
|
let max_fragment_length_extension = []
|
|
if (use_max_fragment_length){
|
|
max_fragment_length_extension.push(0x00, 0x01) //Type: max_fragment_length
|
|
max_fragment_length_extension.push(0x00, 0x01) //Length
|
|
//allowed values 0x01 = 512 0x02 = 1024 0x03 = 2048 0x04 = 4096
|
|
//some servers support 0x04 but send alert if < 0x04
|
|
max_fragment_length_extension.push(0x04)
|
|
}
|
|
|
|
let extlen = supported_groups_extension.length + signature_algorithm_extension.length + server_name_extension.length + max_fragment_length_extension.length
|
|
|
|
ch = []
|
|
ch.push(0x01) //Handshake type: Client Hello
|
|
ch = ch.concat( bi2ba((extlen + 43), {fixed:3}) ) //Length
|
|
ch.push(0x03, 0x03) //Version: TLS 1.2
|
|
var client_random = getRandom(32)
|
|
ch = ch.concat(client_random)
|
|
ch.push(0x00) //Session ID Length
|
|
ch.push(0x00, 0x02) //Cipher Suites Length
|
|
ch.push(0xc0, 0x2f) //Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
|
ch.push(0x01) //Compression Methods Length
|
|
ch.push(0x00) //Compression Method: null
|
|
|
|
ch = ch.concat( bi2ba((extlen), {fixed:2}) )
|
|
ch = [].concat(ch, supported_groups_extension, signature_algorithm_extension, server_name_extension, max_fragment_length_extension)
|
|
all_handshakes = all_handshakes.concat(ch)
|
|
|
|
var tls_record_header = []
|
|
tls_record_header.push(0x16) //Type: Handshake
|
|
tls_record_header.push(0x03, 0x03) // Version: TLS 1.2
|
|
tls_record_header = tls_record_header.concat(bi2ba((ch.length), {fixed:2})) // Length
|
|
|
|
var sckt = new Socket(server, port);
|
|
await sckt.connect()
|
|
console.log('connected')
|
|
sckt.send([].concat(tls_record_header, ch)); //Send Client Hello
|
|
|
|
//asynchronously prepair keypair for auditee<-->auditor ECDH
|
|
var rvgk = await crypto.subtle.generateKey({'name':'ECDH', 'namedCurve':'P-256'}, true, ['deriveBits']);
|
|
var commPubkey = rvgk.publicKey
|
|
var commPrivkey = rvgk.privateKey
|
|
var commRawPubkey = await crypto.subtle.exportKey('raw', commPubkey);
|
|
var commRawPubkey_ba = ab2ba(commRawPubkey)
|
|
|
|
var s = await sckt.recv(true);
|
|
console.log(s)
|
|
|
|
//Parse Server Hello, Certificate, Server Key Exchange, Server Hello Done
|
|
if (eq(s.slice(0,2), [0x15, 0x03])){
|
|
console.log('Server sent Alert instead of Server Hello')
|
|
throw ('Unfortunately PageSigner is not yet able to notarize this website. You can contact the PageSigner developers and ask to add support for this website.');
|
|
}
|
|
var p = 0 //current position in byte stream
|
|
assert(eq(s.slice(p, p+=1), [0x16])) //Type: Handshake
|
|
assert(eq(s.slice(p, p+=2), [0x03, 0x03])) //Version: TLS 1.2
|
|
let handshakelen = ba2int(s.slice(p, p+=2))
|
|
//This may be the length of multiple handshake messages (MHM)
|
|
//For MHM there is only 1 TLS Record layer header followed by Handshake layer messages
|
|
//Without MHM, each handshake message has its own TLS Record header
|
|
|
|
var shlen = ba2int(s.slice(p+1, p+4))
|
|
var sh = s.slice(p, p + 4 + shlen)
|
|
all_handshakes = all_handshakes.concat(sh)
|
|
|
|
assert(eq(s.slice(p, p+=1), [0x02])) //Server Hello
|
|
var shlen = ba2int(s.slice(p, p+=3))
|
|
assert(eq(s.slice(p, p+=2), [0x03, 0x03])) //Version: TLS 1.2
|
|
var server_random = s.slice(p, p+=32)
|
|
let sidlen = ba2int(s.slice(p, p+=1))
|
|
if (sidlen) p+=sidlen //32 bytes of session ID, if any
|
|
assert(eq(s.slice(p, p+=2), [0xc0, 0x2f])) //Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
|
assert(eq(s.slice(p, p+=1), [0x00])) //Compression Method: null (0)
|
|
//May contain Extensions. We don't need to parse them
|
|
p = 5+4+shlen
|
|
|
|
if (handshakelen > shlen+4) mhm = true //multiple handshake messages
|
|
|
|
if (!mhm){
|
|
//read the TLS Record header
|
|
assert(eq(s.slice(p, p+=3), [0x16, 0x03, 0x03])) //Type: Handshake # Version: TLS 1.2
|
|
var reclen = ba2int(s.slice(p, p+=2))
|
|
}
|
|
|
|
var clen = ba2int(s.slice(p+1, p+4))
|
|
var c = s.slice(p, p + 4 + clen)
|
|
all_handshakes = all_handshakes.concat(c)
|
|
|
|
assert(eq(s.slice(p, p+=1), [0x0b])) //Certificate
|
|
var clen = ba2int(s.slice(p, p+=3))
|
|
if (!mhm) assert(reclen == clen+4)
|
|
var certslen = ba2int(s.slice(p, p+=3))
|
|
var certs_last_pos = p + certslen
|
|
var certs = []
|
|
while (p < certs_last_pos){
|
|
let certlen = ba2int(s.slice(p, p+=3))
|
|
let certder = s.slice(p, p+certlen); p+=certlen
|
|
certs.push(certder)
|
|
}
|
|
var vcrv = await verifyChain(certs);
|
|
if (vcrv[0] != true) {
|
|
throw ('Cannot notarize because the website presented an untrusted certificate');
|
|
}
|
|
if (vcrv[1]) certs.push(vcrv[1]) //add an intermediate certificate which was missing from the chain
|
|
|
|
|
|
var commonName = getCommonName(certs[0]);
|
|
assert(checkCertSubjName(certs[0], server) == true, "server name is not the same as the certificate's subject name(s)")
|
|
|
|
|
|
if (mhm && (handshakelen+5 == p)){
|
|
//another MHM header will follow, read its header
|
|
assert(eq(s.slice(p, p+=1), [0x16])) //Type: Handshake
|
|
assert(eq(s.slice(p, p+=2), [0x03, 0x03])) //Version: TLS 1.2
|
|
handshakelen = ba2int(s.slice(p, p+=2)) //This may be the length of multiple handshake messages (MHM)
|
|
}
|
|
if (!mhm){
|
|
//read the TLS Record header
|
|
assert(eq(s.slice(p, p+=3), [0x16, 0x03, 0x03])) //Type: Handshake # Version: TLS 1.2
|
|
var reclen = ba2int(s.slice(p, p+=2))
|
|
}
|
|
|
|
var skelen = ba2int(s.slice(p+1, p+4))
|
|
var ske = s.slice(p, p + 4+ skelen)
|
|
all_handshakes = all_handshakes.concat(ske)
|
|
|
|
assert(eq(s.slice(p, p+=1), [0x0c])) //Handshake Type: Server Key Exchange (12)
|
|
var skelen = ba2int(s.slice(p, p+=3))
|
|
if (!mhm) assert(reclen == skelen+4)
|
|
// EC Diffie-Hellman Server Params
|
|
assert(eq(s.slice(p, p+=1), [0x03])) //Curve Type: named_curve (0x03)
|
|
assert(eq(s.slice(p, p+=2), [0x00, 0x17])) //Named Curve: secp256r1 (0x0017)
|
|
var pklen = ba2int(s.slice(p, p+=1))
|
|
assert (pklen == 65) //Pubkey Length: 65
|
|
var ec_pubkey_server = s.slice(p, p+=pklen)
|
|
assert(eq(s.slice(p, p+=2), [0x04, 0x01])) //#Signature Algorithm: rsa_pkcs1_sha256 (0x0401)
|
|
var siglen = ba2int(s.slice(p, p+=2))
|
|
var rsa_sig = s.slice(p, p+=siglen)
|
|
|
|
var vecprv = await verifyECParamsSig(certs[0], ec_pubkey_server, rsa_sig, client_random, server_random)
|
|
if (vecprv != true){
|
|
throw ('EC parameters signature verification failed');
|
|
}
|
|
|
|
//Parse Server Hello Done
|
|
if (!mhm) {
|
|
//read the TLS Record header
|
|
assert(eq(s.slice(p, p+=3), [0x16, 0x03, 0x03])) //Type: Handshake # Version: TLS 1.2
|
|
let reclen = ba2int(s.slice(p, p+=2))
|
|
}
|
|
var shd = s.slice(p, p+=4)
|
|
assert(eq(shd, [0x0e, 0x00, 0x00, 0x00]))
|
|
assert(p == s.length)
|
|
|
|
all_handshakes = all_handshakes.concat(shd)
|
|
|
|
var reply_data_enc = await send_and_recv('cr_sr_spk_commpk', [].concat(
|
|
client_random, server_random, ec_pubkey_server, commRawPubkey_ba), 'commpk_commpksig_cpk_cwk_cwi', uid)
|
|
//communication pubkey is not encrypted
|
|
//Notary's EC pubkey for ECDH secret for communication
|
|
var comm_pk = reply_data_enc.slice(0,65)
|
|
var commSymmetricKey = await getECDHSecret(comm_pk, commPrivkey)
|
|
var reply_data = await decryptNotaryResponse(commSymmetricKey, reply_data_enc.slice(65))
|
|
|
|
var o = 0 //offset
|
|
//get signature over communication pubkey
|
|
var siglen = ba2int(reply_data.slice(o,o+=1))
|
|
var commpk_sig = reply_data.slice(o, o+=siglen)
|
|
//check signature
|
|
var to_be_signed = await sha256(comm_pk)
|
|
assert(await verifyNotarySig(commpk_sig, chosen_notary.pubkeyPEM, to_be_signed) == true)
|
|
|
|
var cpk = reply_data.slice(o,o+=65) //Client's pubkey for ECDH
|
|
client_write_key = reply_data.slice(o, o+=16)
|
|
client_write_IV = reply_data.slice(o, o+=4)
|
|
|
|
|
|
//Send Client Key Exchange, Change Cipher Spec and Encrypted Handshake Message
|
|
var cke_tls_record_header = [0x16, 0x03, 0x03, 0x00, 0x46] //Type: Handshake, Version: TLS 1.3, Length
|
|
cke = [0x10] //Handshake type: Client Key Exchange
|
|
cke.push(0x00, 0x00, 0x42) // Length
|
|
cke.push(0x41) //Pubkey Length: 65
|
|
cke = [].concat(cke, cpk) // Client's Pubkey
|
|
|
|
ccs = [0x14, 0x03, 0x03, 0x00, 0x01, 0x01]
|
|
|
|
//hash of all the handshakes. This is only data visible at the handshake layer
|
|
//and does not include record layer headers
|
|
all_handshakes = [].concat(all_handshakes, cke)
|
|
var hs_hash = await sha256(all_handshakes)
|
|
|
|
var enc = await encryptNotaryRequest(commSymmetricKey, hs_hash)
|
|
var reply_data_enc = await send_and_recv('hshash', enc, 'vd', uid)
|
|
var reply_data = await decryptNotaryResponse(commSymmetricKey, reply_data_enc)
|
|
|
|
assert(reply_data.length == 12)
|
|
verify_data = reply_data.slice(0, 12)
|
|
|
|
|
|
var client_finished = await (async function encrypt_client_finished(){
|
|
let finished = [].concat([0x14, 0x00, 0x00, 0x0c], verify_data) //Finished (0x14) with length 12
|
|
all_handshakes = [].concat(all_handshakes, finished)
|
|
|
|
let explicit_nonce = getRandom(8)
|
|
let nonce = [].concat(client_write_IV, explicit_nonce)
|
|
let seq_num = 0
|
|
let aad = [] //additional_data
|
|
aad = [].concat(aad, bi2ba(seq_num, {fixed:8})) // seq_num
|
|
aad.push(0x16) // type 0x16 = Handshake
|
|
aad.push(0x03, 0x03) // TLS Version 1.2
|
|
aad.push(0x00, 0x10) // 16 bytes of unencrypted data
|
|
let ck = await crypto.subtle.importKey("raw", ba2ab(client_write_key), "AES-GCM", true, ["encrypt", "decrypt"]);
|
|
cwkCryptoKey = ck
|
|
let ciphertext = await crypto.subtle.encrypt(
|
|
{name: 'AES-GCM', iv: ba2ab(nonce), additionalData: ba2ab(aad)}, cwkCryptoKey, ba2ab(finished));
|
|
let ct = ab2ba(ciphertext)
|
|
let f = [0x16, 0x03, 0x03, 0x00, 0x28] //Finished message of 40 (0x28) bytes length
|
|
f = [].concat(f, explicit_nonce, ct)
|
|
return f
|
|
})()
|
|
|
|
var data_to_send = [].concat(cke_tls_record_header, cke, ccs, client_finished)
|
|
|
|
sckt.send(data_to_send); //Send Client Key Exchange
|
|
var data = await sckt.recv(true)
|
|
|
|
if (eq(data.slice(0,2), [0x15, 0x03])){
|
|
console.log('Server sent Alert instead of Server Finished')
|
|
throw('Server sent Alert instead of Server Finished')
|
|
}
|
|
//Parse CCS and Server's Finished
|
|
var ccs = data.slice(0,6)
|
|
assert(eq(ccs, [0x14, 0x03, 0x03, 0x00, 0x01, 0x01]))
|
|
|
|
var f = null; //server finished
|
|
if (data.length == 6) {
|
|
//didnt receive the Finished message, try again
|
|
f = await recv_socket(sock, True);
|
|
}
|
|
else {
|
|
f = data.slice(6)
|
|
}
|
|
|
|
assert (eq(f.slice(0,5), [0x16, 0x03, 0x03, 0x00, 0x28]))
|
|
var enc_f = f.slice(5, 45) //encrypted Finished message
|
|
//There may be some other garbage received after the Finished message
|
|
|
|
//Send Server's encrypted Finished for decryption and Handshake hash to check server's verify data
|
|
let hshash2 = await sha256(all_handshakes)
|
|
|
|
var enc = await encryptNotaryRequest(commSymmetricKey, [].concat(enc_f, hshash2))
|
|
var reply_data_enc = await send_and_recv('encf_hshash2', enc, 'verify_status', uid)
|
|
var reply_data = await decryptNotaryResponse(commSymmetricKey, reply_data_enc)
|
|
|
|
assert(eq(reply_data, [0x01]))
|
|
|
|
|
|
var appdata = await (async function encrypt_request(){
|
|
let headers_ba = str2ba(headers);
|
|
let explicit_nonce = getRandom(8)
|
|
let nonce = [].concat(client_write_IV, explicit_nonce)
|
|
let aad = []
|
|
let seq_num = 1
|
|
aad = [].concat(aad, bi2ba(seq_num, {fixed:8}))
|
|
aad = [].concat(aad, [0x17, 0x03, 0x03]) //type 0x17 = Application data , TLS Version 1.2
|
|
aad = [].concat(aad, bi2ba(headers_ba.length, {fixed:2})) //length bytes of unencrypted data
|
|
let ciphertext = await crypto.subtle.encrypt(
|
|
{name: 'AES-GCM', iv: ba2ab(nonce),
|
|
additionalData: ba2ab(aad)}, cwkCryptoKey, ba2ab(headers_ba));
|
|
let ct = ab2ba(ciphertext)
|
|
let appdata = []
|
|
appdata = [].concat(appdata, [0x17, 0x03, 0x03]) // Type: Application data, TLS Version 1.2
|
|
appdata = [].concat(appdata, bi2ba(explicit_nonce.length + ct.length, {fixed:2})) //2-byte length of encrypted data
|
|
appdata = [].concat(appdata, explicit_nonce, ct)
|
|
return appdata
|
|
})()
|
|
|
|
sckt.send(appdata)
|
|
var server_response = await sckt.recv();
|
|
console.log('server_reply.length', server_response.length)
|
|
|
|
var encRecords = splitResponseIntoRecords(server_response)
|
|
var commitHash = await computeCommitHash(encRecords)
|
|
|
|
var enc = await encryptNotaryRequest(commSymmetricKey, commitHash)
|
|
var reply_data_enc = await send_and_recv('commithash', enc, 'swk_swi_sig_time', uid)
|
|
var reply_data = await decryptNotaryResponse(commSymmetricKey, reply_data_enc)
|
|
|
|
var o = 0; //offset
|
|
var server_write_key = reply_data.slice(o, o+=16)
|
|
var server_write_IV = reply_data.slice(o, o+=4)
|
|
console.log('server_write_key, server_write_IV', server_write_key, server_write_IV)
|
|
var sig_len = ba2int(reply_data.slice(o, o+=1))
|
|
var notary_signature = reply_data.slice(o, o+=sig_len)
|
|
var time = reply_data.slice(o, o+=4)
|
|
console.log('resp.length', reply_data.length)
|
|
assert(reply_data.length == o)
|
|
|
|
//Check notary server signature
|
|
var signed_data = await sha256([].concat(ec_pubkey_server, server_write_key, server_write_IV, commitHash, time))
|
|
assert(await verifyNotarySig(notary_signature, chosen_notary.pubkeyPEM, signed_data) == true)
|
|
|
|
var cleartexts = await decrypt_tls_responseV4(encRecords, server_write_key, server_write_IV)
|
|
|
|
var dechunked = dechunk_http(ba2str(cleartexts))
|
|
var ungzipped = gunzip_http(dechunked)
|
|
console.log('ungzipped.length', ungzipped.length)
|
|
|
|
return [certs, rsa_sig, client_random, server_random, ec_pubkey_server, server_write_key, server_write_IV, encRecords, ungzipped, notary_signature, time ]
|
|
}
|
|
|
|
|
|
//split a raw TLS response into encrypted application layer records
|
|
function splitResponseIntoRecords(s){
|
|
var records = []
|
|
var p = 0 //position in the stream
|
|
var alertSeen = false
|
|
|
|
while (p < s.length){
|
|
if (alertSeen){
|
|
console.log('Server unexpectedly sent more data after Alert')
|
|
throw('Server unexpectedly sent more data after Alert')
|
|
}
|
|
if (! eq(s.slice(p,p+3), [0x17,0x03,0x03])){
|
|
if (eq(s.slice(p,p+3), [0x15,0x03,0x03])){
|
|
if (records.length == 0){
|
|
console.log('Server sent Alert instead of response')
|
|
throw('Server sent Alert instead of response')
|
|
}
|
|
alertSeen = true
|
|
console.log('server sent Alert, presumably Close Notify')
|
|
}
|
|
else{
|
|
console.log('Server sent an unknown message')
|
|
throw('Server sent an unknown message')
|
|
}
|
|
}
|
|
|
|
p+=3
|
|
let reclen = ba2int(s.slice(p, p+=2))
|
|
let record = s.slice(p, p+=reclen)
|
|
if (alertSeen){
|
|
continue
|
|
}
|
|
else {
|
|
records.push(record)
|
|
}
|
|
}
|
|
assert(p == s.length, 'The server sent a misformatted reponse')
|
|
return records
|
|
}
|
|
|
|
|
|
//commit hash inputs are sha256 hashes of "AES GCM authentication tag" for each TLS record
|
|
async function computeCommitHash(encRecords){
|
|
var hashesOfAuthTags = []
|
|
for (let encRec of encRecords){
|
|
//The last 16 bytes of encrypted TLS application layer record (in AES GCM)
|
|
//is the record's authentication tag
|
|
var authTag = encRec.slice(-16)
|
|
//poseidon works with ints
|
|
var hash = await sha256(authTag)
|
|
hashesOfAuthTags.push(hash)
|
|
}
|
|
//convert all hashes into a byte array
|
|
var commitHashInput = []
|
|
for (let hash of hashesOfAuthTags){
|
|
commitHashInput = commitHashInput.concat(hash)
|
|
}
|
|
var commitHash = await sha256(commitHashInput)
|
|
return commitHash
|
|
}
|
|
|
|
|
|
async function decryptNotaryResponse (key, enc){
|
|
var IV = enc.slice(0,12)
|
|
var ciphertext = enc.slice(12)
|
|
var data_ab = await crypto.subtle.decrypt(
|
|
{name: 'AES-GCM', iv: ba2ab(IV)},
|
|
key,
|
|
ba2ab(ciphertext));
|
|
var data = ab2ba(data_ab)
|
|
return data;
|
|
}
|
|
|
|
|
|
async function encryptNotaryRequest(key, cleartext){
|
|
var IV = getRandom(12)
|
|
var enc = await crypto.subtle.encrypt(
|
|
{name: 'AES-GCM', iv: ba2ab(IV)},
|
|
key,
|
|
ba2ab(cleartext));
|
|
var data = [].concat(IV, ab2ba(enc))
|
|
return data;
|
|
}
|
|
|
|
|
|
//pub/privkey must be in WebCrypto format
|
|
async function getExpandedKeys(hisPubkey, myPrivkey, cr, sr){
|
|
var Secret = await crypto.subtle.deriveBits(
|
|
{'name': 'ECDH', 'public': hisPubkey },
|
|
myPrivkey,
|
|
256)
|
|
var Secret_CryptoKey = await crypto.subtle.importKey(
|
|
"raw",
|
|
Secret,
|
|
{name: 'HMAC', hash:'SHA-256'},
|
|
true,
|
|
['sign']);
|
|
|
|
//calculate Master Secret and expanded keys
|
|
var seed = [].concat(str2ba('master secret'), cr, sr);
|
|
var a0 = ba2ab(seed)
|
|
var a1 = await crypto.subtle.sign('HMAC', Secret_CryptoKey, a0);
|
|
var a2 = await crypto.subtle.sign('HMAC', Secret_CryptoKey, a1);
|
|
var p1 = await crypto.subtle.sign('HMAC', Secret_CryptoKey, ba2ab([].concat(ab2ba(a1),seed)));
|
|
var p2 = await crypto.subtle.sign('HMAC', Secret_CryptoKey, ba2ab([].concat(ab2ba(a2),seed)));
|
|
var ms = [].concat(ab2ba(p1), ab2ba(p2)).slice(0,48)
|
|
var MS_CryptoKey = await crypto.subtle.importKey("raw", ba2ab(ms), {name: 'HMAC', hash:'SHA-256'}, true, ['sign']);
|
|
|
|
//Expand keys
|
|
var eseed = [].concat(str2ba('key expansion'), sr, cr);
|
|
var ea0 = ba2ab(eseed)
|
|
var ea1 = await crypto.subtle.sign('HMAC', MS_CryptoKey, ea0);
|
|
var ea2 = await crypto.subtle.sign('HMAC', MS_CryptoKey, ea1);
|
|
var ep1 = await crypto.subtle.sign('HMAC', MS_CryptoKey, ba2ab([].concat(ab2ba(ea1),eseed)));
|
|
var ep2 = await crypto.subtle.sign('HMAC', MS_CryptoKey, ba2ab([].concat(ab2ba(ea2),eseed)));
|
|
|
|
var ek = [].concat(ab2ba(ep1), ab2ba(ep2)).slice(0,40)
|
|
//GCM doesnt need MAC keys
|
|
var client_write_key = ek.slice(0, 16)
|
|
var server_write_key = ek.slice(16, 32)
|
|
var client_write_IV = ek.slice(32, 36)
|
|
var server_write_IV = ek.slice(36, 40)
|
|
return [client_write_key, server_write_key, client_write_IV, server_write_IV, MS_CryptoKey]
|
|
}
|
|
|
|
|
|
//Calculate ECDH shared secret between auditee and auditor
|
|
//16 bytes of that secret is the symmetric key
|
|
async function getECDHSecret(hisPubkeyRaw_ba, myPrivkey){
|
|
var comm_pk_CryptoKey = await crypto.subtle.importKey(
|
|
"raw",
|
|
ba2ab(hisPubkeyRaw_ba),
|
|
{name: 'ECDH', namedCurve:'P-256'},
|
|
true,
|
|
[]);
|
|
|
|
var Secret = await crypto.subtle.deriveBits(
|
|
{'name': 'ECDH', 'public': comm_pk_CryptoKey },
|
|
myPrivkey,
|
|
256)
|
|
|
|
var commSymmetricKey = await crypto.subtle.importKey(
|
|
"raw",
|
|
ba2ab(ab2ba(Secret).slice(0,16)),
|
|
"AES-GCM", true, ["encrypt", "decrypt"]);
|
|
|
|
return commSymmetricKey;
|
|
}
|
|
|
|
|
|
async function decrypt_tls_responseV3(s, server_write_key, server_write_IV){
|
|
|
|
var swkCryptoKey = await crypto.subtle.importKey("raw",
|
|
ba2ab(server_write_key), "AES-GCM", true, ["encrypt", "decrypt"]);
|
|
|
|
//split up into TLS segments
|
|
var p = 0 //position in the stream
|
|
var seq_num = 0 // seq_num 0 was in the Server Finished message, we will start with seq_num 1
|
|
var cleartext = []
|
|
|
|
while (p < s.length){
|
|
p+=3
|
|
let seglen = ba2int(s.slice(p, p+=2))
|
|
p-=5 //go back
|
|
let segment = s.slice(p, p+=5+seglen)
|
|
if (! eq(segment.slice(0,3), [0x17,0x03,0x03])){
|
|
if (eq(segment.slice(0,3), [0x15,0x03,0x03])){
|
|
console.log('Server sent Alert')
|
|
throw('Server sent Alert')
|
|
}
|
|
else{
|
|
console.log('Server sent an unknown message')
|
|
throw('Server sent an unknown message')
|
|
}
|
|
}
|
|
|
|
var seg_enc = segment.slice(5)
|
|
var explicit_nonce = seg_enc.slice(0,8)
|
|
var nonce = [].concat(server_write_IV, explicit_nonce)
|
|
|
|
var aad = [] //additional_data
|
|
seq_num += 1
|
|
aad = [].concat(aad, bi2ba(seq_num, {fixed:8}))
|
|
aad = [].concat(aad, [0x17,0x03,0x03]) //type 0x17 = Application Data, TLS Version 1.2
|
|
//len(unencrypted data) == len (encrypted data) - len(explicit nonce) - len (auth tag)
|
|
aad = [].concat(aad, bi2ba(seg_enc.length - 8 - 16, {fixed:2}))
|
|
|
|
try {
|
|
var cleartext_segment = await crypto.subtle.decrypt(
|
|
{name: 'AES-GCM', iv: ba2ab(nonce), additionalData: ba2ab(aad)},
|
|
swkCryptoKey,
|
|
ba2ab(seg_enc.slice(8))); //encrypted segment is prepended with 8 bytes of IV
|
|
}
|
|
catch (e) {
|
|
console.log(e)
|
|
throw('Decryption error', e.name)
|
|
}
|
|
cleartext = [].concat(cleartext, ab2ba(cleartext_segment))
|
|
}
|
|
assert(p == s.length)
|
|
return cleartext
|
|
}
|
|
|
|
|
|
|
|
async function decrypt_tls_responseV4(encRecords, server_write_key, server_write_IV){
|
|
|
|
var swkCryptoKey = await crypto.subtle.importKey("raw",
|
|
ba2ab(server_write_key), "AES-GCM", true, ["encrypt", "decrypt"]);
|
|
|
|
var seq_num = 0 // seq_num 0 was in the Server Finished message, we will start with seq_num 1
|
|
var cleartext = []
|
|
|
|
for (let rec of encRecords){
|
|
var explicit_nonce = rec.slice(0,8)
|
|
var nonce = [].concat(server_write_IV, explicit_nonce)
|
|
|
|
var aad = [] //additional_data
|
|
seq_num += 1
|
|
aad = [].concat(aad, bi2ba(seq_num, {fixed:8}))
|
|
aad = [].concat(aad, [0x17,0x03,0x03]) //type 0x17 = Application Data, TLS Version 1.2
|
|
//len(unencrypted data) == len (encrypted data) - len(explicit nonce) - len (auth tag)
|
|
aad = [].concat(aad, bi2ba(rec.length - 8 - 16, {fixed:2}))
|
|
|
|
try {
|
|
var cleartext_segment = await crypto.subtle.decrypt(
|
|
{name: 'AES-GCM', iv: ba2ab(nonce), additionalData: ba2ab(aad)},
|
|
swkCryptoKey,
|
|
ba2ab(rec.slice(8))); //encrypted segment is prepended with 8 bytes of IV
|
|
}
|
|
catch (e) {
|
|
console.log(e)
|
|
throw('Decryption error', e.name)
|
|
}
|
|
cleartext = [].concat(cleartext, ab2ba(cleartext_segment))
|
|
}
|
|
return cleartext
|
|
}
|
|
|
|
|
|
//verify signature over EC parameters from Server Key Exchange
|
|
async function verifyECParamsSig(cert_ba, ECpubkey, sig, cr, sr){
|
|
var nb64url = b64urlencode(getModulus(cert_ba));
|
|
//JSON web key format for public key with exponent 65537
|
|
jwk = {'kty':'RSA', 'use':'sig', 'e': 'AQAB', 'n': nb64url}
|
|
|
|
var to_be_signed = [].concat(cr, sr, [0x03, 0x00, 0x17, 0x41], ECpubkey) //4 bytes of EC Diffie-Hellman Server Params + pubkey
|
|
try {
|
|
var rsa_pubkey = await crypto.subtle.importKey(
|
|
"jwk",
|
|
jwk,
|
|
{name: 'RSASSA-PKCS1-v1_5', hash:'SHA-256'},
|
|
true, ["verify"]);
|
|
var result = await crypto.subtle.verify('RSASSA-PKCS1-v1_5', rsa_pubkey, ba2ab(sig), ba2ab(to_be_signed))
|
|
} catch (e) {
|
|
console.log(e, e.name)
|
|
throw(e)
|
|
}
|
|
if (!result) return false
|
|
else return true;
|
|
}
|
|
|
|
|
|
async function verifyNotarySig(sigDER, pkPEM, signed_data_ba){
|
|
var sig_p1363 = sigDER2p1363(sigDER)
|
|
var notaryPubkey_ba = pubkeyPEM2raw(pkPEM)
|
|
try {
|
|
var notary_pk_CryptoKey = await crypto.subtle.importKey(
|
|
"raw", ba2ab(notaryPubkey_ba), {name: 'ECDSA', namedCurve:'P-256'}, true, ["verify"]);
|
|
var result = await crypto.subtle.verify(
|
|
{'name':'ECDSA', 'hash':'SHA-256'}, notary_pk_CryptoKey, ba2ab(sig_p1363), ba2ab(signed_data_ba))
|
|
if (!result) throw('notary signature verification failed')
|
|
} catch (e) {
|
|
console.log(e, e.name)
|
|
throw(e)
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (typeof module !== 'undefined'){ //we are in node.js environment
|
|
module.exports={
|
|
computeCommitHash,
|
|
decrypt_tls_responseV4,
|
|
start_audit,
|
|
verifyECParamsSig,
|
|
verifyNotarySig,
|
|
getExpandedKeys
|
|
}
|
|
} |