aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/helpers
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-10-10 11:12:23 +0200
committerChocobozzz <me@florianbigard.com>2022-10-10 11:12:23 +0200
commita3e5f804ad821f6979e8735b0569b1209986fedc (patch)
tree5b34a6bd6b3cb1c5e3eed32a72d02922100d53dc /server/helpers
parenta0da6f90d16027b385a67da6a5691b163626a363 (diff)
downloadPeerTube-a3e5f804ad821f6979e8735b0569b1209986fedc.tar.gz
PeerTube-a3e5f804ad821f6979e8735b0569b1209986fedc.tar.zst
PeerTube-a3e5f804ad821f6979e8735b0569b1209986fedc.zip
Encrypt OTP secret
Diffstat (limited to 'server/helpers')
-rw-r--r--server/helpers/core-utils.ts14
-rw-r--r--server/helpers/otp.ts10
-rw-r--r--server/helpers/peertube-crypto.ts47
3 files changed, 63 insertions, 8 deletions
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts
index c762f6a29..73bd994c1 100644
--- a/server/helpers/core-utils.ts
+++ b/server/helpers/core-utils.ts
@@ -6,7 +6,7 @@
6*/ 6*/
7 7
8import { exec, ExecOptions } from 'child_process' 8import { exec, ExecOptions } from 'child_process'
9import { ED25519KeyPairOptions, generateKeyPair, randomBytes, RSAKeyPairOptions } from 'crypto' 9import { ED25519KeyPairOptions, generateKeyPair, randomBytes, RSAKeyPairOptions, scrypt } from 'crypto'
10import { truncate } from 'lodash' 10import { truncate } from 'lodash'
11import { pipeline } from 'stream' 11import { pipeline } from 'stream'
12import { URL } from 'url' 12import { URL } from 'url'
@@ -311,7 +311,17 @@ function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A)
311 } 311 }
312} 312}
313 313
314// eslint-disable-next-line max-len
315function promisify3<T, U, V, A> (func: (arg1: T, arg2: U, arg3: V, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U, arg3: V) => Promise<A> {
316 return function promisified (arg1: T, arg2: U, arg3: V): Promise<A> {
317 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
318 func.apply(null, [ arg1, arg2, arg3, (err: any, res: A) => err ? reject(err) : resolve(res) ])
319 })
320 }
321}
322
314const randomBytesPromise = promisify1<number, Buffer>(randomBytes) 323const randomBytesPromise = promisify1<number, Buffer>(randomBytes)
324const scryptPromise = promisify3<string, string, number, Buffer>(scrypt)
315const execPromise2 = promisify2<string, any, string>(exec) 325const execPromise2 = promisify2<string, any, string>(exec)
316const execPromise = promisify1<string, string>(exec) 326const execPromise = promisify1<string, string>(exec)
317const pipelinePromise = promisify(pipeline) 327const pipelinePromise = promisify(pipeline)
@@ -339,6 +349,8 @@ export {
339 promisify1, 349 promisify1,
340 promisify2, 350 promisify2,
341 351
352 scryptPromise,
353
342 randomBytesPromise, 354 randomBytesPromise,
343 355
344 generateRSAKeyPairPromise, 356 generateRSAKeyPairPromise,
diff --git a/server/helpers/otp.ts b/server/helpers/otp.ts
index a13edc5e2..a32cc9621 100644
--- a/server/helpers/otp.ts
+++ b/server/helpers/otp.ts
@@ -1,11 +1,15 @@
1import { Secret, TOTP } from 'otpauth' 1import { Secret, TOTP } from 'otpauth'
2import { CONFIG } from '@server/initializers/config'
2import { WEBSERVER } from '@server/initializers/constants' 3import { WEBSERVER } from '@server/initializers/constants'
4import { decrypt } from './peertube-crypto'
3 5
4function isOTPValid (options: { 6async function isOTPValid (options: {
5 secret: string 7 encryptedSecret: string
6 token: string 8 token: string
7}) { 9}) {
8 const { token, secret } = options 10 const { token, encryptedSecret } = options
11
12 const secret = await decrypt(encryptedSecret, CONFIG.SECRETS.PEERTUBE)
9 13
10 const totp = new TOTP({ 14 const totp = new TOTP({
11 ...baseOTPOptions(), 15 ...baseOTPOptions(),
diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts
index dcf47ce76..ae7d11800 100644
--- a/server/helpers/peertube-crypto.ts
+++ b/server/helpers/peertube-crypto.ts
@@ -1,11 +1,11 @@
1import { compare, genSalt, hash } from 'bcrypt' 1import { compare, genSalt, hash } from 'bcrypt'
2import { createSign, createVerify } from 'crypto' 2import { createCipheriv, createDecipheriv, createSign, createVerify } from 'crypto'
3import { Request } from 'express' 3import { Request } from 'express'
4import { cloneDeep } from 'lodash' 4import { cloneDeep } from 'lodash'
5import { sha256 } from '@shared/extra-utils' 5import { sha256 } from '@shared/extra-utils'
6import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' 6import { BCRYPT_SALT_SIZE, ENCRYPTION, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants'
7import { MActor } from '../types/models' 7import { MActor } from '../types/models'
8import { generateRSAKeyPairPromise, promisify1, promisify2 } from './core-utils' 8import { generateRSAKeyPairPromise, promisify1, promisify2, randomBytesPromise, scryptPromise } from './core-utils'
9import { jsonld } from './custom-jsonld-signature' 9import { jsonld } from './custom-jsonld-signature'
10import { logger } from './logger' 10import { logger } from './logger'
11 11
@@ -21,7 +21,9 @@ function createPrivateAndPublicKeys () {
21 return generateRSAKeyPairPromise(PRIVATE_RSA_KEY_SIZE) 21 return generateRSAKeyPairPromise(PRIVATE_RSA_KEY_SIZE)
22} 22}
23 23
24// ---------------------------------------------------------------------------
24// User password checks 25// User password checks
26// ---------------------------------------------------------------------------
25 27
26function comparePassword (plainPassword: string, hashPassword: string) { 28function comparePassword (plainPassword: string, hashPassword: string) {
27 if (!plainPassword) return Promise.resolve(false) 29 if (!plainPassword) return Promise.resolve(false)
@@ -35,7 +37,9 @@ async function cryptPassword (password: string) {
35 return bcryptHashPromise(password, salt) 37 return bcryptHashPromise(password, salt)
36} 38}
37 39
40// ---------------------------------------------------------------------------
38// HTTP Signature 41// HTTP Signature
42// ---------------------------------------------------------------------------
39 43
40function isHTTPSignatureDigestValid (rawBody: Buffer, req: Request): boolean { 44function isHTTPSignatureDigestValid (rawBody: Buffer, req: Request): boolean {
41 if (req.headers[HTTP_SIGNATURE.HEADER_NAME] && req.headers['digest']) { 45 if (req.headers[HTTP_SIGNATURE.HEADER_NAME] && req.headers['digest']) {
@@ -64,7 +68,9 @@ function parseHTTPSignature (req: Request, clockSkew?: number) {
64 return parsed 68 return parsed
65} 69}
66 70
71// ---------------------------------------------------------------------------
67// JSONLD 72// JSONLD
73// ---------------------------------------------------------------------------
68 74
69function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> { 75function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> {
70 if (signedDocument.signature.type === 'RsaSignature2017') { 76 if (signedDocument.signature.type === 'RsaSignature2017') {
@@ -114,6 +120,8 @@ async function signJsonLDObject <T> (byActor: MActor, data: T) {
114 return Object.assign(data, { signature }) 120 return Object.assign(data, { signature })
115} 121}
116 122
123// ---------------------------------------------------------------------------
124
117function buildDigest (body: any) { 125function buildDigest (body: any) {
118 const rawBody = typeof body === 'string' ? body : JSON.stringify(body) 126 const rawBody = typeof body === 'string' ? body : JSON.stringify(body)
119 127
@@ -121,6 +129,34 @@ function buildDigest (body: any) {
121} 129}
122 130
123// --------------------------------------------------------------------------- 131// ---------------------------------------------------------------------------
132// Encryption
133// ---------------------------------------------------------------------------
134
135async function encrypt (str: string, secret: string) {
136 const iv = await randomBytesPromise(ENCRYPTION.IV)
137
138 const key = await scryptPromise(secret, ENCRYPTION.SALT, 32)
139 const cipher = createCipheriv(ENCRYPTION.ALGORITHM, key, iv)
140
141 let encrypted = iv.toString(ENCRYPTION.ENCODING) + ':'
142 encrypted += cipher.update(str, 'utf8', ENCRYPTION.ENCODING)
143 encrypted += cipher.final(ENCRYPTION.ENCODING)
144
145 return encrypted
146}
147
148async function decrypt (encryptedArg: string, secret: string) {
149 const [ ivStr, encryptedStr ] = encryptedArg.split(':')
150
151 const iv = Buffer.from(ivStr, 'hex')
152 const key = await scryptPromise(secret, ENCRYPTION.SALT, 32)
153
154 const decipher = createDecipheriv(ENCRYPTION.ALGORITHM, key, iv)
155
156 return decipher.update(encryptedStr, ENCRYPTION.ENCODING, 'utf8') + decipher.final('utf8')
157}
158
159// ---------------------------------------------------------------------------
124 160
125export { 161export {
126 isHTTPSignatureDigestValid, 162 isHTTPSignatureDigestValid,
@@ -131,7 +167,10 @@ export {
131 comparePassword, 167 comparePassword,
132 createPrivateAndPublicKeys, 168 createPrivateAndPublicKeys,
133 cryptPassword, 169 cryptPassword,
134 signJsonLDObject 170 signJsonLDObject,
171
172 encrypt,
173 decrypt
135} 174}
136 175
137// --------------------------------------------------------------------------- 176// ---------------------------------------------------------------------------