mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
JWT signing and encryption methods and tests
This commit is contained in:
parent
de2566d09b
commit
c75e43eafe
161
backend/src/auth/jwt/JWT.test.ts
Normal file
161
backend/src/auth/jwt/JWT.test.ts
Normal file
@ -0,0 +1,161 @@
|
||||
import { ApolloServerTestClient } from 'apollo-server-testing'
|
||||
import { FederatedCommunity as DbFederatedCommunity } from 'database'
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import { Response } from 'graphql-request/dist/types'
|
||||
import { DataSource } from 'typeorm'
|
||||
|
||||
import { cleanDB, testEnvironment } from '@test/helpers'
|
||||
import { logger } from '@test/testSetup'
|
||||
|
||||
import { encode, decode, verify, encrypt, decrypt, createKeyPair } from './JWT'
|
||||
import { OpenConnectionJwtPayloadType } from './payloadtypes/OpenConnectionJwtPayloadType'
|
||||
import { EncryptedJWEJwtPayloadType } from './payloadtypes/EncryptedJWEJwtPayloadType'
|
||||
|
||||
let con: DataSource
|
||||
let testEnv: {
|
||||
mutate: ApolloServerTestClient['mutate']
|
||||
query: ApolloServerTestClient['query']
|
||||
con: DataSource
|
||||
}
|
||||
let keypairComA: { publicKey: string; privateKey: string }
|
||||
let keypairComB: { publicKey: string; privateKey: string }
|
||||
|
||||
beforeAll(async () => {
|
||||
testEnv = await testEnvironment(logger)
|
||||
con = testEnv.con
|
||||
await cleanDB()
|
||||
|
||||
keypairComA = await createKeyPair()
|
||||
keypairComB = await createKeyPair()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
// await cleanDB()
|
||||
await con.destroy()
|
||||
})
|
||||
|
||||
describe('test JWS creation and verification', () => {
|
||||
let jwsComA: string
|
||||
let jwsComB: string
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
jwsComA = await encode(new OpenConnectionJwtPayloadType('http://localhost:5001/api/'), keypairComA.privateKey)
|
||||
console.log('jwsComA=', jwsComA)
|
||||
jwsComB = await encode(new OpenConnectionJwtPayloadType('http://localhost:5002/api/'), keypairComB.privateKey)
|
||||
console.log('jwsComB=', jwsComB)
|
||||
})
|
||||
it('decode jwsComA', async () => {
|
||||
const decodedJwsComA = await decode(jwsComA)
|
||||
console.log('decodedJwsComA=', decodedJwsComA)
|
||||
expect(decodedJwsComA).toEqual({
|
||||
expiration: '10m',
|
||||
tokentype: OpenConnectionJwtPayloadType.OPEN_CONNECTION_TYPE,
|
||||
url: 'http://localhost:5001/api/',
|
||||
})
|
||||
})
|
||||
it('decode jwsComB', async () => {
|
||||
const decodedJwsComB = await decode(jwsComB)
|
||||
console.log('decodedJwsComB=', decodedJwsComB)
|
||||
expect(decodedJwsComB).toEqual({
|
||||
expiration: '10m',
|
||||
tokentype: OpenConnectionJwtPayloadType.OPEN_CONNECTION_TYPE,
|
||||
url: 'http://localhost:5002/api/',
|
||||
})
|
||||
})
|
||||
it('verify jwsComA', async () => {
|
||||
const verifiedJwsComA = await verify(jwsComA, keypairComA.publicKey)
|
||||
console.log('verify jwsComA=', verifiedJwsComA)
|
||||
expect(verifiedJwsComA).toEqual(expect.objectContaining({
|
||||
payload: expect.objectContaining({
|
||||
tokentype: OpenConnectionJwtPayloadType.OPEN_CONNECTION_TYPE,
|
||||
url: 'http://localhost:5001/api/',
|
||||
})
|
||||
}))
|
||||
})
|
||||
it('verify jwsComB', async () => {
|
||||
const verifiedJwsComB = await verify(jwsComB, keypairComB.publicKey)
|
||||
console.log('verify jwsComB=', verifiedJwsComB)
|
||||
expect(verifiedJwsComB).toEqual(expect.objectContaining({
|
||||
payload: expect.objectContaining({
|
||||
tokentype: OpenConnectionJwtPayloadType.OPEN_CONNECTION_TYPE,
|
||||
url: 'http://localhost:5002/api/',
|
||||
})
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
describe('test JWE encryption and decryption', () => {
|
||||
let jweComA: string
|
||||
let jweComB: string
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
jweComA = await encrypt(new OpenConnectionJwtPayloadType('http://localhost:5001/api/'), keypairComB.publicKey)
|
||||
console.log('jweComA=', jweComA)
|
||||
jweComB = await encrypt(new OpenConnectionJwtPayloadType('http://localhost:5002/api/'), keypairComA.publicKey)
|
||||
console.log('jweComB=', jweComB)
|
||||
})
|
||||
it('decrypt jweComA', async () => {
|
||||
const decryptedAJwT = await decrypt(jweComA, keypairComB.privateKey)
|
||||
console.log('decryptedAJwT=', decryptedAJwT)
|
||||
expect(JSON.parse(decryptedAJwT)).toEqual(expect.objectContaining({
|
||||
tokentype: OpenConnectionJwtPayloadType.OPEN_CONNECTION_TYPE,
|
||||
url: 'http://localhost:5001/api/',
|
||||
}))
|
||||
})
|
||||
it('decrypt jweComB', async () => {
|
||||
const decryptedBJwT = await decrypt(jweComB, keypairComA.privateKey)
|
||||
console.log('decryptedBJwT=', decryptedBJwT)
|
||||
expect(JSON.parse(decryptedBJwT)).toEqual(expect.objectContaining({
|
||||
tokentype: OpenConnectionJwtPayloadType.OPEN_CONNECTION_TYPE,
|
||||
url: 'http://localhost:5002/api/',
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
describe('test encrypted and signed JWT', () => {
|
||||
let jweComA: string
|
||||
let jwsComA: string
|
||||
let jwtComA: string
|
||||
let jweComB: string
|
||||
let jwsComB: string
|
||||
let jwtComB: string
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
jweComA = await encrypt(new OpenConnectionJwtPayloadType('http://localhost:5001/api/'), keypairComB.publicKey)
|
||||
console.log('jweComA=', jweComA)
|
||||
jwsComA = await encode(new EncryptedJWEJwtPayloadType(jweComA), keypairComA.privateKey)
|
||||
console.log('jwsComA=', jwsComA)
|
||||
jweComB = await encrypt(new OpenConnectionJwtPayloadType('http://localhost:5002/api/'), keypairComA.publicKey)
|
||||
console.log('jweComB=', jweComB)
|
||||
jwsComB = await encode(new EncryptedJWEJwtPayloadType(jweComB), keypairComB.privateKey)
|
||||
console.log('jwsComB=', jwsComB)
|
||||
})
|
||||
it('verify jwsComA', async () => {
|
||||
expect(await verify(jwsComA, keypairComA.publicKey)).toEqual(expect.objectContaining({
|
||||
payload: expect.objectContaining({
|
||||
jwe: jweComA,
|
||||
tokentype: EncryptedJWEJwtPayloadType.ENCRYPTED_JWE_TYPE,
|
||||
})
|
||||
}))
|
||||
})
|
||||
it('verify jwsComB', async () => {
|
||||
expect(await verify(jwsComB, keypairComB.publicKey)).toEqual(expect.objectContaining({
|
||||
payload: expect.objectContaining({
|
||||
jwe: jweComB,
|
||||
tokentype: EncryptedJWEJwtPayloadType.ENCRYPTED_JWE_TYPE,
|
||||
})
|
||||
}))
|
||||
})
|
||||
it('decrypt jweComA', async () => {
|
||||
expect(JSON.parse(await decrypt(jweComA, keypairComB.privateKey))).toEqual(expect.objectContaining({
|
||||
tokentype: OpenConnectionJwtPayloadType.OPEN_CONNECTION_TYPE,
|
||||
url: 'http://localhost:5001/api/',
|
||||
}))
|
||||
})
|
||||
it('decrypt jweComB', async () => {
|
||||
expect(JSON.parse(await decrypt(jweComB, keypairComA.privateKey))).toEqual(expect.objectContaining({
|
||||
tokentype: OpenConnectionJwtPayloadType.OPEN_CONNECTION_TYPE,
|
||||
url: 'http://localhost:5002/api/',
|
||||
}))
|
||||
})
|
||||
})
|
||||
@ -1,16 +1,28 @@
|
||||
|
||||
import { GeneralEncrypt, KeyLike, SignJWT, decodeJwt, jwtVerify } from 'jose'
|
||||
import { generateKeyPair, exportSPKI, exportPKCS8, KeyLike, SignJWT, decodeJwt, generalDecrypt, importPKCS8, importSPKI, jwtVerify, CompactEncrypt, compactDecrypt } from 'jose'
|
||||
|
||||
import { GeneralJWE } from 'jose/dist/types/types'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
|
||||
import { JwtPayloadType } from './payloadtypes/JwtPayloadType'
|
||||
|
||||
export const verify = async (token: string, signkey: string): Promise<JwtPayloadType | null> => {
|
||||
export const createKeyPair = async (): Promise<{ publicKey: string; privateKey: string }> => {
|
||||
// Generate key pair using jose library
|
||||
const keyPair = await generateKeyPair('RS256');
|
||||
logger.debug(`Federation: writeJwtKeyPairInHomeCommunity generated keypair=`, keyPair);
|
||||
|
||||
// Convert keys to PEM format for storage in database
|
||||
const publicKeyPem = await exportSPKI(keyPair.publicKey);
|
||||
const privateKeyPem = await exportPKCS8(keyPair.privateKey);
|
||||
return { publicKey: publicKeyPem, privateKey: privateKeyPem };
|
||||
}
|
||||
|
||||
export const verify = async (token: string, publicKey: string): Promise<JwtPayloadType | null> => {
|
||||
if (!token) {
|
||||
throw new LogError('401 Unauthorized')
|
||||
}
|
||||
logger.info('JWT.verify... token, signkey=', token, signkey)
|
||||
logger.info('JWT.verify... token, publicKey=', token, publicKey)
|
||||
|
||||
try {
|
||||
/*
|
||||
@ -24,7 +36,12 @@ export const verify = async (token: string, signkey: string): Promise<JwtPayload
|
||||
logger.info('JWT.verify... keyObject.asymmetricKeyType=', keyObject.asymmetricKeyType)
|
||||
logger.info('JWT.verify... keyObject.asymmetricKeySize=', keyObject.asymmetricKeySize)
|
||||
*/
|
||||
const secret = new TextEncoder().encode(signkey)
|
||||
const importedKey = await importSPKI(publicKey, 'RS256')
|
||||
// Convert the key to JWK format if needed
|
||||
const secret = typeof importedKey === 'string'
|
||||
? JSON.parse(importedKey)
|
||||
: importedKey;
|
||||
// const secret = new TextEncoder().encode(publicKey)
|
||||
const { payload } = await jwtVerify(token, secret, {
|
||||
issuer: 'urn:gradido:issuer',
|
||||
audience: 'urn:gradido:audience',
|
||||
@ -37,14 +54,19 @@ export const verify = async (token: string, signkey: string): Promise<JwtPayload
|
||||
}
|
||||
}
|
||||
|
||||
export const encode = async (payload: JwtPayloadType, signkey: string): Promise<string> => {
|
||||
export const encode = async (payload: JwtPayloadType, privatekey: string): Promise<string> => {
|
||||
logger.info('JWT.encode... payload=', payload)
|
||||
logger.info('JWT.encode... signkey=', signkey)
|
||||
logger.info('JWT.encode... privatekey=', privatekey)
|
||||
try {
|
||||
const secret = new TextEncoder().encode(signkey)
|
||||
const importedKey = await importPKCS8(privatekey, 'RS256')
|
||||
const secret = typeof importedKey === 'string'
|
||||
? JSON.parse(importedKey)
|
||||
: importedKey;
|
||||
|
||||
// const secret = new TextEncoder().encode(privatekey)
|
||||
const token = await new SignJWT({ payload, 'urn:gradido:claim': true })
|
||||
.setProtectedHeader({
|
||||
alg: 'HS256',
|
||||
alg: 'RS256',
|
||||
})
|
||||
.setIssuedAt()
|
||||
.setIssuer('urn:gradido:issuer')
|
||||
@ -58,8 +80,8 @@ export const encode = async (payload: JwtPayloadType, signkey: string): Promise<
|
||||
}
|
||||
}
|
||||
|
||||
export const verifyJwtType = async (token: string, signkey: string): Promise<string> => {
|
||||
const payload = await verify(token, signkey)
|
||||
export const verifyJwtType = async (token: string, publicKey: string): Promise<string> => {
|
||||
const payload = await verify(token, publicKey)
|
||||
return payload ? payload.tokentype : 'unknown token type'
|
||||
}
|
||||
|
||||
@ -68,26 +90,33 @@ export const decode = (token: string): JwtPayloadType => {
|
||||
return payload as JwtPayloadType
|
||||
}
|
||||
|
||||
export const encrypt = async(payload: JwtPayloadType, encryptkey: KeyLike): Promise<string> => {
|
||||
export const encrypt = async (payload: JwtPayloadType, publicKey: string): Promise<string> => {
|
||||
logger.info('JWT.encrypt... payload=', payload)
|
||||
logger.info('JWT.encrypt... encryptkey=', encryptkey)
|
||||
logger.info('JWT.encrypt... publicKey=', publicKey)
|
||||
try {
|
||||
const encryptKey = await importSPKI(publicKey, 'RS256')
|
||||
// Convert the key to JWK format if needed
|
||||
const recipientKey = typeof encryptkey === 'string'
|
||||
? JSON.parse(encryptkey)
|
||||
: encryptkey;
|
||||
const recipientKey = typeof encryptKey === 'string'
|
||||
? JSON.parse(encryptKey)
|
||||
: encryptKey;
|
||||
|
||||
const jwe = await new GeneralEncrypt(
|
||||
const jwe = await new CompactEncrypt(
|
||||
new TextEncoder().encode(JSON.stringify(payload)),
|
||||
)
|
||||
.setProtectedHeader({ enc: 'A256GCM' })
|
||||
.addRecipient(recipientKey)
|
||||
.setUnprotectedHeader({ alg: 'ECDH-ES+A256KW' })
|
||||
.encrypt()
|
||||
.setProtectedHeader({ alg: 'RSA-OAEP-256', enc: 'A256GCM' })
|
||||
.encrypt(recipientKey)
|
||||
/*
|
||||
const jwe = await new EncryptJWT({ payload, 'urn:gradido:claim': true })
|
||||
.setProtectedHeader({ alg: 'RSA-OAEP-256', enc: 'A256GCM' })
|
||||
.setIssuedAt()
|
||||
.setIssuer('urn:gradido:issuer')
|
||||
.setAudience('urn:gradido:audience')
|
||||
.setExpirationTime('5m')
|
||||
.encrypt(recipientKey);
|
||||
|
||||
const token = await new EncryptJWT({ payload, 'urn:gradido:claim': true })
|
||||
.setProtectedHeader({
|
||||
alg: 'HS256',
|
||||
alg: 'RS256',
|
||||
enc: 'A256GCM',
|
||||
})
|
||||
.setIssuedAt()
|
||||
@ -102,4 +131,32 @@ export const encrypt = async(payload: JwtPayloadType, encryptkey: KeyLike): Prom
|
||||
logger.error('Failed to encrypt JWT:', e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const decrypt = async(jwe: string, privateKey: string): Promise<string> => {
|
||||
logger.info('JWT.decrypt... jwe=', jwe)
|
||||
logger.info('JWT.decrypt... privateKey=', privateKey)
|
||||
try {
|
||||
const decryptKey = await importPKCS8(privateKey, 'RS256')
|
||||
const { plaintext, protectedHeader } =
|
||||
await compactDecrypt(jwe, decryptKey)
|
||||
logger.info('JWT.decrypt... plaintext=', plaintext)
|
||||
logger.info('JWT.decrypt... protectedHeader=', protectedHeader)
|
||||
return plaintext.toString()
|
||||
/*
|
||||
const generalJwe = await GeneralJWE.parse(jwe)
|
||||
const jws = await generalDecrypt(generalJwe, privateKey, { alg: 'ECDH-ES+A256KW', enc: 'A256GCM' })
|
||||
|
||||
const { payload, protectedHeader } = await jwtDecrypt(jwe, privateKey);
|
||||
|
||||
console.log(payload);
|
||||
console.log(protectedHeader);
|
||||
|
||||
logger.info('JWT.decrypt... jws=', jws)
|
||||
return jws.toString()
|
||||
*/
|
||||
} catch (e) {
|
||||
logger.error('Failed to decrypt JWT:', e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
// import { JWTPayload } from 'jose'
|
||||
import { JwtPayloadType } from './JwtPayloadType'
|
||||
|
||||
export class EncryptedJWEJwtPayloadType extends JwtPayloadType {
|
||||
static ENCRYPTED_JWE_TYPE = 'encrypted-jwe'
|
||||
|
||||
jwe: string
|
||||
|
||||
constructor(
|
||||
jwe: string,
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
super()
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
this.tokentype = EncryptedJWEJwtPayloadType.ENCRYPTED_JWE_TYPE
|
||||
this.jwe = jwe
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user