From c2c9e98787b55f02b7a0c004193bac4492483c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 20 Mar 2019 12:33:10 +0100 Subject: [PATCH] Fix http signature unit test --- .../security/httpSignature.spec.js | 131 ++++++++++-------- src/activitypub/security/index.js | 15 +- src/activitypub/utils/index.js | 7 +- 3 files changed, 88 insertions(+), 65 deletions(-) diff --git a/src/activitypub/security/httpSignature.spec.js b/src/activitypub/security/httpSignature.spec.js index fe09eda8a..bddb05ea9 100644 --- a/src/activitypub/security/httpSignature.spec.js +++ b/src/activitypub/security/httpSignature.spec.js @@ -1,69 +1,84 @@ -import { createSignature, verifySignature } from '.' -import Factory from '../../seed/factories' -import { host, login } from '../../jest/helpers' -import { GraphQLClient } from 'graphql-request' +import { generateRsaKeyPair, createSignature, verifySignature } from '.' import crypto from 'crypto' -import { expect } from 'chai' -const factory = Factory() +jest.mock('request') +import request from 'request' -describe('Signature creation and verification', () => { - let user = null - let client = null - const headers = { - 'Date': '2019-03-08T14:35:45.759Z', - 'Host': 'democracy-app.de', - 'Content-Type': 'application/json' - } +let privateKey +let publicKey +let headers +const passphrase = "a7dsf78sadg87ad87sfagsadg78" - beforeEach(async () => { - await factory.create('User', { - 'slug': 'test-user', - 'name': 'Test User', - 'email': 'user@example.org', - 'password': 'swordfish' +describe('activityPub/security', () => { + beforeEach(() => { + const pair = generateRsaKeyPair({passphrase}) + privateKey = pair.privateKey + publicKey = pair.publicKey + headers = { + 'Date': '2019-03-08T14:35:45.759Z', + 'Host': 'democracy-app.de', + 'Content-Type': 'application/json' + } + }) + + describe('createSignature', () => { + describe('returned http signature', () => { + let signatureB64 + let httpSignature + + beforeEach(() => { + const signer = crypto.createSign('rsa-sha256') + signer.update('(request-target): post /activitypub/users/max/inbox\ndate: 2019-03-08T14:35:45.759Z\nhost: democracy-app.de\ncontent-type: application/json') + signatureB64 = signer.sign({ key: privateKey, passphrase: 'a7dsf78sadg87ad87sfagsadg78' }, 'base64') + httpSignature = createSignature({privateKey, keyId: 'https://human-connection.org/activitypub/users/lea#main-key', url: 'https://democracy-app.de/activitypub/users/max/inbox', headers, passphrase}) + }) + + it('contains keyId', () => { + expect(httpSignature).toContain('keyId="https://human-connection.org/activitypub/users/lea#main-key"') + }) + + it('contains default algorithm "rsa-sha256"', () => { + expect(httpSignature).toContain('algorithm="rsa-sha256"') + }) + + it('contains headers', () => { + expect(httpSignature).toContain('headers="(request-target) date host content-type"') + }) + + it('contains signature', () => { + expect(httpSignature).toContain('signature="' + signatureB64 + '"') + }) }) - const headers = await login({ email: 'user@example.org', password: 'swordfish' }) - client = new GraphQLClient(host, { headers }) - const result = await client.request(`query { - User(slug: "test-user") { - privateKey - publicKey + }) + + describe('verifySignature', () => { + let httpSignature + + beforeEach(() => { + httpSignature = createSignature({privateKey, keyId: 'http://localhost:4001/activitypub/users/test-user#main-key', url: 'https://democracy-app.de/activitypub/users/max/inbox', headers, passphrase}) + const body = { + "publicKey":{ + "id":"https://localhost:4001/activitypub/users/test-user#main-key", + "owner":"https://localhost:4001/activitypub/users/test-user", + "publicKeyPem": publicKey + } } - }`) - user = result.User[0] - }) - afterEach(async () => { - await factory.cleanDatabase() - }) - - describe('Signature creation', () => { - let signatureB64 = '' - beforeEach(() => { - const signer = crypto.createSign('rsa-sha256') - signer.update('(request-target): post /activitypub/users/max/inbox\ndate: 2019-03-08T14:35:45.759Z\nhost: democracy-app.de\ncontent-type: application/json') - signatureB64 = signer.sign({ key: user.privateKey, passphrase: 'a7dsf78sadg87ad87sfagsadg78' }, 'base64') - }) - it('creates a Signature with given privateKey, keyId, url and headers (default algorithm: "rsa-sha256")', () => { - const httpSignature = createSignature(user.privateKey, 'https://human-connection.org/activitypub/users/lea#main-key', 'https://democracy-app.de/activitypub/users/max/inbox', headers) - - expect(httpSignature).to.contain('keyId="https://human-connection.org/activitypub/users/lea#main-key"') - expect(httpSignature).to.contain('algorithm="rsa-sha256"') - expect(httpSignature).to.contain('headers="(request-target) date host content-type"') - expect(httpSignature).to.contain('signature="' + signatureB64 + '"') - }) - }) - - describe('Signature verification', () => { - let httpSignature = '' - beforeEach(() => { - httpSignature = createSignature(user.privateKey, 'http://localhost:4001/activitypub/users/test-user#main-key', 'https://democracy-app.de/activitypub/users/max/inbox', headers) + const mockedRequest = jest.fn((_, callback) => callback(null, null, JSON.stringify(body))) + request.mockImplementation(mockedRequest) }) - it('verifies a Signature correct', async () => { - headers['Signature'] = httpSignature - const isVerified = await verifySignature('https://democracy-app.de/activitypub/users/max/inbox', headers) - expect(isVerified).to.equal(true) + it('resolves false', async () => { + await expect(verifySignature('https://democracy-app.de/activitypub/users/max/inbox', headers)).resolves.toEqual(false) + }) + + describe('valid signature', () => { + beforeEach(() => { + headers.Signature = httpSignature + }) + + it('resolves true', async () => { + await expect(verifySignature('https://democracy-app.de/activitypub/users/max/inbox', headers)).resolves.toEqual(true) + }) }) }) }) diff --git a/src/activitypub/security/index.js b/src/activitypub/security/index.js index 412084022..fdb1e27c6 100644 --- a/src/activitypub/security/index.js +++ b/src/activitypub/security/index.js @@ -6,7 +6,8 @@ const debug = require('debug')('ea:security') dotenv.config({ path: resolve('src', 'activitypub', '.env') }) -export function generateRsaKeyPair () { +export function generateRsaKeyPair (options = {}) { + const { passphrase = process.env.PRIVATE_KEY_PASSPHRASE } = options return crypto.generateKeyPairSync('rsa', { modulusLength: 4096, publicKeyEncoding: { @@ -17,18 +18,24 @@ export function generateRsaKeyPair () { type: 'pkcs8', format: 'pem', cipher: 'aes-256-cbc', - passphrase: process.env.PRIVATE_KEY_PASSPHRASE + passphrase } }) } // signing -export function createSignature (privKey, keyId, url, headers = {}, algorithm = 'rsa-sha256') { +export function createSignature (options) { + const { + privateKey, keyId, url, + headers = {}, + algorithm = 'rsa-sha256', + passphrase = process.env.PRIVATE_KEY_PASSPHRASE + } = options if (!SUPPORTED_HASH_ALGORITHMS.includes(algorithm)) { throw Error(`SIGNING: Unsupported hashing algorithm = ${algorithm}`) } const signer = crypto.createSign(algorithm) const signingString = constructSigningString(url, headers) signer.update(signingString) - const signatureB64 = signer.sign({ key: privKey, passphrase: process.env.PRIVATE_KEY_PASSPHRASE }, 'base64') + const signatureB64 = signer.sign({ key: privateKey, passphrase }, 'base64') const headersString = Object.keys(headers).reduce((result, key) => { return result + ' ' + key.toLowerCase() }, '') return `keyId="${keyId}",algorithm="${algorithm}",headers="(request-target)${headersString}",signature="${signatureB64}"` } diff --git a/src/activitypub/utils/index.js b/src/activitypub/utils/index.js index 0cb4b7d0d..6940ca082 100644 --- a/src/activitypub/utils/index.js +++ b/src/activitypub/utils/index.js @@ -75,12 +75,13 @@ export function signAndSend (activity, fromName, targetDomain, url) { headers: { 'Host': targetDomain, 'Date': date, - 'Signature': createSignature(privateKey, `http://${activityPub.domain}/activitypub/users/${fromName}#main-key`, url, - { + 'Signature': createSignature({privateKey, keyId: `http://${activityPub.domain}/activitypub/users/${fromName}#main-key`, url, + headers: { 'Host': targetDomain, 'Date': date, 'Content-Type': 'application/activity+json' - }), + } + }), 'Content-Type': 'application/activity+json' }, method: 'POST',