This repository has been archived by the owner on Jul 21, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tlsn.js
653 lines (550 loc) · 24.6 KB
/
tlsn.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
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
}
}