From e566b16ea67549421a68a58ade888456da375bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sof=C3=ADa=20Aritz?= Date: Sun, 16 Jun 2024 12:18:19 +0200 Subject: [PATCH] start implementing asset-api and m2m comms with identity-api --- .gitignore | 2 + asset-api/index.js | 59 +++++++++++++++++++++++- identity-api/index.js | 103 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cfb38e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.keys/ +.assets/ \ No newline at end of file diff --git a/asset-api/index.js b/asset-api/index.js index b1cdc9b..6860c5b 100644 --- a/asset-api/index.js +++ b/asset-api/index.js @@ -1,13 +1,68 @@ +import { readFileSync } from "node:fs" +import { createSign } from "node:crypto"; import Fastify from "fastify"; +const { private: M2M_PRIVATE_KEY, public: M2M_PUBLIC_KEY } = loadM2MKeys() +const M2M_ALGORITHM = "RSA-SHA512" + +const ASSETS_FOLDER = "~/.assets/" const ASSET_API_LANDING_MESSAGE = "asset-api v1.0.0" +const IDENTITY_API_ENDPOINT = "http://localhost:3000" + const fastify = new Fastify({ logger: true, }) fastify.get("/", async (request, reply) => { - return ASSET_API_LANDING_MESSAGE + return signString(ASSET_API_LANDING_MESSAGE) }) -fastify.listen({ port: 3001 }) \ No newline at end of file +fastify.get("/crypto/cert", async (request, reply) => { + return M2M_PUBLIC_KEY +}) + +fastify.get("/crypto/algo", (request, reply) => { + return M2M_ALGORITHM +}) + +fastify.get("/asset", { + async handler(request, reply) { + let url = new URL(IDENTITY_API_ENDPOINT) + url.pathname = "/auth/account/fromkey" + + let res = await fetch(url, { + method: "POST", + body: signString(JSON.stringify({ + session_key: request.query.session_key, + })) + }) + + return await res.text() + }, + schema: { + query: { + type: "object", + properties: { + id: { type: "string" }, + session_key: { type: "string" }, + }, + required: ["id", "session_key"], + }, + }, +}) + +fastify.listen({ port: 3001 }) + +function loadM2MKeys() { + return { + private: readFileSync("../.keys/m2m-dev.pem").toString("ascii"), + public: readFileSync("../.keys/m2m-dev.pub").toString("ascii"), + } +} + +function signString(content) { + let sign = createSign(M2M_ALGORITHM) + sign.update(content) + return `-----BEGIN SIGNED MESSAGE-----\n\n${content}\n\n-----BEGIN SIGNATURE-----\n\n${sign.sign(M2M_PRIVATE_KEY, "base64")}\n-----END SIGNATURE-----` +} \ No newline at end of file diff --git a/identity-api/index.js b/identity-api/index.js index d73e9da..49870c2 100644 --- a/identity-api/index.js +++ b/identity-api/index.js @@ -1,6 +1,8 @@ +import { createVerify } from "node:crypto" import Fastify from 'fastify'; import * as Jose from 'jose'; import { v4 as uuidv4 } from 'uuid' +import assert from "node:assert"; const IDENTITY_API_LANDING_MESSAGE = 'identity-api v1.0.0'; const JWT_SECRET = new TextEncoder().encode( @@ -8,6 +10,15 @@ const JWT_SECRET = new TextEncoder().encode( ) const JWT_ALG = 'HS256' +const ASSET_API_ENDPOINT = "http://localhost:3001/" +const ASSET_API_PUBKEY = await loadAssetPubkey() +const ASSET_API_ALGORITHM = await loadAssetAlgo() + +let session_keys = { + 'uid:005d6417-a23c-48bd-b348-eafeae649b94': 'e381ba8c-e18a-4bca-afce-b212b37bc26b', + 'key:e381ba8c-e18a-4bca-afce-b212b37bc26b': '005d6417-a23c-48bd-b348-eafeae649b94' +}; + let users = { 'jane@identity.net': { uid: '005d6417-a23c-48bd-b348-eafeae649b94', @@ -24,6 +35,47 @@ fastify.get('/', async (request, reply) => { return IDENTITY_API_LANDING_MESSAGE; }) +fastify.post("/auth/account/fromkey", { + async handler(request, reply) { + if (!verifySignature(request.body, ASSET_API_PUBKEY)) { + reply.statusCode(401) + } + + let body = JSON.parse(getContentFromSigned(request.body)) + + let uid = session_keys[`key:${body.session_key}`] + let user = Object.values(users).filter(v => v.uid === uid) + + assert(user.length === 1) + user[0].password = undefined + + return user[0] + } +}) + +fastify.get('/auth/genkey', { + async handler(request, reply) { + let jwt = request.headers['authorization'].replace('Bearer', '').trim() + let { payload } = await Jose.jwtVerify(jwt, JWT_SECRET) + + let key = uuidv4() + session_keys[`uid:${payload.uid}`] = key + session_keys[`key:${key}`] = payload.uid + + return { + session_key: session_keys[payload.uid] + } + }, + schema: { + headers: { + type: 'object', + properties: { + authorization: { type: 'string' }, + }, + }, + }, +}) + fastify.get('/auth/account', { async handler(request, reply) { let jwt = request.headers['authorization'].replace('Bearer', '').trim() @@ -118,4 +170,53 @@ fastify.post('/auth/register', { }, }) -fastify.listen({ port: 3000 }) \ No newline at end of file +fastify.listen({ port: 3000 }) + +async function loadAssetPubkey() { + let url = new URL(ASSET_API_ENDPOINT) + url.pathname = "/crypto/cert" + + let res = await fetch(url) + return await res.text() +} + +async function loadAssetAlgo() { + let url = new URL(ASSET_API_ENDPOINT) + url.pathname = "/crypto/algo" + + let res = await fetch(url) + return await res.text() +} + +/** + * + * @param {string} content + * @param {string} pubkeyText + */ +function verifySignature(content, pubkeyText) { + let parts = content + .replace("-----BEGIN SIGNED MESSAGE-----\n\n", "") + .replace("\n-----END SIGNATURE-----", "") + .split("\n\n-----BEGIN SIGNATURE-----\n\n") + + assert(parts.length === 2); + + let verify = createVerify(ASSET_API_ALGORITHM) + verify.update(parts[0]) + + let pubkey = Buffer.from(pubkeyText, "ascii") + let digest = Buffer.from(parts[1], "base64") + + return verify.verify(pubkey, digest) +} + +function getContentFromSigned(content) { + let parts = content + .replace("-----BEGIN SIGNED MESSAGE-----\n\n", "") + .replace("\n-----END SIGNATURE-----", "") + .split("\n\n-----BEGIN SIGNATURE-----\n\n") + + assert(parts.length === 2); + + return parts[0] +} \ No newline at end of file