Skip to content
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
57 changes: 28 additions & 29 deletions examples/browser-extension/src/background.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Algorithm,
KeyedSigner,
signatureHeadersSync,
helpers,
jwkToKeyID,
Expand All @@ -18,39 +19,37 @@ const MAX_AGE_IN_MS = 1000 * 60 * 60; // 1 hour
const SIGNATURE_AGENT =
"https://http-message-signatures-example.research.cloudflare.com";

class Ed25519Signer {
public alg: Algorithm = "ed25519";
public keyid: string;
private privateKey: Uint8Array<ArrayBuffer>;
// libsodium signs synchronously, which is what a blocking listener needs. A signer may return the
// signature directly rather than a Promise of it, so no wrapper is involved.
function ed25519Signer(jwk: JsonWebKey): KeyedSigner {
const sodium = _sodium;

constructor(public jwk: JsonWebKey) {
const sodium = _sodium;
// Base64URL decode helper
const base64urlDecode = (str) =>
sodium.from_base64(str, sodium.base64_variants.URLSAFE_NO_PADDING);

// Base64URL decode helper
const base64urlDecode = (str) =>
sodium.from_base64(str, sodium.base64_variants.URLSAFE_NO_PADDING);
// Decode keys
const privateKey = base64urlDecode(jwk.d); // 32 bytes
const publicKey = base64urlDecode(jwk.x); // 32 bytes

// Decode keys
const privateKey = base64urlDecode(jwk.d); // 32 bytes
const publicKey = base64urlDecode(jwk.x); // 32 bytes

// Build the full 64-byte secret key: privateKey || publicKey
const fullSecretKey = new Uint8Array(64);
fullSecretKey.set(privateKey);
fullSecretKey.set(publicKey, 32);

this.privateKey = fullSecretKey;
// Build the full 64-byte secret key: privateKey || publicKey
const fullSecretKey = new Uint8Array(64);
fullSecretKey.set(privateKey);
fullSecretKey.set(publicKey, 32);

const alg: Algorithm = "ed25519";
return {
alg,
// NOTE: this MUST be computed from the public key bytes. It just so happen Chrome does not easily allow to perform a sha256 synchronously
this.keyid = KEY_ID;
}

signSync(data: string): Uint8Array {
const sodium = _sodium;
const message = sodium.from_string(data);
const signedMessage = sodium.crypto_sign(message, this.privateKey);
return signedMessage.slice(0, sodium.crypto_sign_BYTES);
}
keyid: KEY_ID,
signer: () => ({
alg,
sign: (data) =>
sodium
.crypto_sign(data, fullSecretKey)
.slice(0, sodium.crypto_sign_BYTES),
}),
};
}

chrome.webRequest.onBeforeSendHeaders.addListener(
Expand All @@ -66,7 +65,7 @@ chrome.webRequest.onBeforeSendHeaders.addListener(
headers: details.requestHeaders?.map((h) => [h.name, h.value!])!,
});
const now = new Date();
const headers = signatureHeadersSync(request, new Ed25519Signer(jwk), {
const headers = signatureHeadersSync(request, ed25519Signer(jwk), {
components: recommendedComponents("sig1"),
created: now,
expires: new Date(now.getTime() + MAX_AGE_IN_MS),
Expand Down
49 changes: 16 additions & 33 deletions examples/verification-workers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
Signer,
SignatureAgentCard,
SignatureAgentEntry,
VerificationParams,
VerifierFactory,
directoryResponseHeaders,
helpers,
jwkToKeyID,
Expand All @@ -34,7 +34,7 @@ import { generateDebugHTML } from "./debug-html";
import { invalidHTML, neutralHTML, validHTML } from "./index-html";
import { proxyDirectoryRequest } from "./proxy-directory";
import jwk from "../../rfc9421-keys/ed25519.json" assert { type: "json" };
import { Ed25519Signer } from "web-bot-auth/crypto";
import { signerFromJWK, verifier } from "web-bot-auth/crypto";

function errorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
Expand Down Expand Up @@ -164,39 +164,22 @@ async function fetchDirectory(entry: SignatureAgentEntry): Promise<Directory> {
}

async function getSigner(): Promise<Signer> {
return Ed25519Signer.fromJWK(jwk);
return signerFromJWK(jwk);
}

function verifyEd25519(
directory: Directory
): (
data: string,
signature: Uint8Array,
params: VerificationParams
) => Promise<void> {
return async (data, signature, _params) => {
void _params;
const key = await crypto.subtle.importKey(
"jwk",
directory.keys[0],
{ name: "Ed25519" },
true,
["verify"]
);

const encodedData = new TextEncoder().encode(data);

const isValid = await crypto.subtle.verify(
{ name: "Ed25519" },
key,
signature,
encodedData
);

if (!isValid) {
throw new Error("invalid signature");
}
};
function verifyEd25519(directory: Directory): VerifierFactory {
// Awaited inside the factory, which may return a Promise so that key material can be resolved
// per signature rather than once up front.
return async (signature, context) =>
verifier(
await crypto.subtle.importKey(
"jwk",
directory.keys[0],
{ name: "Ed25519" },
true,
["verify"]
)
)(signature, context);
}

const SignatureValidationStatus = {
Expand Down
40 changes: 11 additions & 29 deletions package-lock.json

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

Loading