make compatible with pagesigner-cli

This commit is contained in:
themighty1
2021-12-08 09:59:29 +03:00
parent 665163c1f1
commit fe6a75e41f
24 changed files with 241 additions and 15504 deletions

View File

@@ -43,16 +43,19 @@ class FakeFS{
// All future invocations of Pagesigner use these serialized cached circuits.
export class FirstTimeSetup{
async start(pm){
const worker = new Worker(chrome.extension.getURL('core/twopc/webWorkers/serializeCircuits.js'));
console.log('parsing circuits, this is done only once on first launch and will take ~30 secs');
console.time('parsing raw circuits');
const url = chrome.extension.getURL('core/twopc/webWorkers/serializeCircuits.js')
const worker = new Worker(url);
console.log('Parsing circuits. This is done only once on first launch and will take ~30 secs');
console.time('time_to_parse');
const obj = {};
const oldfs = window['fs'];
window['fs'] = new FakeFS();
await window['fs'].init();
if (typeof(window) !== 'undefined') {
// we are in the browser; init a fake filesystem
window['fs'] = new FakeFS();
await window['fs'].init();
}
// start from the last circuits in order to give the user a quicker initial update
for (let n=1; n < 7; n++){
const i = 7-n;
const i = 7-n;
const text = CASM.parseAndAssemble('c'+i+'.casm');
const newobj = await new Promise(function(resolve) {
worker.onmessage = function(event) {
@@ -63,82 +66,10 @@ export class FirstTimeSetup{
worker.postMessage({'text': text});
});
obj[i] = newobj;
pm.update('first_time', {'current': n, 'total': 6});
if (pm) pm.update('first_time', {'current': n, 'total': 6});
await wait(100); // make sure update reaches popup
}
window['fs'] = oldfs;
console.timeEnd('parsing raw circuits');
console.timeEnd('time_to_parse');
return obj;
}
parseCircuitFirstTime(text){
const obj = {};
// we don't do any sanity/formatting checks because the circuits
// were output by casm.js and have a well-defined structure
const rows = text.split('\n');
obj['gatesCount'] = Number(rows[0].split(' ')[0]);
console.log('obj[\'gatesCount\']', obj['gatesCount']);
obj['wiresCount'] = Number(rows[0].split(' ')[1]);
obj['notaryInputSize'] = Number(rows[1].split(' ')[1]);
obj['clientInputSize'] = Number(rows[1].split(' ')[2]);
obj['outputSize'] = Number(rows[2].split(' ')[1]);
// each gate is serialized as
// 1 byte: gate type XOR==0 AND==1 INV==2
// 3 bytes: 1st input wire number
// 3 bytes: 2nd input wire number
// 3 bytes: output wire number
const gateByteSize = 10;
const opBytes = {'XOR': 0, 'AND': 1, 'INV': 2};
// first 3 rows are not gates but metadata
const blob = new Uint8Array((rows.length-3)*gateByteSize);
let blobOffset = 0;
let andCount = 0;
for (let i=0; i < (rows.length-3); i++){
const gate = rows[3+i];
const tokens = gate.split(' ');
const op = tokens[tokens.length-1];
const opByte = opBytes[op];
blob.set([opByte], blobOffset);
blobOffset+=1;
if (op === 'XOR' || op === 'AND'){
const in1 = this.intToThreeBytes(tokens[tokens.length-4]);
const in2 = this.intToThreeBytes(tokens[tokens.length-3]);
const out = this.intToThreeBytes(tokens[tokens.length-2]);
blob.set(in1, blobOffset);
blobOffset+=3;
blob.set(in2, blobOffset);
blobOffset+=3;
blob.set(out, blobOffset);
blobOffset+=3;
if (op == 'AND'){
andCount+=1;
}
}
else if (op === 'INV'){
const in1 = this.intToThreeBytes(tokens[tokens.length-3]);
const out = this.intToThreeBytes(tokens[tokens.length-2]);
blob.set(in1, blobOffset);
blobOffset+=3;
blob.set([0,0,0], blobOffset);
blobOffset+=3;
blob.set(out, blobOffset);
blobOffset+=3;
}
else {
throw('unknown op');
}
}
obj['andGateCount'] = andCount;
obj['gatesBlob'] = blob;
return obj;
}
intToThreeBytes(i){
const byteArray = Array(3);
byteArray[0] = (i >> 16) & 0xFF;
byteArray[1] = (i >> 8) & 0xFF;
byteArray[2] = i & 0xFF;
return byteArray;
}
}
}

View File

@@ -1,20 +1,19 @@
/* eslint-disable no-import-assign */
/* eslint-disable no-case-declarations */
import {parse_certs, verifyChain, checkCertSubjName, getCommonName, getAltNames} from './verifychain.js';
import {ba2str, b64decode, concatTA, int2ba, sha256, b64encode, str2ba, verifySig, assert,
ba2int, getTime, dechunk_http, gunzip_http, xor, eq, wait, AESECBencrypt, wildcardTest,
pubkeyPEM2raw, ba2hex} from './utils.js';
import {getPref, getSessionBlob, getSession, getAllSessions, saveNewSession as saveNewSession, init_db,
import {parse_certs, verifyChain, getCommonName, getAltNames} from './verifychain.js';
import {ba2str, b64decode, concatTA, int2ba, sha256, b64encode, verifySig, assert,
ba2int, xor, eq, wait, AESECBencrypt, wildcardTest, pubkeyPEM2raw, import_resource} from './utils.js';
import {getPref, getSessionBlob, getSession, getAllSessions, saveNewSession, init_db,
addNewPreference, setPref, renameSession, deleteSession} from './indexeddb.js';
import {global} from './globals.js';
import {globals} from './globals.js';
import {Socket} from './Socket.js';
import {TLS, getExpandedKeys, decrypt_tls_responseV6} from './TLS.js';
import {verify_oracle as verifyNotary, getURLFetcherDoc} from './oracles.js';
import {verifyNotary, getURLFetcherDoc} from './oracles.js';
import {TLSNotarySession} from './TLSNotarySession.js';
import {ProgressMonitor} from './ProgressMonitor.js';
import {FirstTimeSetup} from './FirstTimeSetup.js';
class Main{
export class Main{
constructor(){
this.messageListener;
this.notarization_in_progress = false;
@@ -31,13 +30,15 @@ class Main{
// trustedOracle is an object {'IP':<IP address>, 'pubkeyPEM':<pubkey in PEM format>}
// describing the oracle server which was verified and can be used for notarization.
this.trustedOracle = null;
this.is_chrome = window.navigator.userAgent.match('Chrome') ? true : false;
this.is_firefox = window.navigator.userAgent.match('Firefox') ? true : false;
this.is_edge = window.navigator.userAgent.match('Edg') ? true : false;
this.is_opera = window.navigator.userAgent.match('OPR') ? true : false;
// pm is the only instance of ProgressMonitor that is reused between
// notarization sesions
this.pm = new ProgressMonitor();
if (typeof(window) != 'undefined') {
this.is_chrome = window.navigator.userAgent.match('Chrome') ? true : false;
this.is_firefox = window.navigator.userAgent.match('Firefox') ? true : false;
this.is_edge = window.navigator.userAgent.match('Edg') ? true : false;
this.is_opera = window.navigator.userAgent.match('OPR') ? true : false;
// pm is the only instance of ProgressMonitor that is reused between
// notarization sesions
this.pm = new ProgressMonitor();
}
// trustedOracleReady will be set to true after we performed AWS HTTP queries
// and verified that an oracle is trusted (we verify only once)
this.trustedOracleReady = false;
@@ -46,7 +47,7 @@ class Main{
async main() {
// perform browser-specific init first
if (this.is_edge || this.is_firefox || this.is_opera){
global.usePythonBackend = true;
globals.usePythonBackend = true;
}
if (this.is_firefox){
// Firefox asks user for permission to access the current website.
@@ -84,9 +85,13 @@ class Main{
if (await getPref('trustedOracle') === null){
await addNewPreference('trustedOracle', {});
}
await parse_certs();
if (global.useNotaryNoSandbox){
await this.queryNotaryNoSandbox(global.defaultNotaryIP);
const text = await import_resource('core/third-party/certs.txt');
await parse_certs(text);
if (globals.useNotaryNoSandbox){
const obj = await this.queryNotaryNoSandbox(globals.defaultNotaryIP);
this.trustedOracle = obj;
this.trustedOracleReady = true;
await setPref('trustedOracle', obj);
return;
}
this.trustedOracle = await getPref('trustedOracle');
@@ -100,17 +105,17 @@ class Main{
return;
}
// on first launch trustedOracle is not set, verify the default one asynchronously
if (await this.pingNotary(global.defaultNotaryIP) !== true){
await this.tryBackupNotary(global.defaultNotaryIP);
if (await this.pingNotary(globals.defaultNotaryIP) !== true){
await this.tryBackupNotary(globals.defaultNotaryIP);
return;
}
// notary is online
const URLFetcherDoc = await getURLFetcherDoc(global.defaultNotaryIP);
const URLFetcherDoc = await getURLFetcherDoc(globals.defaultNotaryIP, globals.defaultNotaryPort);
const trustedPubkeyPEM = await verifyNotary(URLFetcherDoc);
assert(trustedPubkeyPEM != undefined);
// verification was successful
const obj = {
'IP': global.defaultNotaryIP,
'IP': globals.defaultNotaryIP,
'pubkeyPEM': trustedPubkeyPEM,
'URLFetcherDoc': URLFetcherDoc
};
@@ -121,25 +126,22 @@ class Main{
async queryNotaryNoSandbox(IP){
// just fetch the pubkey and trust it
const resp = await fetch('http://'+IP+':' + global.defaultNotaryPort + '/getPubKey', {
const resp = await fetch('http://'+IP+':' + globals.defaultNotaryPort + '/getPubKey', {
method: 'POST',
mode: 'cors',
cache: 'no-store',
});
const trustedPubkeyPEM = await resp.text();
const obj = {
return {
'IP': IP,
'pubkeyPEM': trustedPubkeyPEM,
};
await setPref('trustedOracle', obj);
this.trustedOracle = obj;
this.trustedOracleReady = true;
}
// pingNotary returns true if notary's IP address is reachable
async pingNotary(IP){
// ping the notary, it should respond with 404 not found
const fProm = fetch('http://'+IP+':'+ global.defaultNotaryPort + '/ping', {
const fProm = fetch('http://'+IP+':'+ globals.defaultNotaryPort + '/ping', {
mode: 'no-cors'
});
const out = await Promise.race([fProm, wait(5000)])
@@ -156,7 +158,7 @@ class Main{
// tryBackupNotary tries to use a backup notary. It checks that the backup notary
// is not the same as failedNotaryIP
async tryBackupNotary(failedNotaryIP){
const resp = await fetch(global.backupUrl);
const resp = await fetch(globals.backupUrl);
const backupIP = await resp.text();
if (backupIP === failedNotaryIP){
throw('Notary is unreachable. Please let the Pagesigner devs know about this.');
@@ -165,7 +167,7 @@ class Main{
console.log('Backup notary is unreachable.');
throw('Notary is unreachable. Please let the Pagesigner devs know about this.');
}
const URLFetcherDoc = await getURLFetcherDoc(backupIP);
const URLFetcherDoc = await getURLFetcherDoc(backupIP, globals.defaultNotaryPort);
const trustedPubkeyPEM = await verifyNotary(URLFetcherDoc);
assert(trustedPubkeyPEM != undefined);
const obj = {
@@ -458,7 +460,7 @@ class Main{
x.splice(0, 3);
const resource_url = x.join('/');
const http_version = global.useHTTP11 ? ' HTTP/1.1':' HTTP/1.0';
const http_version = globals.useHTTP11 ? ' HTTP/1.1':' HTTP/1.0';
let headers = obj.method + ' /' + resource_url + http_version + '\r\n';
// Chrome doesnt add Host header. Firefox does
if (this.is_chrome){
@@ -604,11 +606,11 @@ class Main{
}
const circuits = await getPref('parsedCircuits');
const session = new TLSNotarySession(
server, port, headers, this.trustedOracle, global.sessionOptions, circuits, this.pm);
server, port, headers, this.trustedOracle, globals.sessionOptions, circuits, this.pm);
const obj = await session.start();
obj['title'] = 'PageSigner notarization file';
obj['version'] = 6;
if (! global.useNotaryNoSandbox){
if (! globals.useNotaryNoSandbox){
obj['URLFetcher attestation'] = this.trustedOracle.URLFetcherDoc;
}
const [host, request, response, date] = await this.verifyPgsgV6(obj);
@@ -704,7 +706,7 @@ class Main{
assert(obj['version'] === 6);
// Step 1. Verify URLFetcher attestation doc and get notary's pubkey
if (! global.useNotaryNoSandbox){
if (! globals.useNotaryNoSandbox){
// by default we verify that the notary is indeed a properly sandboxed machine
var URLFetcherDoc = obj['URLFetcher attestation'];
var notaryPubkey = await verifyNotary(URLFetcherDoc);
@@ -844,9 +846,7 @@ class Main{
const responseRecords = await decrypt_tls_responseV6(
obj['server response records'], swk, siv);
const response = ba2str(concatTA(...responseRecords));
console.log(response);
return [host, request, response, date.toGMTString()];
return [host, request, response, date.toGMTString()];
}
async importPgsgAndShow(importedData) {
@@ -1045,8 +1045,8 @@ class Main{
}
async useNotaryNoSandbox(IP){
global.defaultNotaryIP = IP;
global.useNotaryNoSandbox = true;
globals.defaultNotaryIP = IP;
globals.useNotaryNoSandbox = true;
await this.queryNotaryNoSandbox(IP);
}
}
@@ -1062,12 +1062,4 @@ if (typeof(window) != 'undefined') {
text: err
});
});
}
if (typeof module !== 'undefined'){ // we are in node.js environment
module.exports={
save_session,
verifyPgsg
};
}

View File

@@ -8,7 +8,7 @@
// We do not communicate directly with the server but we send messages to the helper app
// It is the helper app which opens a TCP socket and sends/receives data
import {global} from './globals.js';
import {globals} from './globals.js';
import {ba2str, b64decode, concatTA, b64encode, str2ba, ba2int} from './utils.js';
export class Socket {
@@ -42,7 +42,7 @@ export class Socket {
}, that.connectTimeout);
const msg = {'command': 'connect','args': {'name': that.name,'port': that.port},'uid': that.uid};
if (global.usePythonBackend){
if (globals.usePythonBackend){
const url = 'http://127.0.0.1:' + that.backendPort;
const payload = JSON.stringify(msg);
try{
@@ -58,7 +58,7 @@ export class Socket {
return;
}
else {
chrome.runtime.sendMessage(global.appId, msg, function(response) {resolve(response);});
chrome.runtime.sendMessage(globals.appId, msg, function(response) {resolve(response);});
}
})
.catch(function(e){
@@ -87,13 +87,13 @@ export class Socket {
async send(data_in) {
var msg = {'command': 'send', 'args': {'data': Array.from(data_in)}, 'uid': this.uid};
if (global.usePythonBackend){
if (globals.usePythonBackend){
msg.args.data = Array.from(b64encode(msg.args.data));
await fetch('http://127.0.0.1:20022', {method:'POST', body: JSON.stringify(msg),
cache: 'no-store'});
}
else{
chrome.runtime.sendMessage(global.appId, msg);
chrome.runtime.sendMessage(globals.appId, msg);
}
}
@@ -105,7 +105,7 @@ export class Socket {
var that = this;
var response = await new Promise(async function(resolve){
var msg = {'command': 'recv', 'uid': that.uid};
if (global.usePythonBackend){
if (globals.usePythonBackend){
var req = await fetch('http://127.0.0.1:20022', {method:'POST', body: JSON.stringify(msg),
cache: 'no-store'});
var text = new Uint8Array(await req.arrayBuffer());
@@ -116,7 +116,7 @@ export class Socket {
resolve(response);
}
else {
chrome.runtime.sendMessage(global.appId, msg, function(response) {resolve(response);});
chrome.runtime.sendMessage(globals.appId, msg, function(response) {resolve(response);});
}
});
if (response.data.length > 0){
@@ -130,11 +130,8 @@ export class Socket {
// fetchLoop has built up the recv buffer
// check if there are complete records in the buffer,return them if yes or wait some more if no
recv (is_handshake) {
if (is_handshake == undefined) {
is_handshake = false;
}
var that = this;
recv (is_handshake = false) {
const that = this;
return new Promise(function(resolve, reject) {
var dataLastSeen = new Date().getTime();
var complete_records = new Uint8Array();
@@ -179,7 +176,7 @@ export class Socket {
const rv = that.check_complete_records(buf);
complete_records = concatTA(complete_records, rv.comprecs);
if (!rv.is_complete) {
console.log('check_complete_records failed', that.uid);
console.log('waiting for complete records...', that.uid);
buf = rv.incomprecs;
setTimeout(function() {check();}, 100);
return;
@@ -210,12 +207,12 @@ export class Socket {
this.wasClosed = true;
var msg = {'command': 'close','uid': this.uid};
console.log('closing socket', this.uid);
if (global.usePythonBackend){
if (globals.usePythonBackend){
await fetch('http://127.0.0.1:20022', {method:'POST', body: JSON.stringify(msg),
cache: 'no-store'});
}
else {
chrome.runtime.sendMessage(global.appId, msg);
chrome.runtime.sendMessage(globals.appId, msg);
}
}

View File

@@ -1,8 +1,5 @@
import {TWOPC} from './twopc/TWOPC.js';
import {global} from './globals.js';
import {ba2str, b64decode, concatTA, int2ba, sha256, b64encode, str2ba, assert,
ba2int, dechunk_http, gunzip_http, getRandom, sigDER2p1363, pubkeyPEM2raw, eq,
xor, AESECBencrypt, buildChunkMetadata, b64urlencode} from './utils.js';
import {concatTA, int2ba, sha256, str2ba, assert, ba2int, getRandom, sigDER2p1363,
pubkeyPEM2raw, eq, xor, AESECBencrypt, b64urlencode} from './utils.js';
import {verifyChain, checkCertSubjName} from './verifychain.js';
import {Socket} from './Socket.js';
@@ -44,7 +41,12 @@ export class TLS {
this.useMaxFragmentLength = options.useMaxFragmentLength;
this.notaryWillEncryptRequest = options.notaryWillEncryptRequest;
this.mustVerifyCert = options.mustVerifyCert;
this.sckt = new Socket(serverName, port);
if (typeof(window) !== 'undefined'){
this.sckt = new Socket(serverName, port);
} else {
// in node SocketNode was made global
this.sckt = new SocketNode(serverName, port);
}
}
buildClientHello(){
@@ -614,14 +616,4 @@ export async function getExpandedKeys(preMasterSecret, cr, sr){
const client_write_IV = ek.slice(32, 36);
const server_write_IV = ek.slice(36, 40);
return [client_write_key, server_write_key, client_write_IV, server_write_IV, MS_CryptoKey];
}
if (typeof module !== 'undefined'){ // we are in node.js environment
module.exports={
computeCommitHash,
decrypt_tls_responseV5,
start_audit,
getExpandedKeys
};
}

View File

@@ -1,6 +1,6 @@
import {TWOPC} from './twopc/TWOPC.js';
import {TLS} from './TLS.js';
import {concatTA, sha256, assert, eq, xor, str2ba, pubkeyPEM2raw} from './utils.js';
import {concatTA, sha256, assert, xor, str2ba} from './utils.js';
// class TLSNotarySession impements one notarization session
@@ -17,7 +17,7 @@ export class TLSNotarySession{
request;
notary;
pm;
constructor(server, port, request, notary, sessionOptions, circuits, progressMonitor){
constructor(server, port, request, notary, sessionOptions, circuits, progressMonitor){
this.twopc = new TWOPC(notary, request.length, circuits, progressMonitor);
this.tls = new TLS(server, port, request, sessionOptions);
this.probeTLS = new TLS(server, port, request, sessionOptions);
@@ -35,9 +35,9 @@ export class TLSNotarySession{
const serverEcPubkey = await this.tls.receiveAndParseServerHello();
const serverX = serverEcPubkey.slice(1,33);
const serverY = serverEcPubkey.slice(33,65);
this.pm.update('last_stage', {'current': 3, 'total': 10});
if ( this.pm) this.pm.update('last_stage', {'current': 3, 'total': 10});
const [pmsShare, cpubBytes] = await this.twopc.getECDHShare(serverX, serverY);
this.pm.update('last_stage', {'current': 4, 'total': 10});
if ( this.pm) this.pm.update('last_stage', {'current': 4, 'total': 10});
await this.tls.buildClientKeyExchange(cpubBytes);
const [cr, sr] = await this.tls.getRandoms();
@@ -47,13 +47,13 @@ export class TLSNotarySession{
await this.tls.sendClientFinished(encCF, tagCF);
const encSF = await this.tls.receiveServerFinished();
await this.twopc.checkServerFinished(encSF, this.tls.getAllHandshakes());
this.pm.update('last_stage', {'current': 5, 'total': 10});
if ( this.pm) this.pm.update('last_stage', {'current': 5, 'total': 10});
const encCountersForRequest = await this.twopc.getEncryptedCounters();
this.pm.update('last_stage', {'current': 9, 'total': 10});
if ( this.pm) this.pm.update('last_stage', {'current': 9, 'total': 10});
const encRequestBlocks = this.encryptRequest(this.request, encCountersForRequest);
const gctrBlocks = await this.twopc.getGctrBlocks();
this.pm.update('last_stage', {'current': 10, 'total': 10});
if ( this.pm) this.pm.update('last_stage', {'current': 10, 'total': 10});
const [ghashOutputs, ghashInputsBlob] = await this.twopc.getTagFromPowersOfH(encRequestBlocks);
await this.tls.buildAndSendRequest(gctrBlocks, ghashOutputs, encRequestBlocks);
const serverRecords = await this.tls.receiveServerResponse();

View File

@@ -1,4 +1,4 @@
export const global = {
export const globals = {
// defaultNotaryIP/Port is the default IP address/port of the notary server.
// If this IP address becomes unavailable, Pagesigner will query backupUrl for
// a new notary's IP address and will save the new IP address in the preferences.

View File

@@ -1,4 +1,3 @@
import {global} from './globals.js';
import {ba2str, b64decode, assert, ba2int, verifyAttestationDoc, ba2hex, eq,
sha256} from './utils.js';
@@ -224,11 +223,11 @@ async function fetch_and_parse(obj){
return xmlDoc;
}
export async function getURLFetcherDoc(IP){
export async function getURLFetcherDoc(IP, port = 10011){
// get URLFetcher document containing attestation for AWS HTTP API URLs needed to
// verify that the oracle was correctly set up.
// https://github.com/tlsnotary/URLFetcher
const resp = await fetch('http://' + IP + ':' + global.defaultNotaryPort + '/getURLFetcherDoc', {
const resp = await fetch('http://' + IP + ':' + port + '/getURLFetcherDoc', {
method: 'POST',
mode: 'cors',
cache: 'no-store',
@@ -236,7 +235,7 @@ export async function getURLFetcherDoc(IP){
return new Uint8Array(await resp.arrayBuffer());
}
export async function verify_oracle(URLFetcherDoc) {
export async function verifyNotary(URLFetcherDoc) {
// URLFetcherDoc is a concatenation of 4-byte transcript length | transcript | attestation doc
const transcriptLen = ba2int(URLFetcherDoc.slice(0,4));
const transcript = URLFetcherDoc.slice(4,4+transcriptLen);

View File

@@ -13,7 +13,7 @@ sed -i 's#from "bytestreamjs"#from "./bytestream.js"#g' *
bigint-crypto-utils.esm.js from
https://github.com/juanelas/bigint-crypto-utils/tree/954321199d4a8038c3d27113aec825ff2e5bf544/dist/bundles
https://github.com/juanelas/bigint-crypto-utils/tree/954321199d4a8038c3d27113aec825ff2e5bf544/dist/bundles -> math.js
https://github.com/Azero123/simple-js-ec-math
browserify simple-js-ec-math/src/index.js --standalone ECSimple > simple-js-ec-math.js
@@ -24,7 +24,7 @@ cbor.js and cose.js are used to verify the enclave attestation document
cbor.js is from https://github.com/paroga/cbor-js/blob/master/cbor.js
cose.js was built with browserify coseverify.js --standalone COSE > cose.js
pako.js ---> SOURCE???
https://github.com/jedisct1/libsodium.js/blob/354ec814b92e2e5ba0471e9a8a96c1e21c30f171/dist/browsers-sumo/sodium.js --> sodium.js
certs.txt is Mozilla'a root store taken from
https://ccadb-public.secure.force.com/mozilla/IncludedCACertificateReportPEMCSV

View File

@@ -1,76 +0,0 @@
/* This is free and unencumbered software released into the public domain. */
"use strict";
const CHACHA_ROUNDS = 20;
const CHACHA_KEYSIZE = 32;
const CHACHA_IVSIZE = 8;
/**
* Create a new ChaCha generator seeded from the given KEY and IV.
* Both KEY and IV must be ArrayBuffer objects of at least the
* appropriate byte lengths (CHACHA_KEYSIZE, CHACHA_IVSIZE). Bytes
* beyond the specified length are ignored.
*
* Returns a closure that takes no arguments. This closure, when
* invoked, returns a 64-byte ArrayBuffer of the next 64 bytes of
* output. Note: This buffer object is "owned" by the closure, and
* the same buffer object is always returned, each time filled with
* fresh output.
*/
function ChaCha(key, iv) {
if (key.byteLength < CHACHA_KEYSIZE)
throw new Error('key too short');
if (iv.byteLength < CHACHA_IVSIZE)
throw new Error('IV too short');
const state = new Uint32Array(16);
let view = new DataView(key);
state[ 0] = 0x61707865; // "expand 32-byte k"
state[ 1] = 0x3320646e; //
state[ 2] = 0x79622d32; //
state[ 3] = 0x6b206574; //
state[ 4] = view.getUint32( 0, true);
state[ 5] = view.getUint32( 4, true);
state[ 6] = view.getUint32( 8, true);
state[ 7] = view.getUint32(12, true);
state[ 8] = view.getUint32(16, true);
state[ 9] = view.getUint32(20, true);
state[10] = view.getUint32(24, true);
state[11] = view.getUint32(28, true);
view = new DataView(iv);
state[14] = view.getUint32( 0, true);
state[15] = view.getUint32( 4, true);
/* Generator */
const output = new Uint32Array(16);
const outview = new DataView(output.buffer);
return function() {
function quarterround(x, a, b, c, d) {
function rotate(v, n) { return (v << n) | (v >>> (32 - n)); }
x[a] += x[b]; x[d] = rotate(x[d] ^ x[a], 16);
x[c] += x[d]; x[b] = rotate(x[b] ^ x[c], 12);
x[a] += x[b]; x[d] = rotate(x[d] ^ x[a], 8);
x[c] += x[d]; x[b] = rotate(x[b] ^ x[c], 7);
}
output.set(state);
for (let i = 0; i < CHACHA_ROUNDS; i += 2) {
quarterround(output, 0, 4, 8, 12);
quarterround(output, 1, 5, 9, 13);
quarterround(output, 2, 6, 10, 14);
quarterround(output, 3, 7, 11, 15);
quarterround(output, 0, 5, 10, 15);
quarterround(output, 1, 6, 11, 12);
quarterround(output, 2, 7, 8, 13);
quarterround(output, 3, 4, 9, 14);
}
for (let i = 0; i < 16; i++)
outview.setUint32(i * 4, output[i] + state[i], true);
state[12]++;
if (state[12] == 0) {
state[13]++;
if (state[13] == 0) {
/* One zebibyte of output reached! */
throw new Error('output exhausted');
}
}
return output.buffer;
}
}

View File

@@ -1,10 +1,11 @@
// this file is not used by the extension, it serves as an input to to create cose.js with:
// browserify coseverify.js --standalone COSE > cose.js
const cose = require('cose-js');
//const cose = require('cose-js');
import * as cose from 'cose-js'
// x,y,doc is an ArrayBuffer
const verify = function (x, y, doc){
export const verify = function (x, y, doc){
const verifier = {
'key': {
'x': Buffer.from(x),

View File

@@ -1,3 +1,4 @@
/**
* Absolute value. abs(a)==a if a>=0. abs(a)==-a if a<0
*

File diff suppressed because it is too large Load Diff

6373
core/third-party/pako.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -72,7 +72,7 @@ export class Garbler{
// garble the rest of the circuits asyncronously
const allPromises = [];
for (let cNo =1; cNo < this.s.cs.length; cNo++){
for (let cNo = 1; cNo < this.s.cs.length; cNo++){
if (cNo === 5){
allPromises.push(Promise.resolve('empty'));
continue;

View File

@@ -3,7 +3,7 @@ import {concatTA, int2ba, assert, encrypt_generic, decrypt_generic} from './../u
export class OT{
// class OT implements oblivious transfer protocol based on
// [1] The Simplest Protocol for Oblivious Transfer
// Chou-Orlandi "Simplest OT"
// as much pre-computation as possiblle is done in the offline phase
constructor(){

View File

@@ -1,8 +1,14 @@
/* eslint-disable no-console */
/* eslint-disable max-classes-per-file */
import * as bcu from './../third-party/math.js';
import {ba2str, concatTA, int2ba, str2ba, ba2hex, ba2int} from './../utils.js';
var bcu
if (typeof(window) !== 'undefined'){
bcu = await import('./../third-party/math.js');
} else {
// we are in node. bcuNode must have been made global
bcu = bcuNode
}
import {ba2str, concatTA, int2ba, str2ba, ba2int} from './../utils.js';
function pad(str) {
if (str.length % 2 === 1) {

View File

@@ -10,7 +10,7 @@ import {Evaluator} from './Evaluator.js';
import {OT} from './OT.js';
import {Paillier2PC} from './Paillier2PC.js';
import {GCWorker} from './GCWorker.js';
import {global} from './../globals.js';
import {globals} from './../globals.js';
// class C is initialized once and then it is a read-only struct
@@ -132,7 +132,7 @@ export class TWOPC {
// destroy de-registers listeners, terminates workers
destroy(){
this.pm.destroy();
if (this.pm) this.pm.destroy();
for (let i=1; i < this.workers.length; i++){
const gcworker = this.workers[i];
for (let j=0; j < gcworker.workers.length; j++){
@@ -784,12 +784,13 @@ export class TWOPC {
}
const fetchPromise = fetch(
'http://'+this.notary.IP+':'+global.defaultNotaryPort+'/'+cmd+'?'+this.uid,
'http://'+this.notary.IP+':'+globals.defaultNotaryPort+'/'+cmd+'?'+this.uid,
{
method: 'POST',
mode: 'cors',
cache: 'no-store',
// keepalive: true, <-- trips up chrome
agent: typeof(window) === 'undefined' ? keepAliveAgent : null,
//keepalive: true, // <-- trips up chrome
body: to_be_sent.buffer
});
const that = this;
@@ -839,7 +840,7 @@ export class TWOPC {
console.log('allFixedInputs.length', allFixedInputsCount);
console.log('precomputePool size', allNonFixedInputsCount + this.ghashOTNeeded);
this.pm.update('last_stage', {'current': 1, 'total': 10});
if (this.pm) this.pm.update('last_stage', {'current': 1, 'total': 10});
const receiverBs = await this.ot.saveDecryptionKeys([].concat(...allFixedInputs));
await this.ot.precomputePool(allNonFixedInputsCount + this.ghashOTNeeded);
@@ -867,7 +868,7 @@ export class TWOPC {
}
}
assert(fixedOTBlob.length === idx*32);
this.pm.update('last_stage', {'current': 2, 'total': 10});
if (this.pm) this.pm.update('last_stage', {'current': 2, 'total': 10});
}
// preCompute() should be called before run() is called
@@ -954,7 +955,7 @@ export class TWOPC {
let chunk = blob.slice(sentSoFar, sentSoFar + oneMB);
await that.send('setBlobChunk', chunk);
sentSoFar += chunk.length;
that.pm.update('upload', {'current': i+1, 'total': chunkCount});
if (that.pm) that.pm.update('upload', {'current': i+1, 'total': chunkCount});
}
that.send('setBlobChunk', str2ba('magic: no more data'));
}
@@ -975,7 +976,7 @@ export class TWOPC {
let chunk = await this.send('getBlobChunk', int2ba(needBytes, 4));
allChunks.push(chunk);
soFarBytes += chunk.length;
this.pm.update('download', {'current': soFarBytes, 'total': totalBytes});
if (this.pm) this.pm.update('download', {'current': soFarBytes, 'total': totalBytes});
}
const rv = concatTA(...allChunks);
assert(rv.length === totalBytes);

View File

@@ -25,12 +25,10 @@ export default class WorkerPool{
promises.push(Promise.resolve('empty'));
}
for (let i=0; i < batch.length; i++){
console.log('round ', i);
// console.log('round ', i);
// wait until we have a free worker
const out = await Promise.any(promises);
if (pm != undefined){
pm.update(pmtype, {'current': i+1, 'total': batch.length});
}
if (pm){pm.update(pmtype, {'current': i+1, 'total': batch.length});}
// find which worker resolved
let worker = null;
let idx = null;

View File

@@ -2,8 +2,7 @@
// of garbled circuits
// eslint-disable-next-line no-undef
importScripts('./../../third-party/nacl-fast.js');
var parentPort_;
let circuit = null;
let truthTable = null;
let timeEvaluating = 0;
@@ -16,11 +15,41 @@ const byteArray = new Uint8Array(24);
let randomPool;
// randomPoolOffset will be moved after data was read from randomPool
let randomPoolOffset = 0;
let garbledAssigment;
let garbledAssigment;
var crypto_;
var nacl;
self.onmessage = function(event) {
const msg = event.data.msg;
const data = event.data.data;
if (typeof(importScripts) !== 'undefined') {
importScripts('./../../third-party/nacl-fast.js');
crypto_ = self.crypto;
self.onmessage = function(event) {
processMessage(event.data);
};
} else {
// we are in nodejs
import('module').then((module) => {
// we cannot use the "import" keyword here because on first pass the browser unconditionaly
// parses this if clause and will error out if "import" is found
// using process.argv instead of import.meta.url to get the name of this script
const filePath = 'file://' + process.argv[1];
// this workaround allows to require() from ES6 modules, which is not allowed by default
const require = module.createRequire(filePath)
nacl = require('tweetnacl')
const { parentPort } = require('worker_threads');
parentPort_ = parentPort
const { Crypto } = require("@peculiar/webcrypto");
crypto_ = new Crypto();
const perf = {'now':function(){return 0;}}
global.performance = perf;
parentPort.on('message', msg => {
processMessage(msg);
})
})
}
function processMessage(obj){
const msg = obj.msg;
const data = obj.data;
if (msg === 'parse'){
circuit = data;
garbledAssigment = new Uint8Array(32*(circuit.wiresCount));
@@ -31,7 +60,6 @@ self.onmessage = function(event) {
truthTable = new Uint8Array(data);
}
else if (msg === 'garble'){
console.log('in garble with circuit', circuit);
if (circuit == null){
console.log('error: need to parse circuit before garble');
return;
@@ -47,7 +75,7 @@ self.onmessage = function(event) {
assert (outputLabels.length === circuit.outputSize*32);
const obj = {'tt': truthTable.buffer, 'il': inputLabels.buffer, 'ol': outputLabels.buffer, 'R': R};
console.timeEnd('garbling done in');
postMessage(obj, [truthTable.buffer, inputLabels.buffer, outputLabels.buffer]);
postMsg(obj, [truthTable.buffer, inputLabels.buffer, outputLabels.buffer]);
}
else if (msg === 'evaluate'){
if (circuit == null || truthTable == null){
@@ -59,12 +87,22 @@ self.onmessage = function(event) {
assert (inputLabels.length === circuit.clientInputSize*16 + circuit.notaryInputSize*16);
const outputLabels = evaluate(circuit, garbledAssigment, truthTable, inputLabels);
assert (outputLabels.length === circuit.outputSize*16);
postMessage(outputLabels.buffer);
postMsg(outputLabels.buffer);
}
else {
console.log('Error: unexpected message in worker');
}
};
}
function postMsg(value, transferList){
if (typeof importScripts !== 'function'){
parentPort_.postMessage({data:value}, transferList)
} else {
postMessage(value, transferList);
}
}
function newR(){
const R = getRandom(16);
@@ -324,7 +362,7 @@ function encrypt(a, b, t, m) {
}
function randomOracle(m, t) {
return self.nacl.secretbox(
return nacl.secretbox(
m,
longToByteArray(t),
sha0,
@@ -372,7 +410,7 @@ function fillRandom(count){
const randomChunks = [];
const chunkCount = Math.ceil(count/65536);
for (let i=0; i < chunkCount; i++){
randomChunks.push(self.crypto.getRandomValues(new Uint8Array(65536)));
randomChunks.push(crypto_.getRandomValues(new Uint8Array(65536)));
}
randomPool = concatTA(...randomChunks);
randomPoolOffset = 0;

View File

@@ -1,32 +1,67 @@
// otworker.js is a WebWorker where most of the heavy computations for
// oblivious transfer happen
// Oblivious Transfer happen. Based on Chou-Orlandi "Simplest OT"
importScripts('./../../third-party/sodium.js');
var sodium
var parentPort_;
self.onmessage = function(event) {
const msg = event.data.msg;
const data = event.data.data;
if (typeof(importScripts) === 'undefined'){
// we are in nodejs
import('module').then((module) => {
// we cannot use the "import" keyword here because on first pass the browser unconditionaly
// parses this if clause and will error out if "import" is found
// using process.argv instead of import.meta.url to get the name of this script
const filePath = 'file://' + process.argv[1];
// this workaround allows to require() from ES6 modules, which is not allowed by default
const require = module.createRequire(filePath)
const { parentPort } = require('worker_threads');
parentPort_ = parentPort
sodium = require('libsodium-wrappers-sumo');
parentPort.on('message', msg => {
processMessage(msg);
})
})
} else {
importScripts('./../../third-party/sodium.js');
self.onmessage = function(event) {
processMessage(event.data);
};
}
function processMessage(obj){
const msg = obj.msg;
const data = obj.data;
if (msg === 'saveDecryptionKeys'){
const bytes = new Uint8Array(data.bytes);
const A = new Uint8Array(data.A);
const rv = saveDecryptionKeys(bytes, A);
postMessage({'blob': rv.buffer});
postMsg({'blob': rv.buffer});
}
else if (msg === 'precomputePool'){
const count = data.count;
const A = new Uint8Array(data.A);
const rv = precomputePool(count, A);
postMessage({'blob': rv.buffer});
postMsg({'blob': rv.buffer});
}
else if (msg === 'prepareEncryptionKeys'){
const bytes = new Uint8Array(data.bytes);
const a = new Uint8Array(data.a, data.A);
const A = new Uint8Array(data.A);
const rv = prepareEncryptionKeys(bytes, a, A);
postMessage({'blob': rv.buffer});
postMsg({'blob': rv.buffer});
}
};
}
function postMsg(value, transferList){
if (typeof importScripts !== 'function'){
parentPort_.postMessage({data:value}, transferList)
} else {
postMessage(value, transferList);
}
}
function prepareEncryptionKeys(bytes, a, A){

View File

@@ -1,10 +1,30 @@
// Serializes the circuit into a compact representation
self.onmessage = function(event) {
const text = event.data.text;
const [obj, blob] = serializeCircuit(text);
postMessage({'obj': obj, blob: blob.buffer});
};
if (typeof(importScripts) === 'undefined'){
// we are in nodejs
import('module').then((module) => {
// we cannot use the "import" keyword here because on first pass the browser unconditionaly
// parses this if clause and will error out if "import" is found
// using process.argv instead of import.meta.url to get the name of this script
const filePath = 'file://' + process.argv[1];
// this workaround allows to require() from ES6 modules, which is not allowed by default
const require = module.createRequire(filePath)
const { parentPort } = require('worker_threads');
parentPort.on('message', msg => {
const text = msg.text;
const [obj, blob] = serializeCircuit(text);
parentPort.postMessage({data: {'obj': obj, blob: blob.buffer}});
})
});
}
else {
self.onmessage = function(event) {
const text = event.data.text;
const [obj, blob] = serializeCircuit(text);
postMessage({'obj': obj, blob: blob.buffer});
};
}
function serializeCircuit(text){
const obj = {};

View File

@@ -168,9 +168,9 @@ export async function verifySig(pubkeyRaw, sig_p1363, signed_digest){
// input is Uint8Array
export async function verifyAttestationDoc(doc){
// extract x,y from EC pubkey
const decoded = window.CBOR.decode(doc.buffer);
const decoded = CBOR.decode(doc.buffer);
const payload = decoded[2].slice(); // need to copy otherwise .buffer will access ab
const doc_obj = window.CBOR.decode(payload.buffer);
const doc_obj = CBOR.decode(payload.buffer);
const leafCertDer = doc_obj.certificate.slice();
const cert_asn1 = asn1js.fromBER(leafCertDer.buffer);
const leafCert = new Certificate({ schema: cert_asn1.result });
@@ -205,7 +205,7 @@ export async function verifyAttestationDoc(doc){
export function b64encode(ba) {
assert(ba instanceof Uint8Array);
if (typeof window === 'undefined') {
if (typeof(window) === 'undefined') {
// running in nodejs
return Buffer.from(ba).toString('base64');
}
@@ -224,29 +224,23 @@ export function b64encode(ba) {
export function b64decode(str) {
if (typeof window === 'undefined') {
let dec;
if (typeof(window) === 'undefined') {
// running in nodejs
return Buffer.from(str, 'base64').toJSON().data;
dec = Buffer.from(str, 'base64');
}
else {
const arr = atob(str).split('').map(function(c) {
dec = atob(str).split('').map(function(c) {
return c.charCodeAt(0);
});
return new Uint8Array(arr);
}
return new Uint8Array(dec);
}
// conform to base64url format replace +/= with -_
export function b64urlencode (ba){
assert(ba instanceof Uint8Array);
let str;
if (typeof window === 'undefined') {
// running in nodejs
str = Buffer.from(ba).toString('base64');
}
else {
str = b64encode(ba);
}
let str = b64encode(ba);
return str.split('+').join('-').split('/').join('_').split('=').join('');
}

View File

@@ -8,7 +8,7 @@ var trustedCertificates = [];
// extract PEMs from Mozilla's CA store and convert into asn1js's Certificate object
export async function parse_certs(){
export async function parse_certs(text){
// wait for pkijs module to load
while (typeof(asn1js) == 'undefined'){
console.log('waiting for pkijs');
@@ -19,7 +19,6 @@ export async function parse_certs(){
});
}
const text = await import_resource('core/third-party/certs.txt');
const lines = text.split('"\n"').slice(1); // discard the first line - headers
for (const line of lines){
const fields = line.split('","');