Skip to content

Commit

Permalink
add DPoP encoding utils (#184)
Browse files Browse the repository at this point in the history
  • Loading branch information
jshawl authored Feb 16, 2024
1 parent 7faa180 commit 28aa77f
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 0 deletions.
31 changes: 31 additions & 0 deletions src/dpop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* @flow */
/* eslint-disable promise/no-native, no-restricted-globals */

export const stringToBytes = (string: string): Uint8Array => {
return new Uint8Array([...string].map((c) => c.charCodeAt(0)));
};

export const bytesToString = (bytes: Uint8Array): string => {
return String.fromCharCode(...bytes);
};

export const base64encodeUrlSafe = (string: string): string => {
// https://datatracker.ietf.org/doc/html/rfc7515#appendix-C
return btoa(string)
.replace(/[=]+/g, "")
.replace(/\+/g, "-")
.replace(/\//g, "_");
};

export const base64decodeUrlSafe = (string: string): string => {
return atob(string.replace(/-/g, "+").replace(/_/g, "/"));
};

export const sha256 = async (string: string): Promise<string> => {
const bytes = stringToBytes(string);
const digest = await window.crypto.subtle.digest("sha-256", bytes);
const binaryString = bytesToString(new Uint8Array(digest));
return base64encodeUrlSafe(binaryString);
};

/* eslint-enable promise/no-native, no-restricted-globals */
47 changes: 47 additions & 0 deletions src/dpop.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* @flow */

import { describe, expect, it } from "vitest";

import {
base64decodeUrlSafe,
base64encodeUrlSafe,
bytesToString,
sha256,
stringToBytes,
} from "./dpop";

describe("DPoP", () => {
describe("base64 encoding and decoding", () => {
const decoded = "i·?i·>i·";
const encoded = "abc_abc-abc";
it("encoding replaces '/', '+', and '='", () => {
expect(btoa(decoded)).toEqual("abc/abc+abc=");
expect(base64encodeUrlSafe(decoded)).toEqual(encoded);
});
it("decoding adds back the url unsafe characters", () => {
expect(base64decodeUrlSafe(encoded)).toEqual(decoded);
});
});
describe("byte array <-> string conversion", () => {
it("converts strings to bytes and back again", () => {
const string = "abcdefg123456890";
expect(bytesToString(stringToBytes(string))).toEqual(string);
});
it("converts bytes to binary strings and back again", () => {
// >= 128 should not be encoded as utf-8
const bytes = new Uint8Array([128]);
expect(...stringToBytes(bytesToString(bytes))).toEqual(...bytes);
});
});
describe("sha256", () => {
it("base64 encodes the hash", async () => {
// testing a known string and its base64 encoded hash value from:
// https://datatracker.ietf.org/doc/html/rfc9449#name-dpop-protected-resource-req
const digest = await sha256(
"Kz~8mXK1EalYznwH-LC-1fBAo.4Ljp~zsPE_NeO.gxU"
);
expect(digest).toEqual("fUHyO2r2Z3DZ53EsNrWBb0xWXoaNy59IiKCAqksmQEo");
expect.assertions(1);
});
});
});
2 changes: 2 additions & 0 deletions test/globals.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint flowtype/require-valid-file-annotation: off, flowtype/require-return-type: off */
import crypto from "crypto";

export const sdkClientTestGlobals = {
__PORT__: 8000,
Expand All @@ -20,4 +21,5 @@ export const sdkClientTestGlobals = {
__EXPERIENCE__: "1122",
__TREATMENT__: "1234",
},
crypto: crypto.webcrypto,
};

0 comments on commit 28aa77f

Please sign in to comment.