Skip to content

Commit

Permalink
Merge pull request #64 from vaxxnz/feature/remove_cbor_package
Browse files Browse the repository at this point in the history
Feature/remove cbor package
  • Loading branch information
noway authored Mar 7, 2022
2 parents 5d7154f + ec3f9b1 commit 87e626e
Show file tree
Hide file tree
Showing 15 changed files with 266 additions and 894 deletions.
6 changes: 0 additions & 6 deletions build.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { build } from "esbuild";
import { nodeBuiltIns } from "esbuild-node-builtins";
async function main() {
const resultBrowser = await build({
plugins: [nodeBuiltIns()],
entryPoints: ["src/browser.ts"],
bundle: true,
outfile: "dist/esbuild/browser.js",
Expand All @@ -11,10 +9,6 @@ async function main() {
platform: "browser",
format: "cjs",
target: "es6",
define: {
"process.env.NODE_DEBUG": "false",
"global": "window"
},
});
console.log("resultBrowser", resultBrowser);

Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module.exports = {
testEnvironment: "node",
roots: ['<rootDir>/src'],
testMatch: [
"**/__tests__/**/*.+(ts|tsx|js)",
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,11 @@
"@types/node-fetch": "^2.5.6",
"@typescript-eslint/eslint-plugin": "^4.19.0",
"@typescript-eslint/parser": "^4.19.0",
"cbor": "^8.1.0",
"base64-arraybuffer": "^1.0.2",
"did-resolver": "^3.1.3",
"dotenv": "^10.0.0",
"elliptic": "^6.5.4",
"esbuild": "^0.11.11",
"esbuild-node-builtins": "^0.1.0",
"eslint": "^7.22.0",
"jest": "^26.6.3",
"js-sha256": "^0.9.0",
Expand Down
17 changes: 2 additions & 15 deletions src/cbor.test.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
import { Buffer } from "buffer";
import { base32 } from "rfc4648";
import { decodeCBOR, encodeOneCBOR } from "./cbor";
import { decodeCOSE } from "./cbor";

test("CBOR library decodes", async () => {
const res = base32.parse(
"2KCEVIQEIVVWK6JNGEASNICZAEP2KALYDZSGSZB2O5SWEOTOPJRXALTDN53GSZBRHEXGQZLBNR2GQLTOPICRUYMBTIFAIGTUKBAAUYTWMOSGQQDDN5XHIZLYOSBHQJTIOR2HA4Z2F4XXO53XFZ3TGLTPOJTS6MRQGE4C6Y3SMVSGK3TUNFQWY4ZPOYYXQKTIOR2HA4Z2F4XW46TDOAXGG33WNFSDCOJONBSWC3DUNAXG46RPMNXW45DFPB2HGL3WGFTXMZLSONUW63TFGEXDALRQMR2HS4DFQJ2FMZLSNFTGSYLCNRSUG4TFMRSW45DJMFWG6UDVMJWGSY2DN53GSZCQMFZXG4LDOJSWIZLOORUWC3CTOVRGUZLDOSRWSZ3JOZSW4TTBNVSWISTBMNVWUZTBNVUWY6KOMFWWKZ2TOBQXE4TPO5RWI33CNIYTSNRQFUYDILJRGYDVAYFE6VGU4MCDGK7DHLLYWHVPUS2YIDJOA6Y524TD3AZRM263WTY2BE4DPKIF27WKF3UDNNVSVWRDYIYVJ65IRJJJ6Z25M2DO4YZLBHWFQGVQR5ZLIWEQJOZTS3IQ7JTNCFDX"
);

const cborobj = decodeCBOR(res);
const cborobj = decodeCOSE(res);

expect(cborobj.err).toEqual(undefined);
expect(cborobj.tag).toEqual(18);
expect(cborobj.value.length).toEqual(4);
});

test("CBOR library encodes", async () => {
const res = Buffer.from(base32.parse(
"2KCEVIQEIVVWK6JNGEASNICZAEP2KALYDZSGSZB2O5SWEOTOPJRXALTDN53GSZBRHEXGQZLBNR2GQLTOPICRUYMBTIFAIGTUKBAAUYTWMOSGQQDDN5XHIZLYOSBHQJTIOR2HA4Z2F4XXO53XFZ3TGLTPOJTS6MRQGE4C6Y3SMVSGK3TUNFQWY4ZPOYYXQKTIOR2HA4Z2F4XW46TDOAXGG33WNFSDCOJONBSWC3DUNAXG46RPMNXW45DFPB2HGL3WGFTXMZLSONUW63TFGEXDALRQMR2HS4DFQJ2FMZLSNFTGSYLCNRSUG4TFMRSW45DJMFWG6UDVMJWGSY2DN53GSZCQMFZXG4LDOJSWIZLOORUWC3CTOVRGUZLDOSRWSZ3JOZSW4TTBNVSWISTBMNVWUZTBNVUWY6KOMFWWKZ2TOBQXE4TPO5RWI33CNIYTSNRQFUYDILJRGYDVAYFE6VGU4MCDGK7DHLLYWHVPUS2YIDJOA6Y524TD3AZRM263WTY2BE4DPKIF27WKF3UDNNVSVWRDYIYVJ65IRJJJ6Z25M2DO4YZLBHWFQGVQR5ZLIWEQJOZTS3IQ7JTNCFDX"
));

const cborobj = decodeCBOR(res);
const result = encodeOneCBOR(cborobj);

expect(res.toString("hex")).toEqual(result.toString("hex"));
});

216 changes: 203 additions & 13 deletions src/cbor.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,212 @@
// centralized place where cbor is included, in case we need to patch it
import { Data } from "./cborTypes";
import { DecodedCOSEStructure } from "./coseTypes";

import { Buffer } from "buffer";
// author: putara
// https://github.com/putara/nzcp/blob/master/verifier.js
class Stream {
data: Uint8Array;
ptr: number;
len: number;

import process from "process";
global.process = process;
constructor(data: Uint8Array) {
this.data = data;
this.ptr = 0;
this.len = data.length;
}
getc() {
if (this.ptr >= this.len) {
throw new Error("invalid data");
}
return this.data[this.ptr++];
}
ungetc() {
if (this.ptr <= 0) {
throw new Error("invalid data");
}
--this.ptr;
}
chop(len: number) {
if (len < 0) {
throw new Error("invalid length");
}
if (this.ptr + len > this.len) {
throw new Error("invalid data");
}
const out = this.data.subarray(this.ptr, this.ptr + len);
this.ptr += len;
return out;
}
}

import util from "util";
// @ts-ignore
global.TextDecoder = util.TextDecoder;
// @ts-ignore
global.TextEncoder = util.TextEncoder;
// RFC 7049
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function decodeCBORStream(stream: Stream) {
function decodeUint(stream: Stream, v: number) {
let x = v & 31;
if (x <= 23) {
// small
} else if (x === 24) {
// 8-bit
x = stream.getc();
} else if (x === 25) {
// 16-bit
x = stream.getc() << 8;
x |= stream.getc();
} else if (x === 26) {
// 32-bit
x = stream.getc() << 24;
x |= stream.getc() << 16;
x |= stream.getc() << 8;
x |= stream.getc();
} else if (x === 27) {
// 64-bit
x = stream.getc() << 56;
x |= stream.getc() << 48;
x |= stream.getc() << 40;
x |= stream.getc() << 32;
x |= stream.getc() << 24;
x |= stream.getc() << 16;
x |= stream.getc() << 8;
x |= stream.getc();
} else {
throw new Error("invalid data");
}
return x;
}
function decode(stream: Stream, isKeyString?: boolean): Data {
const v = stream.getc();
const type = v >> 5;
if (type === 0) {
// positive int
return decodeUint(stream, v);
} else if (type === 1) {
// negative int
return ~decodeUint(stream, v);
} else if (type === 2) {
// byte array
return stream.chop(decodeUint(stream, v));
} else if (type === 3) {
// utf-8 string
return new TextDecoder("utf-8").decode(
stream.chop(decodeUint(stream, v))
);
} else if (type === 4) {
// array
const d = new Array(decodeUint(stream, v))
.fill(undefined)
.map(() => decode(stream));
return d;
} else if (type === 5) {
// object
const dMap: Map<Data, Data> = new Map();
const dObj: { [key: string]: Data } = {};
const len = decodeUint(stream, v);
for (let i = 0; i < len; ++i) {
const key = decode(stream);
const value = decode(stream, typeof key === "string");
dMap.set(key, value);
dObj[`${key}`] = value;
}
return isKeyString ? dObj : dMap;
}
return null
}
return decode(stream);
}

import cbor from "cbor";
const encodeBytes = (data: Uint8Array | never[]) => {
const x = data.length;
if (x === 0) {
return [0x40];
} else if (x <= 23) {
// small
return [0x40 + x, ...data];
} else if (x < 256) {
// 8-bit
return [0x40 + 24, x, ...data];
} else if (x < 65536) {
// 16-bit
return [0x40 + 25, x >> 8, x & 0xff, ...data];
} // leave 32-bit and 64-bit unimplemented
throw new Error("Too big data");
};

export function encodeToBeSigned(bodyProtected: Uint8Array, payload: Uint8Array): Uint8Array {
const sig_structure = new Uint8Array([
// array w/ 4 items
0x84,
// #1: context: "Signature1"
0x6a,
0x53,
0x69,
0x67,
0x6e,
0x61,
0x74,
0x75,
0x72,
0x65,
0x31,
// #2: body_protected: CWT headers
...encodeBytes(bodyProtected),
// #3: external_aad: empty
...encodeBytes([]),
// #4: payload: CWT claims
...encodeBytes(payload),
]);
const ToBeSigned = sig_structure;
return ToBeSigned;
}

function decodeCOSEStream(stream: Stream) {
const vtag = stream.getc();
const tag = vtag & 31;

try {
if (vtag !== 0xD2) {
throw new Error('invalid data');
}
const data = decodeCBORStream(stream);
if (!(data instanceof Array)) {
throw new Error('invalid data');
}

const data1 = data[1];
if (!(data1 instanceof Map)) {
throw new Error('invalid data');
}

if (!(data instanceof Array) || data.length !== 4 || !(data[0] instanceof Uint8Array) || typeof data1 !== 'object' || Object.keys(data1).length !== 0 || !(data[2] instanceof Uint8Array) || !(data[3] instanceof Uint8Array)) {
throw new Error('invalid data');
}

return {
tag,
value: [
data[0],
data[1],
data[2],
data[3],
],
err: undefined,
};
}
catch (err) {
return {
tag,
value: [],
err,
}
}
}

export const encodeOneCBOR = (obj: any): Buffer => {
return cbor.encodeOne(obj, { genTypes: [Buffer, cbor.Encoder._pushBuffer] });
export const decodeCBOR = (buf: Uint8Array): Data => {
const data = decodeCBORStream(new Stream(buf))
return data
};

export const decodeCBOR = (buf: Buffer | Uint8Array): any => {
return cbor.decode(buf);
export const decodeCOSE = (buf: Uint8Array): DecodedCOSEStructure => {
const data = decodeCOSEStream(new Stream(buf))
return data
};
1 change: 1 addition & 0 deletions src/cborTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type Data = string | number | Uint8Array | Data[] | Map<Data, Data> | { [key: string]: Data } | null;
20 changes: 15 additions & 5 deletions src/coseTypes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
type DecodedCOSEValue = (Buffer | Record<string, never>)[];
import { Data } from "./cborTypes";

export interface DecodedCOSEStructure {
tag: number;
value: DecodedCOSEValue;
err?: Error;
type DecodedCOSEValue = Data[];

interface DecodedCOSEStructureSuccess {
tag: number
value: DecodedCOSEValue
err: Error
}

interface DecodedCOSEStructureError {
tag: number
value: DecodedCOSEValue
err: undefined
}

export type DecodedCOSEStructure = DecodedCOSEStructureSuccess | DecodedCOSEStructureError
26 changes: 9 additions & 17 deletions src/crypto.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { sha256 } from "js-sha256";
import elliptic from "elliptic";
import { DecodedCOSEStructure } from "./coseTypes";
import { encodeOneCBOR } from "./cbor";
import { Buffer } from "buffer";
import { encodeToBeSigned } from "./cbor";
import { decode } from 'base64-arraybuffer'
import { toHex } from "./util";

const EC = elliptic.ec;
const ec = new EC("p256");
Expand All @@ -19,11 +20,11 @@ export function validateCOSESignature(
return false;
}

const xBuf = Buffer.from(publicKeyJwt.x, "base64");
const yBuf = Buffer.from(publicKeyJwt.y, "base64");
const xBuf = new Uint8Array(decode(publicKeyJwt.x.replace(/-/g, '+').replace(/_/g, '/')))
const yBuf = new Uint8Array(decode(publicKeyJwt.y.replace(/-/g, '+').replace(/_/g, '/')))

// 1) '04' + hex string of x + hex string of y
const publicKeyHex = `04${xBuf.toString("hex")}${yBuf.toString("hex")}`;
const publicKeyHex = `04${toHex(xBuf)}${toHex(yBuf)}`;
const key = ec.keyFromPublic(publicKeyHex, "hex");
// Sig_structure = [
// context : "Signature" / "Signature1" / "CounterSignature",
Expand All @@ -32,21 +33,12 @@ export function validateCOSESignature(
// external_aad : bstr,
// payload : bstr
// ]
const bufferProtected_ = Buffer.from(protected_ as Buffer);
const buffer0 = Buffer.alloc(0);
const bufferPayload_ = Buffer.from(payload_ as Buffer);
const SigStructure = [
"Signature1",
bufferProtected_,
buffer0,
bufferPayload_,
];

const ToBeSigned = encodeOneCBOR(SigStructure);
const ToBeSigned = encodeToBeSigned(protected_ as Uint8Array, payload_ as Uint8Array);
const messageHash = sha256.digest(ToBeSigned);
const signature = {
r: signature_.slice(0, signature_.length / 2),
s: signature_.slice(signature_.length / 2),
r: (signature_ as Uint8Array).slice(0, (signature_ as Uint8Array).length / 2),
s: (signature_ as Uint8Array).slice((signature_ as Uint8Array).length / 2),
};
const result = key.verify(messageHash, signature);
return result;
Expand Down
4 changes: 2 additions & 2 deletions src/cwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function parseCWTClaims(
// Section 2.1.1.2
// CWT Token ID claim MUST be a valid UUID in the form of a URI as specified by [RFC4122]
try {
const jtiResult = decodeCtiToJti(ctiClaimRaw as Buffer);
const jtiResult = decodeCtiToJti(ctiClaimRaw as Uint8Array);
jti = jtiResult;
} catch (error) {
// continue parsing, but don't set jti
Expand Down Expand Up @@ -275,7 +275,7 @@ export function parseCWTHeaders(
const CWTHeaderAlg = rawCWTHeaders.get(1);
// Section 2.2.1
// `kid` value MUST be encoded as a Major Type 3
const kid = CWTHeaderKid ? CWTHeaderKid.toString() : undefined;
const kid = CWTHeaderKid ? new TextDecoder("utf-8").decode(CWTHeaderKid as Uint8Array) : undefined;
// Section 2.2.2
// `alg` claim value MUST be set to the value corresponding to ES256 algorithm registration, which is the numeric value of -7
const alg = CWTHeaderAlg === -7 ? "ES256" : undefined;
Expand Down
Loading

0 comments on commit 87e626e

Please sign in to comment.