coze
Version:
Coze - A cryptographic JSON messaging specification designed for human readability.
812 lines (715 loc) • 24.5 kB
JavaScript
"use strict";
// Unit tests are ran using the `browsertestjs` directory/package.
import * as Coze from './coze_all.min.js';
export {
TestBrowserJS, // Export "TestBrowserJS" is expected by `browsertestjs`
};
/**
@typedef {import('./browsertestjs/test.js').Test} Test
@typedef {import('./browsertestjs/test.js').Tests} Tests
@typedef {import('./browsertestjs/test.js').TestsToRun} TestsToRun
@typedef {import('./browsertestjs/test.js').TestGUIOptions} TestGUIOptions
@typedef {import('./browsertestjs/test.js').TestBrowserJS} TestBrowserJS
*/
/**@type {Test} */
let t_Sign = {
"name": "Sign",
"func": test_Sign,
"golden": true,
};
let t_SignPay = {
"name": "Sign Pay",
"func": test_SignPay,
"golden": true,
};
let t_Verify = {
"name": "VerifyCoze",
"func": test_Verify,
"golden": true,
};
let t_VerifyArray = {
"name": "VerifyCozeArray",
"func": test_VerifyArray,
"golden": true
};
let t_CryptoKeySign = {
"name": "CryptoKey",
"func": test_CryptoKeySign,
"golden": true
};
let t_Valid = {
"name": "Valid",
"func": test_Valid,
"golden": true,
};
let t_Correct = {
"name": "Correct",
"func": test_CozeKeyCorrect,
"golden": true
};
let t_Revoke = {
"name": "Revoke",
"func": test_Revoke,
"golden": true,
};
let t_Thumbprint = {
"name": "Thumbprint",
"func": test_Thumbprint,
"golden": true
};
let t_Param = {
"name": "Param",
"func": test_Param,
"golden": `
{"Name":"ES224","Genus":"ECDSA","Family":"EC","Use":"sig","Hash":"SHA-224","HashSize":28,"HashSizeB64":38,"XSize":56,"XSizeB64":75,"DSize":28,"DSizeB64":38,"Curve":"P-224","SigSize":56,"SigSizeB64":75}
{"Name":"ES256","Genus":"ECDSA","Family":"EC","Use":"sig","Hash":"SHA-256","HashSize":32,"HashSizeB64":43,"XSize":64,"XSizeB64":86,"DSize":32,"DSizeB64":43,"Curve":"P-256","SigSize":64,"SigSizeB64":86}
{"Name":"ES384","Genus":"ECDSA","Family":"EC","Use":"sig","Hash":"SHA-384","HashSize":48,"HashSizeB64":64,"XSize":96,"XSizeB64":128,"DSize":48,"DSizeB64":64,"Curve":"P-384","SigSize":96,"SigSizeB64":128}
{"Name":"ES512","Genus":"ECDSA","Family":"EC","Use":"sig","Hash":"SHA-512","HashSize":64,"HashSizeB64":86,"XSize":132,"XSizeB64":176,"DSize":66,"DSizeB64":88,"Curve":"P-521","SigSize":132,"SigSizeB64":176}
{"Name":"Ed25519","Genus":"EdDSA","Family":"EC","Use":"sig","Hash":"SHA-512","HashSize":64,"HashSizeB64":86,"XSize":32,"XSizeB64":43,"DSize":32,"DSizeB64":43,"Curve":"Curve25519","SigSize":64,"SigSizeB64":86}
{"Name":"Ed25519ph","Genus":"EdDSA","Family":"EC","Use":"sig","Hash":"SHA-512","HashSize":64,"HashSizeB64":86,"XSize":32,"XSizeB64":43,"DSize":32,"DSizeB64":43,"Curve":"Curve25519","SigSize":64,"SigSizeB64":86}
{"Name":"Ed448","Genus":"EdDSA","Family":"EC","Use":"sig","Hash":"SHAKE256","HashSize":64,"HashSizeB64":86,"XSize":57,"XSizeB64":76,"DSize":57,"DSizeB64":76,"Curve":"Curve448","SigSize":114,"SigSizeB64":152}
{"Name":"SHA-224","Genus":"SHA2","Family":"SHA","Use":"hsh","Hash":"SHA-224","HashSize":28,"HashSizeB64":38}
{"Name":"SHA-256","Genus":"SHA2","Family":"SHA","Use":"hsh","Hash":"SHA-256","HashSize":32,"HashSizeB64":43}
{"Name":"SHA-384","Genus":"SHA2","Family":"SHA","Use":"hsh","Hash":"SHA-384","HashSize":48,"HashSizeB64":64}
{"Name":"SHA-512","Genus":"SHA2","Family":"SHA","Use":"hsh","Hash":"SHA-512","HashSize":64,"HashSizeB64":86}
{"Name":"SHA3-224","Genus":"SHA3","Family":"SHA","Use":"hsh","Hash":"SHA3-224","HashSize":28,"HashSizeB64":38}
{"Name":"SHA3-256","Genus":"SHA3","Family":"SHA","Use":"hsh","Hash":"SHA3-256","HashSize":32,"HashSizeB64":43}
{"Name":"SHA3-384","Genus":"SHA3","Family":"SHA","Use":"hsh","Hash":"SHA3-384","HashSize":48,"HashSizeB64":64}
{"Name":"SHA3-512","Genus":"SHA3","Family":"SHA","Use":"hsh","Hash":"SHA3-512","HashSize":64,"HashSizeB64":86}
{"Name":"SHAKE128","Genus":"SHA3","Family":"SHA","Use":"hsh","Hash":"SHAKE128","HashSize":32,"HashSizeB64":43}
{"Name":"SHAKE256","Genus":"SHA3","Family":"SHA","Use":"hsh","Hash":"SHAKE256","HashSize":64,"HashSizeB64":86}
`
};
let t_Meta = {
"name": "Meta",
"func": test_Meta,
"golden": true
};
let t_CanonicalHash = {
"name": "CanonicalHash",
"func": test_CanonicalHashB64,
"golden": true
};
let t_Canon = {
"name": "Canon",
"func": test_Canon,
"golden": '{"Action":{"POST":"cyphr.me/api/v1/image"},"Hello World":"!","Image":"6gt3OmYBEDHODQ9SUc8q2momInw6GR9GT_AVN2DTZ5U","hello":"world!"}'
};
let t_CanonRepeat = {
"name": "Canon Repeat Fields",
"func": test_Canon_repeat,
"golden": '{"a":"a","b":"b","c":"c"}'
};
let t_Duplicate = {
"name": "Duplicate",
"func": test_Duplicate,
"golden": true
};
let t_LowS = {
"name": "LowS",
"func": test_LowS,
"golden": true
};
let t_B64Canonical = {
"name": "B64 Canonical",
"func": test_B64Canonical,
"golden": true
}
////////////////////
// Testing Variables
////////////////////
// x": "2nTOaFVm2QLxmUO_SjgyscVHBtvHEfo2rq65MvgNRjM"
// "y": "kaI6t_R2qva1zcb18cG2v149Beb2YmyUd4rAXTlm6OY"
let GoldenCozeKey = {
"alg": "ES256",
"iat": 1623132000,
"kid": "Zami's Majuscule Key.",
"d": "bNstg4_H3m3SlROufwRSEgibLrBuRq9114OvdapcpVA",
"tmb": "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
"x": "2nTOaFVm2QLxmUO_SjgyscVHBtvHEfo2rq65MvgNRjORojq39Haq9rXNxvXxwba_Xj0F5vZibJR3isBdOWbo5g"
}
let GoldenBadCozeKey = {
"alg": "ES256",
"iat": 1623132000,
"kid": "Zami's Majuscule Key.",
"d": "bNstg4_H3m3SlROufwRSEgibLrBuRq9114OvdapcpVE", // Ending A to E (one bit off) A-D are encoded as the same
"tmb": "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
"x": "2nTOaFVm2QLxmUO_SjgyscVHBtvHEfo2rq65MvgNRjORojq39Haq9rXNxvXxwba_Xj0F5vZibJR3isBdOWbo5g"
}
let GoldenCoze = {
"pay": {
"msg": "Coze Rocks",
"alg": "ES256",
"iat": 1623132000,
"tmb": "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
"typ": "cyphr.me/msg"
},
"sig": "Jl8Kt4nznAf0LGgO5yn_9HkGdY3ulvjg-NyRGzlmJzhncbTkFFn9jrwIwGoRAQYhjc88wmwFNH5u_rO56USo_w"
}
let GoldenCozeBad = {
"pay": {
"msg": "Coze Rocks",
"alg": "ES256",
"iat": 1623132000,
"tmb": "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
"typ": "cyphr.me/msg"
},
"sig": "Jl8Kt4nznAf0LGgO5yn_9HkGdY3ulvjg-NyRGzlmJzhncbTkFFn9jrwIwGoRAQYhjc88wmwFNH5u_rO56USo_g" // bad signature, last byte is off by one bit.
}
let Algs = ["ES256", "ES384", "ES512"];
////////////////////
// Tests
////////////////////
// test_SignCoze
// Tests each support alg.
// 1.) Coze.NewKey
// 2.) Coze.SignCoze
// 3.) Coze.VerifyCoze
async function test_Sign() {
for (const alg of Algs) {
let cozeKey = await Coze.NewKey(alg);
let coze = await Coze.Sign({
"pay": {
"msg": "Test Message",
"iat": 3,
}
},
cozeKey
);
if (true !== await Coze.Verify(coze, cozeKey)) {
return false
}
}
return true;
};
// test_SignPay
// Tests each support alg.
// 1.) Coze.NewKey
// 2.) Coze.Sign
// 3.) Coze.Verify
async function test_SignPay() {
for (const alg of Algs) {
let cozeKey = await Coze.NewKey(alg);
let pay = `{"msg":"Test Message"}`;
let sig = await Coze.SignPay(pay, cozeKey);
if ((await Coze.VerifyPay(pay, cozeKey, sig)) !== true) {
console.error("Failed on alg: " + alg)
return false
}
}
return true;
};
async function test_Verify() {
let v = await Coze.Verify(GoldenCoze, GoldenCozeKey)
if (v !== true) {
console.error(`Coze test: Failed on Verify: Coze: ${GoldenCoze}, Key: ${GoldenCozeKey}`)
return false
}
v = await Coze.Verify(GoldenCozeBad, GoldenCozeKey)
if (v !== false) {
return false
}
return true
}
// Tests VerifyCozeArray().
async function test_VerifyArray() {
let cozeKey = await Coze.NewKey(Coze.Algs.ES256);
let cozies = [await Coze.Sign({
"pay": {
"msg": "First",
"iat": 1,
}
},
cozeKey
),
await Coze.Sign({
"pay": {
"msg": "Second",
"iat": 2,
}
},
cozeKey
),
await Coze.Sign({
"pay": {
"msg": "Third",
"iat": 3,
}
},
cozeKey
),
];
let v = await Coze.VerifyCozeArray(cozies, cozeKey);
if (v.FailedCount !== 0 || v.FailedCozies.length > 0 || !v.VerifiedAll || v.VerifiedCount !== 3) {
return false;
}
return true;
}
// test_Thumbprint tests generating a thumbprint for a known `tmb`.
async function test_Thumbprint() {
let t = await Coze.Thumbprint(GoldenCozeKey);
if (t !== GoldenCozeKey.tmb) {
console.error("Thumbprint does not match: Calculated: " + t);
return false;
}
return true;
}
// Tests "Alg.Param".
async function test_Param() {
let algs = ["ES224", "ES256", "ES384", "ES512", "Ed25519", "Ed25519ph", "Ed448", "SHA-224", "SHA-256", "SHA-384", "SHA-512", "SHA3-224", "SHA3-256", "SHA3-384", "SHA3-512", "SHAKE128", "SHAKE256"];
let results = "";
for (let alg of algs) {
results += JSON.stringify(Coze.Params(alg)) + "\n";
}
return results;
};
// This test should closely resemble the Go implementation test
// `ExampleCoze_MetaWithAlg`
async function test_Meta() {
let meta = JSON.stringify(await Coze.Meta(GoldenCoze))
let goldenMeta = `{"alg":"SHA-256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg","can":["msg","alg","iat","tmb","typ"],"cad":"Ie3xL77AsiCcb4r0pbnZJqMcfSBqg5Lk0npNJyJ9BC4","sig":"Jl8Kt4nznAf0LGgO5yn_9HkGdY3ulvjg-NyRGzlmJzhncbTkFFn9jrwIwGoRAQYhjc88wmwFNH5u_rO56USo_w","czd":"TnRe4DRuGJlw280u3pGhMDOIYM7ii7J8_PhNuSScsIU"}`
if (meta != goldenMeta) {
throw new Error("meta and goldenMeta not equal")
}
// No coze.pay.alg but parameter alg given.
meta = JSON.stringify(await Coze.Meta(JSON.parse(`{
"pay": {
"msg": "Coze Rocks",
"iat": 1623132000,
"tmb": "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
"typ": "cyphr.me/msg"
},
"sig": "reOiKUO--OwgTNlYpKN60_gZARnW5X6PmQw4zWYbz2QryetRg_qS4KvwEVe1aiSAsWlkVA3MqYuaIM5ihY_8NQ"
}`), "ES256"))
goldenMeta = `{"alg":"SHA-256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg","can":["msg","iat","tmb","typ"],"cad":"K6MVyIFqhBhLvNafZ8sMCRpCqR1oeFpowi7j8P1uE0M","sig":"reOiKUO--OwgTNlYpKN60_gZARnW5X6PmQw4zWYbz2QryetRg_qS4KvwEVe1aiSAsWlkVA3MqYuaIM5ihY_8NQ","czd":"g6kRqHesiST6L38eZPcTk4Bq-fCxtbD6jTvRS8LKMv8"}`
if (meta != goldenMeta) {
throw new Error("meta and goldenMeta not equal")
}
// No coze.pay.alg but parameter alg given. No coze.sig so coze.sig must not be
// populated and coze.czd not calculated.
meta = JSON.stringify(await Coze.Meta(JSON.parse(`{
"pay": {
"msg": "Coze Rocks",
"iat": 1623132000,
"tmb": "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
"typ": "cyphr.me/msg"
}
}`), "ES256"))
goldenMeta = `{"alg":"SHA-256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg","can":["msg","iat","tmb","typ"],"cad":"K6MVyIFqhBhLvNafZ8sMCRpCqR1oeFpowi7j8P1uE0M"}`
if (meta != goldenMeta) {
throw new Error("meta and goldenMeta not equal")
}
// Meta with mismatched alg must fail
let errored = false
try {
meta = JSON.stringify(await Coze.Meta(GoldenCoze, "ES512"))
} catch (e) {
errored = true
}
if (errored == false) {
throw new Error("Coze.Meta must fail if coze.pay.alg is mismatched with alg. ")
}
// Meta with no alg must fail.
errored = false
try {
meta = await Coze.Meta(JSON.parse(`{
"pay": {
"msg": "Coze Rocks",
"iat": 1623132000,
"tmb": "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
"typ": "cyphr.me/msg"
},
"sig": "reOiKUO--OwgTNlYpKN60_gZARnW5X6PmQw4zWYbz2QryetRg_qS4KvwEVe1aiSAsWlkVA3MqYuaIM5ihY_8NQ"
}`))
} catch (e) {
errored = true
}
if (errored == false) {
throw new Error("Coze.Meta must fail if coze.pay.alg is unpopulated and alg is not provided.")
}
return true
}
// test_Canon tests CanonicalS(). Checks for UTF-8 order, removal of
// whitespace (outside of values), and has trailing commas.
//
// Javascript JSON normalization: Escapes special characters, object keys that
// are not strings are converted to strings, and other normalizations that are
// outside the scope of these tests.
async function test_Canon() {
// "Image" should go after "Hello".
// "hello" should go after "Image".
let object = {
"Action": {
"POST": "cyphr.me/api/v1/image"
},
"hello": "world!",
"Image": "6gt3OmYBEDHODQ9SUc8q2momInw6GR9GT_AVN2DTZ5U",
"Hello World": "!",
};
let canon = ["Action", "Hello World", "Image", "hello"];
return Coze.CanonicalS(object, canon);
};
// Tests the behavior of calling Canon with duplicate fields, as well as with
// non-duplicate fields.
async function test_Canon_repeat() {
let object = {
"c": "c",
"b": "b",
"b": "B",
"a": "a",
// Repeated fields
"c": "c",
"b": "b",
// Not in Canon fields
"A": "a",
};
try {
// Test for proper failure on Canonical with duplicate fields in Canon.
let badCanon = ["a", "b", "c", "c", "b", "a"];
await Coze.CanonicalS(object, badCanon);
} catch (e) {
if (e.message !== "Canonical: Canon cannot have duplicate fields.") {
throw new Error(e);
}
}
let goodCanon = ["a", "b", "c"];
return await Coze.CanonicalS(object, goodCanon);
};
// test_CanonicalHash tests CanonicalHashB64, for all currently supported
// hashing algorithms.
async function test_CanonicalHashB64() {
let canon = ["Action", "Image", "hello"];
let algs = ["SHA-256", "SHA-384", "SHA-512"];
let results = [];
let golden = ["BmJKvEbaefBhlK6g3XcGNQlrkBySYEbHsgswdWKQlnY", "5CiH8RJmXFDFOBPsyPbeoD2NzFJiwqXlwJPJ-BEbpZ0X_TnHqvBXG7FOkNyeDNxf", "BJ_rBAFi5WAxVMpqPhrpTgvCC6XkTwfrdSitYHGSUkiP-MHznu21LEjjJLlBNu1PpSNvUYH2TIMDCx4CCBPf7g"];
for (let alg of algs) {
let object = {
"alg": alg,
"hello": "world!",
"Image": "6gt3OmYBEDHODQ9SUc8q2momInw6GR9GT_AVN2DTZ5U",
};
results.push(await Coze.CanonicalHash64(object, alg, canon));
}
for (let gold in golden) {
if (golden[gold] != results[gold]) {
return false;
}
}
return true;
};
// test_Duplicate tests duplicate object names in `coze` and `pay`.
async function test_Duplicate() {
// In ES5, should fail since it's in strict mode. In ES6, it seems to be
// last-value-wins.
// https://github.com/json5/json5-spec/issues/38#issuecomment-1224158640
// https://262.ecma-international.org/5.1/#sec-C
// > It is a SyntaxError if strict mode code contains an ObjectLiteral with more
// > than one definition of any data property (11.1.5).
//
// Solution via minification:
// https://www.anycodings.com/1questions/3635977/js-check-json-for-duplicate-keys-prior-to-loading
// Prints if in strict mode.
var mode = (eval("var __temp = null"), (typeof __temp === "undefined")) ? "strict" : "non-strict";
if (mode !== "strict") {
return false;
};
let tc = {
"bob": "bob",
"bob": "bob2"
};
if (JSON.stringify(tc) !== `{"bob":"bob2"}`) {
return false;
}
// JSON parsing uses last-value-wins. Will not fail.
tc = JSON.parse(`{"bob":"bob","bob":"bob2"}`);
if (JSON.stringify(tc) !== `{"bob":"bob2"}`) {
return false;
}
return true;
}
// Tests Tests "Coze.Thumbprint" and "Coze.Valid"
async function test_Valid() {
if (!await Coze.Valid(GoldenCozeKey)) {
return false;
}
if (await Coze.Valid(GoldenBadCozeKey)) {
return false;
}
return true;
};
// test_Revoke test will test signing a message with a Coze Key, and validating
// the coze that is generated.
async function test_Revoke() {
let coze = await Coze.Revoke(GoldenCozeKey, "Test revoke.");
if (!(await Coze.Verify(coze, GoldenCozeKey)) || !Coze.IsRevoked(GoldenCozeKey)) {
return false;
}
return true;
}
// test_CozeKeyCorrect will test correctness for various keys with different
// algorithms when calling Correct().
async function test_CozeKeyCorrect() {
// Bad Key results vary from GO, as we have slightly weaker logic in Correct,
// since we do not have the API capabilities for recalculating `x`, and can
// not perform as many checks that require x to be present, as GO.
let goldenMap = [
[false, true, true, false, true, true], // Bad Key (second result is false in GO)
[true, true, true, true, true, true], // Good key
[true, true, true, true, true, true], // ES256
[true, true, true, true, true, true], // ES384
[true, true, true, true, true, true], // ES512
];
let keys = [GoldenBadCozeKey, GoldenCozeKey];
for (let alg of Algs) {
keys.push(await Coze.NewKey(alg));
}
// On failure, correct is throwing errors, so instead of having to wrap each
// call to Correct in a try, we can use this wrapper function.
let isCorrect = async (k) => {
try {
if (await Coze.Correct(k)) {
return true;
}
return false;
} catch (error) {
// console.error(error);
return false;
}
};
for (let key in keys) {
// Make a copy
let k = {
...keys[key]
};
var results = [];
// Key with with [alg,d,tmb,x]
results.push(await isCorrect(k));
// A key with [alg,tmb,d]
k.x = null;
results.push(await isCorrect(k));
// Key with [alg,d].
k.tmb = null;
results.push(await isCorrect(k));
// A key with [alg,x,d].
k.x = keys[key].x;
results.push(await isCorrect(k));
// A key with [alg,x,tmb]
k.d = null;
k.tmb = keys[key].tmb;
results.push(await isCorrect(k));
// Key with [alg,tmb]
k.x = null;
results.push(await isCorrect(k));
if (results.length !== 6) {
return false;
}
let golden = goldenMap[parseInt(key)];
for (let v in golden) {
if (results[v] !== golden[v]) {
console.error("Unexpected results: ", k, "Expected: ", golden, "Received: ", results);
return false;
}
}
}
return true;
}
/////////////////////////////////////
// CryptoKey Tests
/////////////////////////////////////
// test_CryptoKeySign contains tests for `cryptokey.js`.
// Tests
// 1.) Coze.NewKey
// 2.) CryptoKey.New (called from new coze key)
// 3.) CryptoKey.FromCozeKey
// 4.) CryptoKey.SignString
// 5.) CryptoKey.VerifyMsg
// 6.) CryptoKey.SignBuffer
// 7.) CryptoKey.VerifyArrayBuffer
// 8.) CryptoKey.GetSignHashAlgoFromCryptoKey (calls `algFromCrv`)
//
// `SignBuffer` cannot be tested for throwing an error, since we cannot
// create an invalid cryptokey. The test will fail at `FromCozeKey`.
async function test_CryptoKeySign() {
let msg = "Test Message";
let abMsg = await Coze.SToArrayBuffer(msg);
let results = [];
for (const alg of Algs) {
let cozeKey = await Coze.NewKey(alg);
let cryptoKey = await Coze.CryptoKey.FromCozeKey(cozeKey);
// Sign string
let sig = await Coze.CryptoKey.SignString(cryptoKey, msg);
let pcc = await Coze.CryptoKey.FromCozeKey(cozeKey, true);
let result = await Coze.CryptoKey.VerifyMsg(alg, pcc, msg, sig);
if (result !== true) {
return false
}
// Sign array buffer
sig = await Coze.CryptoKey.SignBuffer(cryptoKey, abMsg);
result = await Coze.CryptoKey.VerifyArrayBuffer(alg, pcc, abMsg, sig);
if (result !== true) {
return false
}
results.push(await Coze.CryptoKey.GetSignHashAlgoFromCryptoKey(cryptoKey));
}
if (JSON.stringify(results) !== JSON.stringify([Coze.Algs.SHA256, Coze.Algs.SHA384, Coze.Algs.SHA512])) {
return false;
}
// Importing an invalid key from `subtle` will throw a DOMException error:
// `DOMException: The imported EC key is invalid`
let e = null;
try {
await Coze.CryptoKey.FromCozeKey(GoldenBadCozeKey);
} catch (error) {
e = error;
}
if (e === null) {
return false;
}
return true;
};
async function test_LowS() {
// All cozies should be low-S
for (const alg of Algs) {
let cozeKey = await Coze.NewKey(alg);
let pay = `{"msg":"Test Message"}`;
let sig = await Coze.SignPay(pay, cozeKey);
if ((await Coze.VerifyPay(pay, cozeKey, sig)) !== true) {
console.error("Failed on alg: " + alg)
return false
}
}
// Make sure high-S cozies will not verify.
let highSCozies = [
'{"pay":{},"sig":"9iesKUSV7L1-xz5yd3A94vCkKLmdOAnrcPXTU3_qeKSuk4RMG7Qz0KyubpATy0XA_fXrcdaxJTvXg6saaQQcVQ"}',
'{"pay":{"msg":"Coze Rocks","alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"sig":"mVw8N6ZncWcObVGvnwUMRIC6m2fbX3Sr1LlHMbj_tZ3ji1rNL-00pVaB12_fmlK3d_BVDipNQUsaRyIlGJudtg"}',
'{"pay":{"msg":"Coze Rocks","alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"sig":"cn6KNl4VQlk5MzmhYFVyyJoTOU57O5Bq-8r-yXXR6Ojfs0-6LFGd8j1Y6wiJAQrGpWj_RptsiEg49v95FsVWMQ"}',
'{"pay":{"msg":"Coze Rocks","alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"sig":"9KvWfOSIZUjW8Ie0jbdVdu9UlIP4TT4MXz3YyNW3fCTWXHnO1MPROwcXvfNZN_icOvMAK3vfsr2w-CeBozS81w"}',
]
for (let c of highSCozies) {
let coze = JSON.parse(c);
let v = await Coze.Verify(coze, GoldenCozeKey);
if (v) {
return ("High-S Should not be valid. ");
}
coze.sig = await Coze.SigToLowS("ES256", coze.sig);
v = await Coze.Verify(coze, GoldenCozeKey);
if (!v) {
return ("High-S to low-S should be valid. ");
}
}
return true;
}
// Demonstrates Javascript's behavior for non-canonical base 64 encoding.
// Enforcing canonical only stop malleability. See
// https://github.com/Cyphrme/Coze/issues/18. The last three characters of
// example `tmb` is `hOk`, but `hOl` also decodes to the same byte value (in
// Hex, `84E9`) even though they are different UTF-8 values. Tool for decoding
// [hOk](https://convert.zamicol.com/#?inAlph=base64&in=hOk&outAlph=Hex) and
// [hOl](https://convert.zamicol.com/#?inAlph=base64&in=hOl&outAlph=Hex).
//
// As an added concern, Go's base64 ignores new line and carriage return.
// Thankfully, JSON unmarshal does not, making Coze's interpretation of base 64
// non-malleable since Coze is JSON.
async function test_B64Canonical() {
let ab1 = Coze.B64uToArrayBuffer("hOk") // correct
let failed = false
try {
let ab2 = Coze.B64uToArrayBuffer("hOl") // non-canonical
} catch (e) {
failed = true;
}
if (failed != true) {
return false
}
let nonCanonicalCozeSig = {
"pay": {
"msg": "Coze Rocks",
"alg": "ES256",
"iat": 1623132000,
"tmb": "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
"typ": "cyphr.me/msg"
},
"sig": "Jl8Kt4nznAf0LGgO5yn_9HkGdY3ulvjg-NyRGzlmJzhncbTkFFn9jrwIwGoRAQYhjc88wmwFNH5u_rO56USo_x" // Non canonical sig (last "x" should be a "w")
}
failed = false
try {
failed = await Coze.Verify(nonCanonicalCozeSig, GoldenCozeKey)
} catch (e) {
failed = true
}
if (failed != true) {
return false
}
let nonCanonicalCozeTmb = {
"pay": {
"msg": "Coze Rocks",
"alg": "ES256",
"iat": 1623132000,
"tmb": "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOl", // Non canonical tmb (last "l" should be a "k")
"typ": "cyphr.me/msg"
},
"sig": "Jl8Kt4nznAf0LGgO5yn_9HkGdY3ulvjg-NyRGzlmJzhncbTkFFn9jrwIwGoRAQYhjc88wmwFNH5u_rO56USo_w"
}
failed = false
try {
failed = await Coze.Verify(nonCanonicalCozeTmb, GoldenCozeKey)
} catch (e) {
failed = true
}
if (failed != true) {
return false
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/////////////////////// Interface to browsertestjs package ///////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/**
TestsToRun must be declared at the bottom of the file, as the variables
cannot be accessed before initialization.
@type {TestsToRun}
**/
let TestsToRun = [
t_Verify,
t_VerifyArray,
t_Sign,
t_SignPay,
t_CryptoKeySign,
t_Valid,
t_Correct,
t_Revoke,
t_Thumbprint,
t_Param,
t_Meta,
t_Canon,
t_CanonRepeat,
t_CanonicalHash,
t_Duplicate,
t_LowS,
t_B64Canonical,
];
/** @type {TestGUIOptions} **/
let TestGUIOptions = {
footer: `<div class="mt-4">
<a href="/"><img src="../coze_logo_zami_white_450x273.png" alt="Browser Test JS"></a>
<p><a class="account_keys text-center" href="https://github.com/cyphrme/coze"> Coze Github</a></p>
<p><a class="account_keys text-center" href="https://github.com/cyphrme/cozejs"> Coze js Github</a></p>
<p><a href="https://cyphr.me/coze">Cyphr.me Coze Verifier</a></p>
<div class="level-item has-text-centered text-muted footer_logo mt-5">
<a href="https://cyphr.me">Sponsored by: <img src="../cyphrme_long_500x135.png"></a>
<p class="mt-3">Coze is released under The 3-Clause BSD License. <br>
"Cyphr.me" is a trademark of Cypherpunk, LLC. The Cyphr.me logo is all rights reserved Cypherpunk, LLC and may not be used without permission.
</p>
</div>`,
stylesheet: {
href: "../cyphrme_bootstrap.min.css"
},
main_image: "../coze_logo_zami_white_450x273.png",
};
/** @type {TestBrowserJS} **/
let TestBrowserJS = {
TestsToRun,
TestGUIOptions
};