X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fhelpers%2Fpeertube-crypto.ts;h=ab9ec077ee3957806d6009207d5349fedb0209f6;hb=5c6d985faeef1d6793d3f44ca6374f1a9b722806;hp=c61abfa8e70f43fd23c1965ae4b005054f149d35;hpb=9a27cdc27c900feaae5f6db4315c4ccdfc0c4493;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index c61abfa8e..ab9ec077e 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts @@ -1,17 +1,14 @@ -import { - PRIVATE_RSA_KEY_SIZE, - BCRYPT_SALT_SIZE -} from '../initializers' -import { - bcryptComparePromise, - bcryptGenSaltPromise, - bcryptHashPromise, - createPrivateKey, - getPublicKey -} from './core-utils' +import { Request } from 'express' +import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers' +import { ActorModel } from '../models/activitypub/actor' +import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey, sha256 } from './core-utils' +import { jsig, jsonld } from './custom-jsonld-signature' import { logger } from './logger' -import { AccountInstance } from '../models/account/account-interface' -import { jsig } from './custom-jsonld-signature' +import { cloneDeep } from 'lodash' +import { createVerify } from 'crypto' +import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' + +const httpSignature = require('http-signature') async function createPrivateAndPublicKeys () { logger.info('Generating a RSA key...') @@ -22,18 +19,57 @@ async function createPrivateAndPublicKeys () { return { privateKey: key, publicKey } } -function isSignatureVerified (fromAccount: AccountInstance, signedDocument: object) { +// User password checks + +function comparePassword (plainPassword: string, hashPassword: string) { + return bcryptComparePromise(plainPassword, hashPassword) +} + +async function cryptPassword (password: string) { + const salt = await bcryptGenSaltPromise(BCRYPT_SALT_SIZE) + + return bcryptHashPromise(password, salt) +} + +// HTTP Signature + +function isHTTPSignatureDigestValid (rawBody: Buffer, req: Request): boolean { + if (req.headers[HTTP_SIGNATURE.HEADER_NAME] && req.headers['digest']) { + return buildDigest(rawBody.toString()) === req.headers['digest'] + } + + return true +} + +function isHTTPSignatureVerified (httpSignatureParsed: any, actor: ActorModel): boolean { + return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true +} + +function parseHTTPSignature (req: Request, clockSkew?: number) { + return httpSignature.parse(req, { authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, clockSkew }) +} + +// JSONLD + +async function isJsonLDSignatureVerified (fromActor: ActorModel, 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 publicKeyObject = { '@context': jsig.SECURITY_CONTEXT_URL, - '@id': fromAccount.url, - '@type': 'CryptographicKey', - owner: fromAccount.url, - publicKeyPem: fromAccount.publicKey + id: fromActor.url, + type: 'CryptographicKey', + owner: fromActor.url, + publicKeyPem: fromActor.publicKey } const publicKeyOwnerObject = { '@context': jsig.SECURITY_CONTEXT_URL, - '@id': fromAccount.url, + id: fromActor.url, publicKey: [ publicKeyObject ] } @@ -42,38 +78,72 @@ function isSignatureVerified (fromAccount: AccountInstance, signedDocument: obje publicKeyOwner: publicKeyOwnerObject } - return jsig.promises.verify(signedDocument, options) - .catch(err => { - logger.error('Cannot check signature.', err) - return false - }) + return jsig.promises + .verify(signedDocument, options) + .then((result: { verified: boolean }) => result.verified) + .catch(err => { + logger.error('Cannot check signature.', { err }) + return false + }) } -function signObject (byAccount: AccountInstance, data: any) { - const options = { - privateKeyPem: byAccount.privateKey, - creator: byAccount.url +// Backward compatibility with "other" implementations +async function isJsonLDRSA2017Verified (fromActor: ActorModel, signedDocument: any) { + function hash (obj: any): Promise { + return jsonld.promises + .normalize(obj, { + algorithm: 'URDNA2015', + format: 'application/n-quads' + }) + .then(res => sha256(res)) } - return jsig.promises.sign(data, options) -} + 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 -function comparePassword (plainPassword: string, hashPassword: string) { - return bcryptComparePromise(plainPassword, hashPassword) + const docWithoutSignature = cloneDeep(signedDocument) + delete docWithoutSignature.signature + + const [ documentHash, optionsHash ] = await Promise.all([ + hash(docWithoutSignature), + hash(signatureCopy) + ]) + + const toVerify = optionsHash + documentHash + + const verify = createVerify('RSA-SHA256') + verify.update(toVerify, 'utf8') + + return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64') } -async function cryptPassword (password: string) { - const salt = await bcryptGenSaltPromise(BCRYPT_SALT_SIZE) +function signJsonLDObject (byActor: ActorModel, data: any) { + const options = { + privateKeyPem: byActor.privateKey, + creator: byActor.url, + algorithm: 'RsaSignature2017' + } - return bcryptHashPromise(password, salt) + return jsig.promises.sign(data, options) } // --------------------------------------------------------------------------- export { - isSignatureVerified, + isHTTPSignatureDigestValid, + parseHTTPSignature, + isHTTPSignatureVerified, + isJsonLDSignatureVerified, comparePassword, createPrivateAndPublicKeys, cryptPassword, - signObject + signJsonLDObject }