From 7eb9eb465075d7411445b51e378be882e0fdb22c Mon Sep 17 00:00:00 2001 From: devin ivy Date: Tue, 5 Mar 2024 17:02:07 -0500 Subject: [PATCH] Entryway: pull correct key from based on mod service did ref (#2275) entryway: pull correct key from based on mod service did ref --- packages/common-web/src/did-doc.ts | 21 ++++++++++++++++++ packages/identity/src/did/atproto-data.ts | 17 ++++++++++++++ packages/pds/src/auth-verifier.ts | 27 +++++++++++++++++++---- packages/pds/tests/admin-auth.test.ts | 21 +++++++++++++++--- 4 files changed, 79 insertions(+), 7 deletions(-) diff --git a/packages/common-web/src/did-doc.ts b/packages/common-web/src/did-doc.ts index c2a05b796d3..f9fea309716 100644 --- a/packages/common-web/src/did-doc.ts +++ b/packages/common-web/src/did-doc.ts @@ -45,6 +45,27 @@ export const getSigningKey = ( } } +export const getVerificationMaterial = ( + doc: DidDocument, + keyId: string, +): { type: string; publicKeyMultibase: string } | undefined => { + const did = getDid(doc) + let keys = doc.verificationMethod + if (!keys) return undefined + if (typeof keys !== 'object') return undefined + if (!Array.isArray(keys)) { + keys = [keys] + } + const found = keys.find( + (key) => key.id === `#${keyId}` || key.id === `${did}#${keyId}`, + ) + if (!found?.publicKeyMultibase) return undefined + return { + type: found.type, + publicKeyMultibase: found.publicKeyMultibase, + } +} + export const getPdsEndpoint = (doc: DidDocument): string | undefined => { return getServiceEndpoint(doc, { id: '#atproto_pds', diff --git a/packages/identity/src/did/atproto-data.ts b/packages/identity/src/did/atproto-data.ts index c03f76ef598..cc1483b1d57 100644 --- a/packages/identity/src/did/atproto-data.ts +++ b/packages/identity/src/did/atproto-data.ts @@ -34,6 +34,23 @@ export const getKey = (doc: DidDocument): string | undefined => { return didKey } +export const getDidKeyFromMultibase = (key: { + type: string + publicKeyMultibase: string +}): string | undefined => { + const keyBytes = crypto.multibaseToBytes(key.publicKeyMultibase) + let didKey: string | undefined = undefined + if (key.type === 'EcdsaSecp256r1VerificationKey2019') { + didKey = crypto.formatDidKey(crypto.P256_JWT_ALG, keyBytes) + } else if (key.type === 'EcdsaSecp256k1VerificationKey2019') { + didKey = crypto.formatDidKey(crypto.SECP256K1_JWT_ALG, keyBytes) + } else if (key.type === 'Multikey') { + const parsed = crypto.parseMultikey(key.publicKeyMultibase) + didKey = crypto.formatDidKey(parsed.jwtAlg, parsed.keyBytes) + } + return didKey +} + export const parseToAtprotoDocument = ( doc: DidDocument, ): Partial => { diff --git a/packages/pds/src/auth-verifier.ts b/packages/pds/src/auth-verifier.ts index 3a001733025..d32cb91cf8c 100644 --- a/packages/pds/src/auth-verifier.ts +++ b/packages/pds/src/auth-verifier.ts @@ -12,7 +12,7 @@ import { InvalidRequestError, verifyJwt as verifyServiceJwt, } from '@atproto/xrpc-server' -import { IdResolver } from '@atproto/identity' +import { IdResolver, getDidKeyFromMultibase } from '@atproto/identity' import * as ui8 from 'uint8arrays' import express from 'express' import * as jose from 'jose' @@ -20,6 +20,7 @@ import KeyEncoder from 'key-encoder' import Database from './db' import { softDeleted } from './db/util' import { SigningKeyMemory, VerifyKey } from './config' +import { getVerificationMaterial } from '@atproto/common' type ReqCtx = { req: express.Request @@ -260,14 +261,32 @@ export class AuthVerifier { const payload = await verifyServiceJwt( jwtStr, this._pdsServiceDid, - async (did, forceRefresh) => { - if (did !== this._modServiceDid) { + async (iss, forceRefresh) => { + if ( + iss !== this._modServiceDid && + iss !== `${this._modServiceDid}#atproto_labeler` + ) { throw new AuthRequiredError( 'Untrusted issuer for admin actions', 'UntrustedIss', ) } - return this.idResolver.did.resolveAtprotoKey(did, forceRefresh) + const [did, serviceId] = iss.split('#') + const keyId = + serviceId === 'atproto_labeler' ? 'atproto_label' : 'atproto' + const didDoc = await this.idResolver.did.resolve(did, forceRefresh) + if (!didDoc) { + throw new AuthRequiredError('could not resolve iss did') + } + const parsedKey = getVerificationMaterial(didDoc, keyId) + if (!parsedKey) { + throw new AuthRequiredError('missing or bad key in did doc') + } + const didKey = getDidKeyFromMultibase(parsedKey) + if (!didKey) { + throw new AuthRequiredError('missing or bad key in did doc') + } + return didKey }, ) return { diff --git a/packages/pds/tests/admin-auth.test.ts b/packages/pds/tests/admin-auth.test.ts index 548c9af3c94..a681ddd53c5 100644 --- a/packages/pds/tests/admin-auth.test.ts +++ b/packages/pds/tests/admin-auth.test.ts @@ -28,13 +28,28 @@ describe('admin auth', () => { pdsDid = network.pds.ctx.cfg.service.did modServiceKey = await Secp256k1Keypair.create() - const origResolve = network.pds.ctx.idResolver.did.resolveAtprotoKey - network.pds.ctx.idResolver.did.resolveAtprotoKey = async function ( + const origResolve = network.pds.ctx.idResolver.did.resolve + network.pds.ctx.idResolver.did.resolve = async function ( did: string, forceRefresh?: boolean, ) { if (did === modServiceDid || did === altModDid) { - return modServiceKey.did() + return { + '@context': [ + 'https://www.w3.org/ns/did/v1', + 'https://w3id.org/security/multikey/v1', + 'https://w3id.org/security/suites/secp256k1-2019/v1', + ], + id: did, + verificationMethod: [ + { + id: `${did}#atproto`, + type: 'Multikey', + controller: did, + publicKeyMultibase: modServiceKey.did().replace('did:key:', ''), + }, + ], + } } return origResolve.call(this, did, forceRefresh) }