Skip to content

Commit

Permalink
More tests
Browse files Browse the repository at this point in the history
  • Loading branch information
dahlia committed Mar 5, 2024
1 parent 4f45369 commit 61af958
Show file tree
Hide file tree
Showing 11 changed files with 460 additions and 37 deletions.
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
},
"[typescript]": {
"editor.defaultFormatter": "denoland.vscode-deno",
"editor.formatOnSave": true
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
},
"cSpell.words": [
"bccs",
Expand Down
38 changes: 12 additions & 26 deletions federation/handler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { accepts } from "jsr:@std/http@^0.218.2";
import { doesActorOwnKey, verify } from "../httpsig/mod.ts";
import { DocumentLoader } from "../runtime/docloader.ts";
import {
Activity,
Link,
Object,
OrderedCollection,
OrderedCollectionPage,
} from "../vocab/vocab.ts";
import {
ActorDispatcher,
CollectionCounter,
Expand All @@ -7,16 +16,6 @@ import {
InboxListener,
} from "./callback.ts";
import { RequestContext } from "./context.ts";
import { verify } from "../httpsig/mod.ts";
import { DocumentLoader } from "../runtime/docloader.ts";
import { isActor } from "../vocab/actor.ts";
import {
Activity,
Link,
Object,
OrderedCollection,
OrderedCollectionPage,
} from "../vocab/mod.ts";

function acceptsJsonLd(request: Request): boolean {
const types = accepts(request);
Expand Down Expand Up @@ -250,8 +249,8 @@ export async function handleInbox<TContextData>(
return response instanceof Promise ? await response : response;
}
}
const keyId = await verify(request, documentLoader);
if (keyId == null) {
const key = await verify(request, documentLoader);
if (key == null) {
const response = new Response("Failed to verify the request signature.", {
status: 401,
headers: { "Content-Type": "text/plain; charset=utf-8" },
Expand Down Expand Up @@ -300,7 +299,7 @@ export async function handleInbox<TContextData>(
});
return response;
}
if (!await doesActorOwnKey(activity, keyId)) {
if (!await doesActorOwnKey(activity, key, documentLoader)) {
const response = new Response("The signer and the actor do not match.", {
status: 401,
headers: { "Content-Type": "text/plain; charset=utf-8" },
Expand Down Expand Up @@ -341,16 +340,3 @@ export async function handleInbox<TContextData>(
headers: { "Content-Type": "text/plain; charset=utf-8" },
});
}

async function doesActorOwnKey(
activity: Activity,
keyId: URL,
): Promise<boolean> {
if (activity.actorId?.href === keyId.href.replace(/#.*$/, "")) return true;
const actor = await activity.getActor();
if (actor == null || !isActor(actor)) return false;
for (const publicKeyId of actor.publicKeyIds) {
if (publicKeyId.href === keyId.href) return true;
}
return false;
}
54 changes: 54 additions & 0 deletions federation/send.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { assertEquals } from "jsr:@std/assert@^0.218.2";
import { Service } from "../mod.ts";
import { Actor } from "../vocab/actor.ts";
import { Application, Endpoints, Group, Person } from "../vocab/vocab.ts";
import { extractInboxes } from "./send.ts";

Deno.test("extractInboxes()", () => {
const recipients: Actor[] = [
new Person({
id: new URL("https://example.com/alice"),
inbox: new URL("https://example.com/alice/inbox"),
endpoints: new Endpoints({
sharedInbox: new URL("https://example.com/inbox"),
}),
}),
new Application({
id: new URL("https://example.com/app"),
inbox: new URL("https://example.com/app/inbox"),
endpoints: new Endpoints({
sharedInbox: new URL("https://example.com/inbox"),
}),
}),
new Group({
id: new URL("https://example.org/group"),
inbox: new URL("https://example.org/group/inbox"),
}),
new Service({
id: new URL("https://example.net/service"),
inbox: new URL("https://example.net/service/inbox"),
endpoints: new Endpoints({
sharedInbox: new URL("https://example.net/inbox"),
}),
}),
];
let inboxes = extractInboxes({ recipients });
assertEquals(
inboxes,
new Set([
new URL("https://example.com/alice/inbox"),
new URL("https://example.com/app/inbox"),
new URL("https://example.org/group/inbox"),
new URL("https://example.net/service/inbox"),
]),
);
inboxes = extractInboxes({ recipients, preferSharedInbox: true });
assertEquals(
inboxes,
new Set([
new URL("https://example.com/inbox"),
new URL("https://example.org/group/inbox"),
new URL("https://example.net/inbox"),
]),
);
});
6 changes: 3 additions & 3 deletions federation/send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ export interface ExtractInboxesParameters {
export function extractInboxes(
{ recipients, preferSharedInbox }: ExtractInboxesParameters,
): Set<URL> {
const inboxes = new Set<URL>();
const inboxes: Record<string, URL> = {};
for (const recipient of recipients) {
const inbox = preferSharedInbox
? recipient.endpoints?.sharedInbox ?? recipient.inboxId
: recipient.inboxId;
if (inbox != null) inboxes.add(inbox);
if (inbox != null) inboxes[inbox.href] = inbox;
}
return inboxes;
return new Set(Object.values(inboxes));
}

/**
Expand Down
60 changes: 60 additions & 0 deletions httpsig/key.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { assertThrows } from "jsr:@std/assert@^0.218.2";
import { validateCryptoKey } from "./key.ts";

Deno.test("validateCryptoKey()", async () => {
const pkcs1v15 = await crypto.subtle.generateKey(
{
name: "RSASSA-PKCS1-v1_5",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true,
["sign", "verify"],
);
validateCryptoKey(pkcs1v15.privateKey, "private");
validateCryptoKey(pkcs1v15.privateKey);
validateCryptoKey(pkcs1v15.publicKey, "public");
validateCryptoKey(pkcs1v15.publicKey);

assertThrows(
() => validateCryptoKey(pkcs1v15.privateKey, "public"),
TypeError,
"The key is not a public key.",
);
assertThrows(
() => validateCryptoKey(pkcs1v15.publicKey, "private"),
TypeError,
"The key is not a private key.",
);

const ecdsa = await crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-256",
},
true,
["sign", "verify"],
);
assertThrows(
() => validateCryptoKey(ecdsa.publicKey),
TypeError,
"only RSASSA-PKCS1-v1_5",
);

const pkcs1v15Sha512 = await crypto.subtle.generateKey(
{
name: "RSASSA-PKCS1-v1_5",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-512",
},
true,
["sign", "verify"],
);
assertThrows(
() => validateCryptoKey(pkcs1v15Sha512.privateKey),
TypeError,
"hash algorithm must be SHA-256",
);
});
2 changes: 1 addition & 1 deletion httpsig/key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function validateCryptoKey(
}
if (key.algorithm.name != "RSASSA-PKCS1-v1_5") {
throw new TypeError(
"Curently only RSASSA-PKCS1-v1_5 key is supported. " +
"Currently only RSASSA-PKCS1-v1_5 key is supported. " +
"More algorithms will be added in the future!",
);
}
Expand Down
Loading

0 comments on commit 61af958

Please sign in to comment.