Split out crypto to get it to run in the browser as well as node.js

This commit is contained in:
Tony Garnock-Jones 2021-03-04 21:01:28 +01:00
parent 50a563210c
commit e7ebf8e821
4 changed files with 65 additions and 35 deletions

4
.gitignore vendored
View File

@ -1,5 +1,3 @@
*.d.ts
*.js
*.js.map
package-lock.json
node_modules/
lib/

13
rollup.config.js Normal file
View File

@ -0,0 +1,13 @@
export default {
input: 'lib/sturdy.js',
output: {
file: 'index.js',
format: 'umd',
name: 'Main',
globals: {
'preserves': 'Preserves',
'crypto': 'crypto', // this is a lie, but it lets the detection code in cryptography.ts run
},
},
external: ['preserves', 'crypto'],
};

39
src/cryptography.ts Normal file
View File

@ -0,0 +1,39 @@
import { Bytes, underlying } from 'preserves';
import * as node_crypto from 'crypto';
export const KEY_LENGTH = 16; // 128 bits
export const newKey: () => Promise<Bytes> =
(typeof crypto !== 'undefined' && 'getRandomValues' in crypto)
? (async () => {
const bs = new Bytes(KEY_LENGTH);
crypto.getRandomValues(underlying(bs));
return bs;
})
: (async () => Bytes.from(node_crypto.randomBytes(KEY_LENGTH)));
export const mac: (secretKey: Bytes, data: Bytes) => Promise<Bytes> =
(typeof crypto !== 'undefined' && 'subtle' in crypto)
? (async (secretKey, data) => {
if (secretKey.length !== KEY_LENGTH) throw new Error("Invalid key length");
const k = await window.crypto.subtle.importKey(
"raw",
underlying(secretKey),
{
hash: 'SHA-256',
name: 'HMAC',
},
false,
['sign']);
const bs = await window.crypto.subtle.sign({ name: 'HMAC' }, k, underlying(data));
return Bytes.from(new Uint8Array(bs, 0, KEY_LENGTH));
})
: (typeof node_crypto.createHmac !== 'undefined')
? (async (secretKey, data) => {
const hmac = node_crypto.createHmac('sha256', underlying(secretKey));
hmac.update(underlying(data));
return Bytes.from(hmac.digest().subarray(0, KEY_LENGTH));
})
: (async (_secretKey, _data) => {
throw new Error('No HMAC SHA-256 available');
});

View File

@ -6,7 +6,8 @@
// In Network and Distributed System Security Symposium. San Diego,
// California: Internet Society, 2014.
import { Bytes, Dictionary, encode, is, Record, underlying, Value } from 'preserves';
import { mac, newKey } from './cryptography.js';
import { Bytes, Dictionary, encode, is, Record, Value } from 'preserves';
import type { Pattern, Template } from './rewrite.js';
import * as RW from './rewrite.js';
@ -41,12 +42,6 @@ export type Rewrite = ReturnType<typeof Rewrite>;
export const KEY_LENGTH = 16; // 128 bits
export async function newKey(): Promise<Bytes> {
const bs = new Bytes(KEY_LENGTH);
window.crypto.getRandomValues(underlying(bs));
return bs;
}
export function sturdyEncode(v: SturdyValue): Bytes {
return encode<EmbeddedRef>(v, {
canonical: true,
@ -55,37 +50,22 @@ export function sturdyEncode(v: SturdyValue): Bytes {
});
}
export async function mac(secretKey: Bytes, term: SturdyValue): Promise<Bytes> {
if (secretKey.length !== KEY_LENGTH) throw new Error("Invalid key length");
const k = await window.crypto.subtle.importKey(
"raw",
underlying(secretKey),
{
hash: 'SHA-256',
name: 'HMAC',
},
false,
['sign']);
const bs = await window.crypto.subtle.sign({ name: 'HMAC' }, k, underlying(sturdyEncode(term)));
return Bytes.from(new Uint8Array(bs, 0, KEY_LENGTH));
}
export async function mint(oid: SturdyValue, secretKey: Bytes): Promise<SturdyRef> {
return SturdyRef(oid, [], await mac(secretKey, oid));
return SturdyRef(oid, [], await mac(secretKey, sturdyEncode(oid)));
}
export async function attenuate(r: SturdyRef, ... a: Attenuation): Promise<SturdyRef> {
return SturdyRef(
SturdyRef._.oid(r),
[... SturdyRef._.attenuations(r), a],
await mac(SturdyRef._.sig(r), a)
await mac(SturdyRef._.sig(r), sturdyEncode(a))
);
}
export async function validate(r: SturdyRef, secretKey: Bytes): Promise<boolean> {
const sig = await SturdyRef._.attenuations(r).reduce(
async (sig, a) => mac(await sig, a),
mac(secretKey, SturdyRef._.oid(r)));
async (sig, a) => mac(await sig, sturdyEncode(a)),
mac(secretKey, sturdyEncode(SturdyRef._.oid(r))));
return is(sig, SturdyRef._.sig(r));
}
@ -98,16 +78,16 @@ async function main() {
[0, RW.Lit('Tony')]]))),
RW.TRef('a')));
console.log(m2.asPreservesText());
console.log(await validate(m1, new Bytes(KEY_LENGTH)));
console.log(await validate(m2, new Bytes(KEY_LENGTH)));
console.log(await validate(m2, Bytes.of(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)));
console.log('should be true:', await validate(m1, new Bytes(KEY_LENGTH)));
console.log('should be true:', await validate(m2, new Bytes(KEY_LENGTH)));
console.log('should be false:', await validate(m2, Bytes.of(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)));
m2[0] = 'hello world2';
console.log(m2.asPreservesText());
console.log(await validate(m2, new Bytes(KEY_LENGTH)));
console.log('should be false:', await validate(m2, new Bytes(KEY_LENGTH)));
m2[0] = 'hello world';
console.log(m2.asPreservesText());
console.log(await validate(m2, new Bytes(KEY_LENGTH)));
console.log(await validate(m2, await newKey()));
console.log('should be true:', await validate(m2, new Bytes(KEY_LENGTH)));
console.log('should be false:', await validate(m2, await newKey()));
console.log((await newKey()).asPreservesText());
console.log((await newKey()).asPreservesText());
console.log(sturdyEncode(m2).asPreservesText());