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

Proxy support #2

Merged
merged 20 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ on:
jobs:
build:
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
node-version: [16.x, 18.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
node-version: [16.x, 18.x, 20.x]

steps:
- uses: actions/checkout@v3
Expand All @@ -35,6 +34,8 @@ jobs:
run: npm ci

- run: npm run test -- --coverage
env:
NODE_VERSION: ${{ matrix.node-version }}

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ coverage
docs
shell.nix
.envrc
.direnv
.direnv
scratchpad.js
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.yml
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,62 @@ Algorithms are split into their own modules, which enforces consumption of crypt
```sh
npm i @nfen/webcrypto-ts
```

## Examples

Many more examples in the [Documentation](https://neal.codes/webcrypto-ts/).

### ECDSA

```ts
import ECDSA from "@nfen/webcrypto-ts/lib/ec/ecdsa";
const keyPair = await ECDSA.generateKey();

const message = new TextEncoder().encode("a message");
const signature = await keyPair.privateKey.sign({ hash: "SHA-512" }, message);

const isVerified = await someOtherKeypair.publicKey.verify(
{ hash: "SHA-512" },
signature,
message
);
```

### RSA-OAEP

```ts
import * as RSA_OAEP from "@nfen/webcrypto-ts/lib/rsa/rsa_oaep";
import * as AES_CBC from "@nfen/webcrypto-ts/lib/aes/aes_cbc";
import * as Random from "@nfen/webcrypto-ts/random";

const kek = await RSA_OAEP.generateKey(
{
hash: "SHA-512",
modulusLength: 4096,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
},
true,
["wrapKey", "unwrapKey"]
);
const dek = await AES_CBC.generateKey();
const label = await Random.getValues(8);
const wrappedCbcKey = await kek.wrapKey("raw", dek.self, { label });
```

### AES-GCM

```ts
import * as AES_GCM from "@nfen/webcrypto-ts/lib/aes/aes_gcm";
import { IV } from "@nfen/webcrypto-ts/lib/random";

const iv = await IV.generate();
const key = await AES_GCM.generateKey();
const message = "a message";
const cipherText = await key.encrypt(
{ iv },
new TextEncoder().encode("a message")
);
console.assert(
new TextDecoder().decode(await key.decrypt({ iv }, message)) === message
);
```
7 changes: 7 additions & 0 deletions global.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
declare var encode: Function;
declare var decode: Function;

interface ProxyConstructor {
new <TSource extends object, TTarget extends object>(
target: TSource,
handler: ProxyHandler<TSource>
): TTarget;
}
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"exports": "./lib/index.js",
"type": "module",
"engines": {
"node": ">=16"
"node": ">=18"
},
"scripts": {
"prepublishOnly": "npm run test && npm run build",
Expand Down
196 changes: 135 additions & 61 deletions src/aes/__tests__/aes_cbc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,75 +2,149 @@ import * as Random from "../../random.js";
import * as AES from "../index.js";

describe("AES_CBC", () => {
let iv: Uint8Array, key: AES.AesCbcCryptoKey;
const text = "brown fox fox fox fox fox fox fox fox fox";
beforeEach(async () => {
iv = await Random.IV.generate();
key = await AES.AES_CBC.generateKey();
});
it("should encrypt and decrypt", async () => {
const ciphertextBytes = await AES.AES_CBC.encrypt(
{ iv },
key,
new TextEncoder().encode(text)
);
const plaintextBytes = await AES.AES_CBC.decrypt(
{ iv },
key,
ciphertextBytes
);
expect(new TextDecoder().decode(plaintextBytes)).toEqual(text);
});
it("should import and export keys", async () => {
const ciphertextBytes = await AES.AES_CBC.encrypt(
{ iv },
key,
new TextEncoder().encode(text)
);
describe("Original", () => {
let iv: Uint8Array,
proxiedKey: AES.AesCbcProxiedCryptoKey,
key: AES.AesCbcCryptoKey;
const text = "brown fox fox fox fox fox fox fox fox fox";
beforeEach(async () => {
iv = await Random.IV.generate();
proxiedKey = await AES.AES_CBC.generateKey();
key = proxiedKey.self;
});
it("should encrypt and decrypt", async () => {
const ciphertextBytes = await AES.AES_CBC.encrypt(
{ iv },
key,
new TextEncoder().encode(text)
);
const plaintextBytes = await AES.AES_CBC.decrypt(
{ iv },
key,
ciphertextBytes
);
expect(new TextDecoder().decode(plaintextBytes)).toEqual(text);
});
it("should import and export keys", async () => {
const ciphertextBytes = await AES.AES_CBC.encrypt(
{ iv },
key,
new TextEncoder().encode(text)
);

const jwk = await AES.AES_CBC.exportKey("jwk", key);
const importedKey = await AES.AES_CBC.importKey("jwk", jwk, {
length: 256,
});

const jwk = await AES.AES_CBC.exportKey("jwk", key);
const importedKey = await AES.AES_CBC.importKey("jwk", jwk, {
length: 256,
const plaintextBytes = await AES.AES_CBC.decrypt(
{ iv },
importedKey.self,
ciphertextBytes
);
expect(new TextDecoder().decode(plaintextBytes)).toEqual(text);
});
it("should wrap and unwrap keys", async () => {
const kek = await AES.AES_CBC.generateKey({ length: 256 }, true, [
"wrapKey",
"unwrapKey",
]);
const dek = await AES.AES_CBC.generateKey({
length: 256,
});

const ciphertextBytes = await AES.AES_CBC.encrypt(
{ iv },
dek.self,
new TextEncoder().encode(text)
);

const plaintextBytes = await AES.AES_CBC.decrypt(
{ iv },
importedKey,
ciphertextBytes
);
expect(new TextDecoder().decode(plaintextBytes)).toEqual(text);
const wrappedKey = await AES.AES_CBC.wrapKey(
"raw",
dek.self,
kek.self,
{
iv,
}
);
const unwrappedkey = (await AES.AES_CBC.unwrapKey(
"raw",
wrappedKey,
{ name: AES.Alg.Mode.AES_CBC },
kek.self,
{ iv }
)) as AES.AesCbcCryptoKey;

const plaintextBytes = await AES.AES_CBC.decrypt(
{ iv },
unwrappedkey,
ciphertextBytes
);
expect(new TextDecoder().decode(plaintextBytes)).toEqual(text);
});
});
it("should wrap and unwrap keys", async () => {
const kek = await AES.AES_CBC.generateKey({ length: 256 }, true, [
"wrapKey",
"unwrapKey",
]);
const dek: AES.AesCbcCryptoKey = await AES.AES_CBC.generateKey({
length: 256,
describe("Proxied", () => {
let iv: Uint8Array, key: AES.AesCbcProxiedCryptoKey;
const text = "brown fox fox fox fox fox fox fox fox fox";
beforeEach(async () => {
iv = await Random.IV.generate();
key = await AES.AES_CBC.generateKey();
});
it("should encrypt and decrypt", async () => {
const ciphertextBytes = await key.encrypt(
{ iv },
new TextEncoder().encode(text)
);
const plaintextBytes = await key.decrypt({ iv }, ciphertextBytes);
expect(new TextDecoder().decode(plaintextBytes)).toEqual(text);
});
it("should import and export keys", async () => {
const ciphertextBytes = await key.encrypt(
{ iv },
new TextEncoder().encode(text)
);

const ciphertextBytes = await AES.AES_CBC.encrypt(
{ iv },
dek,
new TextEncoder().encode(text)
);
const jwk = await key.exportKey("jwk");
const importedKey = await AES.AES_CBC.importKey("jwk", jwk, {
length: 256,
});

const wrappedKey = await AES.AES_CBC.wrapKey("raw", dek, kek, {
iv,
const plaintextBytes = await importedKey.decrypt(
{ iv },
ciphertextBytes
);
expect(new TextDecoder().decode(plaintextBytes)).toEqual(text);
});
const unwrappedkey = (await AES.AES_CBC.unwrapKey(
"raw",
wrappedKey,
{ name: AES.Alg.Mode.AES_CBC },
kek,
{ iv }
)) as AES.AesCbcCryptoKey;
it("should wrap and unwrap keys", async () => {
const kek = await AES.AES_CBC.generateKey({ length: 256 }, true, [
"wrapKey",
"unwrapKey",
]);
const dek = await AES.AES_CBC.generateKey({
length: 256,
});

const plaintextBytes = await AES.AES_CBC.decrypt(
{ iv },
unwrappedkey,
ciphertextBytes
);
expect(new TextDecoder().decode(plaintextBytes)).toEqual(text);
const ciphertextBytes = await dek.encrypt(
{ iv },
new TextEncoder().encode(text)
);

const wrappedKey = await kek.wrapKey("raw", dek.self, {
iv,
});
const unwrappedkey = (await kek.unwrapKey(
"raw",
wrappedKey,
{ name: AES.Alg.Mode.AES_CBC },
{ iv }
)) as AES.AesCbcCryptoKey;

const plaintextBytes = await AES.AES_CBC.decrypt(
{ iv },
unwrappedkey,
ciphertextBytes
);
expect(new TextDecoder().decode(plaintextBytes)).toEqual(text);
});
});
});