From de2566d09b48081e7036ee1c08b27a61cfdd6780 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Tue, 17 Jun 2025 02:27:46 +0200 Subject: [PATCH] change to encryption on openCommunication() --- backend/src/auth/jwt/JWT.ts | 39 ++++++++++++++++++- .../OpenConnectionJwtPayloadType.ts | 18 +++++++++ .../src/federation/authenticateCommunities.ts | 17 ++++++-- .../client/1_0/model/OpenConnectionArgs.ts | 2 +- .../client/1_0/model/PublicCommunityInfo.ts | 1 + .../1_0/query/getPublicCommunityInfo.ts | 1 + backend/src/federation/validateCommunities.ts | 8 +++- ...etPublicCommunityInfoResultLogging.view.ts | 1 + .../1_0/model/GetPublicCommunityInfoResult.ts | 6 +++ 9 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 backend/src/auth/jwt/payloadtypes/OpenConnectionJwtPayloadType.ts diff --git a/backend/src/auth/jwt/JWT.ts b/backend/src/auth/jwt/JWT.ts index 6f6581773..6f1fa34c2 100644 --- a/backend/src/auth/jwt/JWT.ts +++ b/backend/src/auth/jwt/JWT.ts @@ -1,6 +1,5 @@ -import { createPrivateKey, sign } from 'node:crypto' -import { JWTPayload, SignJWT, decodeJwt, jwtVerify } from 'jose' +import { GeneralEncrypt, KeyLike, SignJWT, decodeJwt, jwtVerify } from 'jose' import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' @@ -68,3 +67,39 @@ export const decode = (token: string): JwtPayloadType => { const { payload } = decodeJwt(token) return payload as JwtPayloadType } + +export const encrypt = async(payload: JwtPayloadType, encryptkey: KeyLike): Promise => { + logger.info('JWT.encrypt... payload=', payload) + logger.info('JWT.encrypt... encryptkey=', encryptkey) + try { + // Convert the key to JWK format if needed + const recipientKey = typeof encryptkey === 'string' + ? JSON.parse(encryptkey) + : encryptkey; + + const jwe = await new GeneralEncrypt( + new TextEncoder().encode(JSON.stringify(payload)), + ) + .setProtectedHeader({ enc: 'A256GCM' }) + .addRecipient(recipientKey) + .setUnprotectedHeader({ alg: 'ECDH-ES+A256KW' }) + .encrypt() + /* + const token = await new EncryptJWT({ payload, 'urn:gradido:claim': true }) + .setProtectedHeader({ + alg: 'HS256', + enc: 'A256GCM', + }) + .setIssuedAt() + .setIssuer('urn:gradido:issuer') + .setAudience('urn:gradido:audience') + .setExpirationTime(payload.expiration) + .encrypt(encryptkey) + */ + logger.info('JWT.encrypt... jwe=', jwe) + return jwe.toString() + } catch (e) { + logger.error('Failed to encrypt JWT:', e) + throw e + } +} \ No newline at end of file diff --git a/backend/src/auth/jwt/payloadtypes/OpenConnectionJwtPayloadType.ts b/backend/src/auth/jwt/payloadtypes/OpenConnectionJwtPayloadType.ts new file mode 100644 index 000000000..48e0ae0a3 --- /dev/null +++ b/backend/src/auth/jwt/payloadtypes/OpenConnectionJwtPayloadType.ts @@ -0,0 +1,18 @@ +// import { JWTPayload } from 'jose' +import { JwtPayloadType } from './JwtPayloadType' + +export class OpenConnectionJwtPayloadType extends JwtPayloadType { + static OPEN_CONNECTION_TYPE = 'open-connection' + + url: string + + constructor( + url: string, + ) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + super() + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + this.tokentype = OpenConnectionJwtPayloadType.OPEN_CONNECTION_TYPE + this.url = url + } +} diff --git a/backend/src/federation/authenticateCommunities.ts b/backend/src/federation/authenticateCommunities.ts index c4263445b..6b4f3b52c 100644 --- a/backend/src/federation/authenticateCommunities.ts +++ b/backend/src/federation/authenticateCommunities.ts @@ -9,6 +9,9 @@ import { ensureUrlEndsWithSlash } from '@/util/utilities' import { OpenConnectionArgs } from './client/1_0/model/OpenConnectionArgs' import { AuthenticationClientFactory } from './client/AuthenticationClientFactory' +import { OpenConnectionJwtPayloadType } from '@/auth/jwt/payloadtypes/OpenConnectionJwtPayloadType' +import { importSPKI } from 'jose' +import { encrypt } from '@/auth/jwt/JWT' export async function startCommunityAuthentication( foreignFedCom: DbFederatedCommunity, @@ -36,14 +39,22 @@ export async function startCommunityAuthentication( const client = AuthenticationClientFactory.getInstance(foreignFedCom) if (client instanceof V1_0_AuthenticationClient) { + if (!foreignCom.publicJwtKey) { + throw new Error('Public JWT key not found for foreign community') + } const args = new OpenConnectionArgs() args.publicKey = homeCom.publicKey.toString('hex') - // TODO encrypt url with foreignCom.publicKey and sign it with homeCom.privateKey - args.url = ensureUrlEndsWithSlash(homeFedCom.endPoint).concat(homeFedCom.apiVersion) + //create JWT with url in payload encrypted by foreignCom.publicKey and signed with homeCom.privateKey + const payload = new OpenConnectionJwtPayloadType( + ensureUrlEndsWithSlash(homeFedCom.endPoint).concat(homeFedCom.apiVersion), + ) + const encryptKey = await importSPKI(foreignCom.publicJwtKey!, 'RS256') + const jwt = await encrypt(payload, encryptKey) + args.jwt = jwt logger.debug( 'Authentication: before client.openConnection() args:', homeCom.publicKey.toString('hex'), - args.url, + args.jwt, ) if (await client.openConnection(args)) { logger.debug(`Authentication: successful initiated at community:`, foreignFedCom.endPoint) diff --git a/backend/src/federation/client/1_0/model/OpenConnectionArgs.ts b/backend/src/federation/client/1_0/model/OpenConnectionArgs.ts index 9afdbca5f..7e436da05 100644 --- a/backend/src/federation/client/1_0/model/OpenConnectionArgs.ts +++ b/backend/src/federation/client/1_0/model/OpenConnectionArgs.ts @@ -6,5 +6,5 @@ export class OpenConnectionArgs { publicKey: string @Field(() => String) - url: string + jwt: string } diff --git a/backend/src/federation/client/1_0/model/PublicCommunityInfo.ts b/backend/src/federation/client/1_0/model/PublicCommunityInfo.ts index cad8176be..1abbeb9e7 100644 --- a/backend/src/federation/client/1_0/model/PublicCommunityInfo.ts +++ b/backend/src/federation/client/1_0/model/PublicCommunityInfo.ts @@ -3,4 +3,5 @@ export interface PublicCommunityInfo { description: string creationDate: Date publicKey: string + publicJwtKey: string } diff --git a/backend/src/federation/client/1_0/query/getPublicCommunityInfo.ts b/backend/src/federation/client/1_0/query/getPublicCommunityInfo.ts index f075b2aae..36350fae8 100644 --- a/backend/src/federation/client/1_0/query/getPublicCommunityInfo.ts +++ b/backend/src/federation/client/1_0/query/getPublicCommunityInfo.ts @@ -7,6 +7,7 @@ export const getPublicCommunityInfo = gql` description creationDate publicKey + publicJwtKey } } ` diff --git a/backend/src/federation/validateCommunities.ts b/backend/src/federation/validateCommunities.ts index ec26aba84..4ba350322 100644 --- a/backend/src/federation/validateCommunities.ts +++ b/backend/src/federation/validateCommunities.ts @@ -37,6 +37,7 @@ export async function startValidateCommunities(timerInterval: number): Promise { + // search all foreign federated communities which are still not verified or have not been verified since last dht-announcement const dbFederatedCommunities: DbFederatedCommunity[] = await DbFederatedCommunity.createQueryBuilder() .where({ foreign: true, verifiedAt: IsNull() }) @@ -104,12 +105,14 @@ export async function writeJwtKeyPairInHomeCommunity(): Promise { logger.debug(`Federation: writeJwtKeyPairInHomeCommunity publicKey=`, publicKeyPem); logger.debug(`Federation: writeJwtKeyPairInHomeCommunity privateKey=`, privateKeyPem); - homeCom.publicJwtKey = Buffer.from(publicKeyPem); + homeCom.publicJwtKey = publicKeyPem; logger.debug(`Federation: writeJwtKeyPairInHomeCommunity publicJwtKey.length=`, homeCom.publicJwtKey.length); - homeCom.privateJwtKey = Buffer.from(privateKeyPem); + homeCom.privateJwtKey = privateKeyPem; logger.debug(`Federation: writeJwtKeyPairInHomeCommunity privateJwtKey.length=`, homeCom.privateJwtKey.length); await DbCommunity.save(homeCom) logger.debug(`Federation: writeJwtKeyPairInHomeCommunity done`) + } else { + logger.debug(`Federation: writeJwtKeyPairInHomeCommunity: keypair already exists`) } } else { throw new Error(`Error! A HomeCommunity-Entry still not exist! Please start the DHT-Modul first.`) @@ -141,6 +144,7 @@ async function writeForeignCommunity( com.foreign = true com.name = pubInfo.name com.publicKey = dbCom.publicKey + com.publicJwtKey = pubInfo.publicJwtKey com.url = dbCom.endPoint await DbCommunity.save(com) } diff --git a/federation/src/graphql/api/1_0/logger/GetPublicCommunityInfoResultLogging.view.ts b/federation/src/graphql/api/1_0/logger/GetPublicCommunityInfoResultLogging.view.ts index 77224fabc..3eb7fd208 100644 --- a/federation/src/graphql/api/1_0/logger/GetPublicCommunityInfoResultLogging.view.ts +++ b/federation/src/graphql/api/1_0/logger/GetPublicCommunityInfoResultLogging.view.ts @@ -12,6 +12,7 @@ export class GetPublicCommunityInfoResultLoggingView extends AbstractLoggingView description: this.self.description, creationDate: this.dateToString(this.self.creationDate), publicKey: this.self.publicKey, + publicJwtKey: this.self.publicJwtKey, } } } diff --git a/federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts b/federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts index edcdccb91..55292cee2 100644 --- a/federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts +++ b/federation/src/graphql/api/1_0/model/GetPublicCommunityInfoResult.ts @@ -6,6 +6,9 @@ import { Field, ObjectType } from 'type-graphql' export class GetPublicCommunityInfoResult { constructor(dbCom: DbCommunity) { this.publicKey = dbCom.publicKey.toString('hex') + if (dbCom.publicJwtKey) { + this.publicJwtKey = dbCom.publicJwtKey + } this.name = dbCom.name this.description = dbCom.description this.creationDate = dbCom.creationDate @@ -22,4 +25,7 @@ export class GetPublicCommunityInfoResult { @Field(() => String) publicKey: string + + @Field(() => String) + publicJwtKey: string }