X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fhelpers%2Fpeertube-crypto.ts;h=ab9ec077ee3957806d6009207d5349fedb0209f6;hb=5c6d985faeef1d6793d3f44ca6374f1a9b722806;hp=feb32a4cd7a2374425c4342a8bdce0a03b904cb1;hpb=4d4e5cd4dca78480ec7f40e747f424cd107376a4;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index feb32a4cd..ab9ec077e 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts @@ -1,171 +1,149 @@ -import * as crypto from 'crypto' -import * as bcrypt from 'bcrypt' -import * as fs from 'fs' -import * as openssl from 'openssl-wrapper' -import { join } from 'path' - -import { - SIGNATURE_ALGORITHM, - SIGNATURE_ENCODING, - PRIVATE_CERT_NAME, - CONFIG, - BCRYPT_SALT_SIZE, - PUBLIC_CERT_NAME -} from '../initializers' +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 { cloneDeep } from 'lodash' +import { createVerify } from 'crypto' +import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' -function checkSignature (publicKey, data, hexSignature) { - 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.', { error: err }) - return false - } - } +const httpSignature = require('http-signature') + +async function createPrivateAndPublicKeys () { + logger.info('Generating a RSA key...') - verify.update(dataString, 'utf8') + const { key } = await createPrivateKey(PRIVATE_RSA_KEY_SIZE) + const { publicKey } = await getPublicKey(key) - const isValid = verify.verify(publicKey, hexSignature, SIGNATURE_ENCODING) - return isValid + return { privateKey: key, publicKey } } -function sign (data) { - const sign = crypto.createSign(SIGNATURE_ALGORITHM) - - let dataString - if (typeof data === 'string') { - dataString = data - } else { - try { - dataString = JSON.stringify(data) - } catch (err) { - logger.error('Cannot sign data.', { error: err }) - return '' - } - } +// User password checks - sign.update(dataString, 'utf8') +function comparePassword (plainPassword: string, hashPassword: string) { + return bcryptComparePromise(plainPassword, hashPassword) +} - // TODO: make async - const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME) - const myKey = fs.readFileSync(certPath) - const signature = sign.sign(myKey.toString(), SIGNATURE_ENCODING) +async function cryptPassword (password: string) { + const salt = await bcryptGenSaltPromise(BCRYPT_SALT_SIZE) - return signature + return bcryptHashPromise(password, salt) } -function comparePassword (plainPassword, hashPassword, callback) { - bcrypt.compare(plainPassword, hashPassword, function (err, isPasswordMatch) { - if (err) return callback(err) +// HTTP Signature - return callback(null, isPasswordMatch) - }) +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 createCertsIfNotExist (callback) { - certsExist(function (err, exist) { - if (err) return callback(err) +function parseHTTPSignature (req: Request, clockSkew?: number) { + return httpSignature.parse(req, { authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, clockSkew }) +} - if (exist === true) { - return callback(null) - } +// JSONLD - createCerts(function (err) { - return callback(err) - }) - }) +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: fromActor.url, + type: 'CryptographicKey', + owner: fromActor.url, + publicKeyPem: fromActor.publicKey + } + + 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 + }) } -function cryptPassword (password, callback) { - bcrypt.genSalt(BCRYPT_SALT_SIZE, function (err, salt) { - if (err) return callback(err) +// 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)) + } - bcrypt.hash(password, salt, function (err, hash) { - return callback(err, hash) - }) + 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) + ]) -function getMyPrivateCert (callback) { - const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME) - fs.readFile(certPath, 'utf8', callback) + const toVerify = optionsHash + documentHash + + const verify = createVerify('RSA-SHA256') + verify.update(toVerify, 'utf8') + + return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64') } -function getMyPublicCert (callback) { - const certPath = join(CONFIG.STORAGE.CERT_DIR, PUBLIC_CERT_NAME) - fs.readFile(certPath, 'utf8', callback) +function signJsonLDObject (byActor: ActorModel, data: any) { + const options = { + privateKeyPem: byActor.privateKey, + creator: byActor.url, + algorithm: 'RsaSignature2017' + } + + return jsig.promises.sign(data, options) } // --------------------------------------------------------------------------- export { - checkSignature, + isHTTPSignatureDigestValid, + parseHTTPSignature, + isHTTPSignatureVerified, + isJsonLDSignatureVerified, comparePassword, - createCertsIfNotExist, + createPrivateAndPublicKeys, cryptPassword, - getMyPrivateCert, - getMyPublicCert, - sign -} - -// --------------------------------------------------------------------------- - -function certsExist (callback) { - const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME) - fs.access(certPath, function (err) { - // If there is an error the certificates do not exist - const exists = !err - return callback(null, exists) - }) -} - -function createCerts (callback) { - certsExist(function (err, exist) { - if (err) return callback(err) - - if (exist === true) { - const string = 'Certs already exist.' - logger.warning(string) - return callback(new Error(string)) - } - - logger.info('Generating a RSA key...') - - const privateCertPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME) - const genRsaOptions = { - 'out': privateCertPath, - '2048': false - } - openssl.exec('genrsa', genRsaOptions, function (err) { - if (err) { - logger.error('Cannot create private key on this pod.') - return callback(err) - } - - 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 - } - openssl.exec('rsa', rsaOptions, function (err) { - if (err) { - logger.error('Cannot create public key on this pod.') - return callback(err) - } - - logger.info('Public key managed.') - return callback(null) - }) - }) - }) + signJsonLDObject }