aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2019-08-29 16:15:41 +0200
committerChocobozzz <me@florianbigard.com>2019-08-29 16:15:41 +0200
commitad513607a3886cfabe083531c42911bc3c67bdfb (patch)
tree275af782814ad3aa62da4834ec5696467aacdd87 /server
parentf0a47bc92aa20b91b46197a4d3fc430aea962848 (diff)
downloadPeerTube-ad513607a3886cfabe083531c42911bc3c67bdfb.tar.gz
PeerTube-ad513607a3886cfabe083531c42911bc3c67bdfb.tar.zst
PeerTube-ad513607a3886cfabe083531c42911bc3c67bdfb.zip
Remove old JSON LD signature implementation
Only PeerTube was compatible with it, and the library has moved on RsaSignature2018 and removed RsaSignature2017 support. We had to create a dirty fork of the RsaSignature2017 branch, which is not ideal. Now we use the Mastodon implementation, that most other AP implementations that support JSONLD signatures use.
Diffstat (limited to 'server')
-rw-r--r--server/helpers/custom-jsonld-signature.ts5
-rw-r--r--server/helpers/peertube-crypto.ts121
-rw-r--r--server/middlewares/activitypub.ts2
-rw-r--r--server/tests/api/activitypub/helpers.ts13
4 files changed, 61 insertions, 80 deletions
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 @@
1import * as AsyncLRU from 'async-lru' 1import * as AsyncLRU from 'async-lru'
2import * as jsonld from 'jsonld' 2import * as jsonld from 'jsonld'
3import * as jsig from 'jsonld-signatures'
4import { logger } from './logger' 3import { logger } from './logger'
5 4
6const CACHE = { 5const CACHE = {
@@ -79,6 +78,4 @@ jsonld.documentLoader = (url, cb) => {
79 lru.get(url, cb) 78 lru.get(url, cb)
80} 79}
81 80
82jsig.use('jsonld', jsonld) 81export { jsonld }
83
84export { jsig, 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 @@
1import { Request } from 'express' 1import { Request } from 'express'
2import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' 2import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants'
3import { ActorModel } from '../models/activitypub/actor'
4import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils' 3import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils'
5import { jsig, jsonld } from './custom-jsonld-signature' 4import { jsonld } from './custom-jsonld-signature'
6import { logger } from './logger' 5import { logger } from './logger'
7import { cloneDeep } from 'lodash' 6import { cloneDeep } from 'lodash'
8import { createVerify } from 'crypto' 7import { createSign, createVerify } from 'crypto'
9import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' 8import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils'
10import * as bcrypt from 'bcrypt' 9import * as bcrypt from 'bcrypt'
11import { MActor } from '../typings/models' 10import { MActor } from '../typings/models'
@@ -57,70 +56,21 @@ function parseHTTPSignature (req: Request, clockSkew?: number) {
57 56
58// JSONLD 57// JSONLD
59 58
60async function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> { 59function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> {
61 if (signedDocument.signature.type === 'RsaSignature2017') { 60 if (signedDocument.signature.type === 'RsaSignature2017') {
62 // Mastodon algorithm 61 return isJsonLDRSA2017Verified(fromActor, signedDocument)
63 const res = await isJsonLDRSA2017Verified(fromActor, signedDocument)
64 // Success? If no, try with our library
65 if (res === true) return true
66 } 62 }
67 63
68 const publicKeyObject = { 64 logger.warn('Unknown JSON LD signature %s.', signedDocument.signature.type, signedDocument)
69 '@context': jsig.SECURITY_CONTEXT_URL,
70 id: fromActor.url,
71 type: 'CryptographicKey',
72 owner: fromActor.url,
73 publicKeyPem: fromActor.publicKey
74 }
75
76 const publicKeyOwnerObject = {
77 '@context': jsig.SECURITY_CONTEXT_URL,
78 id: fromActor.url,
79 publicKey: [ publicKeyObject ]
80 }
81 65
82 const options = { 66 return Promise.resolve(false)
83 publicKey: publicKeyObject,
84 publicKeyOwner: publicKeyOwnerObject
85 }
86
87 return jsig.promises
88 .verify(signedDocument, options)
89 .then((result: { verified: boolean }) => result.verified)
90 .catch(err => {
91 logger.error('Cannot check signature.', { err })
92 return false
93 })
94} 67}
95 68
96// Backward compatibility with "other" implementations 69// Backward compatibility with "other" implementations
97async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any) { 70async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any) {
98 function hash (obj: any): Promise<any> {
99 return jsonld.promises
100 .normalize(obj, {
101 algorithm: 'URDNA2015',
102 format: 'application/n-quads'
103 })
104 .then(res => sha256(res))
105 }
106
107 const signatureCopy = cloneDeep(signedDocument.signature)
108 Object.assign(signatureCopy, {
109 '@context': [
110 'https://w3id.org/security/v1',
111 { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
112 ]
113 })
114 delete signatureCopy.type
115 delete signatureCopy.id
116 delete signatureCopy.signatureValue
117
118 const docWithoutSignature = cloneDeep(signedDocument)
119 delete docWithoutSignature.signature
120
121 const [ documentHash, optionsHash ] = await Promise.all([ 71 const [ documentHash, optionsHash ] = await Promise.all([
122 hash(docWithoutSignature), 72 createDocWithoutSignatureHash(signedDocument),
123 hash(signatureCopy) 73 createSignatureHash(signedDocument.signature)
124 ]) 74 ])
125 75
126 const toVerify = optionsHash + documentHash 76 const toVerify = optionsHash + documentHash
@@ -131,14 +81,27 @@ async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any)
131 return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64') 81 return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64')
132} 82}
133 83
134function signJsonLDObject (byActor: MActor, data: any) { 84async function signJsonLDObject (byActor: MActor, data: any) {
135 const options = { 85 const signature = {
136 privateKeyPem: byActor.privateKey, 86 type: 'RsaSignature2017',
137 creator: byActor.url, 87 creator: byActor.url,
138 algorithm: 'RsaSignature2017' 88 created: new Date().toISOString()
139 } 89 }
140 90
141 return jsig.promises.sign(data, options) 91 const [ documentHash, optionsHash ] = await Promise.all([
92 createDocWithoutSignatureHash(data),
93 createSignatureHash(signature)
94 ])
95
96 const toSign = optionsHash + documentHash
97
98 const sign = createSign('RSA-SHA256')
99 sign.update(toSign, 'utf8')
100
101 const signatureValue = sign.sign(byActor.privateKey, 'base64')
102 Object.assign(signature, { signatureValue })
103
104 return Object.assign(data, { signature })
142} 105}
143 106
144// --------------------------------------------------------------------------- 107// ---------------------------------------------------------------------------
@@ -155,3 +118,35 @@ export {
155} 118}
156 119
157// --------------------------------------------------------------------------- 120// ---------------------------------------------------------------------------
121
122function hash (obj: any): Promise<any> {
123 return jsonld.promises
124 .normalize(obj, {
125 algorithm: 'URDNA2015',
126 format: 'application/n-quads'
127 })
128 .then(res => sha256(res))
129}
130
131function createSignatureHash (signature: any) {
132 const signatureCopy = cloneDeep(signature)
133 Object.assign(signatureCopy, {
134 '@context': [
135 'https://w3id.org/security/v1',
136 { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
137 ]
138 })
139
140 delete signatureCopy.type
141 delete signatureCopy.id
142 delete signatureCopy.signatureValue
143
144 return hash(signatureCopy)
145}
146
147function createDocWithoutSignatureHash (doc: any) {
148 const docWithoutSignature = cloneDeep(doc)
149 delete docWithoutSignature.signature
150
151 return hash(docWithoutSignature)
152}
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) {
101 const verified = await isJsonLDSignatureVerified(actor, req.body) 101 const verified = await isJsonLDSignatureVerified(actor, req.body)
102 102
103 if (verified !== true) { 103 if (verified !== true) {
104 logger.warn('Signature not verified.', req.body)
105
104 res.sendStatus(403) 106 res.sendStatus(403)
105 return false 107 return false
106 } 108 }
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 () {
53 expect(result).to.be.false 53 expect(result).to.be.false
54 }) 54 })
55 55
56 it('Should fail with an invalid PeerTube URL', async function () {
57 const keys = require('./json/peertube/keys.json')
58 const body = require('./json/peertube/announce-without-context.json')
59
60 const actorSignature = { url: 'http://localhost:9002/accounts/peertube', privateKey: keys.privateKey }
61 const signedBody = await buildSignedActivity(actorSignature as any, body)
62
63 const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9003/accounts/peertube' }
64 const result = await isJsonLDSignatureVerified(fromActor as any, signedBody)
65
66 expect(result).to.be.false
67 })
68
69 it('Should succeed with a valid PeerTube signature', async function () { 56 it('Should succeed with a valid PeerTube signature', async function () {
70 const keys = require('./json/peertube/keys.json') 57 const keys = require('./json/peertube/keys.json')
71 const body = require('./json/peertube/announce-without-context.json') 58 const body = require('./json/peertube/announce-without-context.json')