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