X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fhelpers%2Fpeertube-crypto.ts;h=1d9cab2ce282e86b5f7095098524e608fc0a5eb8;hb=0b6f531653a7a24f82ad65564479a70a9326301a;hp=085cd62c90df1cc5d05610f9b6f9f04f6bf5634d;hpb=5c5e587307a27e173333789b5b5167d35f468b01;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index 085cd62c9..1d9cab2ce 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts @@ -1,28 +1,24 @@ +import { compare, genSalt, hash } from 'bcrypt' +import { createSign, createVerify } from 'crypto' import { Request } from 'express' +import { cloneDeep } from 'lodash' +import { sha256 } from '@shared/extra-utils' 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 { MActor } from '../types/models' +import { generateRSAKeyPairPromise, promisify1, promisify2 } from './core-utils' +import { jsonld } from './custom-jsonld-signature' import { logger } from './logger' -import { cloneDeep } from 'lodash' -import { createVerify } from 'crypto' -import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' -import * as bcrypt from 'bcrypt' -import { MActor } from '../typings/models' -const bcryptComparePromise = promisify2(bcrypt.compare) -const bcryptGenSaltPromise = promisify1(bcrypt.genSalt) -const bcryptHashPromise = promisify2(bcrypt.hash) +const bcryptComparePromise = promisify2(compare) +const bcryptGenSaltPromise = promisify1(genSalt) +const bcryptHashPromise = promisify2(hash) -const httpSignature = require('http-signature') +const httpSignature = require('@peertube/http-signature') -async function createPrivateAndPublicKeys () { +function createPrivateAndPublicKeys () { logger.info('Generating a RSA key...') - const { key } = await createPrivateKey(PRIVATE_RSA_KEY_SIZE) - const { publicKey } = await getPublicKey(key) - - return { privateKey: key, publicKey } + return generateRSAKeyPairPromise(PRIVATE_RSA_KEY_SIZE) } // User password checks @@ -52,75 +48,37 @@ function isHTTPSignatureVerified (httpSignatureParsed: any, actor: MActor): bool } function parseHTTPSignature (req: Request, clockSkew?: number) { - return httpSignature.parse(req, { authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, clockSkew }) -} + const requiredHeaders = req.method === 'POST' + ? [ '(request-target)', 'host', 'digest' ] + : [ '(request-target)', 'host' ] -// JSONLD + const parsed = httpSignature.parse(req, { clockSkew, headers: requiredHeaders }) -async 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 + const parsedHeaders = parsed.params.headers + if (!parsedHeaders.includes('date') && !parsedHeaders.includes('(created)')) { + throw new Error(`date or (created) must be included in signature`) } - const publicKeyObject = { - '@context': jsig.SECURITY_CONTEXT_URL, - id: fromActor.url, - type: 'CryptographicKey', - owner: fromActor.url, - publicKeyPem: fromActor.publicKey - } + return parsed +} - const publicKeyOwnerObject = { - '@context': jsig.SECURITY_CONTEXT_URL, - id: fromActor.url, - publicKey: [ publicKeyObject ] - } +// JSONLD - const options = { - publicKey: publicKeyObject, - publicKeyOwner: publicKeyOwnerObject +function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise { + if (signedDocument.signature.type === 'RsaSignature2017') { + return isJsonLDRSA2017Verified(fromActor, signedDocument) } - return jsig.promises - .verify(signedDocument, options) - .then((result: { verified: boolean }) => result.verified) - .catch(err => { - logger.error('Cannot check signature.', { err }) - return false - }) + logger.warn('Unknown JSON LD signature %s.', signedDocument.signature.type, signedDocument) + + 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 +89,33 @@ 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: T) { + 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 }) +} + +function buildDigest (body: any) { + const rawBody = typeof body === 'string' ? body : JSON.stringify(body) + + return 'SHA-256=' + sha256(rawBody, 'base64') } // --------------------------------------------------------------------------- @@ -147,6 +124,7 @@ export { isHTTPSignatureDigestValid, parseHTTPSignature, isHTTPSignatureVerified, + buildDigest, isJsonLDSignatureVerified, comparePassword, createPrivateAndPublicKeys, @@ -155,3 +133,35 @@ export { } // --------------------------------------------------------------------------- + +function hashObject (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 hashObject(signatureCopy) +} + +function createDocWithoutSignatureHash (doc: any) { + const docWithoutSignature = cloneDeep(doc) + delete docWithoutSignature.signature + + return hashObject(docWithoutSignature) +}