Skip to content

Commit

Permalink
init openid library
Browse files Browse the repository at this point in the history
Signed-off-by: F-Node-Karlsruhe <[email protected]>
  • Loading branch information
F-Node-Karlsruhe committed Apr 23, 2024
1 parent 453e7c0 commit 9915eab
Show file tree
Hide file tree
Showing 7 changed files with 559 additions and 401 deletions.
482 changes: 246 additions & 236 deletions api/__tests__/presentation.test.ts

Large diffs are not rendered by default.

16 changes: 14 additions & 2 deletions api/package-lock.json

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

1 change: 1 addition & 0 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@or13/did-jwk": "^0.0.4",
"@transmute/vc-jwt-sd": "^0.0.4",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"did-resolver": "^4.1.0",
"dotenv": "^16.3.1",
"express": "^4.18.2",
Expand Down
192 changes: 101 additions & 91 deletions api/src/routes/verify/index.ts
Original file line number Diff line number Diff line change
@@ -1,98 +1,108 @@
import { NextFunction, Request, response, Response } from 'express';
import { StatusCodes } from 'http-status-codes';
import { NextFunction, Request, response, Response } from "express";
import { StatusCodes } from "http-status-codes";

import { Verifier, fetch_json } from '../../services/index.js';

const VC_REGISTRY = process.env.VC_REGISTRY ? process.env.VC_REGISTRY : 'https://ssi.eecc.de/api/registry/vcs/';
import { Verifier, fetch_json } from "../../services/index.js";

const VC_REGISTRY = process.env.VC_REGISTRY
? process.env.VC_REGISTRY
: "https://ssi.eecc.de/api/registry/vcs/";

