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
}