]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/helpers/peertube-crypto.ts
Support only ffmpeg >= 4.3
[github/Chocobozzz/PeerTube.git] / server / helpers / peertube-crypto.ts
1 import { compare, genSalt, hash } from 'bcrypt'
2 import { createSign, createVerify } from 'crypto'
3 import { Request } from 'express'
4 import { cloneDeep } from 'lodash'
5 import { sha256 } from '@shared/extra-utils'
6 import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants'
7 import { MActor } from '../types/models'
8 import { generateRSAKeyPairPromise, promisify1, promisify2 } from './core-utils'
9 import { jsonld } from './custom-jsonld-signature'
10 import { logger } from './logger'
11
12 const bcryptComparePromise = promisify2<any, string, boolean>(compare)
13 const bcryptGenSaltPromise = promisify1<number, string>(genSalt)
14 const bcryptHashPromise = promisify2<any, string | number, string>(hash)
15
16 const httpSignature = require('@peertube/http-signature')
17
18 function createPrivateAndPublicKeys () {
19 logger.info('Generating a RSA key...')
20
21 return generateRSAKeyPairPromise(PRIVATE_RSA_KEY_SIZE)
22 }
23
24 // User password checks
25
26 function comparePassword (plainPassword: string, hashPassword: string) {
27 return bcryptComparePromise(plainPassword, hashPassword)
28 }
29
30 async function cryptPassword (password: string) {
31 const salt = await bcryptGenSaltPromise(BCRYPT_SALT_SIZE)
32
33 return bcryptHashPromise(password, salt)
34 }
35
36 // HTTP Signature
37
38 function isHTTPSignatureDigestValid (rawBody: Buffer, req: Request): boolean {
39 if (req.headers[HTTP_SIGNATURE.HEADER_NAME] && req.headers['digest']) {
40 return buildDigest(rawBody.toString()) === req.headers['digest']
41 }
42
43 return true
44 }
45
46 function isHTTPSignatureVerified (httpSignatureParsed: any, actor: MActor): boolean {
47 return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true
48 }
49
50 function parseHTTPSignature (req: Request, clockSkew?: number) {
51 const requiredHeaders = req.method === 'POST'
52 ? [ '(request-target)', 'host', 'digest' ]
53 : [ '(request-target)', 'host' ]
54
55 const parsed = httpSignature.parse(req, { clockSkew, headers: requiredHeaders })
56
57 const parsedHeaders = parsed.params.headers
58 if (!parsedHeaders.includes('date') && !parsedHeaders.includes('(created)')) {
59 throw new Error(`date or (created) must be included in signature`)
60 }
61
62 return parsed
63 }
64
65 // JSONLD
66
67 function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> {
68 if (signedDocument.signature.type === 'RsaSignature2017') {
69 return isJsonLDRSA2017Verified(fromActor, signedDocument)
70 }
71
72 logger.warn('Unknown JSON LD signature %s.', signedDocument.signature.type, signedDocument)
73
74 return Promise.resolve(false)
75 }
76
77 // Backward compatibility with "other" implementations
78 async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any) {
79 const [ documentHash, optionsHash ] = await Promise.all([
80 createDocWithoutSignatureHash(signedDocument),
81 createSignatureHash(signedDocument.signature)
82 ])
83
84 const toVerify = optionsHash + documentHash
85
86 const verify = createVerify('RSA-SHA256')
87 verify.update(toVerify, 'utf8')
88
89 return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64')
90 }
91
92 async function signJsonLDObject <T> (byActor: MActor, data: T) {
93 const signature = {
94 type: 'RsaSignature2017',
95 creator: byActor.url,
96 created: new Date().toISOString()
97 }
98
99 const [ documentHash, optionsHash ] = await Promise.all([
100 createDocWithoutSignatureHash(data),
101 createSignatureHash(signature)
102 ])
103
104 const toSign = optionsHash + documentHash
105
106 const sign = createSign('RSA-SHA256')
107 sign.update(toSign, 'utf8')
108
109 const signatureValue = sign.sign(byActor.privateKey, 'base64')
110 Object.assign(signature, { signatureValue })
111
112 return Object.assign(data, { signature })
113 }
114
115 function buildDigest (body: any) {
116 const rawBody = typeof body === 'string' ? body : JSON.stringify(body)
117
118 return 'SHA-256=' + sha256(rawBody, 'base64')
119 }
120
121 // ---------------------------------------------------------------------------
122
123 export {
124 isHTTPSignatureDigestValid,
125 parseHTTPSignature,
126 isHTTPSignatureVerified,
127 buildDigest,
128 isJsonLDSignatureVerified,
129 comparePassword,
130 createPrivateAndPublicKeys,
131 cryptPassword,
132 signJsonLDObject
133 }
134
135 // ---------------------------------------------------------------------------
136
137 function hashObject (obj: any): Promise<any> {
138 return jsonld.promises.normalize(obj, {
139 safe: false,
140 algorithm: 'URDNA2015',
141 format: 'application/n-quads'
142 }).then(res => sha256(res))
143 }
144
145 function createSignatureHash (signature: any) {
146 const signatureCopy = cloneDeep(signature)
147 Object.assign(signatureCopy, {
148 '@context': [
149 'https://w3id.org/security/v1',
150 { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
151 ]
152 })
153
154 delete signatureCopy.type
155 delete signatureCopy.id
156 delete signatureCopy.signatureValue
157
158 return hashObject(signatureCopy)
159 }
160
161 function createDocWithoutSignatureHash (doc: any) {
162 const docWithoutSignature = cloneDeep(doc)
163 delete docWithoutSignature.signature
164
165 return hashObject(docWithoutSignature)
166 }