export class VerifyRoutes {

fetchAndVerify = async (req: Request, res: Response, next: NextFunction): Promise<any> => {

try {

// fetch credential
let credential;

try {

credential = await fetch_json(decodeURIComponent(req.params.vcid)) as Verifiable;

} catch (error) {
console.log(error)
return res.status(StatusCodes.NOT_FOUND).send('Credential not found!\n' + error);
}

const result = await Verifier.verify(credential);

return res.status(StatusCodes.OK).json(result);

} catch (error) {
return res.status(StatusCodes.BAD_REQUEST).send('Something went wrong!\n' + error);
}

fetchAndVerify = async (
req: Request,
res: Response,
next: NextFunction
): Promise<any> => {
try {
// fetch credential
let credential;

try {
credential = (await fetch_json(
decodeURIComponent(req.params.vcid)
)) as Verifiable;
} catch (error) {
console.log(error);
return res
.status(StatusCodes.NOT_FOUND)
.send("Credential not found!\n" + error);
}

const result = await Verifier.verify(credential);

return res.status(StatusCodes.OK).json(result);
} catch (error) {
return res
.status(StatusCodes.BAD_REQUEST)
.send("Something went wrong!\n" + error);
}

verify = async (req: Request, res: Response, next: NextFunction): Promise<any> => {

try {

// Support W3C and JWT namespaces
const challenge = req.query.challenge || req.query.nonce;
const domain = req.query.domain || req.query.audience || req.query.aud;

if (challenge && typeof challenge != 'string') throw new Error('The challenge/nonce must be provided as a string!');

if (domain && typeof domain != 'string') throw new Error('The domain/audience must be provided as a string!');

let tasks = Promise.all(req.body.map(function (verifiable: Verifiable) {

return Verifier.verify(verifiable, challenge, domain);

}));

// wait for all verifiables to be verified
const results = await tasks;

return res.status(StatusCodes.OK).json(results);

} catch (error) {
return res.status(StatusCodes.BAD_REQUEST).send('Something went wrong verifying!\n' + error);
}

};

verify = async (
req: Request,
res: Response,
next: NextFunction
): Promise<any> => {
try {
// Support W3C and JWT namespaces
const challenge = req.query.challenge || req.query.nonce;
const domain = req.query.domain || req.query.audience || req.query.aud;

if (challenge && typeof challenge != "string")
throw new Error("The challenge/nonce must be provided as a string!");

if (domain && typeof domain != "string")
throw new Error("The domain/audience must be provided as a string!");

let tasks = Promise.all(
req.body.map(function (verifiable: Verifiable) {
return Verifier.verify(verifiable, challenge, domain);
})
);

// wait for all verifiables to be verified
const results = await tasks;

return res.status(StatusCodes.OK).json(results);
} catch (error) {
console.error(error);
return res
.status(StatusCodes.BAD_REQUEST)
.send("Something went wrong verifying!\n" + error);
}

verifySubjectsVCs = async (req: Request, res: Response, next: NextFunction): Promise<any> => {

try {

// fetch credentials
let credentials: Verifiable[];

try {

credentials = await fetch_json(VC_REGISTRY + encodeURIComponent(req.params.subjectId)) as [Verifiable];

} catch (error) {
return res.status(StatusCodes.NOT_FOUND);
}

let tasks = Promise.all(credentials.map(function (vc) {

return Verifier.verify(vc);

}));

// wait for all vcs to be verified
const results = await tasks;

return res.status(StatusCodes.OK).json(results);

} catch (error) {
return res.status(StatusCodes.BAD_REQUEST).send('Something went wrong!\n' + error);
}

};

verifySubjectsVCs = async (
req: Request,
res: Response,
next: NextFunction
): Promise<any> => {
try {
// fetch credentials
let credentials: Verifiable[];

try {
credentials = (await fetch_json(
VC_REGISTRY + encodeURIComponent(req.params.subjectId)
)) as [Verifiable];
} catch (error) {
return res.status(StatusCodes.NOT_FOUND);
}

let tasks = Promise.all(
credentials.map(function (vc) {
return Verifier.verify(vc);
})
);

// wait for all vcs to be verified
const results = await tasks;

return res.status(StatusCodes.OK).json(results);
} catch (error) {
return res
.status(StatusCodes.BAD_REQUEST)
.send("Something went wrong!\n" + error);
}

}
};
}
101 changes: 29 additions & 72 deletions api/src/services/verifier/sdjwt.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,36 @@
import SD from '@transmute/vc-jwt-sd';
import moment from 'moment';
import crypto from "crypto";
import { base64url, decodeProtectedHeader, decodeJwt } from "jose";
import { dereferenceDID } from '../documentLoader/index.js';

const vcjwtsd = (SD as any).default;

const digester = (name: 'sha-256' = 'sha-256'): any => {
if (name !== 'sha-256') {
throw new Error('hash function not supported')
}
return {
name,
digest: async (json: string) => {
return base64url.encode(crypto.createHash("sha256").update(json).digest());
}
};
import { SDJwtVcInstance } from "@sd-jwt/sd-jwt-vc";
import type { DisclosureFrame } from "@sd-jwt/types";
import { ES256, digest, generateSalt } from "@sd-jwt/crypto-nodejs";

const createSignerVerifier = async () => {
const { privateKey, publicKey } = await ES256.generateKeyPair();
return {
signer: await ES256.getSigner(privateKey),
verifier: await ES256.getVerifier(publicKey),
};
};

const { signer, verifier } = await createSignerVerifier();

export async function verifySDJWT(verifiable: string, nonce?: string, aud?: string): Promise<VerificationResult> {

const parsed = vcjwtsd.Parse.compact(verifiable);

const decodedHeader = decodeProtectedHeader(parsed.jwt)

const decodedPayload = decodeJwt(parsed.jwt)

try {

const alg = decodedHeader.alg;

if (!alg) throw new Error('No alg specified in the jwt header!');

// TODO support urls and plain keys as well
const iss = (decodedHeader.iss || decodedPayload.iss) as string

const kid = decodedHeader.kid as string

const absoluteDidUrl = kid && kid.startsWith(iss) ? kid : `${iss}#${kid}`

let { publicKeyJwk } = (await dereferenceDID(absoluteDidUrl)).document;

// in case of did:key use jwk from jwt
if (!publicKeyJwk) publicKeyJwk = (decodedPayload.cnf as any).jwk;

const verifier = new vcjwtsd.Verifier({
alg,
digester: digester(),
verifier: {
verify: async (token: string) => {
const parsed = vcjwtsd.Parse.compact(token)
const verifier = await vcjwtsd.JWS.verifier(publicKeyJwk)
return verifier.verify(parsed.jwt)
}
}
})
const verified = await verifier.verify({
presentation: verifiable,
nonce,
aud
})

const now = moment().unix()

if (now < verified.claimset.iat) throw new Error('Credential is not yet valid!')
if (verified.claimset.exp && now > verified.claimset.exp) throw new Error('Credential expired!')
export async function verifySDJWT(
verifiable: string,
nonce?: string,
aud?: string
): Promise<VerificationResult> {
const sdjwt = new SDJwtVcInstance({
signer,
verifier,
signAlg: "EdDSA",
hasher: digest,
hashAlg: "SHA-256",
saltGenerator: generateSalt,
});

console.log(verified.claimset)
const decodedObject = await sdjwt.decode(verifiable);

} catch (error: any) {
console.log(error)
return { verified: false, error: error.message };
}
const verified = await sdjwt.verify(verifiable, []);

return { verified: true };
console.log(verified);

}
return { verified: true };
}
Loading

0 comments on commit 9915eab

Please sign in to comment.