diff --git a/package.json b/package.json index ce689a4b3..0c1ec93d1 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,6 @@ "iso-639-3": "^1.0.1", "js-yaml": "^3.5.4", "jsonld": "~1.1.0", - "jsonld-signatures": "https://github.com/Chocobozzz/jsonld-signatures#rsa2017", "lodash": "^4.17.10", "lru-cache": "^5.1.1", "magnet-uri": "^5.1.4", diff --git a/server/helpers/custom-jsonld-signature.ts b/server/helpers/custom-jsonld-signature.ts index a3bceb047..cb07fa3b2 100644 --- a/server/helpers/custom-jsonld-signature.ts +++ b/server/helpers/custom-jsonld-signature.ts @@ -1,6 +1,5 @@ import * as AsyncLRU from 'async-lru' import * as jsonld from 'jsonld' -import * as jsig from 'jsonld-signatures' import { logger } from './logger' const CACHE = { @@ -79,6 +78,4 @@ jsonld.documentLoader = (url, cb) => { lru.get(url, cb) } -jsig.use('jsonld', jsonld) - -export { jsig, jsonld } +export { jsonld } diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index 085cd62c9..9eb782302 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts @@ -1,11 +1,10 @@ import { Request } from 'express' import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' -import { ActorModel } from '../models/activitypub/actor' import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils' -import { jsig, jsonld } from './custom-jsonld-signature' +import { jsonld } from './custom-jsonld-signature' import { logger } from './logger' import { cloneDeep } from 'lodash' -import { createVerify } from 'crypto' +import { createSign, createVerify } from 'crypto' import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' import * as bcrypt from 'bcrypt' import { MActor } from '../typings/models' @@ -57,70 +56,21 @@ function parseHTTPSignature (req: Request, clockSkew?: number) { // JSONLD -async function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise { +function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise { if (signedDocument.signature.type === 'RsaSignature2017') { - // Mastodon algorithm - const res = await isJsonLDRSA2017Verified(fromActor, signedDocument) - // Success? If no, try with our library - if (res === true) return true + return isJsonLDRSA2017Verified(fromActor, signedDocument) } - const publicKeyObject = { - '@context': jsig.SECURITY_CONTEXT_URL, - id: fromActor.url, - type: 'CryptographicKey', - owner: fromActor.url, - publicKeyPem: fromActor.publicKey - } + logger.warn('Unknown JSON LD signature %s.', signedDocument.signature.type, signedDocument) - const publicKeyOwnerObject = { - '@context': jsig.SECURITY_CONTEXT_URL, - id: fromActor.url, - publicKey: [ publicKeyObject ] - } - - const options = { - publicKey: publicKeyObject, - publicKeyOwner: publicKeyOwnerObject - } - - return jsig.promises - .verify(signedDocument, options) - .then((result: { verified: boolean }) => result.verified) - .catch(err => { - logger.error('Cannot check signature.', { err }) - return false - }) + return Promise.resolve(false) } // Backward compatibility with "other" implementations async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any) { - function hash (obj: any): Promise { - return jsonld.promises - .normalize(obj, { - algorithm: 'URDNA2015', - format: 'application/n-quads' - }) - .then(res => sha256(res)) - } - - const signatureCopy = cloneDeep(signedDocument.signature) - Object.assign(signatureCopy, { - '@context': [ - 'https://w3id.org/security/v1', - { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' } - ] - }) - delete signatureCopy.type - delete signatureCopy.id - delete signatureCopy.signatureValue - - const docWithoutSignature = cloneDeep(signedDocument) - delete docWithoutSignature.signature - const [ documentHash, optionsHash ] = await Promise.all([ - hash(docWithoutSignature), - hash(signatureCopy) + createDocWithoutSignatureHash(signedDocument), + createSignatureHash(signedDocument.signature) ]) const toVerify = optionsHash + documentHash @@ -131,14 +81,27 @@ async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any) return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64') } -function signJsonLDObject (byActor: MActor, data: any) { - const options = { - privateKeyPem: byActor.privateKey, +async function signJsonLDObject (byActor: MActor, data: any) { + const signature = { + type: 'RsaSignature2017', creator: byActor.url, - algorithm: 'RsaSignature2017' + created: new Date().toISOString() } - return jsig.promises.sign(data, options) + const [ documentHash, optionsHash ] = await Promise.all([ + createDocWithoutSignatureHash(data), + createSignatureHash(signature) + ]) + + const toSign = optionsHash + documentHash + + const sign = createSign('RSA-SHA256') + sign.update(toSign, 'utf8') + + const signatureValue = sign.sign(byActor.privateKey, 'base64') + Object.assign(signature, { signatureValue }) + + return Object.assign(data, { signature }) } // --------------------------------------------------------------------------- @@ -155,3 +118,35 @@ export { } // --------------------------------------------------------------------------- + +function hash (obj: any): Promise { + return jsonld.promises + .normalize(obj, { + algorithm: 'URDNA2015', + format: 'application/n-quads' + }) + .then(res => sha256(res)) +} + +function createSignatureHash (signature: any) { + const signatureCopy = cloneDeep(signature) + Object.assign(signatureCopy, { + '@context': [ + 'https://w3id.org/security/v1', + { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' } + ] + }) + + delete signatureCopy.type + delete signatureCopy.id + delete signatureCopy.signatureValue + + return hash(signatureCopy) +} + +function createDocWithoutSignatureHash (doc: any) { + const docWithoutSignature = cloneDeep(doc) + delete docWithoutSignature.signature + + return hash(docWithoutSignature) +} diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts index b1e5b5236..bea213d27 100644 --- a/server/middlewares/activitypub.ts +++ b/server/middlewares/activitypub.ts @@ -101,6 +101,8 @@ async function checkJsonLDSignature (req: Request, res: Response) { const verified = await isJsonLDSignatureVerified(actor, req.body) if (verified !== true) { + logger.warn('Signature not verified.', req.body) + res.sendStatus(403) return false } diff --git a/server/tests/api/activitypub/helpers.ts b/server/tests/api/activitypub/helpers.ts index 365d0e1ae..0d1f154fe 100644 --- a/server/tests/api/activitypub/helpers.ts +++ b/server/tests/api/activitypub/helpers.ts @@ -53,19 +53,6 @@ describe('Test activity pub helpers', function () { expect(result).to.be.false }) - it('Should fail with an invalid PeerTube URL', async function () { - const keys = require('./json/peertube/keys.json') - const body = require('./json/peertube/announce-without-context.json') - - const actorSignature = { url: 'http://localhost:9002/accounts/peertube', privateKey: keys.privateKey } - const signedBody = await buildSignedActivity(actorSignature as any, body) - - const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9003/accounts/peertube' } - const result = await isJsonLDSignatureVerified(fromActor as any, signedBody) - - expect(result).to.be.false - }) - it('Should succeed with a valid PeerTube signature', async function () { const keys = require('./json/peertube/keys.json') const body = require('./json/peertube/announce-without-context.json') diff --git a/yarn.lock b/yarn.lock index f26763845..b5a3a3f47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -828,24 +828,6 @@ bindings@~1.3.0: resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5" integrity sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew== -bitcore-lib@^0.13.7: - version "0.13.19" - resolved "https://registry.yarnpkg.com/bitcore-lib/-/bitcore-lib-0.13.19.tgz#48af1e9bda10067c1ab16263472b5add2000f3dc" - integrity sha1-SK8em9oQBnwasWJjRyta3SAA89w= - dependencies: - bn.js "=2.0.4" - bs58 "=2.0.0" - buffer-compare "=1.0.0" - elliptic "=3.0.3" - inherits "=2.0.1" - lodash "=3.10.1" - -"bitcore-message@github:CoMakery/bitcore-message#dist": - version "1.0.2" - resolved "https://codeload.github.com/CoMakery/bitcore-message/tar.gz/8799cc327029c3d34fc725f05b2cf981363f6ebf" - dependencies: - bitcore-lib "^0.13.7" - bitfield@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/bitfield/-/bitfield-2.0.0.tgz#fbe6767592fe5b4c87ecf1d04126294cc1bfa837" @@ -968,16 +950,6 @@ bluebird@^3.0.5, bluebird@^3.5.0: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== -bn.js@=2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-2.0.4.tgz#220a7cd677f7f1bfa93627ff4193776fe7819480" - integrity sha1-Igp81nf38b+pNif/QZN3b+eBlIA= - -bn.js@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-2.2.0.tgz#12162bc2ae71fc40a5626c33438f3a875cd37625" - integrity sha1-EhYrwq5x/EClYmwzQ486h1zTdiU= - bn.js@^4.4.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" @@ -1043,11 +1015,6 @@ braces@^3.0.1: dependencies: fill-range "^7.0.1" -brorand@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= - browser-stdout@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" @@ -1058,11 +1025,6 @@ browserify-package-json@^1.0.0: resolved "https://registry.yarnpkg.com/browserify-package-json/-/browserify-package-json-1.0.1.tgz#98dde8aa5c561fd6d3fe49bbaa102b74b396fdea" integrity sha1-mN3oqlxWH9bT/km7qhArdLOW/eo= -bs58@=2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.0.tgz#72b713bed223a0ac518bbda0e3ce3f4817f39eb5" - integrity sha1-crcTvtIjoKxRi72g484/SBfznrU= - buffer-alloc-unsafe@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" @@ -1076,16 +1038,6 @@ buffer-alloc@^1.1.0, buffer-alloc@^1.2.0: buffer-alloc-unsafe "^1.1.0" buffer-fill "^1.0.0" -buffer-compare@=1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-compare/-/buffer-compare-1.0.0.tgz#acaa7a966e98eee9fae14b31c39a5f158fb3c4a2" - integrity sha1-rKp6lm6Y7un64Usxw5pfFY+zxKI= - -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= - buffer-equals@^1.0.3, buffer-equals@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/buffer-equals/-/buffer-equals-1.0.4.tgz#0353b54fd07fd9564170671ae6f66b9cf10d27f5" @@ -2096,13 +2048,6 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -ecdsa-sig-formatter@1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" - integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== - dependencies: - safe-buffer "^5.0.1" - ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -2113,16 +2058,6 @@ elegant-spinner@^1.0.1: resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4= -elliptic@=3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-3.0.3.tgz#865c9b420bfbe55006b9f969f97a0d2c44966595" - integrity sha1-hlybQgv75VAGuflp+XoNLESWZZU= - dependencies: - bn.js "^2.0.0" - brorand "^1.0.1" - hash.js "^1.0.0" - inherits "^2.0.1" - emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -3250,14 +3185,6 @@ has@^1.0.1, has@^1.0.3: dependencies: function-bind "^1.1.1" -hash.js@^1.0.0: - version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - hashish@~0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/hashish/-/hashish-0.0.4.tgz#6d60bc6ffaf711b6afd60e426d077988014e6554" @@ -3481,11 +3408,6 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -inherits@=2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= - ini@^1.3.4, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" @@ -4028,25 +3950,6 @@ jsonify@~0.0.0: resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= -"jsonld-signatures@https://github.com/Chocobozzz/jsonld-signatures#rsa2017": - version "1.2.2-2" - resolved "https://github.com/Chocobozzz/jsonld-signatures#77660963e722eb4541d2d255f9d9d4216329665f" - dependencies: - bitcore-message "github:CoMakery/bitcore-message#dist" - jsonld "^0.5.12" - jws "^3.1.4" - node-forge "^0.7.1" - -jsonld@^0.5.12: - version "0.5.21" - resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-0.5.21.tgz#4d5b78d717eb92bcd1ac9d88e34efad95370c0bf" - integrity sha512-1dQhaw1Eb3p7Cz5ECE2DNPwLvTmK+f6D45hACBdonJaFKP1bN9zlKLZWbPZQeZtduAc/LNv10J4ML0IiTBVahw== - dependencies: - rdf-canonize "^0.2.1" - request "^2.83.0" - semver "^5.5.0" - xmldom "0.1.19" - jsonld@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-1.1.0.tgz#afcb168c44557a7bddead4d4513c3cbcae3bc5b9" @@ -4082,23 +3985,6 @@ junk@^3.1.0: resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ== -jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" - integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jws@^3.1.4: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== - dependencies: - jwa "^1.4.1" - safe-buffer "^5.0.1" - k-bucket@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/k-bucket/-/k-bucket-4.0.1.tgz#3fc2e5693f0b7bff90d7b6b476edd6087955d542" @@ -4335,11 +4221,6 @@ lodash@4.17.4: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" integrity sha1-eCA6TRwyiuHYbcpkYONptX9AVa4= -lodash@=3.10.1: - version "3.10.1" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" - integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y= - lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.3.0, lodash@~4.17.10: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" @@ -4638,11 +4519,6 @@ mimic-response@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== -minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - minimatch@3.0.4, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"