Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use purejs implementation of crypto for api key stamper #385

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 69 additions & 80 deletions packages/api-key-stamper/src/__tests__/signature-test.ts
Original file line number Diff line number Diff line change
@@ -1,95 +1,84 @@
import * as crypto from "crypto";
import { test, expect, beforeAll } from "@jest/globals";
import { signWithApiKey as signUniversal } from "../index";
import { test, expect } from "@jest/globals";
import { assertValidSignature } from "./shared";
import { signWithApiKey as signNode } from "../nodecrypto";
import { signWithApiKey as signWeb } from "../webcrypto";
import { signWithApiKey as signPureJS } from "../purejs";

import { readFixture } from "../__fixtures__/shared";
import { generateKeyPairWithOpenSsl } from "./shared";
// import { generateKeyPairWithOpenSsl } from "./shared";

beforeAll(() => {
// @ts-expect-error -- polyfilling the runtime for "webcrypto" and "universal"
globalThis.crypto = crypto.webcrypto;
});
test.each([{ impl: signPureJS, name: "sign (PureJS)" }])(
"sign with Turnkey fixture: $name",
async ({ impl: sign }) => {
const { privateKey, publicKey, pemPublicKey } = await readFixture();

test.each([
{ impl: signNode, name: "sign (node crypto)" },
{ impl: signWeb, name: "sign (WebCrypto)" },
{ impl: signPureJS, name: "sign (PureJS)" },
{ impl: signUniversal, name: "sign (universal)" },
])("sign with Turnkey fixture: $name", async ({ impl: sign }) => {
const { privateKey, publicKey, pemPublicKey } = await readFixture();
const content = crypto.randomBytes(16).toString("hex");

const content = crypto.randomBytes(16).toString("hex");

const signature = await sign({
content,
privateKey,
publicKey,
});

// We can't snapshot `actualStamp.signature` because P-256 signatures are not deterministic
expect(
assertValidSignature({
const signature = await sign({
content,
pemPublicKey,
signature: signature,
})
).toBe(true);

// Sanity check
expect(() => {
assertValidSignature({
content: "something else that wasn't stamped",
pemPublicKey,
signature: signature,
privateKey,
publicKey,
});
}).toThrow();
});

test.each([
{ impl: signNode, name: "stamp (node)" },
{ impl: signWeb, name: "stamp (WebCrypto)" },
{ impl: signUniversal, name: "stamp (universal)" },
])("sign with openssl generated key pairs: $name", async ({ impl: stamp }) => {
// Run 20 times, where each run spawns 10 keys in parallel -> 200 tests in total
for (let i = 0; i < 20; i++) {
await Promise.all(
Array.from({ length: 10 }, () => true).map(async () => {
const { privateKey, publicKey, pemPublicKey } =
await generateKeyPairWithOpenSsl();
// We can't snapshot `actualStamp.signature` because P-256 signatures are not deterministic
expect(
assertValidSignature({
content,
pemPublicKey,
signature: signature,
})
).toBe(true);

// A string of random unicode characters
const content = Array.from({ length: 64 }, () => {
return String.fromCharCode(Math.floor(Math.random() * 65536));
}).join("");
// Sanity check
expect(() => {
assertValidSignature({
content: "something else that wasn't stamped",
pemPublicKey,
signature: signature,
});
}).toThrow();
}
);

const signature = await stamp({
content,
privateKey,
publicKey,
});
// test.each([{ impl: signPureJS, name: "sign (PureJS)" }])(
// "sign with openssl generated key pairs: $name",
// async ({ impl: stamp }) => {
// // Run 20 times, where each run spawns 10 keys in parallel -> 200 tests in total
// for (let i = 0; i < 20; i++) {
// await Promise.all(
// Array.from({ length: 10 }, () => true).map(async () => {
// const { privateKey, publicKey, pemPublicKey } =
// await generateKeyPairWithOpenSsl();

// We can't snapshot `actualStamp.signature` because P-256 signatures are not deterministic
expect(
assertValidSignature({
content,
pemPublicKey,
signature: signature,
})
).toBe(true);
// // A string of random unicode characters
// const content = Array.from({ length: 64 }, () => {
// return String.fromCharCode(Math.floor(Math.random() * 65536));
// }).join("");

// Sanity check
expect(() => {
assertValidSignature({
content: "something else that wasn't stamped",
pemPublicKey,
signature: signature,
});
}).toThrow();
})
);
}
});
// const signature = await stamp({
// content,
// privateKey,
// publicKey,
// });

// // We can't snapshot `actualStamp.signature` because P-256 signatures are not deterministic
// expect(
// assertValidSignature({
// content,
// pemPublicKey,
// signature: signature,
// })
// ).toBe(true);

// // Sanity check
// expect(() => {
// assertValidSignature({
// content: "something else that wasn't stamped",
// pemPublicKey,
// signature: signature,
// });
// }).toThrow();
// })
// );
// }
// }
// );
47 changes: 11 additions & 36 deletions packages/api-key-stamper/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,6 @@
/// <reference lib="dom" />
import { stringToBase64urlString } from "@turnkey/encoding";

// Header name for an API key stamp
const stampHeaderName = "X-Stamp";

export type TApiKeyStamperConfig = {
apiPublicKey: string;
apiPrivateKey: string;
};

// `window.document` ensures that we're in a browser context
// and `crypto.subtle` ensures that it supports the web crypto APIs
// Inspired by https://github.com/flexdinesh/browser-or-node/blob/master/src/index.ts
const isCryptoEnabledBrowser: boolean =
typeof window !== "undefined" &&
typeof window.document !== "undefined" &&
typeof crypto !== "undefined" &&
typeof crypto.subtle !== "undefined";

// We check `process.versions.node`
// Taken from https://github.com/flexdinesh/browser-or-node/blob/master/src/index.ts
const isNode: boolean =
typeof process !== "undefined" &&
process.versions != null &&
process.versions.node != null;

/**
* Signature function abstracting the differences between NodeJS and web environments for signing with API keys.
*/
Expand All @@ -33,18 +9,17 @@ export const signWithApiKey = async (input: {
publicKey: string;
privateKey: string;
}): Promise<string> => {
if (isCryptoEnabledBrowser) {
const fn = await import("./webcrypto").then((m) => m.signWithApiKey);
return fn(input);
} else if (isNode) {
const fn = await import("./nodecrypto").then((m) => m.signWithApiKey);
return fn(input);
} else {
// If we don't have NodeJS or web crypto at our disposal, default to pure JS implementation
// This is the case for old browsers and react native environments
const fn = await import("./purejs").then((m) => m.signWithApiKey);
return fn(input);
}
// By default, use purejs implementation
const fn = await import("./purejs").then((m) => m.signWithApiKey);
return fn(input);
};

// Header name for an API key stamp
const stampHeaderName = "X-Stamp";

export type TApiKeyStamperConfig = {
apiPublicKey: string;
apiPrivateKey: string;
};

/**
Expand Down
Loading