├── img ├── ledger.png ├── tlsnV3b.png └── androidV2.png ├── proof ├── ledger.proof ├── tlsn1.proof ├── tlsn2.proof ├── tlsn3.proof ├── androidV2.proof ├── computation.proof └── failed │ ├── tlsn1_toFail.proof │ ├── tlsn2_toFail.proof │ ├── tlsn3_toFail.proof │ ├── ledger_toFail.proof │ ├── androidV2_toFail.proof │ ├── computation_toFail.proof │ └── androidV2Newest_toFail.proof ├── .flowconfig ├── .gitignore ├── .snyk ├── .babelrc ├── src ├── test.js ├── helpers.js ├── testFailedProofs.js ├── tlsn │ ├── verifychain │ │ ├── rootcerts.js │ │ └── verifychain.js │ └── tlsn_utils.js ├── cli.js ├── ledger-verify.js ├── computation-verify.js ├── index.js ├── tlsn-verify.js ├── oraclize │ └── oracles.js └── android-verify.js ├── lib ├── tlsn │ ├── verifychain │ │ ├── rootcerts.js │ │ └── verifychain.js │ └── tlsn_utils.js ├── helpers.js ├── test.js ├── testFailedProofs.js ├── cli.js ├── ledger-verify.js ├── index.js ├── computation-verify.js ├── tlsn-verify.js ├── oraclize │ └── oracles.js └── android-verify.js ├── package.json ├── .eslintrc.js ├── README.md └── settings └── settings.json /img/ledger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/provable-things/proof-verification-tool/HEAD/img/ledger.png -------------------------------------------------------------------------------- /img/tlsnV3b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/provable-things/proof-verification-tool/HEAD/img/tlsnV3b.png -------------------------------------------------------------------------------- /img/androidV2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/provable-things/proof-verification-tool/HEAD/img/androidV2.png -------------------------------------------------------------------------------- /proof/ledger.proof: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/provable-things/proof-verification-tool/HEAD/proof/ledger.proof -------------------------------------------------------------------------------- /proof/tlsn1.proof: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/provable-things/proof-verification-tool/HEAD/proof/tlsn1.proof -------------------------------------------------------------------------------- /proof/tlsn2.proof: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/provable-things/proof-verification-tool/HEAD/proof/tlsn2.proof -------------------------------------------------------------------------------- /proof/tlsn3.proof: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/provable-things/proof-verification-tool/HEAD/proof/tlsn3.proof -------------------------------------------------------------------------------- /proof/androidV2.proof: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/provable-things/proof-verification-tool/HEAD/proof/androidV2.proof -------------------------------------------------------------------------------- /proof/computation.proof: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/provable-things/proof-verification-tool/HEAD/proof/computation.proof -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [lints] 8 | 9 | [options] 10 | 11 | [strict] 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .git 2 | node_modules 3 | browserified.js 4 | bundle*.js 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. 2 | version: v1.7.1 3 | ignore: {} 4 | patch: {} 5 | -------------------------------------------------------------------------------- /proof/failed/tlsn1_toFail.proof: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/provable-things/proof-verification-tool/HEAD/proof/failed/tlsn1_toFail.proof -------------------------------------------------------------------------------- /proof/failed/tlsn2_toFail.proof: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/provable-things/proof-verification-tool/HEAD/proof/failed/tlsn2_toFail.proof -------------------------------------------------------------------------------- /proof/failed/tlsn3_toFail.proof: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/provable-things/proof-verification-tool/HEAD/proof/failed/tlsn3_toFail.proof -------------------------------------------------------------------------------- /proof/failed/ledger_toFail.proof: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/provable-things/proof-verification-tool/HEAD/proof/failed/ledger_toFail.proof -------------------------------------------------------------------------------- /proof/failed/androidV2_toFail.proof: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/provable-things/proof-verification-tool/HEAD/proof/failed/androidV2_toFail.proof -------------------------------------------------------------------------------- /proof/failed/computation_toFail.proof: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/provable-things/proof-verification-tool/HEAD/proof/failed/computation_toFail.proof -------------------------------------------------------------------------------- /proof/failed/androidV2Newest_toFail.proof: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/provable-things/proof-verification-tool/HEAD/proof/failed/androidV2Newest_toFail.proof -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-async-to-generator", 4 | "transform-es2015-modules-commonjs", 5 | ], 6 | "presets": ["flow"] 7 | } 8 | -------------------------------------------------------------------------------- /src/test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* eslint-disable no-console */ 3 | 4 | import { getProofType, verifyProof } from './index.js' 5 | const fs = require('fs') 6 | 7 | const testGetProofType = () => { 8 | const proofType_TLSNotary = getProofType('tlsnotary notarization file\n/X]H_<ˆ‘Õ¥ïê') 9 | const proofType_Android = getProofType('AP¿lHTTPResponseY"{"error":[],"result":{') 10 | // $FlowFixMe 11 | console.log(`index.js/getProofType: ${proofType_TLSNotary === 'proofType_TLSNotary' && proofType_Android === 'proofType_Android'}`) // eslint-disable-line no-console 12 | } 13 | 14 | // we have to use literal path so browserify can put the file's content in the bundle 15 | const proofs = [ 16 | fs.readFileSync('./proof/androidV2.proof'), 17 | fs.readFileSync('./proof/androidV2Newest.proof'), 18 | fs.readFileSync('./proof/computation.proof'), 19 | fs.readFileSync('./proof/ledger.proof'), 20 | fs.readFileSync('./proof/tlsn1.proof'), 21 | fs.readFileSync('./proof/tlsn2.proof'), 22 | fs.readFileSync('./proof/tlsn3.proof'), 23 | ] 24 | 25 | const paths = [ 26 | './proof/androidV2.proof', 27 | './proof/androidV2Newest.proof', 28 | './proof/computation.proof', 29 | './proof/ledger.proof', 30 | './proof/tlsn1.proof', 31 | './proof/tlsn2.proof', 32 | './proof/tlsn3.proof', 33 | ] 34 | 35 | 36 | const autoVerify = async () => { 37 | for (let h = 0; h < proofs.length; h++) { 38 | const parsedProof = new Uint8Array(proofs[h]) 39 | console.log('\x1b[32m', 'Proof file: ', paths[h], '\x1b[37m') 40 | try { 41 | const verifiedProof = await verifyProof(parsedProof) 42 | console.log('\x1b[33m', 'Main proof: ', '\x1b[37m', '\n ', verifiedProof.mainProof) 43 | console.log('\x1b[33m', 'Extension proof: ','\x1b[37m', '\n ', verifiedProof.extensionProof) 44 | } catch(e) { 45 | console.log('Error: ', e) 46 | } 47 | } 48 | } 49 | 50 | export const runTest = (() => { 51 | testGetProofType() 52 | // eslint-disable-next-line no-console 53 | autoVerify().then(() => console.log('finish')).catch(e => console.log(e)) 54 | })() 55 | -------------------------------------------------------------------------------- /src/helpers.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import R from 'ramda' 3 | // $FlowFixMe 4 | // import fs from 'fs' 5 | const fs = require('fs') 6 | const filterReduce = R.curry((wrongValue, a, b) => { 7 | if (a === wrongValue) 8 | return b 9 | 10 | if (b === wrongValue) 11 | return a 12 | 13 | return a 14 | }) 15 | 16 | // take valueToDelete and list if in the list there are elements different from valueToDelete return the last one of this elements. 17 | // If not return valueToDelete 18 | export const reduceDeleteValue = R.curry( (valueToDelete, list) => { 19 | return R.reduce(filterReduce(valueToDelete), valueToDelete, list) 20 | }) 21 | 22 | // Subtract smallerList from bigger list: subtractList(['a', 'b', 'c', 'd', 'e', 'f'], ['c', 'd']) => [ 'a', 'b', 'e', 'f' ] 23 | export const subtractList = R.curry( (biggerList, smallerList) => { 24 | return R.reject(R.contains(R.__, smallerList), biggerList) 25 | }) 26 | 27 | export const readDirAsync = (path: string): any => { 28 | return new Promise((resolve, reject) => { 29 | fs.readdir(path, (error, result) => { 30 | if (error) 31 | reject(error) 32 | else 33 | resolve(result) 34 | 35 | }) 36 | }) 37 | } 38 | 39 | export const readFileAsync = (path: string): any => { 40 | return new Promise((resolve, reject) => { 41 | fs.readFile(path, (error, result) => { 42 | if (error) 43 | reject(error) 44 | else 45 | resolve(result) 46 | 47 | }) 48 | }) 49 | } 50 | 51 | // $FlowFixMe 52 | export const writeFileAsync = (path: string, data, binary): any => { 53 | return new Promise((resolve, reject) => { 54 | if ( binary !== 'binary') { 55 | fs.writeFile(path, data, (error, result) => { 56 | if (error) 57 | reject(error) 58 | else 59 | resolve(result) 60 | 61 | }) 62 | } else { 63 | fs.writeFile(path, data, 'binary', (error, result) => { 64 | if (error) 65 | reject(error) 66 | else 67 | resolve(result) 68 | 69 | }) 70 | } 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /src/testFailedProofs.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* eslint-disable no-console */ 3 | 4 | import { getProofType, verifyProof } from './index.js' 5 | const fs = require('fs') 6 | 7 | const testGetProofType = () => { 8 | const proofType_TLSNotary = getProofType('tlsnotary notarization file\n/X]H_<ˆ‘Õ¥ïê') 9 | const proofType_Android = getProofType('AP¿lHTTPResponseY"{"error":[],"result":{') 10 | // $FlowFixMe 11 | console.log(`index.js/getProofType: ${proofType_TLSNotary === 'proofType_TLSNotary' && proofType_Android === 'proofType_Android'}`) // eslint-disable-line no-console 12 | } 13 | 14 | // we have to use literal path so browserify can put the file's content in the bundle 15 | const proofs = [ 16 | fs.readFileSync('./proof/failed/androidV2_toFail.proof'), 17 | fs.readFileSync('./proof/failed/androidV2Newest_toFail.proof'), 18 | fs.readFileSync('./proof/failed/computation_toFail.proof'), 19 | fs.readFileSync('./proof/failed/ledger_toFail.proof'), 20 | fs.readFileSync('./proof/failed/tlsn1_toFail.proof'), 21 | fs.readFileSync('./proof/failed/tlsn2_toFail.proof'), 22 | fs.readFileSync('./proof/failed/tlsn3_toFail.proof'), 23 | ] 24 | 25 | const paths = [ 26 | './proof/failed/androidV2_toFail.proof', 27 | './proof/failed/androidV2Newest_toFail.proof', 28 | './proof/failed/computation_toFail.proof', 29 | './proof/failed/ledger_toFail.proof', 30 | './proof/failed/tlsn1_toFail.proof', 31 | './proof/failed/tlsn2_toFail.proof', 32 | './proof/failed/tlsn3_toFail.proof', 33 | ] 34 | 35 | 36 | const autoVerify = async () => { 37 | for (let h = 0; h < proofs.length; h++) { 38 | const parsedProof = new Uint8Array(proofs[h]) 39 | console.log('\x1b[32m', 'Proof file: ', paths[h], '\x1b[37m') 40 | try { 41 | const verifiedProof = await verifyProof(parsedProof) 42 | console.log('\x1b[33m', 'Main proof: ', '\x1b[37m', '\n ', verifiedProof.mainProof) 43 | console.log('\x1b[33m', 'Extension proof: ','\x1b[37m', '\n ', verifiedProof.extensionProof) 44 | } catch(e) { 45 | console.log('Error: ', e) 46 | } 47 | } 48 | } 49 | 50 | export const runTest = (() => { 51 | testGetProofType() 52 | // eslint-disable-next-line no-console 53 | autoVerify().then(() => console.log('finish')).catch(e => console.log(e)) 54 | })() 55 | -------------------------------------------------------------------------------- /lib/tlsn/verifychain/rootcerts.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 'use strict'; 3 | 4 | var RootCerts = {}; 5 | 6 | const certs = require('./rootcertslist.js').certs; 7 | 8 | // Use hash table for efficiency: 9 | var trusted = Object.keys(certs).reduce(function (trusted, key) { 10 | var pem = certs[key]; 11 | pem = pem.replace(/-----BEGIN CERTIFICATE-----/g, ''); 12 | pem = pem.replace(/-----END CERTIFICATE-----/g, ''); 13 | pem = pem.replace(/\s+/g, ''); 14 | trusted[pem] = key; 15 | return trusted; 16 | }, {}); 17 | 18 | RootCerts.getTrusted = function (pem) { 19 | pem = RootCerts.parsePEM(pem)[0].pem; 20 | if (!Object.prototype.hasOwnProperty.call(trusted, pem)) { 21 | return; 22 | } 23 | return trusted[pem]; 24 | }; 25 | 26 | RootCerts.getCert = function (name) { 27 | name = name.replace(/^s+|s+$/g, ''); 28 | if (!Object.prototype.hasOwnProperty.call(certs, name)) { 29 | return; 30 | } 31 | return certs[name]; 32 | }; 33 | 34 | RootCerts.parsePEM = function (pem) { 35 | pem = pem + ''; 36 | var concatted = pem.trim().split(/-----BEGIN [^\-\r\n]+-----/); 37 | if (concatted.length > 2) { 38 | return concatted.reduce(function (out, pem) { 39 | if (!pem) { 40 | return out; 41 | } 42 | pem = RootCerts.parsePEM(pem)[0].pem; 43 | if (pem) { 44 | out.push(pem); 45 | } 46 | return out; 47 | }, []); 48 | } 49 | var type = /-----BEGIN ([^\-\r\n]+)-----/.exec(pem)[1]; 50 | pem = pem.replace(/-----BEGIN [^\-\r\n]+-----/, ''); 51 | pem = pem.replace(/-----END [^\-\r\n]+-----/, ''); 52 | var parts = pem.trim().split(/(?:\r?\n){2,}/); 53 | var headers = {}; 54 | if (parts.length > 1) { 55 | headers = parts[0].trim().split(/[\r\n]/).reduce(function (out, line) { 56 | var parts = line.split(/:[ \t]+/); 57 | var key = parts[0].trim().toLowerCase(); 58 | var value = (parts.slice(1).join('') || '').trim(); 59 | out[key] = value; 60 | return out; 61 | }, {}); 62 | pem = parts.slice(1).join(''); 63 | } 64 | pem = pem.replace(/\s+/g, ''); 65 | var der = pem ? Buffer.from(pem, 'base64') : null; 66 | return [{ 67 | type: type, 68 | headers: headers, 69 | pem: pem, 70 | der: der, 71 | body: der || Buffer.from([0]) 72 | }]; 73 | }; 74 | 75 | module.exports.certs = certs; 76 | module.exports.trusted = trusted; -------------------------------------------------------------------------------- /src/tlsn/verifychain/rootcerts.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 'use strict'; 3 | 4 | var RootCerts = {}; 5 | 6 | const certs = require('./rootcertslist.js').certs; 7 | 8 | // Use hash table for efficiency: 9 | var trusted = Object.keys(certs).reduce(function(trusted, key) { 10 | var pem = certs[key]; 11 | pem = pem.replace(/-----BEGIN CERTIFICATE-----/g, ''); 12 | pem = pem.replace(/-----END CERTIFICATE-----/g, ''); 13 | pem = pem.replace(/\s+/g, ''); 14 | trusted[pem] = key; 15 | return trusted; 16 | }, {}); 17 | 18 | RootCerts.getTrusted = function(pem) { 19 | pem = RootCerts.parsePEM(pem)[0].pem; 20 | if (!Object.prototype.hasOwnProperty.call(trusted, pem)) { 21 | return; 22 | } 23 | return trusted[pem]; 24 | }; 25 | 26 | RootCerts.getCert = function(name) { 27 | name = name.replace(/^s+|s+$/g, ''); 28 | if (!Object.prototype.hasOwnProperty.call(certs, name)) { 29 | return; 30 | } 31 | return certs[name]; 32 | }; 33 | 34 | RootCerts.parsePEM = function(pem) { 35 | pem = pem + ''; 36 | var concatted = pem.trim().split(/-----BEGIN [^\-\r\n]+-----/); 37 | if (concatted.length > 2) { 38 | return concatted.reduce(function(out, pem) { 39 | if (!pem) { 40 | return out; 41 | } 42 | pem = RootCerts.parsePEM(pem)[0].pem; 43 | if (pem) { 44 | out.push(pem); 45 | } 46 | return out; 47 | }, []); 48 | } 49 | var type = /-----BEGIN ([^\-\r\n]+)-----/.exec(pem)[1]; 50 | pem = pem.replace(/-----BEGIN [^\-\r\n]+-----/, ''); 51 | pem = pem.replace(/-----END [^\-\r\n]+-----/, ''); 52 | var parts = pem.trim().split(/(?:\r?\n){2,}/); 53 | var headers = {}; 54 | if (parts.length > 1) { 55 | headers = parts[0].trim().split(/[\r\n]/).reduce(function(out, line) { 56 | var parts = line.split(/:[ \t]+/); 57 | var key = parts[0].trim().toLowerCase(); 58 | var value = (parts.slice(1).join('') || '').trim(); 59 | out[key] = value; 60 | return out; 61 | }, {}); 62 | pem = parts.slice(1).join(''); 63 | } 64 | pem = pem.replace(/\s+/g, ''); 65 | var der = pem ? Buffer.from(pem, 'base64') : null; 66 | return [{ 67 | type: type, 68 | headers: headers, 69 | pem: pem, 70 | der: der, 71 | body: der || Buffer.from([0]) 72 | }]; 73 | }; 74 | 75 | module.exports.certs = certs; 76 | module.exports.trusted = trusted; 77 | -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.writeFileAsync = exports.readFileAsync = exports.readDirAsync = exports.subtractList = exports.reduceDeleteValue = undefined; 7 | 8 | var _ramda = require('ramda'); 9 | 10 | var _ramda2 = _interopRequireDefault(_ramda); 11 | 12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 13 | 14 | // $FlowFixMe 15 | // import fs from 'fs' 16 | const fs = require('fs'); 17 | 18 | const filterReduce = _ramda2.default.curry((wrongValue, a, b) => { 19 | if (a === wrongValue) return b; 20 | 21 | if (b === wrongValue) return a; 22 | 23 | return a; 24 | }); 25 | 26 | // take valueToDelete and list if in the list there are elements different from valueToDelete return the last one of this elements. 27 | // If not return valueToDelete 28 | const reduceDeleteValue = exports.reduceDeleteValue = _ramda2.default.curry((valueToDelete, list) => { 29 | return _ramda2.default.reduce(filterReduce(valueToDelete), valueToDelete, list); 30 | }); 31 | 32 | // Subtract smallerList from bigger list: subtractList(['a', 'b', 'c', 'd', 'e', 'f'], ['c', 'd']) => [ 'a', 'b', 'e', 'f' ] 33 | const subtractList = exports.subtractList = _ramda2.default.curry((biggerList, smallerList) => { 34 | return _ramda2.default.reject(_ramda2.default.contains(_ramda2.default.__, smallerList), biggerList); 35 | }); 36 | 37 | const readDirAsync = exports.readDirAsync = path => { 38 | return new Promise((resolve, reject) => { 39 | fs.readdir(path, (error, result) => { 40 | if (error) reject(error);else resolve(result); 41 | }); 42 | }); 43 | }; 44 | 45 | const readFileAsync = exports.readFileAsync = path => { 46 | return new Promise((resolve, reject) => { 47 | fs.readFile(path, (error, result) => { 48 | if (error) reject(error);else resolve(result); 49 | }); 50 | }); 51 | }; 52 | 53 | // $FlowFixMe 54 | const writeFileAsync = exports.writeFileAsync = (path, data, binary) => { 55 | return new Promise((resolve, reject) => { 56 | if (binary !== 'binary') { 57 | fs.writeFile(path, data, (error, result) => { 58 | if (error) reject(error);else resolve(result); 59 | }); 60 | } else { 61 | fs.writeFile(path, data, 'binary', (error, result) => { 62 | if (error) reject(error);else resolve(result); 63 | }); 64 | } 65 | }); 66 | }; -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | // @flow 3 | /* eslint-disable no-console */ 4 | 5 | import {verifyProof, getMessageLen, getMessageContent} from './index.js'; 6 | import {readFileAsync, writeFileAsync} from './helpers.js'; 7 | import f from 'figlet'; 8 | import chalk from 'chalk'; 9 | import process from 'process'; 10 | import R from 'ramda'; 11 | import elegantSpinner from 'elegant-spinner'; 12 | import logUpdate from 'log-update'; 13 | 14 | const flags = { 15 | saveMessage: '-s', 16 | }; 17 | 18 | //BANNER 19 | console.log(chalk.cyan(f.textSync('oraclize', {font: 'Isometric1', horizontalLayout: 'default', verticalLayout: 'default'}))); 20 | 21 | //SPINNER 22 | const frames = R.compose(R.map(() => elegantSpinner()), R.range)(0, process.stdout.columns); 23 | console.log(); 24 | console.log(chalk.yellow('PRELIMINARY CHECKS IN PROGRESS')); 25 | setInterval(function () { 26 | logUpdate(frames.map((x) => chalk.green(x()))); 27 | }, 50); 28 | 29 | const saveOutputPath = () => { 30 | return process.argv[R.findIndex(x => x === flags.saveMessage, process.argv) + 1]; 31 | }; 32 | 33 | const parseProof = async (path) => { 34 | const parsedProof = new Uint8Array(await readFileAsync(path)); 35 | 36 | let verifiedProof; 37 | try { 38 | verifiedProof = await verifyProof(parsedProof); 39 | if (!verifiedProof.mainProof.isVerified) 40 | throw new Error(); 41 | 42 | } catch (error) { 43 | throw new Error(error); 44 | } 45 | console.log(); 46 | console.log(chalk.green('Proof file: '), path); 47 | console.log(); 48 | console.log(chalk.yellow('Main proof: '),'\n ', verifiedProof.mainProof); 49 | console.log(chalk.yellow('Extension proof: '),'\n ', verifiedProof.extensionProof); 50 | console.log(chalk.yellow('Proof shield: '),'\n ', verifiedProof.proofShield); 51 | console.log(chalk.yellow('Message: '),'\n ', getMessageLen(verifiedProof.message) < process.stdout.columns * 80 52 | ? getMessageContent(verifiedProof.message) 53 | : 'please use save message flag'); 54 | 55 | console.log(chalk.yellow('Proof ID: '),'\n ', verifiedProof.proofId); 56 | if (R.contains(flags.saveMessage, process.argv)) { 57 | if(typeof verifiedProof.message === 'string') 58 | await writeFileAsync(saveOutputPath(), verifiedProof.message); 59 | else 60 | await writeFileAsync(saveOutputPath(), Buffer.from(getMessageContent(verifiedProof.message, true)), 'binary'); 61 | 62 | } 63 | }; 64 | 65 | parseProof(process.argv[2]).then(() => { 66 | console.log(); 67 | console.log(chalk.green('SUCCESS')); 68 | process.exit(0); 69 | }).catch(e => { 70 | console.log(e); 71 | console.log(); 72 | console.log(chalk.red('FAILURE')); 73 | process.exit(255); 74 | }); 75 | -------------------------------------------------------------------------------- /lib/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.runTest = undefined; 7 | 8 | var _index = require('./index.js'); 9 | 10 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } 11 | /* eslint-disable no-console */ 12 | 13 | const fs = require('fs'); 14 | 15 | const testGetProofType = () => { 16 | const proofType_TLSNotary = (0, _index.getProofType)('tlsnotary notarization file\n/X]H_<ˆ‘Õ¥ïê'); 17 | const proofType_Android = (0, _index.getProofType)('AP¿lHTTPResponseY"{"error":[],"result":{'); 18 | // $FlowFixMe 19 | console.log(`index.js/getProofType: ${proofType_TLSNotary === 'proofType_TLSNotary' && proofType_Android === 'proofType_Android'}`); // eslint-disable-line no-console 20 | }; 21 | 22 | // we have to use literal path so browserify can put the file's content in the bundle 23 | const proofs = [fs.readFileSync('./proof/androidV2.proof'), fs.readFileSync('./proof/androidV2Newest.proof'), fs.readFileSync('./proof/computation.proof'), fs.readFileSync('./proof/ledger.proof'), fs.readFileSync('./proof/tlsn1.proof'), fs.readFileSync('./proof/tlsn2.proof'), fs.readFileSync('./proof/tlsn3.proof')]; 24 | 25 | const paths = ['./proof/androidV2.proof', './proof/androidV2Newest.proof', './proof/computation.proof', './proof/ledger.proof', './proof/tlsn1.proof', './proof/tlsn2.proof', './proof/tlsn3.proof']; 26 | 27 | const autoVerify = (() => { 28 | var _ref = _asyncToGenerator(function* () { 29 | for (let h = 0; h < proofs.length; h++) { 30 | const parsedProof = new Uint8Array(proofs[h]); 31 | console.log('\x1b[32m', 'Proof file: ', paths[h], '\x1b[37m'); 32 | try { 33 | const verifiedProof = yield (0, _index.verifyProof)(parsedProof); 34 | console.log('\x1b[33m', 'Main proof: ', '\x1b[37m', '\n ', verifiedProof.mainProof); 35 | console.log('\x1b[33m', 'Extension proof: ', '\x1b[37m', '\n ', verifiedProof.extensionProof); 36 | } catch (e) { 37 | console.log('Error: ', e); 38 | } 39 | } 40 | }); 41 | 42 | return function autoVerify() { 43 | return _ref.apply(this, arguments); 44 | }; 45 | })(); 46 | 47 | const runTest = exports.runTest = (() => { 48 | testGetProofType(); 49 | // eslint-disable-next-line no-console 50 | autoVerify().then(() => console.log('finish')).catch(e => console.log(e)); 51 | })(); -------------------------------------------------------------------------------- /lib/testFailedProofs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.runTest = undefined; 7 | 8 | var _index = require('./index.js'); 9 | 10 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } 11 | /* eslint-disable no-console */ 12 | 13 | const fs = require('fs'); 14 | 15 | const testGetProofType = () => { 16 | const proofType_TLSNotary = (0, _index.getProofType)('tlsnotary notarization file\n/X]H_<ˆ‘Õ¥ïê'); 17 | const proofType_Android = (0, _index.getProofType)('AP¿lHTTPResponseY"{"error":[],"result":{'); 18 | // $FlowFixMe 19 | console.log(`index.js/getProofType: ${proofType_TLSNotary === 'proofType_TLSNotary' && proofType_Android === 'proofType_Android'}`); // eslint-disable-line no-console 20 | }; 21 | 22 | // we have to use literal path so browserify can put the file's content in the bundle 23 | const proofs = [fs.readFileSync('./proof/failed/androidV2_toFail.proof'), fs.readFileSync('./proof/failed/androidV2Newest_toFail.proof'), fs.readFileSync('./proof/failed/computation_toFail.proof'), fs.readFileSync('./proof/failed/ledger_toFail.proof'), fs.readFileSync('./proof/failed/tlsn1_toFail.proof'), fs.readFileSync('./proof/failed/tlsn2_toFail.proof'), fs.readFileSync('./proof/failed/tlsn3_toFail.proof')]; 24 | 25 | const paths = ['./proof/failed/androidV2_toFail.proof', './proof/failed/androidV2Newest_toFail.proof', './proof/failed/computation_toFail.proof', './proof/failed/ledger_toFail.proof', './proof/failed/tlsn1_toFail.proof', './proof/failed/tlsn2_toFail.proof', './proof/failed/tlsn3_toFail.proof']; 26 | 27 | const autoVerify = (() => { 28 | var _ref = _asyncToGenerator(function* () { 29 | for (let h = 0; h < proofs.length; h++) { 30 | const parsedProof = new Uint8Array(proofs[h]); 31 | console.log('\x1b[32m', 'Proof file: ', paths[h], '\x1b[37m'); 32 | try { 33 | const verifiedProof = yield (0, _index.verifyProof)(parsedProof); 34 | console.log('\x1b[33m', 'Main proof: ', '\x1b[37m', '\n ', verifiedProof.mainProof); 35 | console.log('\x1b[33m', 'Extension proof: ', '\x1b[37m', '\n ', verifiedProof.extensionProof); 36 | } catch (e) { 37 | console.log('Error: ', e); 38 | } 39 | } 40 | }); 41 | 42 | return function autoVerify() { 43 | return _ref.apply(this, arguments); 44 | }; 45 | })(); 46 | 47 | const runTest = exports.runTest = (() => { 48 | testGetProofType(); 49 | // eslint-disable-next-line no-console 50 | autoVerify().then(() => console.log('finish')).catch(e => console.log(e)); 51 | })(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@provable/proof-verification-tool", 3 | "version": "0.2.4", 4 | "description": "Verifies Oraclize proofs locally", 5 | "author": "Provable", 6 | "license": "ISC", 7 | "main": "./lib/index.js", 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/oraclize/proof-verification-tool" 11 | }, 12 | "bugs": { 13 | "url" : "https://github.com/oraclize/proof-verification-tool/issues" 14 | }, 15 | "bin": { 16 | "proof-verication-tool": "lib/cli.js" 17 | }, 18 | "dependencies": { 19 | "asn1.js": "^4.9.0", 20 | "atob": "^2.0.3", 21 | "big-integer": "^1.6.17", 22 | "btoa": "^1.1.2", 23 | "cbor": "^3.0.0", 24 | "chalk": "^2.3.0", 25 | "crypto-js": "^3.1.8", 26 | "elegant-spinner": "^1.0.1", 27 | "figlet": "^1.2.0", 28 | "get-random-values": "^1.2.0", 29 | "isomorphic-fetch": "^2.2.1", 30 | "jsrsasign": "^6.2.1", 31 | "log-update": "^2.3.0", 32 | "pako": "^1.0.6", 33 | "ramda": "^0.25.0", 34 | "safe-buffer": "^5.1.2", 35 | "sha256": "^0.2.0", 36 | "sync-request": "^3.0.1", 37 | "urlsafe-base64": "^1.0.0", 38 | "vm": "^0.1.0", 39 | "xml2js": "^0.4.17" 40 | }, 41 | "browser": { 42 | "vm": false 43 | }, 44 | "browserify": { 45 | "transform": [ 46 | "brfs" 47 | ] 48 | }, 49 | "devDependencies": { 50 | "babel-cli": "^6.26.0", 51 | "babel-core": "^6.26.0", 52 | "babel-eslint": "^8.0.2", 53 | "babel-plugin-transform-async-to-generator": "^6.24.1", 54 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", 55 | "babel-preset-flow": "^6.23.0", 56 | "babelify": "^8.0.0", 57 | "brfs": "^1.4.3", 58 | "browserify": "^14.5.0", 59 | "chai": "^4.2.0", 60 | "eslint": "^5.14.1", 61 | "eslint-config-standard": "^12.0.0", 62 | "eslint-plugin-import": "^2.16.0", 63 | "eslint-plugin-mocha": "^5.3.0", 64 | "eslint-plugin-node": "^8.0.1", 65 | "eslint-plugin-promise": "^4.0.1", 66 | "eslint-plugin-standard": "^4.0.0", 67 | "eslint-plugin-flowtype": "^2.39.1", 68 | "flow-bin": "^0.58.0", 69 | "husky": "^1.3.1", 70 | "mocha": "^6.0.1", 71 | "nyc": "^13.3.0" 72 | }, 73 | "scripts": { 74 | "verify": "node ./lib/cli", 75 | "build": "./node_modules/.bin/babel ./src -d ./lib", 76 | "test:proofs": "node ./lib/test.js", 77 | "test:proofs-failed": "node ./lib/testFailedProofs.js", 78 | "test-bundle": "node -e \"require('./bundleTestNode.js');\"", 79 | "flow": "./node_modules/.bin/flow", 80 | "lint": "eslint '**/*.js'", 81 | "lint:fix": "eslint '**/*.js' --fix", 82 | "browserify": "./node_modules/browserify/bin/cmd.js ./lib/index.js -r fs:browserify-fs -o bundle.js && ./node_modules/browserify/bin/cmd.js ./lib/test.js -r fs:browserify-fs -o bundleTest.js", 83 | "browserify-node": "./node_modules/browserify/bin/cmd.js ./lib/index.js --node --s proofVerifier -o bundleNode.js && ./node_modules/browserify/bin/cmd.js ./lib/test.js --node -o bundleTestNode.js" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "commonjs": true, 6 | "es6": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "parserOptions": { 10 | "sourceType": "module" 11 | }, 12 | rules: { 13 | quotes: [2, 'single', { avoidEscape: true }], 14 | 'no-template-curly-in-string': 2, 15 | 'no-extra-parens': [1, 'all'], 16 | 'no-misleading-character-class': 1, 17 | 'no-prototype-builtins': 1, 18 | 'no-async-promise-executor': 1, 19 | 'no-await-in-loop': 0, // should probably be on for perf dependent application 20 | 'require-atomic-updates': 1, 21 | // best practices 22 | 'accessor-pairs': 1, 23 | 'array-callback-return': 0, // this best practice calls out the use of map over forEach 24 | 'class-methods-use-this': 1, 25 | 'complexity': [1, 5], 26 | 'curly': [1, 'multi-or-nest', 'consistent'], 27 | 'dot-location': [2, 'property'], 28 | 'no-empty-function': 1, 29 | 'no-eval': 2, 30 | 'no-extend-native': 1, 31 | 'no-extra-bind': 1, 32 | 'no-implicit-coercion': 1, 33 | 'no-implicit-globals': 2, 34 | 'no-implied-eval': 2, 35 | 'no-lone-blocks': 1, 36 | 'no-loop-func': 1, 37 | 'no-magic-numbers': 0, // could be useful? 38 | 'no-new': 1, 39 | 'no-new-func': 2, 40 | 'no-new-wrappers': 1, 41 | 'no-param-reassign': 2, 42 | 'no-redeclare': [2, { 'builtinGlobals': true }], 43 | 'no-shadow': [2, { 44 | 'builtinGlobals': true, 45 | 'allow': [ 46 | 'done', 47 | 'resolve', 48 | 'reject', 49 | 'cb' // x could potentially be added 50 | ] 51 | }], 52 | 'no-return-await': 1, 53 | 'no-script-url': 1, 54 | 'no-self-compare': 1, 55 | 'no-sequences': 1, 56 | 'no-throw-literal': 2, 57 | 'no-unmodified-loop-condition': 1, 58 | 'no-useless-call': 1, 59 | 'no-useless-catch': 1, 60 | 'no-useless-concat': 2, 61 | 'no-useless-escape': 2, 62 | 'no-useless-return': 0, 63 | 'no-console': ["error", { 64 | allow: [ 65 | "warn", 66 | "error", 67 | "info" 68 | ] 69 | }], 70 | 'no-warning-comments': [1, { 71 | terms: [ 72 | 'fixme', 73 | 'todo', 74 | 'note' 75 | ], 76 | location: 'anywhere' // useful to highlight comments that need addressing 77 | }], 78 | 'require-await': 1, 79 | 'vars-on-top': 2, 80 | 'wrap-iife': [2, 'inside'], 81 | // strict mode 82 | 'strict': [2, 'safe'], 83 | // variables 84 | 'no-use-before-define': [2, { 85 | variables: true, 86 | functions: true, 87 | classes: true 88 | }], 89 | // node 90 | 'handle-callback-err': 1, 91 | 'no-buffer-constructor': 2, 92 | 'no-mixed-requires': 2, 93 | 'no-new-require': 0, 94 | 'no-path-concat': 1, 95 | 'no-sync': 1 96 | // stylistic (leaving up to standard) 97 | 98 | // ECMAScript 6 (tbd) 99 | }, 100 | "plugins": [ 101 | "flowtype" 102 | ], 103 | }; 104 | -------------------------------------------------------------------------------- /src/ledger-verify.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | const r = require('jsrsasign') 3 | // $FlowFixMe 4 | const Buffer = require('safe-buffer').Buffer 5 | 6 | /* Proof Serialization 7 | * 8 | * |proof_type|app_key_1|ledger_cert|codehash|elapsed_time|n_random_bytes|commitment_nonce|signature1|session_pub_key| signature2 9 | * | 3 | 65 | 2+var-len | 32 | 8 | 1 | 32 | 2+var-len| 65 | 2+var-len 10 | * 11 | * 12 | * 13 | */ 14 | 15 | export const verify = (data: Uint8Array) => { 16 | const proofBuffer = Buffer.from(data) 17 | const ledgerRootKey = '047fb956469c5c9b89840d55b43537e66a98dd4811ea0a27224272c2e5622911e8537a2f8e86a46baec82864e98dd01e9ccc2f8bc5dfc9cbe5a91a290498dd96e4' 18 | const lenCert = proofBuffer.readUInt8(3+65+1) + 2 19 | const appKey1 = proofBuffer.slice(3, 3 + 65) 20 | let ledgerCert = proofBuffer.slice(3+65, 3+65+ lenCert) 21 | const extractedCodeHash = proofBuffer.slice(3+65+lenCert, 3+65+lenCert+32) 22 | const randomCodeHash = Buffer.from('fd94fa71bc0ba10d39d464d0d8f465efeef0a2764e3887fcc9df41ded20f505c', 'hex') 23 | let type 24 | if (extractedCodeHash.equals(randomCodeHash)) 25 | type = 'random' 26 | else 27 | type = 'none' 28 | 29 | const sig = new r.crypto.Signature({alg: 'SHA256withECDSA'}) 30 | const params = {xy: ledgerRootKey, curve: 'secp256k1'} 31 | const key = r.KEYUTIL.getKey(params) 32 | sig.init(key) 33 | sig.updateHex('fe' + appKey1.toString('hex')) 34 | return [type, sig.verify(ledgerCert.toString('hex'))] 35 | } 36 | 37 | export const verifyRandom = (data: Uint8Array) => { 38 | const proofBuffer = Buffer.from(data) 39 | const lenCert = proofBuffer.readUInt8(3+65+1) + 2 40 | let appKey1 = proofBuffer.slice(3, 3 + 65) 41 | const codeHash = proofBuffer.slice(3+65+lenCert, 3+65+lenCert+32) 42 | const ledgerProofLen = 3 + 65 + lenCert + 32 43 | // queryId, elapsedTime, nRandomBytes, commitmentNonce 44 | const commitmentPayloadLen = 32+8+1+32 45 | const tosign1 = proofBuffer.slice(ledgerProofLen, ledgerProofLen + commitmentPayloadLen) 46 | // Signature over commitmentPayload done by session key pair 47 | const sig1offset = ledgerProofLen + commitmentPayloadLen 48 | const sig1len = proofBuffer.readUInt8(sig1offset+1) + 2 49 | const sig1 = proofBuffer.slice(sig1offset, sig1offset + sig1len) 50 | const sig2offset = sig1offset + sig1len + 65 51 | const sessionKeyOffset = sig1offset + sig1len 52 | const sig2len = proofBuffer.readUInt8(sig2offset+1) + 2 53 | const sig2 = proofBuffer.slice(sig2offset, sig2offset + sig2len) 54 | let sessionKey = proofBuffer.slice(sessionKeyOffset, sessionKeyOffset + 65) 55 | const role_byte = Buffer.from([0x1]) 56 | let tosign2 = Buffer.concat([role_byte, sessionKey, codeHash ], 1+65+32) 57 | let params = {xy: sessionKey.toString('hex'), curve: 'secp256k1'} 58 | let sig1Obj = new r.crypto.Signature({alg: 'SHA256withECDSA'}) 59 | sessionKey = r.KEYUTIL.getKey(params) 60 | sig1Obj.init(sessionKey) 61 | sig1Obj.updateHex(tosign1.toString('hex')) 62 | let sig2Obj = new r.crypto.Signature({alg: 'SHA256withECDSA'}) 63 | params = {xy: appKey1.toString('hex'), curve: 'secp256k1'} 64 | appKey1 = r.KEYUTIL.getKey(params) 65 | sig2Obj.init(appKey1) 66 | sig2Obj.updateHex(tosign2.toString('hex')) 67 | sig1[0] = 48 68 | return sig1Obj.verify(sig1.toString('hex')) && sig2Obj.verify(sig2.toString('hex')) 69 | } 70 | 71 | export const verifyLedger = (data: Uint8Array) => { 72 | const result = verify(data) 73 | let status 74 | if (result[1]) { 75 | switch(result[0]) { 76 | case 'random': { 77 | const result = verifyRandom(data) 78 | if (result) 79 | status = ['success', 'random valid'] 80 | else 81 | status = ['success', 'random invalid'] 82 | 83 | break 84 | } 85 | default: 86 | status = ['success', 'not recognized nested poof'] 87 | } 88 | } else { 89 | status = ['failed', ''] 90 | } 91 | const isVerified = status[0] === 'success' ? true : false 92 | const parsedData = '' 93 | return {isVerified, status, parsedData} 94 | } 95 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 'use strict'; 3 | 4 | var _index = require('./index.js'); 5 | 6 | var _helpers = require('./helpers.js'); 7 | 8 | var _figlet = require('figlet'); 9 | 10 | var _figlet2 = _interopRequireDefault(_figlet); 11 | 12 | var _chalk = require('chalk'); 13 | 14 | var _chalk2 = _interopRequireDefault(_chalk); 15 | 16 | var _process = require('process'); 17 | 18 | var _process2 = _interopRequireDefault(_process); 19 | 20 | var _ramda = require('ramda'); 21 | 22 | var _ramda2 = _interopRequireDefault(_ramda); 23 | 24 | var _elegantSpinner = require('elegant-spinner'); 25 | 26 | var _elegantSpinner2 = _interopRequireDefault(_elegantSpinner); 27 | 28 | var _logUpdate = require('log-update'); 29 | 30 | var _logUpdate2 = _interopRequireDefault(_logUpdate); 31 | 32 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 33 | 34 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } 35 | /* eslint-disable no-console */ 36 | 37 | const flags = { 38 | saveMessage: '-s' 39 | }; 40 | 41 | //BANNER 42 | console.log(_chalk2.default.cyan(_figlet2.default.textSync('oraclize', { font: 'Isometric1', horizontalLayout: 'default', verticalLayout: 'default' }))); 43 | 44 | //SPINNER 45 | const frames = _ramda2.default.compose(_ramda2.default.map(() => (0, _elegantSpinner2.default)()), _ramda2.default.range)(0, _process2.default.stdout.columns); 46 | console.log(); 47 | console.log(_chalk2.default.yellow('PRELIMINARY CHECKS IN PROGRESS')); 48 | setInterval(function () { 49 | (0, _logUpdate2.default)(frames.map(x => _chalk2.default.green(x()))); 50 | }, 50); 51 | 52 | const saveOutputPath = () => { 53 | return _process2.default.argv[_ramda2.default.findIndex(x => x === flags.saveMessage, _process2.default.argv) + 1]; 54 | }; 55 | 56 | const parseProof = (() => { 57 | var _ref = _asyncToGenerator(function* (path) { 58 | const parsedProof = new Uint8Array((yield (0, _helpers.readFileAsync)(path))); 59 | 60 | let verifiedProof; 61 | try { 62 | verifiedProof = yield (0, _index.verifyProof)(parsedProof); 63 | if (!verifiedProof.mainProof.isVerified) throw new Error(); 64 | } catch (error) { 65 | throw new Error(error); 66 | } 67 | console.log(); 68 | console.log(_chalk2.default.green('Proof file: '), path); 69 | console.log(); 70 | console.log(_chalk2.default.yellow('Main proof: '), '\n ', verifiedProof.mainProof); 71 | console.log(_chalk2.default.yellow('Extension proof: '), '\n ', verifiedProof.extensionProof); 72 | console.log(_chalk2.default.yellow('Proof shield: '), '\n ', verifiedProof.proofShield); 73 | console.log(_chalk2.default.yellow('Message: '), '\n ', (0, _index.getMessageLen)(verifiedProof.message) < _process2.default.stdout.columns * 80 ? (0, _index.getMessageContent)(verifiedProof.message) : 'please use save message flag'); 74 | 75 | console.log(_chalk2.default.yellow('Proof ID: '), '\n ', verifiedProof.proofId); 76 | if (_ramda2.default.contains(flags.saveMessage, _process2.default.argv)) { 77 | if (typeof verifiedProof.message === 'string') yield (0, _helpers.writeFileAsync)(saveOutputPath(), verifiedProof.message);else yield (0, _helpers.writeFileAsync)(saveOutputPath(), Buffer.from((0, _index.getMessageContent)(verifiedProof.message, true)), 'binary'); 78 | } 79 | }); 80 | 81 | return function parseProof(_x) { 82 | return _ref.apply(this, arguments); 83 | }; 84 | })(); 85 | 86 | parseProof(_process2.default.argv[2]).then(() => { 87 | console.log(); 88 | console.log(_chalk2.default.green('SUCCESS')); 89 | _process2.default.exit(0); 90 | }).catch(e => { 91 | console.log(e); 92 | console.log(); 93 | console.log(_chalk2.default.red('FAILURE')); 94 | _process2.default.exit(255); 95 | }); -------------------------------------------------------------------------------- /lib/ledger-verify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | const r = require('jsrsasign'); 7 | // $FlowFixMe 8 | const Buffer = require('safe-buffer').Buffer; 9 | 10 | /* Proof Serialization 11 | * 12 | * |proof_type|app_key_1|ledger_cert|codehash|elapsed_time|n_random_bytes|commitment_nonce|signature1|session_pub_key| signature2 13 | * | 3 | 65 | 2+var-len | 32 | 8 | 1 | 32 | 2+var-len| 65 | 2+var-len 14 | * 15 | * 16 | * 17 | */ 18 | 19 | const verify = exports.verify = data => { 20 | const proofBuffer = Buffer.from(data); 21 | const ledgerRootKey = '047fb956469c5c9b89840d55b43537e66a98dd4811ea0a27224272c2e5622911e8537a2f8e86a46baec82864e98dd01e9ccc2f8bc5dfc9cbe5a91a290498dd96e4'; 22 | const lenCert = proofBuffer.readUInt8(3 + 65 + 1) + 2; 23 | const appKey1 = proofBuffer.slice(3, 3 + 65); 24 | let ledgerCert = proofBuffer.slice(3 + 65, 3 + 65 + lenCert); 25 | const extractedCodeHash = proofBuffer.slice(3 + 65 + lenCert, 3 + 65 + lenCert + 32); 26 | const randomCodeHash = Buffer.from('fd94fa71bc0ba10d39d464d0d8f465efeef0a2764e3887fcc9df41ded20f505c', 'hex'); 27 | let type; 28 | if (extractedCodeHash.equals(randomCodeHash)) type = 'random';else type = 'none'; 29 | 30 | const sig = new r.crypto.Signature({ alg: 'SHA256withECDSA' }); 31 | const params = { xy: ledgerRootKey, curve: 'secp256k1' }; 32 | const key = r.KEYUTIL.getKey(params); 33 | sig.init(key); 34 | sig.updateHex('fe' + appKey1.toString('hex')); 35 | return [type, sig.verify(ledgerCert.toString('hex'))]; 36 | }; 37 | 38 | const verifyRandom = exports.verifyRandom = data => { 39 | const proofBuffer = Buffer.from(data); 40 | const lenCert = proofBuffer.readUInt8(3 + 65 + 1) + 2; 41 | let appKey1 = proofBuffer.slice(3, 3 + 65); 42 | const codeHash = proofBuffer.slice(3 + 65 + lenCert, 3 + 65 + lenCert + 32); 43 | const ledgerProofLen = 3 + 65 + lenCert + 32; 44 | // queryId, elapsedTime, nRandomBytes, commitmentNonce 45 | const commitmentPayloadLen = 32 + 8 + 1 + 32; 46 | const tosign1 = proofBuffer.slice(ledgerProofLen, ledgerProofLen + commitmentPayloadLen); 47 | // Signature over commitmentPayload done by session key pair 48 | const sig1offset = ledgerProofLen + commitmentPayloadLen; 49 | const sig1len = proofBuffer.readUInt8(sig1offset + 1) + 2; 50 | const sig1 = proofBuffer.slice(sig1offset, sig1offset + sig1len); 51 | const sig2offset = sig1offset + sig1len + 65; 52 | const sessionKeyOffset = sig1offset + sig1len; 53 | const sig2len = proofBuffer.readUInt8(sig2offset + 1) + 2; 54 | const sig2 = proofBuffer.slice(sig2offset, sig2offset + sig2len); 55 | let sessionKey = proofBuffer.slice(sessionKeyOffset, sessionKeyOffset + 65); 56 | const role_byte = Buffer.from([0x1]); 57 | let tosign2 = Buffer.concat([role_byte, sessionKey, codeHash], 1 + 65 + 32); 58 | let params = { xy: sessionKey.toString('hex'), curve: 'secp256k1' }; 59 | let sig1Obj = new r.crypto.Signature({ alg: 'SHA256withECDSA' }); 60 | sessionKey = r.KEYUTIL.getKey(params); 61 | sig1Obj.init(sessionKey); 62 | sig1Obj.updateHex(tosign1.toString('hex')); 63 | let sig2Obj = new r.crypto.Signature({ alg: 'SHA256withECDSA' }); 64 | params = { xy: appKey1.toString('hex'), curve: 'secp256k1' }; 65 | appKey1 = r.KEYUTIL.getKey(params); 66 | sig2Obj.init(appKey1); 67 | sig2Obj.updateHex(tosign2.toString('hex')); 68 | sig1[0] = 48; 69 | return sig1Obj.verify(sig1.toString('hex')) && sig2Obj.verify(sig2.toString('hex')); 70 | }; 71 | 72 | const verifyLedger = exports.verifyLedger = data => { 73 | const result = verify(data); 74 | let status; 75 | if (result[1]) { 76 | switch (result[0]) { 77 | case 'random': 78 | { 79 | const result = verifyRandom(data); 80 | if (result) status = ['success', 'random valid'];else status = ['success', 'random invalid']; 81 | 82 | break; 83 | } 84 | default: 85 | status = ['success', 'not recognized nested poof']; 86 | } 87 | } else { 88 | status = ['failed', '']; 89 | } 90 | const isVerified = status[0] === 'success' ? true : false; 91 | const parsedData = ''; 92 | return { isVerified, status, parsedData }; 93 | }; -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.verifyProof = exports.getMessageContent = exports.getMessageLen = exports.getProofType = undefined; 7 | 8 | var _ramda = require('ramda'); 9 | 10 | var _ramda2 = _interopRequireDefault(_ramda); 11 | 12 | var _crypto = require('crypto'); 13 | 14 | var _crypto2 = _interopRequireDefault(_crypto); 15 | 16 | var _helpers = require('./helpers.js'); 17 | 18 | var _oracles = require('./oraclize/oracles.js'); 19 | 20 | var _tlsnVerify = require('./tlsn-verify.js'); 21 | 22 | var _tlsn_utils = require('./tlsn/tlsn_utils.js'); 23 | 24 | var _androidVerify = require('./android-verify.js'); 25 | 26 | var _ledgerVerify = require('./ledger-verify.js'); 27 | 28 | var _computationVerify = require('./computation-verify.js'); 29 | 30 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 31 | 32 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } 33 | 34 | // not implemented yet 35 | 36 | const getProofType = exports.getProofType = proof => { 37 | const supportedProofs = [{ 38 | slice: 27, 39 | content: 'tlsnotary notarization file', 40 | proofName: 'proofType_TLSNotary' 41 | }, { 42 | slice: 3, 43 | content: 'AP\x01', 44 | proofName: 'proofType_Android' 45 | }, { 46 | slice: 3, 47 | content: 'AP\x02', 48 | proofName: 'proofType_Android_v2' 49 | }, { 50 | slice: 3, 51 | content: 'LP\x01', 52 | proofName: 'proofType_Ledger' 53 | }]; 54 | 55 | const compareProof = _ramda2.default.curry((proof, proofStructure) => { 56 | const proofHeader = proof.slice(0, proofStructure.slice); 57 | if (proofHeader === proofStructure.content) return proofStructure.proofName; 58 | 59 | return 'proofType_NONE'; 60 | }); 61 | return _ramda2.default.compose((0, _helpers.reduceDeleteValue)('proofType_NONE'), _ramda2.default.map(compareProof(proof)))(supportedProofs); 62 | }; 63 | 64 | const findExtensionProof = message => { 65 | let extensionType = ''; 66 | if (message !== null && message !== undefined && (0, _computationVerify.isComputationProof)(message)) extensionType = 'computation';else extensionType = 'proofType_NONE'; 67 | return extensionType; 68 | }; 69 | 70 | const getMessageLen = exports.getMessageLen = message => typeof message === 'string' ? message.length : (0, _tlsn_utils.hex2ba)(message.value).toString().length; 71 | 72 | const getMessageContent = exports.getMessageContent = (message, bin = false) => typeof message === 'string' ? message : bin ? (0, _tlsn_utils.hex2ba)(message.value) : (0, _tlsn_utils.hex2ba)(message.value).toString(); 73 | 74 | const verifyProof = exports.verifyProof = (() => { 75 | var _ref = _asyncToGenerator(function* (proof, callback) { 76 | const proofType = getProofType((0, _tlsn_utils.ba2str)(proof)); 77 | let mainProof; 78 | let message; 79 | let extensionProof; 80 | switch (proofType) { 81 | case 'proofType_TLSNotary': 82 | { 83 | const parsedMessage = yield (0, _tlsnVerify.verifyTLS)(proof, (yield _oracles.verifiedServers), (yield _oracles.notVerifiableServers)); 84 | mainProof = { proofType, isVerified: parsedMessage.isVerified, status: parsedMessage.status }; 85 | message = parsedMessage.parsedData; 86 | break; 87 | } 88 | case 'proofType_Android': 89 | { 90 | const parsedMessage = yield (0, _androidVerify.verifyAndroid)(proof, 'v1'); 91 | mainProof = { proofType, isVerified: parsedMessage.isVerified, status: parsedMessage.status }; 92 | message = parsedMessage.parsedData; 93 | break; 94 | } 95 | case 'proofType_Android_v2': 96 | { 97 | const parsedMessage = yield (0, _androidVerify.verifyAndroid)(proof, 'v2'); 98 | mainProof = { proofType, isVerified: parsedMessage.isVerified, status: parsedMessage.status }; 99 | message = parsedMessage.parsedData; 100 | break; 101 | } 102 | case 'proofType_Ledger': 103 | { 104 | const parsedMessage = (0, _ledgerVerify.verifyLedger)(proof); 105 | mainProof = { proofType, isVerified: parsedMessage.isVerified, status: parsedMessage.status }; 106 | message = parsedMessage.parsedData; 107 | break; 108 | } 109 | default: 110 | { 111 | throw new Error(); 112 | } 113 | } 114 | switch (findExtensionProof(message)) { 115 | case 'computation': 116 | { 117 | const parsedMessage = (0, _computationVerify.verifyComputationProof)(message); 118 | extensionProof = { proofType: 'computation', isVerified: parsedMessage.isVerified, status: parsedMessage.status }; 119 | break; 120 | } 121 | } 122 | const parsedProof = { 123 | mainProof: mainProof, 124 | extensionProof: extensionProof, 125 | proofShield: null, 126 | message: message, 127 | // $FlowFixMe 128 | proofId: _crypto2.default.createHash('sha256').update(proof).digest().toString('hex') 129 | }; 130 | callback && callback(parsedProof); 131 | return parsedProof; 132 | }); 133 | 134 | return function verifyProof(_x, _x2) { 135 | return _ref.apply(this, arguments); 136 | }; 137 | })(); -------------------------------------------------------------------------------- /src/computation-verify.js: -------------------------------------------------------------------------------- 1 | 2 | // @flow 3 | const atob = require('atob') 4 | const tlsn_utils = require('./tlsn/tlsn_utils.js') 5 | const r = require('jsrsasign') 6 | // AWS RSA public key (US East (N. Virginia)) 7 | const awsPublicCertificateRSA = '-----BEGIN CERTIFICATE-----\n\ 8 | MIIDIjCCAougAwIBAgIJAKnL4UEDMN/FMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV\n\ 9 | BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdTZWF0dGxlMRgw\n\ 10 | FgYDVQQKEw9BbWF6b24uY29tIEluYy4xGjAYBgNVBAMTEWVjMi5hbWF6b25hd3Mu\n\ 11 | Y29tMB4XDTE0MDYwNTE0MjgwMloXDTI0MDYwNTE0MjgwMlowajELMAkGA1UEBhMC\n\ 12 | VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1NlYXR0bGUxGDAWBgNV\n\ 13 | BAoTD0FtYXpvbi5jb20gSW5jLjEaMBgGA1UEAxMRZWMyLmFtYXpvbmF3cy5jb20w\n\ 14 | gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIe9GN//SRK2knbjySG0ho3yqQM3\n\ 15 | e2TDhWO8D2e8+XZqck754gFSo99AbT2RmXClambI7xsYHZFapbELC4H91ycihvrD\n\ 16 | jbST1ZjkLQgga0NE1q43eS68ZeTDccScXQSNivSlzJZS8HJZjgqzBlXjZftjtdJL\n\ 17 | XeE4hwvo0sD4f3j9AgMBAAGjgc8wgcwwHQYDVR0OBBYEFCXWzAgVyrbwnFncFFIs\n\ 18 | 77VBdlE4MIGcBgNVHSMEgZQwgZGAFCXWzAgVyrbwnFncFFIs77VBdlE4oW6kbDBq\n\ 19 | MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2Vh\n\ 20 | dHRsZTEYMBYGA1UEChMPQW1hem9uLmNvbSBJbmMuMRowGAYDVQQDExFlYzIuYW1h\n\ 21 | em9uYXdzLmNvbYIJAKnL4UEDMN/FMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF\n\ 22 | BQADgYEAFYcz1OgEhQBXIwIdsgCOS8vEtiJYF+j9uO6jz7VOmJqO+pRlAbRlvY8T\n\ 23 | C1haGgSI/A1uZUKs/Zfnph0oEI0/hu1IIJ/SKBDtN5lvmZ/IzbOPIJWirlsllQIQ\n\ 24 | 7zvWbGd9c9+Rm3p04oTvhup99la7kZqevJK0QRdD/6NpCKsqP/0=\n\ 25 | -----END CERTIFICATE-----' 26 | const awsTrustedAMIlist = ['ami-30176327', 'ami-3200c65d', 'ami-84f6f093', 'ami-1c588d73', 'ami-9cde7af3'] 27 | 28 | //$FlowFixMe 29 | export const verifyComputation = (rawHtml) => { 30 | const bodyHtml = rawHtml.substr(rawHtml.indexOf('\r\n\r\n(.*?)<\/output>/)[1]).split('\n') 34 | let awsOutputClean = [] 35 | for (let i = 0; i < awsOutputDirty.length; i++) { 36 | if (awsOutputDirty[i].substr(0, 2) !== ' *') 37 | awsOutputClean.push(awsOutputDirty[i]) 38 | 39 | } 40 | const awsOutput = awsOutputClean.join('\n') 41 | //$FlowFixMe 42 | let oraclizeDoc = awsOutput.match(/ORACLIZE_DOC:[\s\S]*ORACLIZE_/g)[0].split('ORACLIZE_')[1] 43 | //$FlowFixMe 44 | oraclizeDoc = oraclizeDoc.substr(4, oraclizeDoc.length - 4).split('\r\r\n').join('') 45 | //$FlowFixMe 46 | let oraclizeSig = awsOutput.match(/ORACLIZE_SIG:[\s\S]*$/g)[0].split('ORACLIZE_')[1] 47 | oraclizeSig = oraclizeSig.substr(4, oraclizeSig.indexOf('[') - 4).split('\r\r\n').join('') 48 | const decodedDoc = JSON.parse(atob(oraclizeDoc)) 49 | let awsSignature = atob(oraclizeSig).replace(/\n/g, '') 50 | // convert from base64 to hex 51 | awsSignature = tlsn_utils.ba2hex(tlsn_utils.str2ba(atob(awsSignature))) 52 | // check for trusted AMI 53 | const awsAMIvalid = awsTrustedAMIlist.indexOf(decodedDoc.imageId) !== -1 ? true : false 54 | if (!awsAMIvalid) 55 | throw new Error('unrecognized AMI provided') 56 | 57 | // get instanceId from json doc & xml (from body html) 58 | const awsInstanceIdDoc = decodedDoc.instanceId 59 | //$FlowFixMe 60 | const awsInstanceIdXML = awsXML.match(/(.*?)<\/instanceId>/)[1] 61 | // check if the instance id is the same 62 | const awsInstanceMatch = awsInstanceIdDoc === awsInstanceIdXML 63 | if (!awsInstanceMatch) 64 | throw new Error('instance ID mismatch') 65 | 66 | // Ensure document signature passes verification 67 | const verifier = new r.KJUR.crypto.Signature({alg: 'SHA256withRSA'}) 68 | verifier.init(awsPublicCertificateRSA) 69 | verifier.updateString(atob(oraclizeDoc)) 70 | const awsSignatureValid = verifier.verify(awsSignature) 71 | if (!awsSignatureValid) 72 | throw new Error('signature invalid') 73 | 74 | // archive checksum is completed on server-side 75 | // with the publicly trusted AMI 76 | const archiveChecksumPass = awsAMIvalid 77 | if (!archiveChecksumPass) 78 | throw new Error('archive checksum failed') 79 | 80 | } 81 | 82 | //$FlowFixMe 83 | export const getComputationResult =(rawHtml) => { 84 | try { 85 | const bodyHtml = rawHtml.substr(rawHtml.indexOf('\n\n(.*?)<\/output>/)[1]) 88 | const oraclizeResult = awsOutput.match(/^ORACLIZE_RESULT:.*$/m)[0].substr(16) 89 | return atob(oraclizeResult) 90 | } catch (err) { 91 | throw new Error('Computation result parsing error ' + err) 92 | } 93 | } 94 | 95 | export const isComputationProof = (html: string) => { 96 | const compCheck1 = '' 97 | // Ensure GetConsoleOutputResponse is last element 98 | const validator1 = html.indexOf(compCheck1) + compCheck1.length - html.length 99 | const compCheck2 = 'Server: AmazonEC2' 100 | const validator2 = html.indexOf(compCheck2) 101 | return validator1 === 0 && validator2 !== -1 102 | } 103 | 104 | export const verifyComputationProof = (html: string) => { 105 | let status 106 | try { 107 | verifyComputation(html) 108 | status = ['success', ''] 109 | } catch(e) { 110 | switch (e.message) { 111 | case 'unrecognized AMI provider': { 112 | status = ['failed', 'unrecognized AMI provider'] 113 | break 114 | } 115 | case 'instance ID mismatch': { 116 | status = ['failed', 'instance ID mismatch'] 117 | break 118 | } 119 | case 'signature invalid': { 120 | status = ['failed', 'signature invalid'] 121 | break 122 | } 123 | case 'archive checksum failed': { 124 | status = ['failed', 'archive checksum failed'] 125 | break 126 | } 127 | default: { 128 | status = ['failed', ''] 129 | break 130 | } 131 | } 132 | } 133 | const isVerified = status[0] === 'success' ? true : false 134 | return {status, isVerified} 135 | } 136 | -------------------------------------------------------------------------------- /lib/computation-verify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | const atob = require('atob'); 8 | const tlsn_utils = require('./tlsn/tlsn_utils.js'); 9 | const r = require('jsrsasign'); 10 | // AWS RSA public key (US East (N. Virginia)) 11 | const awsPublicCertificateRSA = '-----BEGIN CERTIFICATE-----\n\ 12 | MIIDIjCCAougAwIBAgIJAKnL4UEDMN/FMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV\n\ 13 | BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdTZWF0dGxlMRgw\n\ 14 | FgYDVQQKEw9BbWF6b24uY29tIEluYy4xGjAYBgNVBAMTEWVjMi5hbWF6b25hd3Mu\n\ 15 | Y29tMB4XDTE0MDYwNTE0MjgwMloXDTI0MDYwNTE0MjgwMlowajELMAkGA1UEBhMC\n\ 16 | VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1NlYXR0bGUxGDAWBgNV\n\ 17 | BAoTD0FtYXpvbi5jb20gSW5jLjEaMBgGA1UEAxMRZWMyLmFtYXpvbmF3cy5jb20w\n\ 18 | gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIe9GN//SRK2knbjySG0ho3yqQM3\n\ 19 | e2TDhWO8D2e8+XZqck754gFSo99AbT2RmXClambI7xsYHZFapbELC4H91ycihvrD\n\ 20 | jbST1ZjkLQgga0NE1q43eS68ZeTDccScXQSNivSlzJZS8HJZjgqzBlXjZftjtdJL\n\ 21 | XeE4hwvo0sD4f3j9AgMBAAGjgc8wgcwwHQYDVR0OBBYEFCXWzAgVyrbwnFncFFIs\n\ 22 | 77VBdlE4MIGcBgNVHSMEgZQwgZGAFCXWzAgVyrbwnFncFFIs77VBdlE4oW6kbDBq\n\ 23 | MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2Vh\n\ 24 | dHRsZTEYMBYGA1UEChMPQW1hem9uLmNvbSBJbmMuMRowGAYDVQQDExFlYzIuYW1h\n\ 25 | em9uYXdzLmNvbYIJAKnL4UEDMN/FMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF\n\ 26 | BQADgYEAFYcz1OgEhQBXIwIdsgCOS8vEtiJYF+j9uO6jz7VOmJqO+pRlAbRlvY8T\n\ 27 | C1haGgSI/A1uZUKs/Zfnph0oEI0/hu1IIJ/SKBDtN5lvmZ/IzbOPIJWirlsllQIQ\n\ 28 | 7zvWbGd9c9+Rm3p04oTvhup99la7kZqevJK0QRdD/6NpCKsqP/0=\n\ 29 | -----END CERTIFICATE-----'; 30 | const awsTrustedAMIlist = ['ami-30176327', 'ami-3200c65d', 'ami-84f6f093', 'ami-1c588d73', 'ami-9cde7af3']; 31 | 32 | //$FlowFixMe 33 | const verifyComputation = exports.verifyComputation = rawHtml => { 34 | const bodyHtml = rawHtml.substr(rawHtml.indexOf('\r\n\r\n(.*?)<\/output>/)[1]).split('\n'); 38 | let awsOutputClean = []; 39 | for (let i = 0; i < awsOutputDirty.length; i++) { 40 | if (awsOutputDirty[i].substr(0, 2) !== ' *') awsOutputClean.push(awsOutputDirty[i]); 41 | } 42 | const awsOutput = awsOutputClean.join('\n'); 43 | //$FlowFixMe 44 | let oraclizeDoc = awsOutput.match(/ORACLIZE_DOC:[\s\S]*ORACLIZE_/g)[0].split('ORACLIZE_')[1]; 45 | //$FlowFixMe 46 | oraclizeDoc = oraclizeDoc.substr(4, oraclizeDoc.length - 4).split('\r\r\n').join(''); 47 | //$FlowFixMe 48 | let oraclizeSig = awsOutput.match(/ORACLIZE_SIG:[\s\S]*$/g)[0].split('ORACLIZE_')[1]; 49 | oraclizeSig = oraclizeSig.substr(4, oraclizeSig.indexOf('[') - 4).split('\r\r\n').join(''); 50 | const decodedDoc = JSON.parse(atob(oraclizeDoc)); 51 | let awsSignature = atob(oraclizeSig).replace(/\n/g, ''); 52 | // convert from base64 to hex 53 | awsSignature = tlsn_utils.ba2hex(tlsn_utils.str2ba(atob(awsSignature))); 54 | // check for trusted AMI 55 | const awsAMIvalid = awsTrustedAMIlist.indexOf(decodedDoc.imageId) !== -1 ? true : false; 56 | if (!awsAMIvalid) throw new Error('unrecognized AMI provided'); 57 | 58 | // get instanceId from json doc & xml (from body html) 59 | const awsInstanceIdDoc = decodedDoc.instanceId; 60 | //$FlowFixMe 61 | const awsInstanceIdXML = awsXML.match(/(.*?)<\/instanceId>/)[1]; 62 | // check if the instance id is the same 63 | const awsInstanceMatch = awsInstanceIdDoc === awsInstanceIdXML; 64 | if (!awsInstanceMatch) throw new Error('instance ID mismatch'); 65 | 66 | // Ensure document signature passes verification 67 | const verifier = new r.KJUR.crypto.Signature({ alg: 'SHA256withRSA' }); 68 | verifier.init(awsPublicCertificateRSA); 69 | verifier.updateString(atob(oraclizeDoc)); 70 | const awsSignatureValid = verifier.verify(awsSignature); 71 | if (!awsSignatureValid) throw new Error('signature invalid'); 72 | 73 | // archive checksum is completed on server-side 74 | // with the publicly trusted AMI 75 | const archiveChecksumPass = awsAMIvalid; 76 | if (!archiveChecksumPass) throw new Error('archive checksum failed'); 77 | }; 78 | 79 | //$FlowFixMe 80 | const getComputationResult = exports.getComputationResult = rawHtml => { 81 | try { 82 | const bodyHtml = rawHtml.substr(rawHtml.indexOf('\n\n(.*?)<\/output>/)[1]); 85 | const oraclizeResult = awsOutput.match(/^ORACLIZE_RESULT:.*$/m)[0].substr(16); 86 | return atob(oraclizeResult); 87 | } catch (err) { 88 | throw new Error('Computation result parsing error ' + err); 89 | } 90 | }; 91 | 92 | const isComputationProof = exports.isComputationProof = html => { 93 | const compCheck1 = ''; 94 | // Ensure GetConsoleOutputResponse is last element 95 | const validator1 = html.indexOf(compCheck1) + compCheck1.length - html.length; 96 | const compCheck2 = 'Server: AmazonEC2'; 97 | const validator2 = html.indexOf(compCheck2); 98 | return validator1 === 0 && validator2 !== -1; 99 | }; 100 | 101 | const verifyComputationProof = exports.verifyComputationProof = html => { 102 | let status; 103 | try { 104 | verifyComputation(html); 105 | status = ['success', '']; 106 | } catch (e) { 107 | switch (e.message) { 108 | case 'unrecognized AMI provider': 109 | { 110 | status = ['failed', 'unrecognized AMI provider']; 111 | break; 112 | } 113 | case 'instance ID mismatch': 114 | { 115 | status = ['failed', 'instance ID mismatch']; 116 | break; 117 | } 118 | case 'signature invalid': 119 | { 120 | status = ['failed', 'signature invalid']; 121 | break; 122 | } 123 | case 'archive checksum failed': 124 | { 125 | status = ['failed', 'archive checksum failed']; 126 | break; 127 | } 128 | default: 129 | { 130 | status = ['failed', '']; 131 | break; 132 | } 133 | } 134 | } 135 | const isVerified = status[0] === 'success' ? true : false; 136 | return { status, isVerified }; 137 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Proof Verification Tool 2 | 3 | [![Node 4 | Version](https://img.shields.io/badge/node-%3E=4.2.6-blue.svg?style=flat)](https://nodejs.org/en/) 5 | [![Join the chat at 6 | https://gitter.im/oraclize/ethereum-api](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/oraclize/ethereum-api?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 7 | [![Docs@Oraclize.it](https://camo.githubusercontent.com/5e89710c6ae9ce0da822eec138ee1a2f08b34453/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f646f63732d536c6174652d627269676874677265656e2e737667)](http://docs.oraclize.it) 8 | [![Contributions 9 | Welcome!](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/oraclize/proof-verification-tool/issues) 10 | [![HitCount](http://hits.dwyl.io/oraclize/proof-verification-tool.svg)](http://hits.dwyl.io/oraclize/proof-verification-tool) 11 | 12 | ## Version 0.2.4 13 | 14 | The `proof-verification-tool` allows users to _verify if an Oraclize proof is valid_. 15 | 16 | It can be used: 17 | 18 | __❍__ From the **Command Line**. 19 | 20 | It can be embedded: 21 | 22 | __❍__ As a Module in a **Node app** (though not yet via `npm`); 23 | 24 | __❍__ In the **Browser**, in `j2v8`. 25 | 26 | ### Functions Exposed 27 | 28 | __❍__ `getProofType(proof: string): ProofType`: accepts a _hexadecimal string_ (the proof), and returns a proof type. For now, the proof types supported are: 29 | 30 | * `proofType_TLSNotary` 31 | 32 | * `proofType_Android` 33 | 34 | * `proofType_Ledger` 35 | 36 | __❍__ `verifyProof(proof: Uint8Array, ?callback): Promise`: accepts a _byte array_ (the proof), an optional callback, and returns a promise containing the following object: 37 | 38 | ```javascript 39 | { 40 | mainProof: { 41 | proofType: MainProof, 42 | isVerified: boolean, 43 | status: VerificationStatus 44 | }, 45 | extensionProof: ?{ 46 | proofType: ExtensionProof, 47 | isVerified: boolean, 48 | status: VerificationStatus 49 | }, 50 | proofShield: ?{ 51 | proofType: ShieldProof, 52 | isVerified: boolean, 53 | status: VerificationStatus }, 54 | message: string | {type: 'hex', value: string}, 55 | proofId: string, 56 | } 57 | ``` 58 | 59 | Please, note that the char `?`, in the json snippet above, stands for **optional**. 60 | 61 | ### :black_nib: Notes: 62 | 63 | __❍__ The `proofType_Android` has two versions. The user should provide the _configuration parameters_ for v1 and v2 in the config file `./settings/settings.json`. These parameters are provided by the Android device and along with the Google API key, are used to generate and validate the proof. The values provided in settings are just examples of how they are used. 64 | 65 | __❍__ All the newly generated `proofType_Android` proofs are **v2**. 66 | 67 | ## :computer: Use from the Command Line 68 | 69 | Please, remember that the target is _ECMA 2015_, but if you want to use `yarn` you should have at least `node 4.2.6`. 70 | 71 | For using the Oraclize Proof Verification Tool from the _command line_, execute the following steps: 72 | 73 | **1)** Clone the repository: 74 | 75 | __`❍ git clone https://github.com/oraclize/proof-verification-tool.git`__ 76 | 77 | **2)** Install the deps: 78 | 79 | __`❍ cd proof-verification-tool && yarn install`__ 80 | 81 | **3)** Build the project: 82 | 83 | __`❍ yarn build`__ 84 | 85 | ### :mag_right: Proof Verification 86 | 87 | When you use the `proof-verification-tool` from the command line, you can check if the proof is valid or extract the message contained in the proof: 88 | 89 | **a)** Check the proof validity: 90 | 91 | __`❍ npm run verify `__ 92 | 93 | * If the proof is _valid_, the tool prints out the `ParsedProof`, then exits cleanly showing a **SUCCESS** message; 94 | 95 | * If the proof is _not valid_, the tool shows a **FAILURE** message, then exits with a non-zero 96 | exit code. 97 | 98 | **b)** Extract the message contained in the proof: 99 | 100 | __`❍ node ./lib/cli -s `__ 101 | 102 | * If the proof is _valid_, the tool prints out the `ParsedProof`, then exits cleanly with an exit code 0; 103 | 104 | * If the proof is _not valid_, the tool exits with a non-zero exit code. 105 | 106 | If the message contained in the proof is of the type `string`, it will be written to the given output-path as a UTF-8 string; if it is of type `hex`, the data wiil be written as binary. 107 | 108 |   109 | 110 | ## Embed in a Node App 111 | 112 | For using the Oraclize Proof Verification Tool from a _Node app_, execute the following steps: 113 | 114 | **1)** Clone the repository: 115 | 116 | __`❍ git clone https://github.com/oraclize/proof-verification-tool.git`__ 117 | 118 | **2)** Install the deps: 119 | 120 | __`❍ cd proof-verification-tool && yarn install`__ 121 | 122 | **3)** Build the project: 123 | 124 | __`❍ yarn build`__ 125 | 126 | **4)** Import the module in your app with: 127 | 128 | __`❍ import { verifyProof, getProofType } from 'path to proof verification tool directory' + '/lib/index.js\'`__ 129 | 130 | The target is _ECMA 2015_, but if you want to use yarn you should have at least `node 4.8.0`. 131 | 132 |   133 | 134 | ## Embed in a Java App 135 | 136 | For using the Oraclize Proof Verification Tool from a _Java app_, execute the following steps: 137 | 138 | **1)** Clone the repository: 139 | 140 | __`❍ git clone https://github.com/oraclize/proof-verification-tool.git`__ 141 | 142 | **2)** Install the deps: 143 | 144 | __`❍ cd proof-verification-tool && yarn install`__ 145 | 146 | **3)** Build the project: 147 | 148 | __`❍ yarn build`__ 149 | 150 | **4)** Create the bundle: 151 | 152 | __`❍ yarn browserify-node`__ 153 | 154 | The target is _ECMA 2015_, but if you want to use yarn you should have at least `node 4.8.0`. 155 | 156 |   157 | 158 | ## Embed in a Browser App 159 | 160 | Same as embed in a [Node app](#embed-in-a-node-app) 161 | 162 | If you use `browserify`, when you build the bundle, execute: 163 | 164 | __`❍ -r fs:browserify-fs`__ 165 | 166 |   167 | 168 | ## :camera: Examples of Passing Proofs: 169 | 170 | ![The passing Android V2 Proof!](./img/androidV2.png) 171 | ![The passing TLSN Proof!](./img/tlsnV3b.png) 172 | ![The passing Ledger Proof!](./img/ledger.png) 173 | 174 |   175 | 176 | ## Test Available Proofs: 177 | 178 | To test all the successfully verified proofs in the folder `src/proof` execute: 179 | 180 | __`❍ npm run test:proofs`__ 181 | 182 | To test all the failing proofs in the folder `src/proof/failed` execute: 183 | 184 | __`❍ npm run test:proofs-failed`__ 185 | 186 |   187 | 188 | ## :loudspeaker: Support 189 | 190 | __❍__ If you have any issues, head on over to our 191 | [Gitter](https://gitter.im/oraclize/ethereum-api?raw=true) channel to get timely support! 192 | 193 | __*Happy verification!*__ 194 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import R from 'ramda' 3 | import crypto from 'crypto' 4 | import { reduceDeleteValue } from './helpers.js' 5 | import { verifiedServers, notVerifiableServers } from './oraclize/oracles.js' 6 | import { verifyTLS } from './tlsn-verify.js' 7 | import { ba2str } from './tlsn/tlsn_utils.js' 8 | import { verifyAndroid } from './android-verify.js' 9 | import { verifyLedger } from './ledger-verify.js' 10 | import { isComputationProof, verifyComputationProof } from './computation-verify.js' 11 | import { hex2ba } from './tlsn/tlsn_utils.js' 12 | 13 | type MainProof = 14 | | 'proofType_TLSNotary' 15 | | 'proofType_Android' 16 | | 'proofType_Android_v2' 17 | | 'proofType_Ledger' 18 | | 'proofType_Native' // Not implemented yet 19 | | 'proofType_NONE' 20 | 21 | type ExtensionProof = 22 | | 'computation' 23 | | 'proofType_NONE' 24 | 25 | type ShieldProof = 26 | | 'type1' 27 | | 'proofType_NONE' 28 | 29 | type TLSNStatus = 30 | | ['failed', 'wrong header'] 31 | | ['failed', 'wrong version'] 32 | | ['failed', 'invalid .pgsg length'] 33 | | ['failed', 'commit hash mismatch'] 34 | | ['failed', 'matching notary server not found'] 35 | | ['success', 'matching notary server not on-line'] 36 | | ['success', 'no exceptions'] 37 | | ['failed', 'Signature not in chert chain'] 38 | 39 | type LedgerStatus = 40 | | ['success', 'random valid'] 41 | | ['success', 'random invalid'] 42 | | ['success', 'nested valid'] 43 | | ['success', 'not recognized nested poof'] 44 | | ['success', 'subproof invalid'] // not implemented yet 45 | 46 | type AndroidStatus = 47 | | ['failed', 'verifyPayload failed: apk hash or signing cert hash mismatch'] 48 | | ['failed', 'verifyAuthenticity failed'] 49 | | ['failed', 'verifyPayload failed: wrong apk hash'] 50 | | ['failed', 'verifyPayload failed: wrong signing certificate hash'] 51 | | ['failed', 'verifyResponseSignature failed'] 52 | | ['failed', 'verifyResponseSignature failed'] 53 | | ['failed', 'verifyAttestationParams failed: keymasterVersion mismatch'] 54 | | ['failed', 'verifyAttestationParams failed: keymasterSecurityLevel mismatch'] 55 | | ['failed', 'verifyAttestationParams failed: attestationChallenge value mismatch'] 56 | | ['failed', 'verifyAttestationParams failed: key purpose mismatch'] 57 | | ['failed', 'verifyAttestationParams failed: key algorithm is not EC based'] 58 | | ['failed', 'verifyAttestationParams failed: key digest mismatch'] 59 | | ['failed', 'verifyAttestationParams failed: ecCurve mismatch'] 60 | | ['failed', 'verifyAttestationParams failed: key was not generated on device'] 61 | | ['success', 'verifyAttestationParams failed: attestationSecurityLevel'] 62 | 63 | type ComputationStatus = 64 | | ['failed', 'unrecognized AMI provider'] 65 | | ['failed', 'instance ID mismatch'] 66 | | ['failed', 'signature invalid'] 67 | | ['failed', 'archive checksum failed'] 68 | 69 | type VerificationStatus = 70 | | ['success', ''] 71 | | ['failed', ''] 72 | | TLSNStatus 73 | | LedgerStatus 74 | | ComputationStatus 75 | | AndroidStatus 76 | 77 | type ProofType = MainProof | ExtensionProof | ShieldProof 78 | 79 | type ProofStructure = { 80 | slice: number, 81 | content: string, 82 | proofName: ProofType, 83 | } 84 | 85 | export type ParsedProof = { 86 | mainProof: { 87 | proofType: MainProof, 88 | isVerified: boolean, 89 | status: VerificationStatus 90 | }, 91 | extensionProof: ?{ 92 | proofType: ExtensionProof, 93 | isVerified: boolean, 94 | status: VerificationStatus 95 | }, 96 | proofShield: ?{ 97 | proofType: ShieldProof, 98 | isVerified: boolean, 99 | status: VerificationStatus }, 100 | message: string | { type: 'hex', value: string }, 101 | proofId: string, 102 | } 103 | 104 | export const getProofType = (proof: string): ProofType => { 105 | const supportedProofs = [ 106 | { 107 | slice: 27, 108 | content: 'tlsnotary notarization file', 109 | proofName: 'proofType_TLSNotary' 110 | }, 111 | { 112 | slice: 3, 113 | content: 'AP\x01', 114 | proofName: 'proofType_Android' 115 | }, 116 | { 117 | slice: 3, 118 | content: 'AP\x02', 119 | proofName: 'proofType_Android_v2' 120 | }, 121 | { 122 | slice: 3, 123 | content: 'LP\x01', 124 | proofName: 'proofType_Ledger' 125 | } 126 | ] 127 | 128 | const compareProof = R.curry((proof: string, proofStructure: ProofStructure): ProofType => { 129 | const proofHeader = proof.slice(0, proofStructure.slice) 130 | if (proofHeader === proofStructure.content) 131 | return proofStructure.proofName 132 | 133 | return 'proofType_NONE' 134 | }) 135 | return R.compose( 136 | reduceDeleteValue('proofType_NONE'), 137 | R.map(compareProof(proof)) 138 | )(supportedProofs) 139 | } 140 | 141 | const findExtensionProof = (message: ?string): ExtensionProof => { 142 | let extensionType = '' 143 | if (message !== null && message !== undefined && isComputationProof(message)) 144 | extensionType = 'computation' 145 | else 146 | extensionType = 'proofType_NONE' 147 | return extensionType 148 | } 149 | 150 | export const getMessageLen = (message: string | { type: 'hex', value: string }) => 151 | typeof message === 'string' 152 | ? message.length 153 | : hex2ba(message.value).toString().length; 154 | 155 | export const getMessageContent = (message: string | { type: 'hex', value: string }, bin: boolean = false) => 156 | typeof message === 'string' 157 | ? message 158 | : bin ? hex2ba(message.value) : hex2ba(message.value).toString(); 159 | 160 | export const verifyProof = async (proof: Uint8Array, callback: any): Promise => { 161 | const proofType = getProofType(ba2str(proof)) 162 | let mainProof 163 | let message 164 | let extensionProof 165 | switch (proofType) { 166 | case 'proofType_TLSNotary': { 167 | const parsedMessage = await verifyTLS(proof, await verifiedServers, await notVerifiableServers) 168 | mainProof = { proofType, isVerified: parsedMessage.isVerified, status: parsedMessage.status } 169 | message = parsedMessage.parsedData 170 | break 171 | } 172 | case 'proofType_Android': { 173 | const parsedMessage = await verifyAndroid(proof, 'v1') 174 | mainProof = { proofType, isVerified: parsedMessage.isVerified, status: parsedMessage.status } 175 | message = parsedMessage.parsedData 176 | break 177 | } 178 | case 'proofType_Android_v2': { 179 | const parsedMessage = await verifyAndroid(proof, 'v2') 180 | mainProof = { proofType, isVerified: parsedMessage.isVerified, status: parsedMessage.status } 181 | message = parsedMessage.parsedData 182 | break 183 | } 184 | case 'proofType_Ledger': { 185 | const parsedMessage = verifyLedger(proof) 186 | mainProof = { proofType, isVerified: parsedMessage.isVerified, status: parsedMessage.status } 187 | message = parsedMessage.parsedData 188 | break 189 | } 190 | default: { 191 | throw new Error() 192 | } 193 | } 194 | switch (findExtensionProof(message)) { 195 | case 'computation': { 196 | const parsedMessage = verifyComputationProof(message) 197 | extensionProof = { proofType: 'computation', isVerified: parsedMessage.isVerified, status: parsedMessage.status } 198 | break 199 | } 200 | } 201 | const parsedProof = { 202 | mainProof: mainProof, 203 | extensionProof: extensionProof, 204 | proofShield: null, 205 | message: message, 206 | // $FlowFixMe 207 | proofId: crypto.createHash('sha256').update(proof).digest().toString('hex'), 208 | } 209 | callback && callback(parsedProof) 210 | return parsedProof 211 | } 212 | -------------------------------------------------------------------------------- /lib/tlsn-verify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // this verification uses portions from the pagesigner 4 | // whose code is slightly modified but mostly kept intact 5 | // for potential web re-use 6 | const sha256 = require('sha256'); 7 | const BigInteger = require('big-integer'); 8 | const tlsnVerifyChain = require('./tlsn/verifychain/verifychain.js'); 9 | const tlsnClientFile = require('./tlsn/tlsn.js'); 10 | const tlsn_utils = require('./tlsn/tlsn_utils.js'); 11 | // $FlowFixMe 12 | const Buffer = require('buffer').Buffer; 13 | const Certificate = tlsnVerifyChain.Certificate; 14 | const TLSNClientSession = tlsnClientFile.TLSNClientSession; 15 | const decrypt_html = tlsnClientFile.decrypt_html; 16 | 17 | function verifyCommitHashSignature(commithash, signature, modulus) { 18 | //RSA verification is sig^e mod n, drop the padding and get the last 32 bytes 19 | const bigint_signature = new BigInteger(tlsn_utils.ba2hex(signature), 16); 20 | const bigint_mod = new BigInteger(tlsn_utils.ba2hex(modulus), 16); 21 | const bigint_exp = new BigInteger(tlsn_utils.ba2hex(tlsn_utils.bi2ba(65537)), 16); 22 | const bigint_result = bigint_signature.modPow(bigint_exp, bigint_mod); 23 | const padded_hash = tlsn_utils.hex2ba(bigint_result.toString(16)); 24 | const hash = padded_hash.slice(padded_hash.length - 32); 25 | if (commithash.toString() === Buffer.from(hash).toString('hex')) return true; 26 | return false; 27 | } 28 | 29 | function getModulus(cert) { 30 | const c = Certificate.decode(Buffer.from(cert), 'der'); 31 | const pk = c.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; 32 | const pkba = tlsn_utils.ua2ba(pk); 33 | // Expected modulus length 256, 384, 512 34 | let modlen = 256; 35 | if (pkba.length > 384) modlen = 384; 36 | if (pkba.length > 512) modlen = 512; 37 | const modulus = pkba.slice(pkba.length - modlen - 5, pkba.length - 5); 38 | return modulus; 39 | } 40 | 41 | function getCommonName(cert) { 42 | const c = Certificate.decode(Buffer.from(cert), 'der'); 43 | const fields = c.tbsCertificate.subject.value; 44 | for (let i = 0; i < fields.length; i++) { 45 | if (fields[i][0].type.toString() !== [2, 5, 4, 3].toString()) continue; 46 | //first 2 bytes are DER-like metadata 47 | return tlsn_utils.ba2str(fields[i][0].value.slice(2)); 48 | } 49 | return 'unknown'; 50 | } 51 | 52 | const verify = (data, servers, notVerifiableServers) => { 53 | data = tlsn_utils.ua2ba(data); 54 | let offset = 0; 55 | const header = tlsn_utils.ba2str(data.slice(offset, offset += 29)); 56 | if (header !== 'tlsnotary notarization file\n\n') throw new Error('wrong header'); 57 | 58 | // Currently supports v1 and v2 59 | const version = tlsn_utils.ba2int(data.slice(offset, offset += 2)); 60 | if (isNaN(version) || version === 0 || version > 2) throw new Error('wrong version'); 61 | 62 | const cs = tlsn_utils.ba2int(data.slice(offset, offset += 2)); 63 | const cr = data.slice(offset, offset += 32); 64 | const sr = data.slice(offset, offset += 32); 65 | const pms1 = data.slice(offset, offset += 24); 66 | const pms2 = data.slice(offset, offset += 24); 67 | const chain_serialized_len = tlsn_utils.ba2int(data.slice(offset, offset += 3)); 68 | const chain_serialized = data.slice(offset, offset += chain_serialized_len); 69 | const tlsver = data.slice(offset, offset += 2); 70 | const tlsver_initial = data.slice(offset, offset += 2); 71 | const response_len = tlsn_utils.ba2int(data.slice(offset, offset += 8)); 72 | const response = data.slice(offset, offset += response_len); 73 | const IV_len = tlsn_utils.ba2int(data.slice(offset, offset += 2)); 74 | const IV = data.slice(offset, offset += IV_len); 75 | const sig_len = tlsn_utils.ba2int(data.slice(offset, offset += 2)); 76 | const sig = data.slice(offset, offset += sig_len); 77 | const commit_hash = data.slice(offset, offset += 32); 78 | const notary_pubkey = data.slice(offset, offset += sig_len); 79 | let time = 0; 80 | // v2 support for time 81 | if (version === 2) time = data.slice(offset, offset += 4); 82 | 83 | // start verification 84 | if (data.length !== offset) throw new Error('invalid .pgsg length'); 85 | 86 | offset = 0; 87 | let chain = []; // For now we only use the 1st cert in the chain 88 | while (offset < chain_serialized.length) { 89 | const len = tlsn_utils.ba2int(chain_serialized.slice(offset, offset += 3)); 90 | const cert = chain_serialized.slice(offset, offset += len); 91 | chain.push(cert); 92 | } 93 | const commonName = getCommonName(chain[0]); 94 | const modulus = getModulus(chain[0]); 95 | // Verify commit hash 96 | if (sha256(response).toString() !== Buffer.from(commit_hash).toString('hex')) throw new Error('commit hash mismatch'); 97 | 98 | let signed_data = 0; 99 | // Verify sig 100 | if (version >= 2) signed_data = sha256([].concat(commit_hash, pms2, modulus, time));else signed_data = sha256([].concat(commit_hash, pms2, modulus)); 101 | 102 | let signingKey; 103 | let commitHashVerified = false; 104 | let isServerVerified = 'yes'; 105 | for (let i = 0; i < servers.length; i++) { 106 | let server = servers[i]; 107 | signingKey = server.sig.modulus; 108 | if (verifyCommitHashSignature(signed_data, sig, signingKey)) { 109 | commitHashVerified = true; 110 | break; 111 | } 112 | } 113 | if (!commitHashVerified) { 114 | for (let j = 0; j < notVerifiableServers.length; j++) { 115 | let server = notVerifiableServers[j]; 116 | if (server === undefined) continue; 117 | 118 | signingKey = server.sig.modulus; 119 | if (verifyCommitHashSignature(signed_data, sig, signingKey)) { 120 | commitHashVerified = true; 121 | isServerVerified = 'no'; 122 | break; 123 | } 124 | } 125 | } 126 | if (!commitHashVerified) throw new Error('Matching notary server not found'); 127 | 128 | //decrypt html and check MAC 129 | let s = new TLSNClientSession(); 130 | s.__init__(); 131 | s.unexpected_server_app_data_count = response.slice(0, 1); 132 | s.chosen_cipher_suite = cs; 133 | s.client_random = cr; 134 | s.server_random = sr; 135 | s.auditee_secret = pms1.slice(2, 2 + s.n_auditee_entropy); 136 | s.initial_tlsver = tlsver_initial; 137 | s.tlsver = tlsver; 138 | s.server_modulus = modulus; 139 | s.set_auditee_secret(); 140 | s.auditor_secret = pms2.slice(0, s.n_auditor_entropy); 141 | s.set_auditor_secret(); 142 | s.set_master_secret_half(); //#without arguments sets the whole MS 143 | s.do_key_expansion(); //#also resets encryption connection state 144 | s.store_server_app_data_records(response.slice(1)); 145 | s.IV_after_finished = IV; 146 | s.server_connection_state.seq_no += 1; 147 | s.server_connection_state.IV = s.IV_after_finished; 148 | const html_with_headers = decrypt_html(s); 149 | return [html_with_headers, commonName, data, notary_pubkey, isServerVerified]; 150 | }; 151 | 152 | const verifyTLS = (data, verifiedServers, notVerifiableServers) => { 153 | let status; 154 | let parsedData; 155 | try { 156 | const verifiedProof = verify(data, verifiedServers, notVerifiableServers); 157 | const isServerVerified = verifiedProof[4]; 158 | parsedData = verifiedProof[0]; 159 | if (isServerVerified === 'no') status = ['success', 'matching notary server not on-line'];else status = ['success', 'no exceptions']; 160 | } catch (err) { 161 | parsedData = ''; 162 | switch (err.message) { 163 | case 'wrong header': 164 | status = ['failed', 'wrong header']; 165 | break; 166 | case 'wrong version': 167 | status = ['failed', 'wrong version']; 168 | break; 169 | case 'invalid .pgsg length': 170 | status = ['failed', 'invalid .pgsg length']; 171 | break; 172 | case 'commit hash mismatch': 173 | status = ['failed', 'commit hash mismatch']; 174 | break; 175 | case 'Matching notary server not found': 176 | status = ['failed', 'matching notary server not found']; 177 | break; 178 | case 'Signature not in chert chain': 179 | status = ['failed', 'Signature not in chert chain']; 180 | break; 181 | default: 182 | throw err; 183 | } 184 | } 185 | const isVerified = status[0] === 'success' ? true : false; 186 | return { status, parsedData, isVerified }; 187 | }; 188 | 189 | module.exports.verify = verify; 190 | module.exports.verifyTLS = verifyTLS; -------------------------------------------------------------------------------- /src/tlsn-verify.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | // this verification uses portions from the pagesigner 3 | // whose code is slightly modified but mostly kept intact 4 | // for potential web re-use 5 | const sha256 = require('sha256') 6 | const BigInteger = require('big-integer') 7 | const tlsnVerifyChain = require('./tlsn/verifychain/verifychain.js') 8 | const tlsnClientFile = require('./tlsn/tlsn.js') 9 | const tlsn_utils = require('./tlsn/tlsn_utils.js') 10 | // $FlowFixMe 11 | const Buffer = require('buffer').Buffer 12 | const Certificate = tlsnVerifyChain.Certificate 13 | const TLSNClientSession = tlsnClientFile.TLSNClientSession 14 | const decrypt_html = tlsnClientFile.decrypt_html 15 | 16 | function verifyCommitHashSignature(commithash, signature, modulus) { 17 | //RSA verification is sig^e mod n, drop the padding and get the last 32 bytes 18 | const bigint_signature = new BigInteger(tlsn_utils.ba2hex(signature), 16) 19 | const bigint_mod = new BigInteger(tlsn_utils.ba2hex(modulus), 16) 20 | const bigint_exp = new BigInteger(tlsn_utils.ba2hex(tlsn_utils.bi2ba(65537)), 16) 21 | const bigint_result = bigint_signature.modPow(bigint_exp, bigint_mod) 22 | const padded_hash = tlsn_utils.hex2ba(bigint_result.toString(16)) 23 | const hash = padded_hash.slice(padded_hash.length - 32) 24 | if (commithash.toString() === Buffer.from(hash).toString('hex')) 25 | return true 26 | return false 27 | } 28 | 29 | function getModulus(cert) { 30 | const c = Certificate.decode(Buffer.from(cert), 'der') 31 | const pk = c.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data 32 | const pkba = tlsn_utils.ua2ba(pk) 33 | // Expected modulus length 256, 384, 512 34 | let modlen = 256 35 | if (pkba.length > 384) modlen = 384 36 | if (pkba.length > 512) modlen = 512 37 | const modulus = pkba.slice(pkba.length - modlen - 5, pkba.length - 5) 38 | return modulus 39 | } 40 | 41 | function getCommonName(cert) { 42 | const c = Certificate.decode(Buffer.from(cert), 'der') 43 | const fields = c.tbsCertificate.subject.value 44 | for (let i = 0; i < fields.length; i++) { 45 | if (fields[i][0].type.toString() !== [2, 5, 4, 3].toString()) 46 | continue 47 | //first 2 bytes are DER-like metadata 48 | return tlsn_utils.ba2str(fields[i][0].value.slice(2)) 49 | } 50 | return 'unknown' 51 | } 52 | 53 | const verify = (data, servers: Array, notVerifiableServers: Array) => { 54 | data = tlsn_utils.ua2ba(data) 55 | let offset = 0 56 | const header = tlsn_utils.ba2str(data.slice(offset, offset += 29)) 57 | if (header !== 'tlsnotary notarization file\n\n') 58 | throw new Error('wrong header') 59 | 60 | // Currently supports v1 and v2 61 | const version = tlsn_utils.ba2int(data.slice(offset, offset += 2)) 62 | if (isNaN(version) || version === 0 || version > 2) 63 | throw new Error('wrong version') 64 | 65 | const cs = tlsn_utils.ba2int(data.slice(offset, offset += 2)) 66 | const cr = data.slice(offset, offset += 32) 67 | const sr = data.slice(offset, offset += 32) 68 | const pms1 = data.slice(offset, offset += 24) 69 | const pms2 = data.slice(offset, offset += 24) 70 | const chain_serialized_len = tlsn_utils.ba2int(data.slice(offset, offset += 3)) 71 | const chain_serialized = data.slice(offset, offset += chain_serialized_len) 72 | const tlsver = data.slice(offset, offset += 2) 73 | const tlsver_initial = data.slice(offset, offset += 2) 74 | const response_len = tlsn_utils.ba2int(data.slice(offset, offset += 8)) 75 | const response = data.slice(offset, offset += response_len) 76 | const IV_len = tlsn_utils.ba2int(data.slice(offset, offset += 2)) 77 | const IV = data.slice(offset, offset += IV_len) 78 | const sig_len = tlsn_utils.ba2int(data.slice(offset, offset += 2)) 79 | const sig = data.slice(offset, offset += sig_len) 80 | const commit_hash = data.slice(offset, offset += 32) 81 | const notary_pubkey = data.slice(offset, offset += sig_len) 82 | let time = 0 83 | // v2 support for time 84 | if (version === 2) 85 | time = data.slice(offset, offset += 4) 86 | 87 | // start verification 88 | if (data.length !== offset) 89 | throw new Error('invalid .pgsg length') 90 | 91 | offset = 0 92 | let chain = [] // For now we only use the 1st cert in the chain 93 | while (offset < chain_serialized.length) { 94 | const len = tlsn_utils.ba2int(chain_serialized.slice(offset, offset += 3)) 95 | const cert = chain_serialized.slice(offset, offset += len) 96 | chain.push(cert) 97 | } 98 | const commonName = getCommonName(chain[0]) 99 | const modulus = getModulus(chain[0]) 100 | // Verify commit hash 101 | if (sha256(response).toString() !== Buffer.from(commit_hash).toString('hex')) 102 | throw new Error('commit hash mismatch') 103 | 104 | let signed_data = 0 105 | // Verify sig 106 | if (version >= 2) 107 | signed_data = sha256([].concat(commit_hash, pms2, modulus, time)) 108 | else 109 | signed_data = sha256([].concat(commit_hash, pms2, modulus)) 110 | 111 | let signingKey 112 | let commitHashVerified = false 113 | let isServerVerified = 'yes' 114 | for (let i = 0; i < servers.length; i++) { 115 | let server = servers[i] 116 | signingKey = server.sig.modulus 117 | if (verifyCommitHashSignature(signed_data, sig, signingKey)) { 118 | commitHashVerified = true 119 | break 120 | } 121 | } 122 | if (! commitHashVerified) { 123 | for (let j = 0; j < notVerifiableServers.length; j++) { 124 | let server = notVerifiableServers[j] 125 | if (server === undefined) 126 | continue 127 | 128 | signingKey = server.sig.modulus 129 | if (verifyCommitHashSignature(signed_data, sig, signingKey)) { 130 | commitHashVerified = true 131 | isServerVerified = 'no' 132 | break 133 | } 134 | } 135 | } 136 | if (!commitHashVerified) 137 | throw new Error('Matching notary server not found') 138 | 139 | //decrypt html and check MAC 140 | let s = new TLSNClientSession() 141 | s.__init__() 142 | s.unexpected_server_app_data_count = response.slice(0, 1) 143 | s.chosen_cipher_suite = cs 144 | s.client_random = cr 145 | s.server_random = sr 146 | s.auditee_secret = pms1.slice(2, 2 + s.n_auditee_entropy) 147 | s.initial_tlsver = tlsver_initial 148 | s.tlsver = tlsver 149 | s.server_modulus = modulus 150 | s.set_auditee_secret() 151 | s.auditor_secret = pms2.slice(0, s.n_auditor_entropy) 152 | s.set_auditor_secret() 153 | s.set_master_secret_half() //#without arguments sets the whole MS 154 | s.do_key_expansion() //#also resets encryption connection state 155 | s.store_server_app_data_records(response.slice(1)) 156 | s.IV_after_finished = IV 157 | s.server_connection_state.seq_no += 1 158 | s.server_connection_state.IV = s.IV_after_finished 159 | const html_with_headers = decrypt_html(s) 160 | return [html_with_headers, commonName, data, notary_pubkey, isServerVerified] 161 | } 162 | 163 | const verifyTLS = (data: Uint8Array, verifiedServers: Array, notVerifiableServers: Array) => { 164 | let status 165 | let parsedData 166 | try { 167 | const verifiedProof = verify(data, verifiedServers, notVerifiableServers) 168 | const isServerVerified = verifiedProof[4] 169 | parsedData = verifiedProof[0] 170 | if (isServerVerified === 'no') 171 | status = ['success', 'matching notary server not on-line'] 172 | else 173 | status = ['success', 'no exceptions'] 174 | } catch(err) { 175 | parsedData = '' 176 | switch(err.message) { 177 | case 'wrong header': 178 | status = ['failed', 'wrong header'] 179 | break 180 | case 'wrong version': 181 | status = ['failed', 'wrong version'] 182 | break 183 | case 'invalid .pgsg length': 184 | status = ['failed', 'invalid .pgsg length'] 185 | break 186 | case 'commit hash mismatch': 187 | status = ['failed', 'commit hash mismatch'] 188 | break 189 | case 'Matching notary server not found': 190 | status = ['failed', 'matching notary server not found'] 191 | break 192 | case 'Signature not in chert chain': 193 | status = ['failed', 'Signature not in chert chain'] 194 | break 195 | default: 196 | throw err 197 | } 198 | } 199 | const isVerified = status[0] === 'success' ? true : false; 200 | return { status, parsedData, isVerified } 201 | } 202 | 203 | module.exports.verify = verify 204 | module.exports.verifyTLS = verifyTLS 205 | -------------------------------------------------------------------------------- /src/tlsn/tlsn_utils.js: -------------------------------------------------------------------------------- 1 | const getRandomValues = require('get-random-values') 2 | const atob = require('atob') 3 | const CryptoJS = require('crypto-js') 4 | import btoa from 'btoa' 5 | import pako from 'pako' 6 | 7 | function assert(condition, message) { 8 | if (!condition) 9 | throw message || 'Assertion failed' 10 | } 11 | 12 | //js native ArrayBuffer to Array of numbers 13 | function ab2ba(ab) { 14 | const view = new DataView(ab) 15 | let int_array = [] 16 | for(let i = 0; i < view.byteLength; i++) 17 | int_array.push(view.getUint8(i)) 18 | return int_array 19 | } 20 | 21 | function ba2ab(ba) { 22 | let ab = new ArrayBuffer(ba.length) 23 | let dv = new DataView(ab) 24 | for(let i = 0; i < ba.length; i++) 25 | dv.setUint8(i, ba[i]) 26 | return ab 27 | } 28 | 29 | function ba2ua(ba) { 30 | let ua = new Uint8Array(ba.length) 31 | for (let i = 0; i < ba.length; i++) 32 | ua[i] = ba[i] 33 | return ua 34 | } 35 | 36 | function ua2ba(ua) { 37 | let ba = [] 38 | for (let i = 0; i < ua.byteLength; i++) 39 | ba.push(ua[i]) 40 | return ba 41 | } 42 | 43 | /*CryptoJS only exposes word arrays of ciphertexts which is awkward to use 44 | so we convert word(4byte) array into a 1-byte array*/ 45 | function wa2ba(wordArray) { 46 | let byteArray = []; 47 | for (let i = 0; i < wordArray.length; ++i) { 48 | let word = wordArray[i] 49 | for (let j = 3; j >= 0; --j) 50 | byteArray.push(word >> 8 * j & 0xFF) 51 | } 52 | return byteArray 53 | } 54 | 55 | //CryptoJS doesnt accept bytearray input but it does accept a hexstring 56 | function ba2hex(bytearray) { 57 | let hexstring = '' 58 | for(let i = 0; i>8; 99 | bytes = [].concat(onebyte, bytes) 100 | } while ( x !== 0 ); 101 | let padding = []; 102 | if (fixed){ 103 | for(let i = 0; i < fixed-bytes.length; i++) 104 | padding = [].concat(padding, 0x00) 105 | } 106 | return [].concat(padding,bytes) 107 | } 108 | 109 | //converts string to bytearray 110 | function str2ba(str) { 111 | if (typeof str !== 'string') 112 | throw new Error('Only type string is allowed in str2ba') 113 | let ba = [] 114 | for(let i = 0; i < str.length; i++) 115 | ba.push(str.charCodeAt(i)) 116 | return ba 117 | } 118 | 119 | function ba2str(ba) { 120 | if (typeof ba !== 'object') 121 | throw new Error('Only type object is allowed in ba2str') 122 | let result = '' 123 | for (let i = 0; i < ba.length; i++) 124 | result += String.fromCharCode(ba[i]) 125 | return result 126 | } 127 | 128 | function hmac(key, msg, algo){ 129 | const key_hex = ba2hex(key) 130 | const msg_hex = ba2hex(msg) 131 | const key_words = CryptoJS.enc.Hex.parse(key_hex) 132 | const msg_words = CryptoJS.enc.Hex.parse(msg_hex) 133 | let hash; 134 | if (algo === 'md5') { 135 | hash = CryptoJS.HmacMD5(msg_words, key_words) 136 | return wa2ba(hash.words) 137 | } else if (algo === 'sha1') { 138 | hash = CryptoJS.HmacSHA1(msg_words, key_words) 139 | return wa2ba(hash.words) 140 | } 141 | } 142 | 143 | function sha1(ba) { 144 | const ba_obj = CryptoJS.enc.Hex.parse(ba2hex(ba)) 145 | const hash = CryptoJS.SHA1(ba_obj) 146 | return wa2ba(hash.words) 147 | } 148 | 149 | function sha256(ba) { 150 | const ba_obj = CryptoJS.enc.Hex.parse(ba2hex(ba)) 151 | const hash = CryptoJS.SHA256(ba_obj) 152 | return wa2ba(hash.words) 153 | } 154 | 155 | function md5(ba) { 156 | const ba_obj = CryptoJS.enc.Hex.parse(ba2hex(ba)) 157 | const hash = CryptoJS.MD5(ba_obj) 158 | return wa2ba(hash.words) 159 | } 160 | 161 | //input bytearrays must be of equal length 162 | function xor(a, b) { 163 | assert(a.length === b.length, 'length mismatch') 164 | let c = [] 165 | for(let i = 0; i < a.length; i++) 166 | c.push(a[i] ^ b[i]) 167 | return c 168 | } 169 | 170 | function isdefined(obj) { 171 | assert(typeof obj !== 'undefined', 'obj was undefined') 172 | } 173 | 174 | function getRandom(number) { 175 | //window was undefined in this context, so i decided to pass it explicitely 176 | const a = getRandomValues(new Uint8Array(number)) 177 | //convert to normal array 178 | const b = Array.prototype.slice.call(a) 179 | return b 180 | } 181 | 182 | function b64encode (aBytes) { 183 | return btoa(String.fromCharCode.apply(null, aBytes)) 184 | } 185 | 186 | function b64decode (sBase64) { 187 | return atob(sBase64).split('').map(function(c) { 188 | return c.charCodeAt(0); }) 189 | } 190 | 191 | //plaintext must be string 192 | function dechunk_http(http_data){ 193 | //'''Dechunk only if http_data is chunked otherwise return http_data unmodified''' 194 | const http_header = http_data.slice(0, http_data.search('\r\n\r\n') + '\r\n\r\n'.length) 195 | //#\s* below means any amount of whitespaces 196 | if (http_header.search(/transfer-encoding:\s*chunked/i) === -1) 197 | return http_data //#nothing to dechunk 198 | const http_body = http_data.slice(http_header.length) 199 | let dechunked = http_header 200 | let cur_offset = 0 201 | let chunk_len = -1 //#initialize with a non-zero value 202 | while (true) { // eslint-disable-line no-constant-condition 203 | const new_offset = http_body.slice(cur_offset).search('\r\n') 204 | if (new_offset === -1) { //#pre-caution against endless looping 205 | //#pinterest.com is known to not send the last 0 chunk when HTTP gzip is disabled 206 | return dechunked 207 | } 208 | const chunk_len_hex = http_body.slice(cur_offset, cur_offset + new_offset) 209 | chunk_len = parseInt(chunk_len_hex, 16) 210 | if (chunk_len === 0) 211 | break //#for properly-formed html we should break here 212 | cur_offset += new_offset + '\r\n'.length 213 | dechunked += http_body.slice(cur_offset, cur_offset + chunk_len) 214 | cur_offset += chunk_len + '\r\n'.length 215 | } 216 | return dechunked 217 | } 218 | 219 | function gunzip_http(http_data) { 220 | const http_header = http_data.slice(0, http_data.search('\r\n\r\n') + '\r\n\r\n'.length) 221 | //#\s* below means any amount of whitespaces 222 | if (http_header.search(/content-encoding:\s*deflate/i) > -1) { 223 | // to add: manually resend the request with compression disabled 224 | throw new Error('Please set gzip_disabled = 1 in tlsnotary.ini and rerun the audit') 225 | } 226 | if (http_header.search(/content-encoding:\s.*gzip/i) === -1) 227 | return http_data //#nothing to gunzip 228 | const http_body = http_data.slice(http_header.length) 229 | let ungzipped = http_header 230 | if (!http_body) { 231 | //HTTP 304 Not Modified has no body 232 | return ungzipped 233 | } 234 | const inflated = pako.inflate(http_body) 235 | ungzipped += ba2str(inflated) 236 | return ungzipped 237 | } 238 | 239 | function getTime() { 240 | const today = new Date() 241 | const time = today.getFullYear()+'-'+('00'+(today.getMonth()+1)).slice(-2)+'-'+('00'+today.getDate()).slice(-2)+'-'+ ('00'+today.getHours()).slice(-2)+'-'+('00'+today.getMinutes()).slice(-2)+'-'+('00'+today.getSeconds()).slice(-2) 242 | return time; 243 | } 244 | 245 | module.exports.ua2ba = ua2ba 246 | module.exports.ba2str = ba2str 247 | module.exports.ba2int = ba2int 248 | module.exports.ba2hex = ba2hex 249 | module.exports.hex2ba = hex2ba 250 | module.exports.bi2ba = bi2ba 251 | module.exports.b64decode = b64decode 252 | module.exports.b64encode = b64encode 253 | module.exports.getRandom = getRandom 254 | module.exports.str2ba = str2ba 255 | module.exports.hmac = hmac 256 | module.exports.xor = xor 257 | module.exports.wa2ba = wa2ba 258 | module.exports.dechunk_http = dechunk_http 259 | module.exports.gunzip_http = gunzip_http 260 | module.exports.ab2ba = ab2ba 261 | module.exports.ba2ab = ba2ab 262 | module.exports.ba2ua = ba2ua 263 | module.exports.sha1 = sha1 264 | module.exports.sha256 = sha256 265 | module.exports.md5 = md5 266 | module.exports.isdefined = isdefined 267 | module.exports.getTime = getTime 268 | -------------------------------------------------------------------------------- /lib/tlsn/tlsn_utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _btoa = require('btoa'); 4 | 5 | var _btoa2 = _interopRequireDefault(_btoa); 6 | 7 | var _pako = require('pako'); 8 | 9 | var _pako2 = _interopRequireDefault(_pako); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | const getRandomValues = require('get-random-values'); 14 | const atob = require('atob'); 15 | const CryptoJS = require('crypto-js'); 16 | 17 | 18 | function assert(condition, message) { 19 | if (!condition) throw message || 'Assertion failed'; 20 | } 21 | 22 | //js native ArrayBuffer to Array of numbers 23 | function ab2ba(ab) { 24 | const view = new DataView(ab); 25 | let int_array = []; 26 | for (let i = 0; i < view.byteLength; i++) int_array.push(view.getUint8(i)); 27 | return int_array; 28 | } 29 | 30 | function ba2ab(ba) { 31 | let ab = new ArrayBuffer(ba.length); 32 | let dv = new DataView(ab); 33 | for (let i = 0; i < ba.length; i++) dv.setUint8(i, ba[i]); 34 | return ab; 35 | } 36 | 37 | function ba2ua(ba) { 38 | let ua = new Uint8Array(ba.length); 39 | for (let i = 0; i < ba.length; i++) ua[i] = ba[i]; 40 | return ua; 41 | } 42 | 43 | function ua2ba(ua) { 44 | let ba = []; 45 | for (let i = 0; i < ua.byteLength; i++) ba.push(ua[i]); 46 | return ba; 47 | } 48 | 49 | /*CryptoJS only exposes word arrays of ciphertexts which is awkward to use 50 | so we convert word(4byte) array into a 1-byte array*/ 51 | function wa2ba(wordArray) { 52 | let byteArray = []; 53 | for (let i = 0; i < wordArray.length; ++i) { 54 | let word = wordArray[i]; 55 | for (let j = 3; j >= 0; --j) byteArray.push(word >> 8 * j & 0xFF); 56 | } 57 | return byteArray; 58 | } 59 | 60 | //CryptoJS doesnt accept bytearray input but it does accept a hexstring 61 | function ba2hex(bytearray) { 62 | let hexstring = ''; 63 | for (let i = 0; i < bytearray.length; i++) { 64 | let hexchar = bytearray[i].toString(16); 65 | if (hexchar.length == 1) hexchar = '0' + hexchar; 66 | hexstring += hexchar; 67 | } 68 | return hexstring; 69 | } 70 | 71 | //convert a hex string into byte array 72 | function hex2ba(_str) { 73 | let str = _str; 74 | let ba = []; 75 | //pad with a leading 0 if necessary 76 | if (str.length % 2) str = '0' + str; 77 | for (let i = 0; i < str.length; i += 2) ba.push(parseInt('0x' + str.substr(i, 2))); 78 | return ba; 79 | } 80 | 81 | //Turn a max 4 byte array (big-endian) into an int. 82 | function ba2int(x) { 83 | assert(x.length <= 8, 'Cannot convert bytearray larger than 8 bytes'); 84 | let retval = 0; 85 | for (let i = 0; i < x.length; i++) retval |= x[x.length - 1 - i] << 8 * i; 86 | return retval; 87 | } 88 | 89 | //Turn an int into a bytearray. Optionally left-pad with zeroes 90 | function bi2ba(_x, args) { 91 | let x = _x; 92 | assert(typeof x == 'number', 'Only can convert numbers'); 93 | let fixed = null; 94 | if (typeof args !== 'undefined') fixed = args.fixed; 95 | let bytes = []; 96 | do { 97 | let onebyte = x & 255; 98 | x = x >> 8; 99 | bytes = [].concat(onebyte, bytes); 100 | } while (x !== 0); 101 | let padding = []; 102 | if (fixed) { 103 | for (let i = 0; i < fixed - bytes.length; i++) padding = [].concat(padding, 0x00); 104 | } 105 | return [].concat(padding, bytes); 106 | } 107 | 108 | //converts string to bytearray 109 | function str2ba(str) { 110 | if (typeof str !== 'string') throw new Error('Only type string is allowed in str2ba'); 111 | let ba = []; 112 | for (let i = 0; i < str.length; i++) ba.push(str.charCodeAt(i)); 113 | return ba; 114 | } 115 | 116 | function ba2str(ba) { 117 | if (typeof ba !== 'object') throw new Error('Only type object is allowed in ba2str'); 118 | let result = ''; 119 | for (let i = 0; i < ba.length; i++) result += String.fromCharCode(ba[i]); 120 | return result; 121 | } 122 | 123 | function hmac(key, msg, algo) { 124 | const key_hex = ba2hex(key); 125 | const msg_hex = ba2hex(msg); 126 | const key_words = CryptoJS.enc.Hex.parse(key_hex); 127 | const msg_words = CryptoJS.enc.Hex.parse(msg_hex); 128 | let hash; 129 | if (algo === 'md5') { 130 | hash = CryptoJS.HmacMD5(msg_words, key_words); 131 | return wa2ba(hash.words); 132 | } else if (algo === 'sha1') { 133 | hash = CryptoJS.HmacSHA1(msg_words, key_words); 134 | return wa2ba(hash.words); 135 | } 136 | } 137 | 138 | function sha1(ba) { 139 | const ba_obj = CryptoJS.enc.Hex.parse(ba2hex(ba)); 140 | const hash = CryptoJS.SHA1(ba_obj); 141 | return wa2ba(hash.words); 142 | } 143 | 144 | function sha256(ba) { 145 | const ba_obj = CryptoJS.enc.Hex.parse(ba2hex(ba)); 146 | const hash = CryptoJS.SHA256(ba_obj); 147 | return wa2ba(hash.words); 148 | } 149 | 150 | function md5(ba) { 151 | const ba_obj = CryptoJS.enc.Hex.parse(ba2hex(ba)); 152 | const hash = CryptoJS.MD5(ba_obj); 153 | return wa2ba(hash.words); 154 | } 155 | 156 | //input bytearrays must be of equal length 157 | function xor(a, b) { 158 | assert(a.length === b.length, 'length mismatch'); 159 | let c = []; 160 | for (let i = 0; i < a.length; i++) c.push(a[i] ^ b[i]); 161 | return c; 162 | } 163 | 164 | function isdefined(obj) { 165 | assert(typeof obj !== 'undefined', 'obj was undefined'); 166 | } 167 | 168 | function getRandom(number) { 169 | //window was undefined in this context, so i decided to pass it explicitely 170 | const a = getRandomValues(new Uint8Array(number)); 171 | //convert to normal array 172 | const b = Array.prototype.slice.call(a); 173 | return b; 174 | } 175 | 176 | function b64encode(aBytes) { 177 | return (0, _btoa2.default)(String.fromCharCode.apply(null, aBytes)); 178 | } 179 | 180 | function b64decode(sBase64) { 181 | return atob(sBase64).split('').map(function (c) { 182 | return c.charCodeAt(0); 183 | }); 184 | } 185 | 186 | //plaintext must be string 187 | function dechunk_http(http_data) { 188 | //'''Dechunk only if http_data is chunked otherwise return http_data unmodified''' 189 | const http_header = http_data.slice(0, http_data.search('\r\n\r\n') + '\r\n\r\n'.length); 190 | //#\s* below means any amount of whitespaces 191 | if (http_header.search(/transfer-encoding:\s*chunked/i) === -1) return http_data; //#nothing to dechunk 192 | const http_body = http_data.slice(http_header.length); 193 | let dechunked = http_header; 194 | let cur_offset = 0; 195 | let chunk_len = -1; //#initialize with a non-zero value 196 | while (true) { 197 | // eslint-disable-line no-constant-condition 198 | const new_offset = http_body.slice(cur_offset).search('\r\n'); 199 | if (new_offset === -1) { 200 | //#pre-caution against endless looping 201 | //#pinterest.com is known to not send the last 0 chunk when HTTP gzip is disabled 202 | return dechunked; 203 | } 204 | const chunk_len_hex = http_body.slice(cur_offset, cur_offset + new_offset); 205 | chunk_len = parseInt(chunk_len_hex, 16); 206 | if (chunk_len === 0) break; //#for properly-formed html we should break here 207 | cur_offset += new_offset + '\r\n'.length; 208 | dechunked += http_body.slice(cur_offset, cur_offset + chunk_len); 209 | cur_offset += chunk_len + '\r\n'.length; 210 | } 211 | return dechunked; 212 | } 213 | 214 | function gunzip_http(http_data) { 215 | const http_header = http_data.slice(0, http_data.search('\r\n\r\n') + '\r\n\r\n'.length); 216 | //#\s* below means any amount of whitespaces 217 | if (http_header.search(/content-encoding:\s*deflate/i) > -1) { 218 | // to add: manually resend the request with compression disabled 219 | throw new Error('Please set gzip_disabled = 1 in tlsnotary.ini and rerun the audit'); 220 | } 221 | if (http_header.search(/content-encoding:\s.*gzip/i) === -1) return http_data; //#nothing to gunzip 222 | const http_body = http_data.slice(http_header.length); 223 | let ungzipped = http_header; 224 | if (!http_body) { 225 | //HTTP 304 Not Modified has no body 226 | return ungzipped; 227 | } 228 | const inflated = _pako2.default.inflate(http_body); 229 | ungzipped += ba2str(inflated); 230 | return ungzipped; 231 | } 232 | 233 | function getTime() { 234 | const today = new Date(); 235 | const time = today.getFullYear() + '-' + ('00' + (today.getMonth() + 1)).slice(-2) + '-' + ('00' + today.getDate()).slice(-2) + '-' + ('00' + today.getHours()).slice(-2) + '-' + ('00' + today.getMinutes()).slice(-2) + '-' + ('00' + today.getSeconds()).slice(-2); 236 | return time; 237 | } 238 | 239 | module.exports.ua2ba = ua2ba; 240 | module.exports.ba2str = ba2str; 241 | module.exports.ba2int = ba2int; 242 | module.exports.ba2hex = ba2hex; 243 | module.exports.hex2ba = hex2ba; 244 | module.exports.bi2ba = bi2ba; 245 | module.exports.b64decode = b64decode; 246 | module.exports.b64encode = b64encode; 247 | module.exports.getRandom = getRandom; 248 | module.exports.str2ba = str2ba; 249 | module.exports.hmac = hmac; 250 | module.exports.xor = xor; 251 | module.exports.wa2ba = wa2ba; 252 | module.exports.dechunk_http = dechunk_http; 253 | module.exports.gunzip_http = gunzip_http; 254 | module.exports.ab2ba = ab2ba; 255 | module.exports.ba2ab = ba2ab; 256 | module.exports.ba2ua = ba2ua; 257 | module.exports.sha1 = sha1; 258 | module.exports.sha256 = sha256; 259 | module.exports.md5 = md5; 260 | module.exports.isdefined = isdefined; 261 | module.exports.getTime = getTime; -------------------------------------------------------------------------------- /settings/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "v1": { 3 | "androidCertChain" : ["v2xpbnRlcm1lZGlhdGVZAnwwggJ4MIICHqADAgECAgIQATAKBggqhkjOPQQDAjCBmDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTATBgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEzMDEGA1UEAwwqQW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBSb290MB4XDTE2MDExMTAwNDYwOVoXDTI2MDEwODAwNDYwOVowgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQxOzA5BgNVBAMMMkFuZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gSW50ZXJtZWRpYXRlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6555+EJjWazLKpFMiYbMcK2QZpOCqXMmE/6sy/ghJ0whdJdKKv6luU1/ZtTgZRBmNbxTt6CjpnFYPts+Ea4QFKNmMGQwHQYDVR0OBBYEFD/8rNYasTqegSC41SUcxWW7HpGpMB8GA1UdIwQYMBaAFMit6XdMRcOjzw0WEOR5QzohWjDPMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgKEMAoGCCqGSM49BAMCA0gAMEUCIEuKm3vugrzAM4euL8CJmLTdw42rJypFn2kMx8OS1A+OAiEA7toBXbb0MunUhDtiTJQE7zp8zL1e+yK75/65dz9ZP/tkbGVhZlkBxzCCAcMwggFqoAMCAQICAQEwCgYIKoZIzj0EAwIwHDEaMBgGA1UEAwwRQW5kcm9pZCBLZXltYXN0ZXIwIBcNNzAwMTAxMDAwMDAwWhgPMjEwNjAyMDcwNjI4MTVaMBoxGDAWBgNVBAMMD0EgS2V5bWFzdGVyIEtleTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDTyt6b8/b44NvWqFfj4YGMimSXTx64MWyTOFJcmJMlIrT6G56qfe3GmzUN7LYKytc3x1GAeGJLEwfzmEmNKkBajgZwwgZkwCwYDVR0PBAQDAgeAMGkGCisGAQQB1nkCAREEWzBZAgEBCgEAAgEBCgEBBAhPcmFjbGl6ZQQAMAy/hT0IAgYBWSGRNtAwMaEFMQMCAQKiAwIBA6MEAgIBAKUFMQMCAQSqAwIBAb+DdwIFAL+FPgMCAQC/hT8CBQAwHwYDVR0jBBgwFoAUP/ys1hqxOp6BILjVJRzFZbsekakwCgYIKoZIzj0EAwIDRwAwRAIgKTl8K4FTtymLq52KcrPQ22A8yhjAZ4PNPoYRdwZVp/gCIAclIURvCfXhyE8IwK++J75nmVhOWQny4itQHa6uG8yvZHJvb3RZAo8wggKLMIICMqADAgECAgkAogWe0Q5DW1cwCgYIKoZIzj0EAwIwgZgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQxMzAxBgNVBAMMKkFuZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gUm9vdDAeFw0xNjAxMTEwMDQzNTBaFw0zNjAxMDYwMDQzNTBaMIGYMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTMwMQYDVQQDDCpBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVzdGF0aW9uIFJvb3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATuXV7H4cDbbQOmfua2G+xNal1qaC4P/39JDn13H0Qibb2xr/oWy8etxXfSVpyqt7AtVAFdPkMrKo7XTuxIdUGko2MwYTAdBgNVHQ4EFgQUyK3pd0xFw6PPDRYQ5HlDOiFaMM8wHwYDVR0jBBgwFoAUyK3pd0xFw6PPDRYQ5HlDOiFaMM8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAoQwCgYIKoZIzj0EAwIDRwAwRAIgNSGj74s0Rh6c1WDzHViJIGrco2VB9g2ezooZjGZIYHsCIE0L81HZMHx9W9o1NB2oRxtjpYVlPK1PJKfnTa9BffG//wo="], 4 | "googleApiKey" : ["AIzaSyCkruvXUsDIVCQubpimWlFFzDFKvv9E71Y"], 5 | "apkDigest": ["iLVOtOJu6SqNBA16v02nZED6eWWslB1Mu4aNwqMNQ3U","9Vv0ZpmiJ2ihINfv0FAun8DFtD03ZyRo1zO65StaOZU=","qviis2L7Md/jZD563DHTn2DQXo3w+rqP+A3/Yu7Nlts="], 6 | "apkCertDigest": ["lXEfU6VUM1DQA2BkSIQqUFHjJGK+hLmORbWg/8dcSvc=","xDk1cd/kftArCHCkk51/78EzjCbMITCaSPJfa37Sb9Y="], 7 | "pubKeys": ["04a9e16dd7a54826782622a38e9fc74f67d8d681de1330d30c4f3a3d81e49b3cb60d52218904accc23b2e0b5c5450d5cfc2e525f423f2496547cb816b778e98f0f","0434f2b7a6fcfdbe3836f5aa15f8f86063229925d3c7ae0c5b24ce14972624c948ad3e86e7aa9f7b71a6cd437b2d82b2b5cdf1d4601e1892c4c1fce612634a9016"], 8 | "alg": "SHA256withECDSA", 9 | "attestationParams": { 10 | "keymasterVersion" : "1", 11 | "attestationSecurityLevel" : "TrustedEnvironment", 12 | "keymasterSecurityLevel" : "TrustedEnvironment", 13 | "attestationChallenge" : "Oraclize", 14 | "teeEnforced": { 15 | "purpose" : "2", 16 | "algorithm" : "3", 17 | "digest" : "4", 18 | "ecCurve" : "1", 19 | "origin" : "0" 20 | } 21 | } 22 | }, 23 | "v2": { 24 | "androidCertChain" : ["v2xpbnRlcm1lZGlhdGVZBOQwggTgMIICyKADAgECAhEAkIgOEDBOOW5NO8ydWMIBEDANBgkqhkiG9w0BAQsFADA5MQwwCgYDVQQMDANURUUxKTAnBgNVBAUTIDJjY2Q4MWVhNTY3NzMyMmM0MTM3MGU1ZmE2ZDgyM2YzMB4XDTIwMDYxMDIxNTEyNVoXDTMwMDYwODIxNTEyNVowOTEMMAoGA1UEDAwDVEVFMSkwJwYDVQQFEyAzODZlOTVlYjdkMzUyNDEwZTFjMDg4OTBlZmIzMGM4ODCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBANJfy+YuyfDC79gegwPtc1Iwi4uzmzqB265cHPbWf/P4T+aYS7eVjYduTh6SZbIbn0fB+52aQS0trDk576fcVwfTHfI1UMpk5wj0K852yd0I10VbNRgRLOqKUC9Dg3tF/807RZ9NJVPM3mTqKAkydI15L59565DEkNLhG2OI1XsuSM0+xiNct/RlrX+FDw/q4FJyqKwuZ2n8gZhsug4Fqnrb4S253NeKf7ugmlP1CBLp9i57Rd+dpIh0kJKwFmko4UIt6YqSJB4W5Qag8CTZLUnvdjStbRmJU6676aJSmCysGqa5XkLNWtj5Iy93l8X05Ivloh2FBWJb3lDHYYPQAhFpzBk/qVYWSP+MJibBRlTcd+jVnEEatb5xGyApTM4bRC0g+6wQsLSSTa+mskotU8tDc6tzOIIosKP3M0hUSzhKjs6Fi0jf07Lg2v7zASslfpXioMeB5qJNEIMxQin1CuLoboFaudMsxxLguZ8ZdYF9zpXeTkWDAAqNPfay5+BhbwIDAQABo2MwYTAdBgNVHQ4EFgQUtOnXU8fQ9adW0YbB9n7KQ/aVDjowHwYDVR0jBBgwFoAULhu9ANTJ0QxrughXYY67q+atPZ4wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADggIBABw78gbqkXa0PCQ07i6h0mo4XhRNBBGw95I3M2l/+H9ctI5JFExDwW6Lx0TIXgrR583F+Q4MPj+xvgr62Y3Rzqk42bAHxXsYKuEMwCOghQK0Y048t9PmoqVnB/46/N+vUUhEAtXAXkEJcAHG6nyBWhjlXyEgfKRnfu9wU68LlBPsbAt2JRMTlsLzvjPbl9JUhc8b6uqeJkEzi+t70Lmv9QoI1gB+Mearn53bCJULRkz36lB459brFk0oRXOHmc9LCDKtWLNDTWEF1tgLFdvA3YiUKP2WTt7StsavoFA8+xgc/i2YTwstt9IOGFSuL0kCzWz/g81W1NAgHl2MDKnKoTPlP/QGXZbQlznUPg7pFXsQchXA2wggVTyasc2a1AUQr2AnycVlaGSDh1RHzFXmcqFVNkIrCR4qDzmB6yUe6r6jObOpBB3l0zBga7h+Y/UVw1aQ0W+RFyxP42jLs/k5P2Vj1PNfoizCRuUpV2smGeOhj0aBHkIeu4BZJgOT9JLAd5oYPfQyKxk74F0F58iNMGkNmY0jcrciYRdo0l/WYzI8mMfaQ2cd8cSWwcKs2PklniR/JUQqvDJAL5a9Tf22T2hMVVgabnAbWimIxpAvpW0nlgI08+x1/GmYUBGZtMcvLM1JzZafN76gsRAwIWPnSoIyEdK75GeKO9N1M46WqqgaZGxlYWZZBQMwggT/MIIDZ6ADAgECAgEBMA0GCSqGSIb3DQEBCwUAMDkxDDAKBgNVBAwMA1RFRTEpMCcGA1UEBRMgMzg2ZTk1ZWI3ZDM1MjQxMGUxYzA4ODkwZWZiMzBjODgwHhcNMjAwNjEwMjE1MTI1WhcNMzAwNjA4MjE1MTI1WjAfMR0wGwYDVQQDDBRBbmRyb2lkIEtleXN0b3JlIEtleTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJsE1Q1lI+u0cTWKUbs2WtcboGxxBghV75wdgL0/3nMOqtWvZDycDVW/bRylMI9FlxMa98cV+O6Q5qM7u5ZM2Rkg0/b2FYe1PBVqwutZu1sLIsLNQ6gvpB0Yyz2so7LHL8RQi2o/jHYezQhQULHSaIAogu2cIq/3Ub3woklhthfX03Fg3HzuNYSBHAFxDdXBSZXapFuWX/TD8sXShehAn/Y7YwscnhexFOhQgIEvHwuPTOSw7smVujyjRURNYR28EBH69rlsFyJuP4/SSOZph9IXJKVLB2GurRrTphHBD9AINx6jM2Y/6MoxemDEOwsZWgQb3hsfKtUfrPOtA5vyD38CAwEAAaOCAaowggGmMB8GA1UdIwQYMBaAFLTp11PH0PWnVtGGwfZ+ykP2lQ46MIIBNgYKKwYBBAHWeQIBEQSCASYwggEiAgEDCgEBAgEECgEBBAhPcmFjbGl6ZQQAMFm/hT0IAgYBf9X3d9K/hUVJBEcwRTEfMB0EGGl0Lm9yYWNsaXplLmFuZHJvaWRwcm9vZgIBAzEiBCDGtjEEFuDMve8F5Xs5B4Fq8rN9k8EtcTkb+uUDV6L63zCBrKEFMQMCAQKiAwIBAaMEAgIIAKUFMQMCAQSmBTEDAgEFv4FIBQIDAQABv4N3AgUAv4U+AwIBAL+FQEwwSgQgTz3Fy7dN7REVILUovGBJhm56bSf/emz1NtFFKYf+tnQBAf8KAQAEIBebWhg3hg2evom6v2IiUhRyucclIVLherAO70aGsQdAv4VBBQIDAYagv4VCBQIDAxV3v4VOBgIEATRifb+FTwYCBAE0Yn0wHQYLKwYBBAGBbAsDFwcEDjAMoAoTCE9yYWNsaXplMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQU5h556M+4puXmdcMOx2c0PwAD9b0wDQYJKoZIhvcNAQELBQADggGBAE7yn8V1oieEDyxVslqo+lNaBM9V1O9IjAV3AYoGhWBmZUtatKfvcSWq8Fs8f9BZa1CfVnmC8xc3ppqHkhJqcl45YUv/wGCN2w7g02iiCV/ByQ6/xS1w3JDzsVDsIuSi8B78Vy2VagYI43dfykJ2NMAxZkCVSNLJylTQUgwf2tCKhnW4TrGsjSIAKYexOuC+No1GO15qMhKjDmos4ClqxnygtaW56qsObmpVA0yV2nd1efj7Z5dEp9C9EmMMzd8gPy7RwwL/gVDlNgS1+A2fqTQea0qPN/PmQsrm5m37F+JV1C6UbgkDhoiIQwYuW0B7z4WeRTI3HZz2hfP/RpssWWIbBS6GxPMBarTlRvToH1Gwfu0Hb5/+bpyQ+XcIwXdYB5BssBNdN2hxSXHol+PiEY7/7JTObmhnRXeMDNa7/97P7mNhapMtwNz9RdNXtnCBFcztB7S+AN+2Kqnb30OKdMslJb+Hmja7p804iY1stqGSbWnlKFLrrzR0DPABm3uA2mRyb290WQVGMIIFQjCCAyqgAwIBAgIRALfFDY9i0viQ9X74pKcpOr4wDQYJKoZIhvcNAQELBQAwGzEZMBcGA1UEBRMQZjkyMDA5ZTg1M2I2YjA0NTAeFw0yMDA2MTAyMTQ4MjhaFw0zMDA2MDgyMTQ4MjhaMDkxDDAKBgNVBAwMA1RFRTEpMCcGA1UEBRMgMmNjZDgxZWE1Njc3MzIyYzQxMzcwZTVmYTZkODIzZjMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKXMGEwibUgr4g+yYnhX+zdvDZb9z7rK9H522i9MUw0Xe/O8Cv7GBHiWf+90iklRKKUdoGQgQEkjV0vY+/OKMMiFamkpXdOrD/l3dXdCnCjNGvP7Zs2LQqCP5Hg/0SQ+xButDPExwsYK5/ZdBGn0vcgYayNsrcQfwqO17IUDUk9w9ap3vXbzTdgdfB0QpmwwNLVJzWIEbNh8GXaDWPmCbxmFp1/DkmMhVwMTb+2/j0VI58pkkgZnbMRLDwdP6h/RHD5lwh4HUGrp8avzLGELXOG88wrrjIbtVfLbIWeKzjGXuk24m1ZI7cx2dTzSSPBYe6B7Y75O6tt+5fnMV4SuleY54Fape4Y9cLEuoO0rxNchS4o5N8+vSLHmWACYjR5Ac924hzy5R3tbZ2xAUHR/Gaw2m4DQ4n3xgyxvJieToVDBTWy9lq8ILpwq7FeW6LnkY5olm/y5Rdh+QH6jDzvwoHQ9vPZ6Lh+RUYeibY6iWPIH87mNVxbshfPugBn0KM81O2QmXiePkm1UQ1asW8S+niYu35AfrfIqdj1HSBefRpZcDEun68Z8rH8AqG/o5PfiFQgkIp2o63Rv2RUSZOUq/fa0Jygmlq+PSALOXkNVF64xBuJkK+teNJOaYoBX1W0z0HzyRcoW0T12Qk/9wBcbBilv6FmtJ5YfTNClw1HFYFkQIDAQABo2MwYTAdBgNVHQ4EFgQULhu9ANTJ0QxrughXYY67q+atPZ4wHwYDVR0jBBgwFoAUNmHhAHyIBQlRi0RsR/8aTMnqTxIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADggIBABEbJ/k93uBTd6DpuSJOtrqiJ2y6u7Eo+zDK0l4bKMoZcZTO8xTcJiAkw2FVvBCS0YK4KBXzPVbQzie/h2X/5FRkK3EXK4QfvYkC7Ht/t2iSmfxqWExVAYG9uXogUAYlJbGLf8jqNzPDtKXHvo4SC6O4JwNpdkvOLQGbKSZS0iGA6bYzNCqycWmDifyzQ/GYgfICJNCL+xsux6bA09QBBbypFldeYfVP21ZAyuDiavL+0UZZf5pkYis3CWZ8a5Hb5WNooIe82YpFEimd4BYPuZ2cX4aRyrM1fq2cY8IDHVfZ6vLAyQDoyHYab85YCubs43MnxVtdaN2rz2HHl6tcQWLApA3o4EMisC8UFovPAru1hCTn64dotRNDSdmmHdTuEqUWBpkZuifMDf6uJRSnazdwOtlzJD5QrV2hcPtmbIRuvYWKLs/Dorl015uhBcNAypEHhvJhbDULMroSvqeUk/kg69aTfFtqkvFJNcDfrgHxfKci0A0QvNTiAY5XjFcYM8uHbK4VpEsvHB6xLVLx1eG4NfVEPsbNUbD/2Whm42mrPX5X5LVSyk2t0wuFVMGL2jxDyW6V0jSaHjl5d9eXos/JP9J18B3Wc7S3bh2IesWFrW+BcoTDZxyiQ+R5tiNlgldGMuOIl2QlpYRVvWCWPaCmGE6Qrhv3VWS+D81KeS32/w=="], 25 | "googleApiKey" : ["AIzaSyCkruvXUsDIVCQubpimWlFFzDFKvv9E71Y", "AIzaSyB7H3VFvXHVsEFEGfWGjQqQe_lf4VGZQzM"], 26 | "apkDigest": ["iLVOtOJu6SqNBA16v02nZED6eWWslB1Mu4aNwqMNQ3U","9Vv0ZpmiJ2ihINfv0FAun8DFtD03ZyRo1zO65StaOZU=","qviis2L7Md/jZD563DHTn2DQXo3w+rqP+A3/Yu7Nlts=","Rzujjnnw0NfK90PLnVSEMxBHo63OOCZwvofIJpUPSx8="], 27 | "apkCertDigest": ["lXEfU6VUM1DQA2BkSIQqUFHjJGK+hLmORbWg/8dcSvc=","xDk1cd/kftArCHCkk51/78EzjCbMITCaSPJfa37Sb9Y=","oAViazrxPSAq1n5M/TPoQWT8pSZmWCRYrnTAuuLQAcU="], 28 | "pubKeys": ["04a9e16dd7a54826782622a38e9fc74f67d8d681de1330d30c4f3a3d81e49b3cb60d52218904accc23b2e0b5c5450d5cfc2e525f423f2496547cb816b778e98f0f","0434f2b7a6fcfdbe3836f5aa15f8f86063229925d3c7ae0c5b24ce14972624c948ad3e86e7aa9f7b71a6cd437b2d82b2b5cdf1d4601e1892c4c1fce612634a9016"], 29 | "alg": "SHA256withRSA", 30 | "attestationParams": { 31 | "keymasterVersion" : "4", 32 | "attestationSecurityLevel" : "TrustedEnvironment", 33 | "keymasterSecurityLevel" : "TrustedEnvironment", 34 | "attestationChallenge" : "Oraclize", 35 | "teeEnforced": { 36 | "purpose" : "2", 37 | "algorithm" : "1", 38 | "digest" : "4", 39 | "ecCurve" : "0", 40 | "origin" : "0" 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/oraclize/oracles.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const request = require('isomorphic-fetch') 3 | const xmlParse = require('xml2js') 4 | const R = require('ramda') 5 | const subtractList = require('../helpers.js').subtractList 6 | const tlsn_utils = require('../tlsn/tlsn_utils.js') 7 | 8 | const kernelId = 'aki-503e7402' 9 | const snapshotIdV1Main = ['snap-cdd399f8'] 10 | const snapshotIdV1Sig = ['snap-00083b35'] 11 | const imageIDV1Main = ['ami-5e39040c'] 12 | const imageIDV1Sig = ['ami-88724fda'] 13 | // const snapshotIdV2 = 'snap-2c1fab9b' // necessary for [oraclize5, oraclize6, oraclize7] 14 | // const imageIdV2 = 'ami-15192302' // necessary for [oraclize5, oraclize6, oraclize7] 15 | 16 | const snapshotIdV2 = ['snap-03bae56722ceec3f0', 'snap-0fa4d717595514c9e', 17 | 'snap-0aae1b6ca8c4b0f8d', 'snap-001724fd6e3219a8b', 18 | 'snap-0d6e71fc4113c1d7f', 'snap-0d65665f4afaf28f8', 19 | 'snap-0f2a4af7bbcaf3374', 'snap-03a7cfbcc835aae69', 20 | 'snap-01bd7dcd6b89c4784', 'snap-03c1d1eaab2ded518'] 21 | 22 | const imageIdV2 = ['ami-1f447c65', 'ami-0900465cc4d168b18', 23 | 'ami-05fd672d486340db9', 'ami-004bb38dca2efe5cf', 24 | 'ami-0fc7d2d0c4a3fe3f5', 'ami-01887359c36e2e246', 25 | 'ami-0d9bdb20c502dbb32', 'ami-01fe7595953c2753b', 26 | 'ami-0d0829459bf2597b0', 'ami-0f558579909742612'] 27 | 28 | const servers = require('./servers.js').servers 29 | 30 | async function getJSON(res) { 31 | const text = await res.text() 32 | return JSON.parse(JSON.stringify(parseSync(text))) 33 | } 34 | 35 | const validateServer = async (server, type) => { 36 | try { 37 | let notaryServer = null 38 | if (type === 'sig') 39 | notaryServer = server.sig 40 | else 41 | notaryServer = server.main 42 | let res = await request(notaryServer.DI) 43 | if (res.status !== 200) 44 | throw new Error('aws_await request_failed') 45 | let json = await getJSON(res) 46 | const args = checkDescribeInstances(json.DescribeInstancesResponse, notaryServer.instanceId, notaryServer.IP, type) 47 | res = await request(notaryServer.DV) 48 | if (res.status !== 200) 49 | throw new Error('aws_await request_failed') 50 | json = await getJSON(res) 51 | checkDescribeVolumes(json.DescribeVolumesResponse, notaryServer.instanceId, args.volumeId, args.volAttachTime, type) 52 | if (typeof window === 'undefined') { //TODO proxy server to be able to access ami.amzon from browser 53 | res = await request(notaryServer.GU) 54 | if (res.status !== 200) 55 | throw new Error('aws_await request_failed') 56 | json = await getJSON(res) 57 | checkGetUser(json.GetUserResponse.GetUserResult, args.ownerId) 58 | } 59 | res = await request(notaryServer.GCO) 60 | if (res.status !== 200) 61 | throw new Error('aws_await request_failed') 62 | json = await getJSON(res) 63 | const pubKey = checkGetConsoleOutput(json.GetConsoleOutputResponse, notaryServer.instanceId, args.launchTime, type) 64 | res = await request(notaryServer.DIA) 65 | json = await getJSON(res) 66 | checkDescribeInstanceAttribute(json.DescribeInstanceAttributeResponse, notaryServer.instanceId) 67 | // Check for v1 68 | if (type !== 'main') 69 | assert(getModulusFromPubKey(pubKey).toString() === server.sig.modulus.toString()) 70 | const mark = 'AWSAccessKeyId=' 71 | let start 72 | let id 73 | let ids = [] 74 | // "AWSAccessKeyId" should be the same to prove that the queries are made on behalf of AWS user "root". 75 | // The attacker can be a user with limited privileges for whom the API would report only partial information. 76 | const urlArray = [notaryServer.DI, notaryServer.DV, notaryServer.GU, notaryServer.GCO, notaryServer.DIA] 77 | for (const url of urlArray) { 78 | start = url.search(mark) + mark.length 79 | id = url.slice(start, start + url.slice(start).search('&')) 80 | ids.push(id) 81 | } 82 | assert(new Set(ids).size === 1) // eslint-disable-line 83 | return pubKey 84 | } catch (err) { 85 | throw err 86 | } 87 | } 88 | 89 | function parseSync(xml) { 90 | let error = null 91 | let json = null 92 | xmlParse.parseString(xml, function (innerError, innerJson) { 93 | error = innerError 94 | json = innerJson 95 | }) 96 | if (error) 97 | throw error 98 | if (!error && !json) 99 | throw new Error('The callback was suddenly async or something.') 100 | return json 101 | } 102 | 103 | // assuming both events happened on the same day, get the time 104 | // difference between them in seconds 105 | // the time string looks like "2015-04-15T19:00:59.000Z" 106 | function getSecondsDelta(later, sooner) { 107 | assert(later.length === 24) 108 | if (later.slice(0, 11) !== sooner.slice(0, 11)) 109 | return 999999 // not on the same day 110 | const laterTime = later.slice(11, 19).split(':') 111 | const soonerTime = sooner.slice(11, 19).split(':') 112 | const laterSecs = parseInt(laterTime[0], 10) * 3600 + parseInt(laterTime[1], 10) * 60 + parseInt(laterTime[2], 10) 113 | const soonerSecs = parseInt(soonerTime[0], 10) * 3600 + parseInt(soonerTime[1], 10) * 60 + parseInt(soonerTime[2], 10) 114 | return laterSecs - soonerSecs 115 | } 116 | 117 | function getModulusFromPubKey(pemPubKey) { 118 | let b64Str = '' 119 | const lines = pemPubKey.split('\n') 120 | // omit header and footer lines 121 | for (let i = 1; i < lines.length - 1; i++) 122 | b64Str += lines[i] 123 | 124 | const der = tlsn_utils.b64decode(b64Str) 125 | // last 5 bytes are 2 DER bytes and 3 bytes exponent, our is the preceding 512 bytes 126 | const pubkey = der.slice(der.length - 517, der.length - 5) 127 | return pubkey 128 | } 129 | 130 | function checkDescribeInstances(jsonInput, instanceId, IP, type) { 131 | try { 132 | let imageId 133 | switch (type) { 134 | case 'main': 135 | imageId = imageIDV1Main 136 | break 137 | case 'sig': 138 | imageId = imageIDV1Sig 139 | break 140 | default: 141 | imageId = imageIdV2 142 | } 143 | const reservationSet = jsonInput.reservationSet 144 | const ownerId = reservationSet[0].item[0].ownerId.toString() 145 | const instancesSet = reservationSet[0].item[0].instancesSet 146 | assert(Object.keys(instancesSet).length === 1) 147 | const currentInstance = instancesSet[0].item[0] 148 | assert(currentInstance.instanceId.toString() === instanceId) 149 | assert(imageId.includes(currentInstance.imageId.toString())) 150 | assert(currentInstance.instanceState[0].name.toString() === 'running') 151 | assert(currentInstance.ipAddress.toString() === IP) 152 | assert(currentInstance.rootDeviceType.toString() === 'ebs') 153 | assert(currentInstance.rootDeviceName.toString() === '/dev/xvda') 154 | // Verify only for TLSNotary Proofs v1 155 | if (type === 'main' || type === 'sig') 156 | assert(currentInstance.kernelId.toString() === kernelId) 157 | const devices = currentInstance.blockDeviceMapping 158 | assert(Object.keys(devices).length === 1) 159 | const device = devices[0].item[0] 160 | assert(device.deviceName.toString() === '/dev/xvda') 161 | assert(device.ebs[0].status.toString() === 'attached') 162 | const volAttachTime = device.ebs[0].attachTime.toString() 163 | const volumeId = device.ebs[0].volumeId.toString() 164 | const launchTime = currentInstance.launchTime.toString() 165 | // Get seconds from "2015-04-15T19:00:59.000Z" 166 | assert(getSecondsDelta(volAttachTime, launchTime) <= 3) 167 | if (type !== 'main' && type !== 'sig') 168 | assert(currentInstance.virtualizationType.toString() === 'hvm') 169 | return {ownerId: ownerId, volumeId: volumeId, volAttachTime: volAttachTime, launchTime: launchTime} 170 | } catch (err) { 171 | throw new Error('checkDescribeInstances_failed') 172 | } 173 | } 174 | 175 | function checkDescribeVolumes(json, instanceId, volumeId, volAttachTime, type) { 176 | try { 177 | let snapshotId = null 178 | switch (type) { 179 | case 'main': 180 | snapshotId = snapshotIdV1Main 181 | break 182 | case 'sig': 183 | snapshotId = snapshotIdV1Sig 184 | break 185 | default: 186 | snapshotId = snapshotIdV2 187 | } 188 | const volumes = json.volumeSet 189 | assert(Object.keys(volumes).length === 1) 190 | const volume = volumes[0].item[0] 191 | assert(volume.volumeId.toString() === volumeId) 192 | assert(snapshotId.includes(volume.snapshotId.toString())) 193 | assert(volume.status.toString() === 'in-use') 194 | const volCreateTime = volume.createTime.toString() 195 | const attachedVolumes = volume.attachmentSet 196 | assert(Object.keys(attachedVolumes).length === 1) 197 | const attVolume = attachedVolumes[0].item[0] 198 | assert(attVolume.volumeId.toString() === volumeId) 199 | assert(attVolume.instanceId.toString() === instanceId) 200 | assert(attVolume.device.toString() === '/dev/xvda') 201 | assert(attVolume.status.toString() === 'attached') 202 | const attTime = attVolume.attachTime.toString() 203 | assert(volAttachTime === attTime) 204 | // Crucial: volume was created from snapshot and attached at the same instant 205 | // currentInstance guarantees that there was no time window to modify it 206 | assert(getSecondsDelta(attTime, volCreateTime) === 0) 207 | } catch (err) { 208 | throw new Error('checkDescribeVolumes_failed') 209 | } 210 | } 211 | 212 | function checkGetConsoleOutput(json, instanceId, launchTime, type) { 213 | try { 214 | assert(json.instanceId.toString() === instanceId) 215 | const timestamp = json.timestamp.toString() 216 | // prevent funny business: last consoleLog entry no later than 4 minutes after instance starts 217 | const consoleOutputB64Encoded = json.output.toString() 218 | const consoleOutputStr = tlsn_utils.ba2str(tlsn_utils.b64decode(consoleOutputB64Encoded)) 219 | if (type === 'main' || type === 'sig') { 220 | // no other string starting with xvd except for xvda 221 | assert(consoleOutputStr.search(/xvd[^a]/g) === -1) 222 | assert(getSecondsDelta(timestamp, launchTime) <= 240) 223 | } 224 | let pubKey = null 225 | let beginMark = '' 226 | switch (type) { 227 | case 'main': 228 | beginMark = consoleOutputStr.search('TLSNotary main server pubkey which is embedded into the signing server:') 229 | assert(beginMark !== -1) 230 | pubKey = getPubKeyFromOutput(consoleOutputStr, beginMark) 231 | assert(pubKey.length > 0) 232 | break 233 | case 'sig': 234 | beginMark = consoleOutputStr.search('TLSNotary siging server pubkey:') 235 | assert(beginMark !== -1) 236 | pubKey = getPubKeyFromOutput(consoleOutputStr, beginMark) 237 | assert(pubKey.length > 0) 238 | // beginMark = consoleOutputStr.search('TLSNotary imported main server pubkey:') 239 | // assert(beginMark !== -1) 240 | // var importedPubKey = getPubKeyFromOutput(consoleOutputStr, beginMark) 241 | // assert(mainPubKey === pubKey) 242 | break 243 | default: 244 | beginMark = consoleOutputStr.search('PageSigner public key for verification') 245 | assert(beginMark !== -1) 246 | pubKey = getPubKeyFromOutput(consoleOutputStr, beginMark) 247 | assert(pubKey.length > 0) 248 | } 249 | return pubKey 250 | } catch (err) { 251 | throw new Error('checkGetConsoleOutput_failed') 252 | } 253 | } 254 | 255 | function getPubKeyFromOutput(consoleOutputStr, beginMark) { 256 | const pubKeyBeginMark = '-----BEGIN PUBLIC KEY-----' 257 | const pubKeyEndMark = '-----END PUBLIC KEY-----' 258 | const pubKeyBegin = beginMark + consoleOutputStr.slice(beginMark).search(pubKeyBeginMark) 259 | const pubKeyEnd = pubKeyBegin + consoleOutputStr.slice(pubKeyBegin).search(pubKeyEndMark) + pubKeyEndMark.length 260 | return consoleOutputStr.slice(pubKeyBegin, pubKeyEnd) 261 | } 262 | 263 | // "userData" allows to pass an arbitrary script to the instance at launch. It MUST be empty. 264 | // currentInstance is a sanity check because the instance is stripped of the code which parses userData. 265 | function checkDescribeInstanceAttribute(json, instanceId) { 266 | try { 267 | assert(json.instanceId.toString() === instanceId) 268 | assert(json.userData.toString() === '') 269 | } catch (err) { 270 | throw new Error('checkDescribeInstanceAttribute_failed') 271 | } 272 | } 273 | 274 | function checkGetUser(json, ownerId) { 275 | try { 276 | assert(json[0].User[0].UserId.toString() === ownerId) 277 | assert(json[0].User[0].Arn.toString().slice(-(ownerId.length + ':root'.length)) === ownerId + ':root') 278 | } catch (err) { 279 | throw new Error('checkGetUser_failed') 280 | } 281 | } 282 | 283 | const getVerifiedServers = async (serversList) => { 284 | let mainPubKey 285 | let verifiedServers = [] 286 | for (let j = 1; j < 3; j++) {// the array offset represents the TLSNotary version (we start with j = 1 for that reason) 287 | let servers = serversList[j] 288 | for (let i = 0; i < servers.length; i++) { 289 | const server = servers[i] 290 | if (server.verifiable === false) 291 | continue 292 | try { 293 | switch (j) { // the array offset represents the TLSNotary version 294 | case 1: 295 | mainPubKey = await validateServer(server, 'main', mainPubKey) 296 | await validateServer(server, 'sig', mainPubKey) 297 | break 298 | default: 299 | mainPubKey = await validateServer(server, '', mainPubKey) 300 | } 301 | verifiedServers.push(server) 302 | } 303 | catch (err) { 304 | if (err.name === 'aws_request_failed' && j === 1) 305 | break; 306 | else if (err.message === 'checkGetConsoleOutput_failed') 307 | break; 308 | else 309 | throw err 310 | } 311 | } 312 | } 313 | return verifiedServers 314 | } 315 | 316 | const avaibleServers = R.flatten(servers) 317 | const verifiedServers = (async () => await getVerifiedServers(servers))() 318 | const notVerifiableServers = (async () => subtractList(avaibleServers, await verifiedServers))() 319 | 320 | module.exports.getVerifiedServers = getVerifiedServers 321 | module.exports.validateServer = validateServer 322 | module.exports.avaibleServers = avaibleServers 323 | module.exports.verifiedServers = verifiedServers 324 | module.exports.notVerifiableServers = notVerifiableServers 325 | -------------------------------------------------------------------------------- /lib/tlsn/verifychain/verifychain.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 'use strict'; 3 | 4 | const origcerts = require('./rootcertslist.js').certs; 5 | const tlsn_utils = require('../tlsn_utils.js'); 6 | let certs = origcerts; 7 | 8 | var asn1 = require('asn1.js'); 9 | var Buffer = require('buffer').Buffer; 10 | 11 | //--------BEGIN copied from https://github.com/indutny/asn1.js/blob/master/rfc/3280/index.js 12 | var AlgorithmIdentifier = asn1.define('AlgorithmIdentifier', function () { 13 | this.seq().obj(this.key('algorithm').objid(), this.key('parameters').optional().any()); 14 | }); 15 | 16 | const Certificate = asn1.define('Certificate', function () { 17 | this.seq().obj(this.key('tbsCertificate').use(TBSCertificate), this.key('signatureAlgorithm').use(AlgorithmIdentifier), this.key('signature').bitstr()); 18 | }); 19 | 20 | var TBSCertificate = asn1.define('TBSCertificate', function () { 21 | this.seq().obj(this.key('version').def('v1').explicit(0).use(Version), this.key('serialNumber').use(CertificateSerialNumber), this.key('signature').use(AlgorithmIdentifier), this.key('issuer').use(Name), this.key('validity').use(Validity), this.key('subject').use(Name), this.key('subjectPublicKeyInfo').use(SubjectPublicKeyInfo), 22 | 23 | // TODO(indutny): validate that version is v2 or v3 24 | this.key('issuerUniqueID').optional().explicit(1).use(UniqueIdentifier), this.key('subjectUniqueID').optional().explicit(2).use(UniqueIdentifier), 25 | 26 | // TODO(indutny): validate that version is v3 27 | this.key('extensions').optional().explicit(3).use(Extensions)); 28 | }); 29 | 30 | var Version = asn1.define('Version', function () { 31 | this.int({ 32 | 0: 'v1', 33 | 1: 'v2', 34 | 2: 'v3' 35 | }); 36 | }); 37 | 38 | var CertificateSerialNumber = asn1.define('CertificateSerialNumber', function () { 39 | this.int(); 40 | }); 41 | 42 | var Validity = asn1.define('Validity', function () { 43 | this.seq().obj(this.key('notBefore').use(Time), this.key('notAfter').use(Time)); 44 | }); 45 | 46 | var Time = asn1.define('Time', function () { 47 | this.choice({ 48 | utcTime: this.utctime(), 49 | genTime: this.gentime() 50 | }); 51 | }); 52 | 53 | var UniqueIdentifier = asn1.define('UniqueIdentifier', function () { 54 | this.bitstr(); 55 | }); 56 | 57 | var SubjectPublicKeyInfo = asn1.define('SubjectPublicKeyInfo', function () { 58 | this.seq().obj(this.key('algorithm').use(AlgorithmIdentifier), this.key('subjectPublicKey').bitstr()); 59 | }); 60 | 61 | var Extensions = asn1.define('Extensions', function () { 62 | this.seqof(Extension); 63 | }); 64 | 65 | var Extension = asn1.define('Extension', function () { 66 | this.seq().obj(this.key('extnID').objid(), this.key('critical').bool().def(false), this.key('extnValue').octstr()); 67 | }); 68 | 69 | var Name = asn1.define('Name', function () { 70 | this.choice({ 71 | rdn: this.use(RDNSequence) 72 | }); 73 | }); 74 | 75 | var RDNSequence = asn1.define('RDNSequence', function () { 76 | this.seqof(RelativeDistinguishedName); 77 | }); 78 | 79 | var RelativeDistinguishedName = asn1.define('RelativeDistinguishedName', function () { 80 | this.setof(AttributeTypeAndValue); 81 | }); 82 | 83 | var AttributeTypeAndValue = asn1.define('AttributeTypeAndValue', function () { 84 | this.seq().obj(this.key('type').use(AttributeType), this.key('value').use(AttributeValue)); 85 | }); 86 | 87 | var AttributeType = asn1.define('AttributeType', function () { 88 | this.objid(); 89 | }); 90 | 91 | var AttributeValue = asn1.define('AttributeValue', function () { 92 | this.any(); 93 | }); 94 | //-----END copied from https://github.com/indutny/asn1.js/blob/master/rfc/3280/index.js 95 | 96 | 97 | function pem2der(certpem) { 98 | var lines = certpem.split('\n'); 99 | var stripped = ''; //strip ascii armor and newlines 100 | for (var i = 1; i < lines.length - 2; i++) { 101 | stripped += lines[i]; 102 | } 103 | stripped = stripped.replace(/\n/g, ''); 104 | var certder = tlsn_utils.b64decode(stripped); 105 | return certder; 106 | } 107 | 108 | //change the keys from ambigous string that bitpay provides 109 | //to unique subject strings and associated pubkeys 110 | function fixcerts() { 111 | var tmpcerts = { 'trusted_pubkeys': [] }; 112 | for (var key in certs) { 113 | var certpem = certs[key]; 114 | var certder = pem2der(certpem); 115 | //getfield(certder); 116 | var subj = getSubjectString(certder, 'own'); 117 | if (tmpcerts.hasOwnProperty(subj)) { 118 | //Duplicate found. Rename the existing entry and the duplicate 119 | //with extended subject 120 | //console.log('adding duplicate:', subj); 121 | var pem_one = tmpcerts[subj]; 122 | var der_one = pem2der(pem_one); 123 | var subj_one = getSubjectString(der_one, 'own', true); 124 | tmpcerts[subj_one] = pem_one; 125 | delete tmpcerts[subj]; 126 | var subj_two = getSubjectString(certder, 'own', true); 127 | subj = subj_two; 128 | } 129 | tmpcerts[subj] = certpem; 130 | var pk = getPubkey(certder); 131 | tmpcerts['trusted_pubkeys'].push(tlsn_utils.b64encode(tlsn_utils.ua2ba(pk))); 132 | } 133 | certs = tmpcerts; 134 | } 135 | 136 | function getPubkey(cert) { 137 | var c = Certificate.decode(Buffer.from(cert), 'der'); 138 | return c.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; 139 | } 140 | 141 | //Usually CN=&O= is enough to uniquely identify a CA, however there are 6 certs 142 | //in rootstore which have the same O&CN and are only distinguished by OU= 143 | function getSubjectString(cert, whose, extended) { 144 | if (typeof extended === 'undefined') { 145 | extended = false; 146 | } 147 | var c = Certificate.decode(Buffer.from(cert), 'der'); 148 | var fields; 149 | if (whose === 'issuer') { 150 | fields = c.tbsCertificate.issuer.value; 151 | } else if (whose === 'own') { 152 | fields = c.tbsCertificate.subject.value; 153 | } 154 | var cn_str = ''; 155 | var o_str = ''; 156 | var ou_str = ''; 157 | for (var i = 0; i < fields.length; i++) { 158 | if (fields[i][0].type.toString() === [2, 5, 4, 3].toString()) { 159 | cn_str = 'CN=' + tlsn_utils.ba2str(fields[i][0].value.slice(2)) + '/'; 160 | } 161 | if (fields[i][0].type.toString() === [2, 5, 4, 10].toString()) { 162 | o_str = 'O=' + tlsn_utils.ba2str(fields[i][0].value.slice(2)) + '/'; 163 | } 164 | if (extended) { 165 | if (fields[i][0].type.toString() === [2, 5, 4, 11].toString()) { 166 | ou_str = 'OU=' + tlsn_utils.ba2str(fields[i][0].value.slice(2)) + '/'; 167 | } 168 | //we have one root CA Autoridad de Certificacion Firmaprofesional CIF A62634068 169 | //which is distinguishable only by L= 170 | //we put the L= value into OU= 171 | if (ou_str === '') { 172 | if (fields[i][0].type.toString() === [2, 5, 4, 7].toString()) { 173 | ou_str = 'OU=' + tlsn_utils.ba2str(fields[i][0].value.slice(2)) + '/'; 174 | } 175 | } 176 | } 177 | } 178 | if (!cn_str && !o_str) { 179 | return false; 180 | } 181 | return cn_str + o_str + ou_str; 182 | } 183 | 184 | //---an adopted copy of PaymentProtocol.verifyCertChain from 185 | //https://github.com/bitpay/bitcore-payment-protocol/blob/master/lib/browser.js 186 | var verifyCertChain = function (chain) { 187 | 188 | //Check if there is no root cert in the chain 189 | //and if so, add it (provided that we know such root CA) 190 | 191 | //Uniquely identify CAs by the O and CN string and by the pubkey, not by PEM of the cert 192 | //e.g. VeriSign Class 3 Public Primary Certification Authority - G5 193 | //has different PEMs for Fiferox vs Chrome 194 | 195 | var find_in_store = function (rootcert) { 196 | var subjown = getSubjectString(rootcert, 'own'); 197 | if (!certs.hasOwnProperty(subjown)) { 198 | //get extended subject string 199 | subjown = getSubjectString(rootcert, 'own', true); 200 | if (!certs.hasOwnProperty(subjown)) return false; 201 | } 202 | var pk = tlsn_utils.b64encode(tlsn_utils.ua2ba(getPubkey(rootcert))); 203 | if (certs.trusted_pubkeys.indexOf(pk) < 0) return false; 204 | return true; 205 | }; 206 | var found = find_in_store(chain[chain.length - 1]); 207 | 208 | if (!found) { 209 | //Usually there is no root cert in chain 210 | var subj = getSubjectString(chain[chain.length - 1], 'issuer'); 211 | if (!certs.hasOwnProperty(subj)) { 212 | //get extended subject string 213 | subj = getSubjectString(chain[chain.length - 1], 'issuer', true); 214 | if (!certs.hasOwnProperty(subj)) return false; 215 | } 216 | chain.push(pem2der(certs[subj])); 217 | } 218 | 219 | return chain.every(function (cert, i) { 220 | if (i === chain.length - 1) { 221 | //we already checked earlier that this CA's pubkey is in store 222 | //or we added this CA to the chain ourselves 223 | return true; 224 | } 225 | 226 | var ncert = chain[i + 1]; 227 | var nder = tlsn_utils.ba2hex(ncert); 228 | var npem = KJUR.asn1.ASN1Util.getPEMStringFromHex(nder, 'CERTIFICATE'); 229 | 230 | // Get Next Certificate: 231 | var ndata = Buffer.from(nder, 'hex'); 232 | //var ndata = ba2ua(ncert); 233 | var nc = Certificate.decode(ndata, 'der'); 234 | 235 | // Get Signature Value from current certificate: 236 | var data = Buffer.from(cert); 237 | //var data = ba2ua(der); 238 | var c = Certificate.decode(data, 'der'); 239 | var sig = c.signature.data; 240 | var alg = c.signatureAlgorithm.algorithm; 241 | var lastalgbyte = alg[alg.length - 1]; 242 | var sigHashAlg; 243 | //1.2.840.113549.1.1.11 sha256 244 | //1.2.840.113549.1.1.5 sha1 245 | //1.2.840.113549.1.1.12 sha384 246 | if (lastalgbyte === 11) { 247 | sigHashAlg = 'SHA256withRSA'; 248 | } else if (lastalgbyte === 5) { 249 | sigHashAlg = 'SHA1withRSA'; 250 | } else if (lastalgbyte === 12) { 251 | sigHashAlg = 'SHA384withRSA'; 252 | } else { 253 | return false; 254 | } 255 | 256 | var npubKey; 257 | // Get Public Key from next certificate (via KJUR because it's a mess): 258 | var js = new KJUR.crypto.Signature({ 259 | alg: sigHashAlg, 260 | prov: 'cryptojs/jsrsa' 261 | }); 262 | js.initVerifyByCertificatePEM(npem); 263 | npubKey = js.pubKey; 264 | 265 | // Check Validity of Certificates 266 | var validityVerified = validateCertTime(c, nc); 267 | 268 | // Check the Issuer matches the Subject of the next certificate: 269 | var issuerVerified = validateCertIssuer(c, nc); 270 | 271 | // Verify current Certificate signature 272 | var jsrsaSig = new KJUR.crypto.Signature({ 273 | alg: sigHashAlg, 274 | prov: 'cryptojs/jsrsa' 275 | }); 276 | jsrsaSig.initVerifyByPublicKey(npubKey); 277 | 278 | // Get the raw DER TBSCertificate 279 | // from the DER Certificate: 280 | var tbs = getTBSCertificate(data, c.signature.data.length); 281 | 282 | jsrsaSig.updateHex(tbs.toString('hex')); 283 | 284 | var sigVerified = jsrsaSig.verify(sig.toString('hex')); 285 | 286 | return validityVerified && issuerVerified && sigVerified; 287 | }); 288 | }; 289 | 290 | var X509_ALGORITHM = { 291 | '1.2.840.113549.1.1.1': 'RSA', 292 | '1.2.840.113549.1.1.2': 'RSA_MD2', 293 | '1.2.840.113549.1.1.4': 'RSA_MD5', 294 | '1.2.840.113549.1.1.5': 'RSA_SHA1', 295 | '1.2.840.113549.1.1.11': 'RSA_SHA256', 296 | '1.2.840.113549.1.1.12': 'RSA_SHA384', 297 | '1.2.840.113549.1.1.13': 'RSA_SHA512', 298 | 299 | '1.2.840.10045.4.3.2': 'ECDSA_SHA256', 300 | '1.2.840.10045.4.3.3': 'ECDSA_SHA384', 301 | '1.2.840.10045.4.3.4': 'ECDSA_SHA512' 302 | }; 303 | 304 | var getAlgorithm = function (value, index) { 305 | if (Array.isArray(value)) { 306 | value = value.join('.'); 307 | } 308 | value = X509_ALGORITHM[value]; 309 | if (typeof index !== 'undefined') { 310 | value = value.split('_'); 311 | if (index === true) { 312 | return { 313 | cipher: value[0], 314 | hash: value[1] 315 | }; 316 | } 317 | return value[index]; 318 | } 319 | return value; 320 | }; 321 | 322 | //---BEGIN copied from https://github.com/bitpay/bitcore-payment-protocol/blob/master/lib/common.js 323 | 324 | // Check Validity of Certificates 325 | var validateCertTime = function (c, nc) { 326 | var validityVerified = true; 327 | var now = Date.now(); 328 | var cBefore = c.tbsCertificate.validity.notBefore.value; 329 | var cAfter = c.tbsCertificate.validity.notAfter.value; 330 | var nBefore = nc.tbsCertificate.validity.notBefore.value; 331 | var nAfter = nc.tbsCertificate.validity.notAfter.value; 332 | if (cBefore > now || cAfter < now || nBefore > now || nAfter < now) { 333 | validityVerified = false; 334 | } 335 | return validityVerified; 336 | }; 337 | 338 | // Check the Issuer matches the Subject of the next certificate: 339 | var validateCertIssuer = function (c, nc) { 340 | var issuer = c.tbsCertificate.issuer; 341 | var subject = nc.tbsCertificate.subject; 342 | var issuerVerified = issuer.type === subject.type && issuer.value.every(function (issuerArray, i) { 343 | var subjectArray = subject.value[i]; 344 | return issuerArray.every(function (issuerObject, i) { 345 | var subjectObject = subjectArray[i]; 346 | 347 | var issuerObjectType = issuerObject.type.join('.'); 348 | var subjectObjectType = subjectObject.type.join('.'); 349 | 350 | var issuerObjectValue = issuerObject.value.toString('hex'); 351 | var subjectObjectValue = subjectObject.value.toString('hex'); 352 | 353 | return issuerObjectType === subjectObjectType && issuerObjectValue === subjectObjectValue; 354 | }); 355 | }); 356 | return issuerVerified; 357 | }; 358 | 359 | // Grab the raw DER To-Be-Signed Certificate 360 | // from a DER Certificate to verify 361 | var getTBSCertificate = function (data, siglen) { 362 | // We start by slicing off the first SEQ of the 363 | // Certificate (TBSCertificate is its own SEQ). 364 | 365 | // The first 10 bytes usually look like: 366 | // [ 48, 130, 5, 32, 48, 130, 4, 8, 160, 3 ] 367 | var start = 0; 368 | var starts = 0; 369 | for (start = 0; start < data.length; start++) { 370 | if (starts === 1 && data[start] === 48) { 371 | break; 372 | } 373 | if (starts < 1 && data[start] === 48) { 374 | starts++; 375 | } 376 | } 377 | 378 | // The bytes *after* the TBS (including the last TBS byte) will look like 379 | // (note the 48 - the start of the sig, and the 122 - the end of the TBS): 380 | // [ 122, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 11, 5, 0, 3, ... ] 381 | 382 | // The certificate in these examples has a `start` of 4, and an `end` of 383 | // 1040. The 4 bytes is the DER SEQ of the Certificate, right before the 384 | // SEQ of the TBSCertificate. 385 | var end = 0; 386 | var ends = 0; 387 | for (end = data.length - 1 - siglen; end > 0; end--) { 388 | if (ends === 2 && data[end] === 48) { 389 | break; 390 | } 391 | if (ends < 2 && data[end] === 0) { 392 | ends++; 393 | } 394 | } 395 | 396 | // Return our raw DER TBSCertificate: 397 | return data.slice(start, end); 398 | }; 399 | 400 | //---END copied from https://github.com/bitpay/bitcore-payment-protocol/blob/master/lib/common.js 401 | 402 | //this must trigger after asn1.js was loaded, so putting this to the bottom 403 | fixcerts(); 404 | 405 | module.exports.Certificate = Certificate; 406 | module.exports.verifyCertChain = verifyCertChain; -------------------------------------------------------------------------------- /lib/oraclize/oracles.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let getJSON = (() => { 4 | var _ref = _asyncToGenerator(function* (res) { 5 | const text = yield res.text(); 6 | return JSON.parse(JSON.stringify(parseSync(text))); 7 | }); 8 | 9 | return function getJSON(_x) { 10 | return _ref.apply(this, arguments); 11 | }; 12 | })(); 13 | 14 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } 15 | 16 | const assert = require('assert'); 17 | const request = require('isomorphic-fetch'); 18 | const xmlParse = require('xml2js'); 19 | const R = require('ramda'); 20 | const subtractList = require('../helpers.js').subtractList; 21 | const tlsn_utils = require('../tlsn/tlsn_utils.js'); 22 | 23 | const kernelId = 'aki-503e7402'; 24 | const snapshotIdV1Main = ['snap-cdd399f8']; 25 | const snapshotIdV1Sig = ['snap-00083b35']; 26 | const imageIDV1Main = ['ami-5e39040c']; 27 | const imageIDV1Sig = ['ami-88724fda']; 28 | // const snapshotIdV2 = 'snap-2c1fab9b' // necessary for [oraclize5, oraclize6, oraclize7] 29 | // const imageIdV2 = 'ami-15192302' // necessary for [oraclize5, oraclize6, oraclize7] 30 | 31 | const snapshotIdV2 = ['snap-03bae56722ceec3f0', 'snap-0fa4d717595514c9e', 'snap-0aae1b6ca8c4b0f8d', 'snap-001724fd6e3219a8b', 'snap-0d6e71fc4113c1d7f', 'snap-0d65665f4afaf28f8', 'snap-0f2a4af7bbcaf3374', 'snap-03a7cfbcc835aae69', 'snap-01bd7dcd6b89c4784', 'snap-03c1d1eaab2ded518']; 32 | 33 | const imageIdV2 = ['ami-1f447c65', 'ami-0900465cc4d168b18', 'ami-05fd672d486340db9', 'ami-004bb38dca2efe5cf', 'ami-0fc7d2d0c4a3fe3f5', 'ami-01887359c36e2e246', 'ami-0d9bdb20c502dbb32', 'ami-01fe7595953c2753b', 'ami-0d0829459bf2597b0', 'ami-0f558579909742612']; 34 | 35 | const servers = require('./servers.js').servers; 36 | 37 | const validateServer = (() => { 38 | var _ref2 = _asyncToGenerator(function* (server, type) { 39 | try { 40 | let notaryServer = null; 41 | if (type === 'sig') notaryServer = server.sig;else notaryServer = server.main; 42 | let res = yield request(notaryServer.DI); 43 | if (res.status !== 200) throw new Error('aws_await request_failed'); 44 | let json = yield getJSON(res); 45 | const args = checkDescribeInstances(json.DescribeInstancesResponse, notaryServer.instanceId, notaryServer.IP, type); 46 | res = yield request(notaryServer.DV); 47 | if (res.status !== 200) throw new Error('aws_await request_failed'); 48 | json = yield getJSON(res); 49 | checkDescribeVolumes(json.DescribeVolumesResponse, notaryServer.instanceId, args.volumeId, args.volAttachTime, type); 50 | if (typeof window === 'undefined') { 51 | //TODO proxy server to be able to access ami.amzon from browser 52 | res = yield request(notaryServer.GU); 53 | if (res.status !== 200) throw new Error('aws_await request_failed'); 54 | json = yield getJSON(res); 55 | checkGetUser(json.GetUserResponse.GetUserResult, args.ownerId); 56 | } 57 | res = yield request(notaryServer.GCO); 58 | if (res.status !== 200) throw new Error('aws_await request_failed'); 59 | json = yield getJSON(res); 60 | const pubKey = checkGetConsoleOutput(json.GetConsoleOutputResponse, notaryServer.instanceId, args.launchTime, type); 61 | res = yield request(notaryServer.DIA); 62 | json = yield getJSON(res); 63 | checkDescribeInstanceAttribute(json.DescribeInstanceAttributeResponse, notaryServer.instanceId); 64 | // Check for v1 65 | if (type !== 'main') assert(getModulusFromPubKey(pubKey).toString() === server.sig.modulus.toString()); 66 | const mark = 'AWSAccessKeyId='; 67 | let start; 68 | let id; 69 | let ids = []; 70 | // "AWSAccessKeyId" should be the same to prove that the queries are made on behalf of AWS user "root". 71 | // The attacker can be a user with limited privileges for whom the API would report only partial information. 72 | const urlArray = [notaryServer.DI, notaryServer.DV, notaryServer.GU, notaryServer.GCO, notaryServer.DIA]; 73 | for (const url of urlArray) { 74 | start = url.search(mark) + mark.length; 75 | id = url.slice(start, start + url.slice(start).search('&')); 76 | ids.push(id); 77 | } 78 | assert(new Set(ids).size === 1); // eslint-disable-line 79 | return pubKey; 80 | } catch (err) { 81 | throw err; 82 | } 83 | }); 84 | 85 | return function validateServer(_x2, _x3) { 86 | return _ref2.apply(this, arguments); 87 | }; 88 | })(); 89 | 90 | function parseSync(xml) { 91 | let error = null; 92 | let json = null; 93 | xmlParse.parseString(xml, function (innerError, innerJson) { 94 | error = innerError; 95 | json = innerJson; 96 | }); 97 | if (error) throw error; 98 | if (!error && !json) throw new Error('The callback was suddenly async or something.'); 99 | return json; 100 | } 101 | 102 | // assuming both events happened on the same day, get the time 103 | // difference between them in seconds 104 | // the time string looks like "2015-04-15T19:00:59.000Z" 105 | function getSecondsDelta(later, sooner) { 106 | assert(later.length === 24); 107 | if (later.slice(0, 11) !== sooner.slice(0, 11)) return 999999; // not on the same day 108 | const laterTime = later.slice(11, 19).split(':'); 109 | const soonerTime = sooner.slice(11, 19).split(':'); 110 | const laterSecs = parseInt(laterTime[0], 10) * 3600 + parseInt(laterTime[1], 10) * 60 + parseInt(laterTime[2], 10); 111 | const soonerSecs = parseInt(soonerTime[0], 10) * 3600 + parseInt(soonerTime[1], 10) * 60 + parseInt(soonerTime[2], 10); 112 | return laterSecs - soonerSecs; 113 | } 114 | 115 | function getModulusFromPubKey(pemPubKey) { 116 | let b64Str = ''; 117 | const lines = pemPubKey.split('\n'); 118 | // omit header and footer lines 119 | for (let i = 1; i < lines.length - 1; i++) b64Str += lines[i]; 120 | 121 | const der = tlsn_utils.b64decode(b64Str); 122 | // last 5 bytes are 2 DER bytes and 3 bytes exponent, our is the preceding 512 bytes 123 | const pubkey = der.slice(der.length - 517, der.length - 5); 124 | return pubkey; 125 | } 126 | 127 | function checkDescribeInstances(jsonInput, instanceId, IP, type) { 128 | try { 129 | let imageId; 130 | switch (type) { 131 | case 'main': 132 | imageId = imageIDV1Main; 133 | break; 134 | case 'sig': 135 | imageId = imageIDV1Sig; 136 | break; 137 | default: 138 | imageId = imageIdV2; 139 | } 140 | const reservationSet = jsonInput.reservationSet; 141 | const ownerId = reservationSet[0].item[0].ownerId.toString(); 142 | const instancesSet = reservationSet[0].item[0].instancesSet; 143 | assert(Object.keys(instancesSet).length === 1); 144 | const currentInstance = instancesSet[0].item[0]; 145 | assert(currentInstance.instanceId.toString() === instanceId); 146 | assert(imageId.includes(currentInstance.imageId.toString())); 147 | assert(currentInstance.instanceState[0].name.toString() === 'running'); 148 | assert(currentInstance.ipAddress.toString() === IP); 149 | assert(currentInstance.rootDeviceType.toString() === 'ebs'); 150 | assert(currentInstance.rootDeviceName.toString() === '/dev/xvda'); 151 | // Verify only for TLSNotary Proofs v1 152 | if (type === 'main' || type === 'sig') assert(currentInstance.kernelId.toString() === kernelId); 153 | const devices = currentInstance.blockDeviceMapping; 154 | assert(Object.keys(devices).length === 1); 155 | const device = devices[0].item[0]; 156 | assert(device.deviceName.toString() === '/dev/xvda'); 157 | assert(device.ebs[0].status.toString() === 'attached'); 158 | const volAttachTime = device.ebs[0].attachTime.toString(); 159 | const volumeId = device.ebs[0].volumeId.toString(); 160 | const launchTime = currentInstance.launchTime.toString(); 161 | // Get seconds from "2015-04-15T19:00:59.000Z" 162 | assert(getSecondsDelta(volAttachTime, launchTime) <= 3); 163 | if (type !== 'main' && type !== 'sig') assert(currentInstance.virtualizationType.toString() === 'hvm'); 164 | return { ownerId: ownerId, volumeId: volumeId, volAttachTime: volAttachTime, launchTime: launchTime }; 165 | } catch (err) { 166 | throw new Error('checkDescribeInstances_failed'); 167 | } 168 | } 169 | 170 | function checkDescribeVolumes(json, instanceId, volumeId, volAttachTime, type) { 171 | try { 172 | let snapshotId = null; 173 | switch (type) { 174 | case 'main': 175 | snapshotId = snapshotIdV1Main; 176 | break; 177 | case 'sig': 178 | snapshotId = snapshotIdV1Sig; 179 | break; 180 | default: 181 | snapshotId = snapshotIdV2; 182 | } 183 | const volumes = json.volumeSet; 184 | assert(Object.keys(volumes).length === 1); 185 | const volume = volumes[0].item[0]; 186 | assert(volume.volumeId.toString() === volumeId); 187 | assert(snapshotId.includes(volume.snapshotId.toString())); 188 | assert(volume.status.toString() === 'in-use'); 189 | const volCreateTime = volume.createTime.toString(); 190 | const attachedVolumes = volume.attachmentSet; 191 | assert(Object.keys(attachedVolumes).length === 1); 192 | const attVolume = attachedVolumes[0].item[0]; 193 | assert(attVolume.volumeId.toString() === volumeId); 194 | assert(attVolume.instanceId.toString() === instanceId); 195 | assert(attVolume.device.toString() === '/dev/xvda'); 196 | assert(attVolume.status.toString() === 'attached'); 197 | const attTime = attVolume.attachTime.toString(); 198 | assert(volAttachTime === attTime); 199 | // Crucial: volume was created from snapshot and attached at the same instant 200 | // currentInstance guarantees that there was no time window to modify it 201 | assert(getSecondsDelta(attTime, volCreateTime) === 0); 202 | } catch (err) { 203 | throw new Error('checkDescribeVolumes_failed'); 204 | } 205 | } 206 | 207 | function checkGetConsoleOutput(json, instanceId, launchTime, type) { 208 | try { 209 | assert(json.instanceId.toString() === instanceId); 210 | const timestamp = json.timestamp.toString(); 211 | // prevent funny business: last consoleLog entry no later than 4 minutes after instance starts 212 | const consoleOutputB64Encoded = json.output.toString(); 213 | const consoleOutputStr = tlsn_utils.ba2str(tlsn_utils.b64decode(consoleOutputB64Encoded)); 214 | if (type === 'main' || type === 'sig') { 215 | // no other string starting with xvd except for xvda 216 | assert(consoleOutputStr.search(/xvd[^a]/g) === -1); 217 | assert(getSecondsDelta(timestamp, launchTime) <= 240); 218 | } 219 | let pubKey = null; 220 | let beginMark = ''; 221 | switch (type) { 222 | case 'main': 223 | beginMark = consoleOutputStr.search('TLSNotary main server pubkey which is embedded into the signing server:'); 224 | assert(beginMark !== -1); 225 | pubKey = getPubKeyFromOutput(consoleOutputStr, beginMark); 226 | assert(pubKey.length > 0); 227 | break; 228 | case 'sig': 229 | beginMark = consoleOutputStr.search('TLSNotary siging server pubkey:'); 230 | assert(beginMark !== -1); 231 | pubKey = getPubKeyFromOutput(consoleOutputStr, beginMark); 232 | assert(pubKey.length > 0); 233 | // beginMark = consoleOutputStr.search('TLSNotary imported main server pubkey:') 234 | // assert(beginMark !== -1) 235 | // var importedPubKey = getPubKeyFromOutput(consoleOutputStr, beginMark) 236 | // assert(mainPubKey === pubKey) 237 | break; 238 | default: 239 | beginMark = consoleOutputStr.search('PageSigner public key for verification'); 240 | assert(beginMark !== -1); 241 | pubKey = getPubKeyFromOutput(consoleOutputStr, beginMark); 242 | assert(pubKey.length > 0); 243 | } 244 | return pubKey; 245 | } catch (err) { 246 | throw new Error('checkGetConsoleOutput_failed'); 247 | } 248 | } 249 | 250 | function getPubKeyFromOutput(consoleOutputStr, beginMark) { 251 | const pubKeyBeginMark = '-----BEGIN PUBLIC KEY-----'; 252 | const pubKeyEndMark = '-----END PUBLIC KEY-----'; 253 | const pubKeyBegin = beginMark + consoleOutputStr.slice(beginMark).search(pubKeyBeginMark); 254 | const pubKeyEnd = pubKeyBegin + consoleOutputStr.slice(pubKeyBegin).search(pubKeyEndMark) + pubKeyEndMark.length; 255 | return consoleOutputStr.slice(pubKeyBegin, pubKeyEnd); 256 | } 257 | 258 | // "userData" allows to pass an arbitrary script to the instance at launch. It MUST be empty. 259 | // currentInstance is a sanity check because the instance is stripped of the code which parses userData. 260 | function checkDescribeInstanceAttribute(json, instanceId) { 261 | try { 262 | assert(json.instanceId.toString() === instanceId); 263 | assert(json.userData.toString() === ''); 264 | } catch (err) { 265 | throw new Error('checkDescribeInstanceAttribute_failed'); 266 | } 267 | } 268 | 269 | function checkGetUser(json, ownerId) { 270 | try { 271 | assert(json[0].User[0].UserId.toString() === ownerId); 272 | assert(json[0].User[0].Arn.toString().slice(-(ownerId.length + ':root'.length)) === ownerId + ':root'); 273 | } catch (err) { 274 | throw new Error('checkGetUser_failed'); 275 | } 276 | } 277 | 278 | const getVerifiedServers = (() => { 279 | var _ref3 = _asyncToGenerator(function* (serversList) { 280 | let mainPubKey; 281 | let verifiedServers = []; 282 | for (let j = 1; j < 3; j++) { 283 | // the array offset represents the TLSNotary version (we start with j = 1 for that reason) 284 | let servers = serversList[j]; 285 | for (let i = 0; i < servers.length; i++) { 286 | const server = servers[i]; 287 | if (server.verifiable === false) continue; 288 | try { 289 | switch (j) {// the array offset represents the TLSNotary version 290 | case 1: 291 | mainPubKey = yield validateServer(server, 'main', mainPubKey); 292 | yield validateServer(server, 'sig', mainPubKey); 293 | break; 294 | default: 295 | mainPubKey = yield validateServer(server, '', mainPubKey); 296 | } 297 | verifiedServers.push(server); 298 | } catch (err) { 299 | if (err.name === 'aws_request_failed' && j === 1) break;else if (err.message === 'checkGetConsoleOutput_failed') break;else throw err; 300 | } 301 | } 302 | } 303 | return verifiedServers; 304 | }); 305 | 306 | return function getVerifiedServers(_x4) { 307 | return _ref3.apply(this, arguments); 308 | }; 309 | })(); 310 | 311 | const avaibleServers = R.flatten(servers); 312 | const verifiedServers = _asyncToGenerator(function* () { 313 | return yield getVerifiedServers(servers); 314 | })(); 315 | const notVerifiableServers = _asyncToGenerator(function* () { 316 | return subtractList(avaibleServers, (yield verifiedServers)); 317 | })(); 318 | 319 | module.exports.getVerifiedServers = getVerifiedServers; 320 | module.exports.validateServer = validateServer; 321 | module.exports.avaibleServers = avaibleServers; 322 | module.exports.verifiedServers = verifiedServers; 323 | module.exports.notVerifiableServers = notVerifiableServers; -------------------------------------------------------------------------------- /src/tlsn/verifychain/verifychain.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 'use strict'; 3 | 4 | const origcerts = require('./rootcertslist.js').certs; 5 | const tlsn_utils = require('../tlsn_utils.js'); 6 | let certs = origcerts; 7 | 8 | var asn1 = require('asn1.js'); 9 | var Buffer = require('buffer').Buffer; 10 | 11 | //--------BEGIN copied from https://github.com/indutny/asn1.js/blob/master/rfc/3280/index.js 12 | var AlgorithmIdentifier = asn1.define('AlgorithmIdentifier', function () { 13 | this.seq().obj( 14 | this.key('algorithm').objid(), 15 | this.key('parameters').optional().any() 16 | ); 17 | }); 18 | 19 | const Certificate = asn1.define('Certificate', function () { 20 | this.seq().obj( 21 | this.key('tbsCertificate').use(TBSCertificate), 22 | this.key('signatureAlgorithm').use(AlgorithmIdentifier), 23 | this.key('signature').bitstr() 24 | ); 25 | }); 26 | 27 | var TBSCertificate = asn1.define('TBSCertificate', function () { 28 | this.seq().obj( 29 | this.key('version').def('v1').explicit(0).use(Version), 30 | this.key('serialNumber').use(CertificateSerialNumber), 31 | this.key('signature').use(AlgorithmIdentifier), 32 | this.key('issuer').use(Name), 33 | this.key('validity').use(Validity), 34 | this.key('subject').use(Name), 35 | this.key('subjectPublicKeyInfo').use(SubjectPublicKeyInfo), 36 | 37 | // TODO(indutny): validate that version is v2 or v3 38 | this.key('issuerUniqueID').optional().explicit(1).use(UniqueIdentifier), 39 | this.key('subjectUniqueID').optional().explicit(2).use(UniqueIdentifier), 40 | 41 | // TODO(indutny): validate that version is v3 42 | this.key('extensions').optional().explicit(3).use(Extensions) 43 | ); 44 | }); 45 | 46 | var Version = asn1.define('Version', function () { 47 | this.int({ 48 | 0: 'v1', 49 | 1: 'v2', 50 | 2: 'v3' 51 | }); 52 | }); 53 | 54 | var CertificateSerialNumber = asn1.define('CertificateSerialNumber', 55 | function () { 56 | this.int(); 57 | }); 58 | 59 | var Validity = asn1.define('Validity', function () { 60 | this.seq().obj( 61 | this.key('notBefore').use(Time), 62 | this.key('notAfter').use(Time) 63 | ); 64 | }); 65 | 66 | var Time = asn1.define('Time', function () { 67 | this.choice({ 68 | utcTime: this.utctime(), 69 | genTime: this.gentime() 70 | }); 71 | }); 72 | 73 | var UniqueIdentifier = asn1.define('UniqueIdentifier', function () { 74 | this.bitstr(); 75 | }); 76 | 77 | var SubjectPublicKeyInfo = asn1.define('SubjectPublicKeyInfo', function () { 78 | this.seq().obj( 79 | this.key('algorithm').use(AlgorithmIdentifier), 80 | this.key('subjectPublicKey').bitstr() 81 | ); 82 | }); 83 | 84 | var Extensions = asn1.define('Extensions', function () { 85 | this.seqof(Extension); 86 | }); 87 | 88 | var Extension = asn1.define('Extension', function () { 89 | this.seq().obj( 90 | this.key('extnID').objid(), 91 | this.key('critical').bool().def(false), 92 | this.key('extnValue').octstr() 93 | ); 94 | }); 95 | 96 | var Name = asn1.define('Name', function () { 97 | this.choice({ 98 | rdn: this.use(RDNSequence) 99 | }); 100 | }); 101 | 102 | var RDNSequence = asn1.define('RDNSequence', function () { 103 | this.seqof(RelativeDistinguishedName); 104 | }); 105 | 106 | var RelativeDistinguishedName = asn1.define('RelativeDistinguishedName', 107 | function () { 108 | this.setof(AttributeTypeAndValue); 109 | }); 110 | 111 | var AttributeTypeAndValue = asn1.define('AttributeTypeAndValue', function () { 112 | this.seq().obj( 113 | this.key('type').use(AttributeType), 114 | this.key('value').use(AttributeValue) 115 | ); 116 | }); 117 | 118 | var AttributeType = asn1.define('AttributeType', function () { 119 | this.objid(); 120 | }); 121 | 122 | var AttributeValue = asn1.define('AttributeValue', function () { 123 | this.any(); 124 | }); 125 | //-----END copied from https://github.com/indutny/asn1.js/blob/master/rfc/3280/index.js 126 | 127 | 128 | function pem2der(certpem) { 129 | var lines = certpem.split('\n'); 130 | var stripped = ''; //strip ascii armor and newlines 131 | for (var i = 1; i < lines.length - 2; i++) { 132 | stripped += lines[i]; 133 | } 134 | stripped = stripped.replace(/\n/g, ''); 135 | var certder = tlsn_utils.b64decode(stripped); 136 | return certder; 137 | } 138 | 139 | 140 | //change the keys from ambigous string that bitpay provides 141 | //to unique subject strings and associated pubkeys 142 | function fixcerts() { 143 | var tmpcerts = { 'trusted_pubkeys': [] }; 144 | for (var key in certs) { 145 | var certpem = certs[key]; 146 | var certder = pem2der(certpem); 147 | //getfield(certder); 148 | var subj = getSubjectString(certder, 'own'); 149 | if (tmpcerts.hasOwnProperty(subj)) { 150 | //Duplicate found. Rename the existing entry and the duplicate 151 | //with extended subject 152 | //console.log('adding duplicate:', subj); 153 | var pem_one = tmpcerts[subj]; 154 | var der_one = pem2der(pem_one); 155 | var subj_one = getSubjectString(der_one, 'own', true); 156 | tmpcerts[subj_one] = pem_one; 157 | delete tmpcerts[subj]; 158 | var subj_two = getSubjectString(certder, 'own', true); 159 | subj = subj_two; 160 | } 161 | tmpcerts[subj] = certpem; 162 | var pk = getPubkey(certder); 163 | tmpcerts['trusted_pubkeys'].push(tlsn_utils.b64encode(tlsn_utils.ua2ba(pk))); 164 | } 165 | certs = tmpcerts; 166 | } 167 | 168 | 169 | function getPubkey(cert) { 170 | var c = Certificate.decode(Buffer.from(cert), 'der'); 171 | return c.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data; 172 | } 173 | 174 | 175 | //Usually CN=&O= is enough to uniquely identify a CA, however there are 6 certs 176 | //in rootstore which have the same O&CN and are only distinguished by OU= 177 | function getSubjectString(cert, whose, extended) { 178 | if (typeof (extended) === 'undefined') { 179 | extended = false; 180 | } 181 | var c = Certificate.decode(Buffer.from(cert), 'der'); 182 | var fields; 183 | if (whose === 'issuer') { 184 | fields = c.tbsCertificate.issuer.value; 185 | } else if (whose === 'own') { 186 | fields = c.tbsCertificate.subject.value; 187 | } 188 | var cn_str = ''; 189 | var o_str = ''; 190 | var ou_str = ''; 191 | for (var i = 0; i < fields.length; i++) { 192 | if (fields[i][0].type.toString() === [2, 5, 4, 3].toString()) { 193 | cn_str = 'CN=' + tlsn_utils.ba2str(fields[i][0].value.slice(2)) + '/'; 194 | } 195 | if (fields[i][0].type.toString() === [2, 5, 4, 10].toString()) { 196 | o_str = 'O=' + tlsn_utils.ba2str(fields[i][0].value.slice(2)) + '/'; 197 | } 198 | if (extended) { 199 | if (fields[i][0].type.toString() === [2, 5, 4, 11].toString()) { 200 | ou_str = 'OU=' + tlsn_utils.ba2str(fields[i][0].value.slice(2)) + '/'; 201 | } 202 | //we have one root CA Autoridad de Certificacion Firmaprofesional CIF A62634068 203 | //which is distinguishable only by L= 204 | //we put the L= value into OU= 205 | if (ou_str === '') { 206 | if (fields[i][0].type.toString() === [2, 5, 4, 7].toString()) { 207 | ou_str = 'OU=' + tlsn_utils.ba2str(fields[i][0].value.slice(2)) + '/'; 208 | } 209 | } 210 | } 211 | } 212 | if (!cn_str && !o_str) { 213 | return false; 214 | } 215 | return cn_str + o_str + ou_str; 216 | } 217 | 218 | 219 | //---an adopted copy of PaymentProtocol.verifyCertChain from 220 | //https://github.com/bitpay/bitcore-payment-protocol/blob/master/lib/browser.js 221 | var verifyCertChain = function (chain) { 222 | 223 | //Check if there is no root cert in the chain 224 | //and if so, add it (provided that we know such root CA) 225 | 226 | //Uniquely identify CAs by the O and CN string and by the pubkey, not by PEM of the cert 227 | //e.g. VeriSign Class 3 Public Primary Certification Authority - G5 228 | //has different PEMs for Fiferox vs Chrome 229 | 230 | var find_in_store = function (rootcert) { 231 | var subjown = getSubjectString(rootcert, 'own'); 232 | if (!certs.hasOwnProperty(subjown)) { 233 | //get extended subject string 234 | subjown = getSubjectString(rootcert, 'own', true); 235 | if (!certs.hasOwnProperty(subjown)) return false; 236 | } 237 | var pk = tlsn_utils.b64encode(tlsn_utils.ua2ba(getPubkey(rootcert))); 238 | if (certs.trusted_pubkeys.indexOf(pk) < 0) return false; 239 | return true; 240 | }; 241 | var found = find_in_store(chain[chain.length - 1]); 242 | 243 | if (!found) { 244 | //Usually there is no root cert in chain 245 | var subj = getSubjectString(chain[chain.length - 1], 'issuer'); 246 | if (!certs.hasOwnProperty(subj)) { 247 | //get extended subject string 248 | subj = getSubjectString(chain[chain.length - 1], 'issuer', true); 249 | if (!certs.hasOwnProperty(subj)) return false; 250 | } 251 | chain.push(pem2der(certs[subj])); 252 | } 253 | 254 | 255 | return chain.every(function (cert, i) { 256 | if (i === chain.length - 1) { 257 | //we already checked earlier that this CA's pubkey is in store 258 | //or we added this CA to the chain ourselves 259 | return true; 260 | } 261 | 262 | var ncert = chain[i + 1]; 263 | var nder = tlsn_utils.ba2hex(ncert); 264 | var npem = KJUR.asn1.ASN1Util.getPEMStringFromHex(nder, 'CERTIFICATE'); 265 | 266 | // Get Next Certificate: 267 | var ndata = Buffer.from(nder, 'hex'); 268 | //var ndata = ba2ua(ncert); 269 | var nc = Certificate.decode(ndata, 'der'); 270 | 271 | // Get Signature Value from current certificate: 272 | var data = Buffer.from(cert); 273 | //var data = ba2ua(der); 274 | var c = Certificate.decode(data, 'der'); 275 | var sig = c.signature.data; 276 | var alg = c.signatureAlgorithm.algorithm; 277 | var lastalgbyte = alg[alg.length - 1]; 278 | var sigHashAlg; 279 | //1.2.840.113549.1.1.11 sha256 280 | //1.2.840.113549.1.1.5 sha1 281 | //1.2.840.113549.1.1.12 sha384 282 | if (lastalgbyte === 11) { 283 | sigHashAlg = 'SHA256withRSA'; 284 | } else if (lastalgbyte === 5) { 285 | sigHashAlg = 'SHA1withRSA'; 286 | } else if (lastalgbyte === 12) { 287 | sigHashAlg = 'SHA384withRSA'; 288 | } else { return false; } 289 | 290 | var npubKey; 291 | // Get Public Key from next certificate (via KJUR because it's a mess): 292 | var js = new KJUR.crypto.Signature({ 293 | alg: sigHashAlg, 294 | prov: 'cryptojs/jsrsa' 295 | }); 296 | js.initVerifyByCertificatePEM(npem); 297 | npubKey = js.pubKey; 298 | 299 | // Check Validity of Certificates 300 | var validityVerified = validateCertTime(c, nc); 301 | 302 | // Check the Issuer matches the Subject of the next certificate: 303 | var issuerVerified = validateCertIssuer(c, nc); 304 | 305 | // Verify current Certificate signature 306 | var jsrsaSig = new KJUR.crypto.Signature({ 307 | alg: sigHashAlg, 308 | prov: 'cryptojs/jsrsa' 309 | }); 310 | jsrsaSig.initVerifyByPublicKey(npubKey); 311 | 312 | // Get the raw DER TBSCertificate 313 | // from the DER Certificate: 314 | var tbs = getTBSCertificate(data, c.signature.data.length); 315 | 316 | jsrsaSig.updateHex(tbs.toString('hex')); 317 | 318 | var sigVerified = jsrsaSig.verify(sig.toString('hex')); 319 | 320 | return validityVerified && issuerVerified && sigVerified; 321 | }); 322 | }; 323 | 324 | var X509_ALGORITHM = { 325 | '1.2.840.113549.1.1.1': 'RSA', 326 | '1.2.840.113549.1.1.2': 'RSA_MD2', 327 | '1.2.840.113549.1.1.4': 'RSA_MD5', 328 | '1.2.840.113549.1.1.5': 'RSA_SHA1', 329 | '1.2.840.113549.1.1.11': 'RSA_SHA256', 330 | '1.2.840.113549.1.1.12': 'RSA_SHA384', 331 | '1.2.840.113549.1.1.13': 'RSA_SHA512', 332 | 333 | '1.2.840.10045.4.3.2': 'ECDSA_SHA256', 334 | '1.2.840.10045.4.3.3': 'ECDSA_SHA384', 335 | '1.2.840.10045.4.3.4': 'ECDSA_SHA512' 336 | }; 337 | 338 | var getAlgorithm = function (value, index) { 339 | if (Array.isArray(value)) { 340 | value = value.join('.'); 341 | } 342 | value = X509_ALGORITHM[value]; 343 | if (typeof (index) !== 'undefined') { 344 | value = value.split('_'); 345 | if (index === true) { 346 | return { 347 | cipher: value[0], 348 | hash: value[1] 349 | }; 350 | } 351 | return value[index]; 352 | } 353 | return value; 354 | }; 355 | 356 | 357 | //---BEGIN copied from https://github.com/bitpay/bitcore-payment-protocol/blob/master/lib/common.js 358 | 359 | // Check Validity of Certificates 360 | var validateCertTime = function (c, nc) { 361 | var validityVerified = true; 362 | var now = Date.now(); 363 | var cBefore = c.tbsCertificate.validity.notBefore.value; 364 | var cAfter = c.tbsCertificate.validity.notAfter.value; 365 | var nBefore = nc.tbsCertificate.validity.notBefore.value; 366 | var nAfter = nc.tbsCertificate.validity.notAfter.value; 367 | if (cBefore > now || cAfter < now || nBefore > now || nAfter < now) { 368 | validityVerified = false; 369 | } 370 | return validityVerified; 371 | }; 372 | 373 | // Check the Issuer matches the Subject of the next certificate: 374 | var validateCertIssuer = function (c, nc) { 375 | var issuer = c.tbsCertificate.issuer; 376 | var subject = nc.tbsCertificate.subject; 377 | var issuerVerified = issuer.type === subject.type && issuer.value.every(function (issuerArray, i) { 378 | var subjectArray = subject.value[i]; 379 | return issuerArray.every(function (issuerObject, i) { 380 | var subjectObject = subjectArray[i]; 381 | 382 | var issuerObjectType = issuerObject.type.join('.'); 383 | var subjectObjectType = subjectObject.type.join('.'); 384 | 385 | var issuerObjectValue = issuerObject.value.toString('hex'); 386 | var subjectObjectValue = subjectObject.value.toString('hex'); 387 | 388 | return issuerObjectType === subjectObjectType && issuerObjectValue === subjectObjectValue; 389 | }); 390 | }); 391 | return issuerVerified; 392 | }; 393 | 394 | 395 | // Grab the raw DER To-Be-Signed Certificate 396 | // from a DER Certificate to verify 397 | var getTBSCertificate = function (data, siglen) { 398 | // We start by slicing off the first SEQ of the 399 | // Certificate (TBSCertificate is its own SEQ). 400 | 401 | // The first 10 bytes usually look like: 402 | // [ 48, 130, 5, 32, 48, 130, 4, 8, 160, 3 ] 403 | var start = 0; 404 | var starts = 0; 405 | for (start = 0; start < data.length; start++) { 406 | if (starts === 1 && data[start] === 48) { 407 | break; 408 | } 409 | if (starts < 1 && data[start] === 48) { 410 | starts++; 411 | } 412 | } 413 | 414 | // The bytes *after* the TBS (including the last TBS byte) will look like 415 | // (note the 48 - the start of the sig, and the 122 - the end of the TBS): 416 | // [ 122, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 11, 5, 0, 3, ... ] 417 | 418 | // The certificate in these examples has a `start` of 4, and an `end` of 419 | // 1040. The 4 bytes is the DER SEQ of the Certificate, right before the 420 | // SEQ of the TBSCertificate. 421 | var end = 0; 422 | var ends = 0; 423 | for (end = data.length - 1 - siglen; end > 0; end--) { 424 | if (ends === 2 && data[end] === 48) { 425 | break; 426 | } 427 | if (ends < 2 && data[end] === 0) { 428 | ends++; 429 | } 430 | } 431 | 432 | // Return our raw DER TBSCertificate: 433 | return data.slice(start, end); 434 | }; 435 | 436 | //---END copied from https://github.com/bitpay/bitcore-payment-protocol/blob/master/lib/common.js 437 | 438 | //this must trigger after asn1.js was loaded, so putting this to the bottom 439 | fixcerts(); 440 | 441 | module.exports.Certificate = Certificate; 442 | module.exports.verifyCertChain = verifyCertChain; 443 | -------------------------------------------------------------------------------- /src/android-verify.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | const cbor = require('cbor') 3 | const URLSafeBase64 = require('urlsafe-base64') 4 | const r = require('jsrsasign') 5 | const request = require('isomorphic-fetch') 6 | const asn = require('asn1.js') 7 | const jsonSettings = require('../settings/settings.json') 8 | const R = require('ramda') 9 | // $FlowFixMe 10 | const Buffer = require('buffer').Buffer 11 | 12 | 13 | export const getCertificateChain = (encodedChain) => { 14 | const decodedChain = cbor.decodeFirstSync(encodedChain) 15 | const leaf = decodedChain.leaf 16 | const intermediate = decodedChain.intermediate 17 | const root = decodedChain.root 18 | const derEncodedChain = [leaf, intermediate, root] 19 | let pemEncodedChain = [] 20 | let cert = null 21 | let out = '' 22 | for (let i = 0; i < 3; i++) { 23 | cert = derEncodedChain[i].toString('base64') 24 | out = '-----BEGIN CERTIFICATE-----\n' 25 | for (let j = 0; j < cert.length; j += 64) 26 | out += cert.slice(j, j + 64) + '\n' 27 | 28 | out += '-----END CERTIFICATE-----' 29 | pemEncodedChain.push(out) 30 | } 31 | return pemEncodedChain 32 | } 33 | 34 | function verifySignature(jws, googleCert) { 35 | return r.jws.JWS.verify(jws, googleCert.subjectPublicKeyRSA, ['RS256']) 36 | } 37 | 38 | function verifyPayload(jwsPayload, response, requestID, signature, apkDigest, apkCertDigest, version) { 39 | const jwsPayloadJSON = JSON.parse(jwsPayload.toString()) 40 | 41 | const md = new r.KJUR.crypto.MessageDigest({alg: 'sha256', prov: 'cryptojs'}) 42 | md.updateString(response) 43 | md.updateHex(signature.toString('hex')) 44 | 45 | if (version === 'v1') 46 | md.updateString(requestID) 47 | else if (version === 'v2') 48 | md.updateHex(requestID.toString('hex')) 49 | else 50 | throw new Error('version unsupported') 51 | 52 | const digest = md.digest() 53 | const nonce = Buffer.from(digest, 'hex').toString('base64') 54 | 55 | if (jwsPayloadJSON.nonce !== nonce.toString('base64')) 56 | throw new Error('verifyPayload failed: unexpected nonce') 57 | 58 | if (jwsPayloadJSON.apkPackageName !== 'it.oraclize.androidproof') 59 | throw new Error('verifyPayload failed: unexpected package name') 60 | 61 | if (jwsPayloadJSON.apkDigestSha256 !== apkDigest) 62 | throw new Error('verifyPayload failed: wrong apk hash') 63 | 64 | if (jwsPayloadJSON.apkCertificateDigestSha256[0] !== apkCertDigest) 65 | throw new Error('verifyPayload failed: wrong signing certificate hash') 66 | 67 | if (jwsPayloadJSON.basicIntegrity !== true) 68 | throw new Error('verifyPayload failed: SafetyNet basicIntegrity is false') 69 | 70 | } 71 | 72 | async function verifyAuthenticity(jws, googleApiKey) { 73 | const postData = {signedAttestation: jws} 74 | const res = await request( 75 | 'https://www.googleapis.com/androidcheck/v1/attestations/verify?key=' + googleApiKey, 76 | {method: 'POST', json: postData}) 77 | const text = await res.text() 78 | 79 | const googleResponse = JSON.parse(text) 80 | if (!googleResponse.isValidSignature) 81 | throw new Error('verifyAuthenticity failed') 82 | 83 | } 84 | 85 | async function verifyAuthenticityV2(jws, googleApiKey) { 86 | const postData = {signedAttestation: jws} 87 | const url = 'https://www.googleapis.com/androidcheck/v1/attestations/verify?key=' + googleApiKey 88 | const res = await request(url, 89 | {method: 'POST', body: JSON.stringify(postData)}) 90 | const text = await res.text() 91 | 92 | if (res.status != 200) 93 | throw new Error('Error status: ' + res.status + ' on verifyAuthenticity for key: ' + googleApiKey + ' ' + res.status) 94 | 95 | 96 | const googleResponse = JSON.parse(text) 97 | 98 | return googleResponse.isValidSignature 99 | } 100 | 101 | function verifyResponseSignature(response, signature, pemLeafCert, whitelistedPubKeys, hashAlg) { 102 | let sig 103 | let result = false 104 | if (pemLeafCert === null && whitelistedPubKeys !== null) { 105 | let i = 0 106 | while (!result && i < whitelistedPubKeys.length) { 107 | sig = new r.crypto.Signature({alg: hashAlg}) 108 | const params = {xy: whitelistedPubKeys[i], curve: 'secp256r1'} 109 | const key = r.KEYUTIL.getKey(params) 110 | sig.initVerifyByPublicKey(key) 111 | sig.updateString(response) 112 | result = sig.verify(signature.toString('hex')) 113 | i += 1 114 | } 115 | } else { 116 | sig = new r.crypto.Signature({alg: hashAlg}) 117 | sig.init(pemLeafCert) 118 | sig.updateString(response) 119 | result = sig.verify(signature.toString('hex')) 120 | return result 121 | } 122 | return false 123 | } 124 | 125 | function verifyAttestationCertChain(leafCert, intermediateCert, rootCert, pemInter, pemRoot) { 126 | const leafHTbsCert = r.ASN1HEX.getDecendantHexTLVByNthList(leafCert.hex, 0, [0]) 127 | const leafAlg = leafCert.getSignatureAlgorithmField() 128 | const leafCertificateSignature = r.X509.getSignatureValueHex(leafCert.hex) 129 | 130 | const intHTbsCert = r.ASN1HEX.getDecendantHexTLVByNthList(intermediateCert.hex, 0, [0]) 131 | const intAlg = intermediateCert.getSignatureAlgorithmField() 132 | const intCertificateSignature = r.X509.getSignatureValueHex(intermediateCert.hex) 133 | 134 | // Verify leaf against intermediate 135 | const intSig = new r.crypto.Signature({alg: leafAlg}) 136 | intSig.init(pemInter) 137 | intSig.updateHex(leafHTbsCert) 138 | 139 | // Verify against root 140 | const rootSig = new r.crypto.Signature({alg: intAlg}) 141 | rootSig.init(pemRoot) 142 | rootSig.updateHex(intHTbsCert) 143 | 144 | if (!intSig.verify(leafCertificateSignature) || 145 | !rootSig.verify(intCertificateSignature)) 146 | throw new Error('verifyAttestationCertChain failed') 147 | 148 | } 149 | 150 | function verifyAttestationParams(leafCert, attestationParams) { 151 | const value = r.X509.getHexOfTLV_V3ExtValue(leafCert.hex, '1.3.6.1.4.1.11129.2.1.17') 152 | const RootOfTrust = asn.define('RootOfTrust', function () { 153 | this.seq().obj( 154 | this.key('verifiedBootKey').octstr(), 155 | this.key('deviceLocked').bool(), 156 | // $FlowFixMe 157 | this.key('verifiedBootState').enum({0: 'Verified', 1: 'SelfSigned', 2: 'TrustedEnvironment', 3: 'Failed'}) 158 | ) 159 | }) 160 | 161 | const Int = asn.define('Int', function () { 162 | this.int() 163 | }) 164 | 165 | const AuthorizationList = asn.define('AuthorizationList', function () { 166 | this.seq().obj( 167 | this.key('purpose').optional().explicit(1).setof(Int), 168 | this.key('algorithm').optional().explicit(2).int(), 169 | this.key('keySize').optional().explicit(3).int(), 170 | this.key('digest').optional().explicit(5).setof(Int), 171 | this.key('padding').optional().explicit(6).setof(Int), 172 | this.key('ecCurve').optional().explicit(10).int(), 173 | this.key('rsaPublicExponent').optional().explicit(200).int(), 174 | this.key('activeDateTime').optional().explicit(400).int(), 175 | this.key('originationExpireDateTime').optional().explicit(401).int(), 176 | this.key('usageExpireDateTime').optional().explicit(402).int(), 177 | this.key('noAuthRequired').optional().explicit(503).null_(), 178 | this.key('userAuthType').optional().explicit(504).int(), 179 | this.key('authTimeout').optional().explicit(505).int(), 180 | this.key('allowWhileOnBody').optional().explicit(506).null_(), 181 | this.key('allApplications').optional().explicit(600).null_(), 182 | this.key('applicationId').optional().explicit(601).octstr(), 183 | this.key('creationDateTime').optional().explicit(701).int(), 184 | this.key('origin').optional().explicit(702).int(), 185 | this.key('rollbackResistant').optional().explicit(703).null_(), 186 | this.key('rootOfTrust').optional().explicit(704).use(RootOfTrust), 187 | this.key('osVersion').optional().explicit(705).int(), 188 | this.key('osPatchLevel').optional().explicit(706).int(), 189 | this.key('attestationChallenge').optional().explicit(708).int(), 190 | this.key('attestationApplicationId').optional().explicit(709).octstr() 191 | ) 192 | }) 193 | 194 | const KeyDescription = asn.define('KeyDescription', function () { 195 | this.seq().obj( 196 | this.key('attestationVersion').int(), 197 | // $FlowFixMe 198 | this.key('attestationSecurityLevel').enum({0: 'Software', 1: 'TrustedEnvironment'}), 199 | this.key('keymasterVersion').int(), 200 | // $FlowFixMe 201 | this.key('keymasterSecurityLevel').enum({0: 'Software', 1: 'TrustedEnvironment'}), 202 | this.key('attestationChallenge').octstr(), 203 | this.key('reserved').octstr(), 204 | this.key('softwareEnforced').use(AuthorizationList), 205 | this.key('teeEnforced').use(AuthorizationList) 206 | ) 207 | }) 208 | const buffer = Buffer.from(value, 'hex') 209 | const keyInfo = KeyDescription.decode(buffer, 'der') 210 | 211 | if (String(keyInfo.keymasterVersion) !== attestationParams.keymasterVersion) 212 | throw new Error('verifyAttestationParams failed: keymasterVersion mismatch') 213 | 214 | if (String(keyInfo.attestationSecurityLevel) !== attestationParams.attestationSecurityLevel) 215 | throw new Error('verifyAttestationParams failed: attestationSecurityLevel') 216 | 217 | if (String(keyInfo.keymasterSecurityLevel) !== attestationParams.keymasterSecurityLevel) 218 | throw new Error('verifyAttestationParams failed: keymasterSecurityLevel mismatch') 219 | 220 | if (String(keyInfo.attestationChallenge) !== attestationParams.attestationChallenge) 221 | throw new Error('verifyAttestationParams failed: attestationChallenge value mismatch') 222 | 223 | if (String(keyInfo.teeEnforced.purpose) !== attestationParams.teeEnforced.purpose) 224 | throw new Error('verifyAttestationParams failed: key purpose mismatch') 225 | 226 | if (String(keyInfo.teeEnforced.algorithm) !== attestationParams.teeEnforced.algorithm) 227 | throw new Error('verifyAttestationParams failed: key algorithm type mismatch') 228 | 229 | if (String(keyInfo.teeEnforced.digest) !== attestationParams.teeEnforced.digest) 230 | throw new Error('verifyAttestationParams failed: key digest mismatch') 231 | 232 | if (typeof keyInfo.teeEnforced.ecCurve !== 'undefined' && String(keyInfo.teeEnforced.ecCurve) !== attestationParams.teeEnforced.ecCurve) 233 | throw new Error('verifyAttestationParams failed: ecCurve mismatch') 234 | 235 | if (String(keyInfo.teeEnforced.origin) !== attestationParams.teeEnforced.origin) 236 | throw new Error('verifyAttestationParams failed: key was not generated on device') 237 | 238 | } 239 | 240 | function extractGoogleCert(header) { 241 | const headerDictionary = JSON.parse(header) 242 | const googleCertChain = headerDictionary.x5c 243 | let cert = new r.X509() 244 | cert.readCertPEM(googleCertChain[0]) 245 | return cert 246 | } 247 | 248 | export const verify = async (data: Uint8Array, version: string) => { 249 | const cborEncodedData = data.slice(3) 250 | const buf = Buffer.from(cborEncodedData.buffer) 251 | const androidProof = cbor.decodeFirstSync(buf) 252 | let status = '' 253 | let settings = null 254 | let requestID = null 255 | let encodedChain = null 256 | let pemEncodedChain = null 257 | if (version === 'v1') { 258 | settings = jsonSettings.v1 259 | requestID = androidProof.requestID.toString() 260 | encodedChain = jsonSettings.v1.androidCertChain.map(cert => Buffer.from(cert, 'base64')) 261 | pemEncodedChain = encodedChain.map(chain => getCertificateChain(chain)) 262 | } else if (version === 'v2'){ 263 | settings = jsonSettings.v2 264 | requestID = androidProof.requestID.toString('hex') 265 | encodedChain = jsonSettings.v2.androidCertChain.map(cert => Buffer.from(cert, 'base64')) 266 | pemEncodedChain = encodedChain.map(chain => getCertificateChain(chain)) 267 | } else { 268 | throw new Error('version unsupported, please use v1 or v2') 269 | } 270 | for (let k = 0; k < encodedChain.length; k++) { 271 | const googleApiKey = settings.googleApiKey 272 | const apkDigest = settings.apkDigest 273 | const apkCertDigest = settings.apkCertDigest 274 | const whitelistedPubKeys = settings.pubKeys 275 | const hashAlg = settings.alg 276 | const attestationParams = settings.attestationParams 277 | const response = androidProof.HTTPResponse.toString() 278 | const signature = androidProof.signature 279 | const jwsHeader = androidProof.JWS_Header 280 | const jwsPayload = androidProof.JWS_Payload 281 | const jwsHeaderEncoded = URLSafeBase64.encode(androidProof.JWS_Header) 282 | const jwsPayloadEncoded = URLSafeBase64.encode(androidProof.JWS_Payload) 283 | const jwsSignatureEncoded = URLSafeBase64.encode(androidProof.JWS_Signature) 284 | const jws = jwsHeaderEncoded.concat('.').concat(jwsPayloadEncoded).concat('.').concat(jwsSignatureEncoded) 285 | const googleCert = extractGoogleCert(jwsHeader) 286 | let respVer = null 287 | if (pemEncodedChain === null) { 288 | respVer = verifyResponseSignature(response, signature, null, whitelistedPubKeys, hashAlg) 289 | } else { 290 | const leafCert = new r.X509() 291 | const intermediateCert = new r.X509() 292 | const rootCert = new r.X509() 293 | leafCert.readCertPEM(pemEncodedChain[k][0]) 294 | intermediateCert.readCertPEM(pemEncodedChain[k][1]) 295 | rootCert.readCertPEM(pemEncodedChain[k][2]) 296 | try { 297 | verifyAttestationParams(leafCert, attestationParams) 298 | } catch(e) { 299 | if (e.message === 'verifyAttestationParams failed: attestationSecurityLevel') 300 | status = e.message 301 | else 302 | throw e 303 | } 304 | verifyAttestationCertChain(leafCert, intermediateCert, rootCert, pemEncodedChain[k][1], pemEncodedChain[k][2]) 305 | respVer = verifyResponseSignature(response, signature, pemEncodedChain[k][0], null, hashAlg) 306 | } 307 | if (!respVer) { 308 | if (k == encodedChain.length - 1) 309 | throw new Error('verifyResponseSignature failed') 310 | continue 311 | } else { 312 | for (let j = 0; j < apkDigest.length; j++) { 313 | for (let i = 0; i < apkCertDigest.length; i++) { 314 | try { 315 | verifyPayload(jwsPayload, response, requestID, signature, apkDigest[j], apkCertDigest[i], version) 316 | } catch (err) { 317 | if (err.message !== 'verifyPayload failed: wrong signing certificate hash' && err.message !== 'verifyPayload failed: wrong apk hash') 318 | throw new Error('verifyPayload failed: apk hash or signing cert hash mismatch') 319 | } 320 | } 321 | } 322 | if (!verifySignature(jws, googleCert)) 323 | throw new Error('verifySignature failed') 324 | for (let i = 0; i < googleApiKey.length; i++) { 325 | try { 326 | let result = null 327 | if (version === 'v1') 328 | result = await verifyAuthenticity(jws, googleApiKey[i]) 329 | else if (version === 'v2') 330 | result = await verifyAuthenticityV2(jws, googleApiKey[i]) 331 | else 332 | throw new Error('version unsupported') 333 | if (result) { 334 | status = '' 335 | break 336 | } 337 | } catch (err) { 338 | if (i == googleApiKey.length - 1) 339 | throw new Error('verifyAuthenticity failed') 340 | } 341 | } 342 | return ['success', status] 343 | } 344 | } 345 | } 346 | 347 | export const verifyAndroid = async (data: Uint8Array, version: string) => { 348 | let status = null 349 | try { 350 | const cborEncodedData = data.slice(3) 351 | const buf = Buffer.from(cborEncodedData.buffer) 352 | const androidProof = cbor.decodeFirstSync(buf) 353 | const response = androidProof.HTTPResponse.toString() 354 | const validErrors = 355 | [ 'verifyPayload failed: apk hash or signing cert hash mismatch' 356 | , 'verifyAuthenticity failed' 357 | , 'verifyPayload failed: wrong apk hash' 358 | , 'verifyPayload failed: wrong signing certificate hash' 359 | , 'verifyResponseSignature failed' 360 | , 'verifyAttestationParams failed: keymasterVersion mismatch' 361 | , 'verifyAttestationParams failed: keymasterSecurityLevel mismatch' 362 | , 'verifyAttestationParams failed: attestationChallenge value mismatch' 363 | , 'verifyAttestationParams failed: key purpose mismatch' 364 | , 'verifyAttestationParams failed: key algorithm type mismatch' 365 | , 'verifyAttestationParams failed: key digest mismatch' 366 | , 'verifyAttestationParams failed: ecCurve mismatch' 367 | , 'verifyAttestationParams failed: key was not generated on device' 368 | ] 369 | try { 370 | status = await verify(data, version) 371 | } catch(err) { 372 | if (R.contains(err.message, validErrors)) 373 | status = ['failed', err.message] 374 | else 375 | throw err 376 | } 377 | const isVerified = status[0] === 'success' ? true : false 378 | return { status: status, parsedData: response, isVerified } 379 | } catch (e) { 380 | status = ['failed', 'generic error message'] 381 | return { status: status, parsedData: '', isVerified: false} 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /lib/android-verify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | let verifyAuthenticity = (() => { 8 | var _ref = _asyncToGenerator(function* (jws, googleApiKey) { 9 | const postData = { signedAttestation: jws }; 10 | const res = yield request('https://www.googleapis.com/androidcheck/v1/attestations/verify?key=' + googleApiKey, { method: 'POST', json: postData }); 11 | const text = yield res.text(); 12 | 13 | const googleResponse = JSON.parse(text); 14 | if (!googleResponse.isValidSignature) throw new Error('verifyAuthenticity failed'); 15 | }); 16 | 17 | return function verifyAuthenticity(_x, _x2) { 18 | return _ref.apply(this, arguments); 19 | }; 20 | })(); 21 | 22 | let verifyAuthenticityV2 = (() => { 23 | var _ref2 = _asyncToGenerator(function* (jws, googleApiKey) { 24 | const postData = { signedAttestation: jws }; 25 | const url = 'https://www.googleapis.com/androidcheck/v1/attestations/verify?key=' + googleApiKey; 26 | const res = yield request(url, { method: 'POST', body: JSON.stringify(postData) }); 27 | const text = yield res.text(); 28 | 29 | if (res.status != 200) throw new Error('Error status: ' + res.status + ' on verifyAuthenticity for key: ' + googleApiKey + ' ' + res.status); 30 | 31 | const googleResponse = JSON.parse(text); 32 | 33 | return googleResponse.isValidSignature; 34 | }); 35 | 36 | return function verifyAuthenticityV2(_x3, _x4) { 37 | return _ref2.apply(this, arguments); 38 | }; 39 | })(); 40 | 41 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } 42 | 43 | const cbor = require('cbor'); 44 | const URLSafeBase64 = require('urlsafe-base64'); 45 | const r = require('jsrsasign'); 46 | const request = require('isomorphic-fetch'); 47 | const asn = require('asn1.js'); 48 | const jsonSettings = require('../settings/settings.json'); 49 | const R = require('ramda'); 50 | // $FlowFixMe 51 | const Buffer = require('buffer').Buffer; 52 | 53 | const getCertificateChain = exports.getCertificateChain = encodedChain => { 54 | const decodedChain = cbor.decodeFirstSync(encodedChain); 55 | const leaf = decodedChain.leaf; 56 | const intermediate = decodedChain.intermediate; 57 | const root = decodedChain.root; 58 | const derEncodedChain = [leaf, intermediate, root]; 59 | let pemEncodedChain = []; 60 | let cert = null; 61 | let out = ''; 62 | for (let i = 0; i < 3; i++) { 63 | cert = derEncodedChain[i].toString('base64'); 64 | out = '-----BEGIN CERTIFICATE-----\n'; 65 | for (let j = 0; j < cert.length; j += 64) out += cert.slice(j, j + 64) + '\n'; 66 | 67 | out += '-----END CERTIFICATE-----'; 68 | pemEncodedChain.push(out); 69 | } 70 | return pemEncodedChain; 71 | }; 72 | 73 | function verifySignature(jws, googleCert) { 74 | return r.jws.JWS.verify(jws, googleCert.subjectPublicKeyRSA, ['RS256']); 75 | } 76 | 77 | function verifyPayload(jwsPayload, response, requestID, signature, apkDigest, apkCertDigest, version) { 78 | const jwsPayloadJSON = JSON.parse(jwsPayload.toString()); 79 | 80 | const md = new r.KJUR.crypto.MessageDigest({ alg: 'sha256', prov: 'cryptojs' }); 81 | md.updateString(response); 82 | md.updateHex(signature.toString('hex')); 83 | 84 | if (version === 'v1') md.updateString(requestID);else if (version === 'v2') md.updateHex(requestID.toString('hex'));else throw new Error('version unsupported'); 85 | 86 | const digest = md.digest(); 87 | const nonce = Buffer.from(digest, 'hex').toString('base64'); 88 | 89 | if (jwsPayloadJSON.nonce !== nonce.toString('base64')) throw new Error('verifyPayload failed: unexpected nonce'); 90 | 91 | if (jwsPayloadJSON.apkPackageName !== 'it.oraclize.androidproof') throw new Error('verifyPayload failed: unexpected package name'); 92 | 93 | if (jwsPayloadJSON.apkDigestSha256 !== apkDigest) throw new Error('verifyPayload failed: wrong apk hash'); 94 | 95 | if (jwsPayloadJSON.apkCertificateDigestSha256[0] !== apkCertDigest) throw new Error('verifyPayload failed: wrong signing certificate hash'); 96 | 97 | if (jwsPayloadJSON.basicIntegrity !== true) throw new Error('verifyPayload failed: SafetyNet basicIntegrity is false'); 98 | } 99 | 100 | function verifyResponseSignature(response, signature, pemLeafCert, whitelistedPubKeys, hashAlg) { 101 | let sig; 102 | let result = false; 103 | if (pemLeafCert === null && whitelistedPubKeys !== null) { 104 | let i = 0; 105 | while (!result && i < whitelistedPubKeys.length) { 106 | sig = new r.crypto.Signature({ alg: hashAlg }); 107 | const params = { xy: whitelistedPubKeys[i], curve: 'secp256r1' }; 108 | const key = r.KEYUTIL.getKey(params); 109 | sig.initVerifyByPublicKey(key); 110 | sig.updateString(response); 111 | result = sig.verify(signature.toString('hex')); 112 | i += 1; 113 | } 114 | } else { 115 | sig = new r.crypto.Signature({ alg: hashAlg }); 116 | sig.init(pemLeafCert); 117 | sig.updateString(response); 118 | result = sig.verify(signature.toString('hex')); 119 | return result; 120 | } 121 | return false; 122 | } 123 | 124 | function verifyAttestationCertChain(leafCert, intermediateCert, rootCert, pemInter, pemRoot) { 125 | const leafHTbsCert = r.ASN1HEX.getDecendantHexTLVByNthList(leafCert.hex, 0, [0]); 126 | const leafAlg = leafCert.getSignatureAlgorithmField(); 127 | const leafCertificateSignature = r.X509.getSignatureValueHex(leafCert.hex); 128 | 129 | const intHTbsCert = r.ASN1HEX.getDecendantHexTLVByNthList(intermediateCert.hex, 0, [0]); 130 | const intAlg = intermediateCert.getSignatureAlgorithmField(); 131 | const intCertificateSignature = r.X509.getSignatureValueHex(intermediateCert.hex); 132 | 133 | // Verify leaf against intermediate 134 | const intSig = new r.crypto.Signature({ alg: leafAlg }); 135 | intSig.init(pemInter); 136 | intSig.updateHex(leafHTbsCert); 137 | 138 | // Verify against root 139 | const rootSig = new r.crypto.Signature({ alg: intAlg }); 140 | rootSig.init(pemRoot); 141 | rootSig.updateHex(intHTbsCert); 142 | 143 | if (!intSig.verify(leafCertificateSignature) || !rootSig.verify(intCertificateSignature)) throw new Error('verifyAttestationCertChain failed'); 144 | } 145 | 146 | function verifyAttestationParams(leafCert, attestationParams) { 147 | const value = r.X509.getHexOfTLV_V3ExtValue(leafCert.hex, '1.3.6.1.4.1.11129.2.1.17'); 148 | const RootOfTrust = asn.define('RootOfTrust', function () { 149 | this.seq().obj(this.key('verifiedBootKey').octstr(), this.key('deviceLocked').bool(), 150 | // $FlowFixMe 151 | this.key('verifiedBootState').enum({ 0: 'Verified', 1: 'SelfSigned', 2: 'TrustedEnvironment', 3: 'Failed' })); 152 | }); 153 | 154 | const Int = asn.define('Int', function () { 155 | this.int(); 156 | }); 157 | 158 | const AuthorizationList = asn.define('AuthorizationList', function () { 159 | this.seq().obj(this.key('purpose').optional().explicit(1).setof(Int), this.key('algorithm').optional().explicit(2).int(), this.key('keySize').optional().explicit(3).int(), this.key('digest').optional().explicit(5).setof(Int), this.key('padding').optional().explicit(6).setof(Int), this.key('ecCurve').optional().explicit(10).int(), this.key('rsaPublicExponent').optional().explicit(200).int(), this.key('activeDateTime').optional().explicit(400).int(), this.key('originationExpireDateTime').optional().explicit(401).int(), this.key('usageExpireDateTime').optional().explicit(402).int(), this.key('noAuthRequired').optional().explicit(503).null_(), this.key('userAuthType').optional().explicit(504).int(), this.key('authTimeout').optional().explicit(505).int(), this.key('allowWhileOnBody').optional().explicit(506).null_(), this.key('allApplications').optional().explicit(600).null_(), this.key('applicationId').optional().explicit(601).octstr(), this.key('creationDateTime').optional().explicit(701).int(), this.key('origin').optional().explicit(702).int(), this.key('rollbackResistant').optional().explicit(703).null_(), this.key('rootOfTrust').optional().explicit(704).use(RootOfTrust), this.key('osVersion').optional().explicit(705).int(), this.key('osPatchLevel').optional().explicit(706).int(), this.key('attestationChallenge').optional().explicit(708).int(), this.key('attestationApplicationId').optional().explicit(709).octstr()); 160 | }); 161 | 162 | const KeyDescription = asn.define('KeyDescription', function () { 163 | this.seq().obj(this.key('attestationVersion').int(), 164 | // $FlowFixMe 165 | this.key('attestationSecurityLevel').enum({ 0: 'Software', 1: 'TrustedEnvironment' }), this.key('keymasterVersion').int(), 166 | // $FlowFixMe 167 | this.key('keymasterSecurityLevel').enum({ 0: 'Software', 1: 'TrustedEnvironment' }), this.key('attestationChallenge').octstr(), this.key('reserved').octstr(), this.key('softwareEnforced').use(AuthorizationList), this.key('teeEnforced').use(AuthorizationList)); 168 | }); 169 | const buffer = Buffer.from(value, 'hex'); 170 | const keyInfo = KeyDescription.decode(buffer, 'der'); 171 | 172 | if (String(keyInfo.keymasterVersion) !== attestationParams.keymasterVersion) throw new Error('verifyAttestationParams failed: keymasterVersion mismatch'); 173 | 174 | if (String(keyInfo.attestationSecurityLevel) !== attestationParams.attestationSecurityLevel) throw new Error('verifyAttestationParams failed: attestationSecurityLevel'); 175 | 176 | if (String(keyInfo.keymasterSecurityLevel) !== attestationParams.keymasterSecurityLevel) throw new Error('verifyAttestationParams failed: keymasterSecurityLevel mismatch'); 177 | 178 | if (String(keyInfo.attestationChallenge) !== attestationParams.attestationChallenge) throw new Error('verifyAttestationParams failed: attestationChallenge value mismatch'); 179 | 180 | if (String(keyInfo.teeEnforced.purpose) !== attestationParams.teeEnforced.purpose) throw new Error('verifyAttestationParams failed: key purpose mismatch'); 181 | 182 | if (String(keyInfo.teeEnforced.algorithm) !== attestationParams.teeEnforced.algorithm) throw new Error('verifyAttestationParams failed: key algorithm type mismatch'); 183 | 184 | if (String(keyInfo.teeEnforced.digest) !== attestationParams.teeEnforced.digest) throw new Error('verifyAttestationParams failed: key digest mismatch'); 185 | 186 | if (typeof keyInfo.teeEnforced.ecCurve !== 'undefined' && String(keyInfo.teeEnforced.ecCurve) !== attestationParams.teeEnforced.ecCurve) throw new Error('verifyAttestationParams failed: ecCurve mismatch'); 187 | 188 | if (String(keyInfo.teeEnforced.origin) !== attestationParams.teeEnforced.origin) throw new Error('verifyAttestationParams failed: key was not generated on device'); 189 | } 190 | 191 | function extractGoogleCert(header) { 192 | const headerDictionary = JSON.parse(header); 193 | const googleCertChain = headerDictionary.x5c; 194 | let cert = new r.X509(); 195 | cert.readCertPEM(googleCertChain[0]); 196 | return cert; 197 | } 198 | 199 | const verify = exports.verify = (() => { 200 | var _ref3 = _asyncToGenerator(function* (data, version) { 201 | const cborEncodedData = data.slice(3); 202 | const buf = Buffer.from(cborEncodedData.buffer); 203 | const androidProof = cbor.decodeFirstSync(buf); 204 | let status = ''; 205 | let settings = null; 206 | let requestID = null; 207 | let encodedChain = null; 208 | let pemEncodedChain = null; 209 | if (version === 'v1') { 210 | settings = jsonSettings.v1; 211 | requestID = androidProof.requestID.toString(); 212 | encodedChain = jsonSettings.v1.androidCertChain.map(function (cert) { 213 | return Buffer.from(cert, 'base64'); 214 | }); 215 | pemEncodedChain = encodedChain.map(function (chain) { 216 | return getCertificateChain(chain); 217 | }); 218 | } else if (version === 'v2') { 219 | settings = jsonSettings.v2; 220 | requestID = androidProof.requestID.toString('hex'); 221 | encodedChain = jsonSettings.v2.androidCertChain.map(function (cert) { 222 | return Buffer.from(cert, 'base64'); 223 | }); 224 | pemEncodedChain = encodedChain.map(function (chain) { 225 | return getCertificateChain(chain); 226 | }); 227 | } else { 228 | throw new Error('version unsupported, please use v1 or v2'); 229 | } 230 | for (let k = 0; k < encodedChain.length; k++) { 231 | const googleApiKey = settings.googleApiKey; 232 | const apkDigest = settings.apkDigest; 233 | const apkCertDigest = settings.apkCertDigest; 234 | const whitelistedPubKeys = settings.pubKeys; 235 | const hashAlg = settings.alg; 236 | const attestationParams = settings.attestationParams; 237 | const response = androidProof.HTTPResponse.toString(); 238 | const signature = androidProof.signature; 239 | const jwsHeader = androidProof.JWS_Header; 240 | const jwsPayload = androidProof.JWS_Payload; 241 | const jwsHeaderEncoded = URLSafeBase64.encode(androidProof.JWS_Header); 242 | const jwsPayloadEncoded = URLSafeBase64.encode(androidProof.JWS_Payload); 243 | const jwsSignatureEncoded = URLSafeBase64.encode(androidProof.JWS_Signature); 244 | const jws = jwsHeaderEncoded.concat('.').concat(jwsPayloadEncoded).concat('.').concat(jwsSignatureEncoded); 245 | const googleCert = extractGoogleCert(jwsHeader); 246 | let respVer = null; 247 | if (pemEncodedChain === null) { 248 | respVer = verifyResponseSignature(response, signature, null, whitelistedPubKeys, hashAlg); 249 | } else { 250 | const leafCert = new r.X509(); 251 | const intermediateCert = new r.X509(); 252 | const rootCert = new r.X509(); 253 | leafCert.readCertPEM(pemEncodedChain[k][0]); 254 | intermediateCert.readCertPEM(pemEncodedChain[k][1]); 255 | rootCert.readCertPEM(pemEncodedChain[k][2]); 256 | try { 257 | verifyAttestationParams(leafCert, attestationParams); 258 | } catch (e) { 259 | if (e.message === 'verifyAttestationParams failed: attestationSecurityLevel') status = e.message;else throw e; 260 | } 261 | verifyAttestationCertChain(leafCert, intermediateCert, rootCert, pemEncodedChain[k][1], pemEncodedChain[k][2]); 262 | respVer = verifyResponseSignature(response, signature, pemEncodedChain[k][0], null, hashAlg); 263 | } 264 | if (!respVer) { 265 | if (k == encodedChain.length - 1) throw new Error('verifyResponseSignature failed'); 266 | continue; 267 | } else { 268 | for (let j = 0; j < apkDigest.length; j++) { 269 | for (let i = 0; i < apkCertDigest.length; i++) { 270 | try { 271 | verifyPayload(jwsPayload, response, requestID, signature, apkDigest[j], apkCertDigest[i], version); 272 | } catch (err) { 273 | if (err.message !== 'verifyPayload failed: wrong signing certificate hash' && err.message !== 'verifyPayload failed: wrong apk hash') throw new Error('verifyPayload failed: apk hash or signing cert hash mismatch'); 274 | } 275 | } 276 | } 277 | if (!verifySignature(jws, googleCert)) throw new Error('verifySignature failed'); 278 | for (let i = 0; i < googleApiKey.length; i++) { 279 | try { 280 | let result = null; 281 | if (version === 'v1') result = yield verifyAuthenticity(jws, googleApiKey[i]);else if (version === 'v2') result = yield verifyAuthenticityV2(jws, googleApiKey[i]);else throw new Error('version unsupported'); 282 | if (result) { 283 | status = ''; 284 | break; 285 | } 286 | } catch (err) { 287 | if (i == googleApiKey.length - 1) throw new Error('verifyAuthenticity failed'); 288 | } 289 | } 290 | return ['success', status]; 291 | } 292 | } 293 | }); 294 | 295 | return function verify(_x5, _x6) { 296 | return _ref3.apply(this, arguments); 297 | }; 298 | })(); 299 | 300 | const verifyAndroid = exports.verifyAndroid = (() => { 301 | var _ref4 = _asyncToGenerator(function* (data, version) { 302 | let status = null; 303 | try { 304 | const cborEncodedData = data.slice(3); 305 | const buf = Buffer.from(cborEncodedData.buffer); 306 | const androidProof = cbor.decodeFirstSync(buf); 307 | const response = androidProof.HTTPResponse.toString(); 308 | const validErrors = ['verifyPayload failed: apk hash or signing cert hash mismatch', 'verifyAuthenticity failed', 'verifyPayload failed: wrong apk hash', 'verifyPayload failed: wrong signing certificate hash', 'verifyResponseSignature failed', 'verifyAttestationParams failed: keymasterVersion mismatch', 'verifyAttestationParams failed: keymasterSecurityLevel mismatch', 'verifyAttestationParams failed: attestationChallenge value mismatch', 'verifyAttestationParams failed: key purpose mismatch', 'verifyAttestationParams failed: key algorithm type mismatch', 'verifyAttestationParams failed: key digest mismatch', 'verifyAttestationParams failed: ecCurve mismatch', 'verifyAttestationParams failed: key was not generated on device']; 309 | try { 310 | status = yield verify(data, version); 311 | } catch (err) { 312 | if (R.contains(err.message, validErrors)) status = ['failed', err.message];else throw err; 313 | } 314 | const isVerified = status[0] === 'success' ? true : false; 315 | return { status: status, parsedData: response, isVerified }; 316 | } catch (e) { 317 | status = ['failed', 'generic error message']; 318 | return { status: status, parsedData: '', isVerified: false }; 319 | } 320 | }); 321 | 322 | return function verifyAndroid(_x7, _x8) { 323 | return _ref4.apply(this, arguments); 324 | }; 325 | })(); --------------------------------------------------------------------------------