X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fhelpers%2Fpeertube-crypto.ts;h=b8f7c782ae7b88ba88463d14f156d43d65a9dec1;hb=0c058f256a195b92f124be10109c95d1fbe93ad8;hp=89aef99c48e43a0c3d1195de7b86f6380bc97a77;hpb=ad0997adfb9e1e3b1ff54338d7558cf7b18440ea;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index 89aef99c4..b8f7c782a 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts @@ -1,155 +1,163 @@ -import * as crypto from 'crypto' -import * as Promise from 'bluebird' -import { join } from 'path' - -import { - SIGNATURE_ALGORITHM, - SIGNATURE_ENCODING, - PRIVATE_CERT_NAME, - CONFIG, - BCRYPT_SALT_SIZE, - PUBLIC_CERT_NAME -} from '../initializers' -import { - readFilePromise, - bcryptComparePromise, - bcryptGenSaltPromise, - bcryptHashPromise, - accessPromise, - opensslExecPromise -} from './core-utils' +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 { MActor } from '../types/models' +import { createPrivateKey, getPublicKey, promisify1, promisify2 } from './core-utils' +import { jsonld } from './custom-jsonld-signature' import { logger } from './logger' -function checkSignature (publicKey: string, data: string, hexSignature: string) { - const verify = crypto.createVerify(SIGNATURE_ALGORITHM) - - let dataString - if (typeof data === 'string') { - dataString = data - } else { - try { - dataString = JSON.stringify(data) - } catch (err) { - logger.error('Cannot check signature.', err) - return false - } - } +const bcryptComparePromise = promisify2(compare) +const bcryptGenSaltPromise = promisify1(genSalt) +const bcryptHashPromise = promisify2(hash) + +const httpSignature = require('@peertube/http-signature') + +async 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 } +} - verify.update(dataString, 'utf8') +// User password checks - const isValid = verify.verify(publicKey, hexSignature, SIGNATURE_ENCODING) - return isValid +function comparePassword (plainPassword: string, hashPassword: string) { + return bcryptComparePromise(plainPassword, hashPassword) } -function sign (data: string|Object) { - const sign = crypto.createSign(SIGNATURE_ALGORITHM) - - let dataString: string - if (typeof data === 'string') { - dataString = data - } else { - try { - dataString = JSON.stringify(data) - } catch (err) { - logger.error('Cannot sign data.', err) - return Promise.resolve('') - } +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'] } - sign.update(dataString, 'utf8') + return true +} - return getMyPrivateCert().then(myKey => { - return sign.sign(myKey, SIGNATURE_ENCODING) - }) +function isHTTPSignatureVerified (httpSignatureParsed: any, actor: MActor): boolean { + return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true } -function comparePassword (plainPassword: string, hashPassword: string) { - return bcryptComparePromise(plainPassword, hashPassword) +function parseHTTPSignature (req: Request, clockSkew?: number) { + const headers = req.method === 'POST' + ? HTTP_SIGNATURE.REQUIRED_HEADERS.POST + : HTTP_SIGNATURE.REQUIRED_HEADERS.ALL + + return httpSignature.parse(req, { clockSkew, headers }) } -function createCertsIfNotExist () { - return certsExist().then(exist => { - if (exist === true) { - return undefined - } +// JSONLD - return createCerts() - }) +function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise { + if (signedDocument.signature.type === 'RsaSignature2017') { + return isJsonLDRSA2017Verified(fromActor, signedDocument) + } + + logger.warn('Unknown JSON LD signature %s.', signedDocument.signature.type, signedDocument) + + return Promise.resolve(false) } -function cryptPassword (password: string) { - return bcryptGenSaltPromise(BCRYPT_SALT_SIZE).then(salt => bcryptHashPromise(password, salt)) +// Backward compatibility with "other" implementations +async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any) { + const [ documentHash, optionsHash ] = await Promise.all([ + createDocWithoutSignatureHash(signedDocument), + createSignatureHash(signedDocument.signature) + ]) + + const toVerify = optionsHash + documentHash + + const verify = createVerify('RSA-SHA256') + verify.update(toVerify, 'utf8') + + return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64') } -function getMyPrivateCert () { - const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME) - return readFilePromise(certPath, 'utf8') +async function signJsonLDObject (byActor: MActor, data: T) { + const signature = { + type: 'RsaSignature2017', + creator: byActor.url, + created: new Date().toISOString() + } + + 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 getMyPublicCert () { - const certPath = join(CONFIG.STORAGE.CERT_DIR, PUBLIC_CERT_NAME) - return readFilePromise(certPath, 'utf8') +function buildDigest (body: any) { + const rawBody = typeof body === 'string' ? body : JSON.stringify(body) + + return 'SHA-256=' + sha256(rawBody, 'base64') } // --------------------------------------------------------------------------- export { - checkSignature, + isHTTPSignatureDigestValid, + parseHTTPSignature, + isHTTPSignatureVerified, + buildDigest, + isJsonLDSignatureVerified, comparePassword, - createCertsIfNotExist, + createPrivateAndPublicKeys, cryptPassword, - getMyPrivateCert, - getMyPublicCert, - sign + signJsonLDObject } // --------------------------------------------------------------------------- -function certsExist () { - const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME) - - // If there is an error the certificates do not exist - return accessPromise(certPath) - .then(() => true) - .catch(() => false) +function hashObject (obj: any): Promise { + return jsonld.promises + .normalize(obj, { + algorithm: 'URDNA2015', + format: 'application/n-quads' + }) + .then(res => sha256(res)) } -function createCerts () { - return certsExist().then(exist => { - if (exist === true) { - const errorMessage = 'Certs already exist.' - logger.warning(errorMessage) - throw new Error(errorMessage) - } - - logger.info('Generating a RSA key...') - - const privateCertPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME) - const genRsaOptions = { - 'out': privateCertPath, - '2048': false - } - return opensslExecPromise('genrsa', genRsaOptions) - .then(() => { - logger.info('RSA key generated.') - logger.info('Managing public key...') - - const publicCertPath = join(CONFIG.STORAGE.CERT_DIR, 'peertube.pub') - const rsaOptions = { - 'in': privateCertPath, - 'pubout': true, - 'out': publicCertPath - } - return opensslExecPromise('rsa', rsaOptions) - .then(() => logger.info('Public key managed.')) - .catch(err => { - logger.error('Cannot create public key on this pod.') - throw err - }) - }) - .catch(err => { - logger.error('Cannot create private key on this pod.') - throw err - }) +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) }