Relax plugin package.json validation
[oweals/peertube.git] / server / helpers / peertube-crypto.ts
1 import { Request } from 'express'
2 import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants'
3 import { ActorModel } from '../models/activitypub/actor'
4 import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils'
5 import { jsig, jsonld } from './custom-jsonld-signature'
6 import { logger } from './logger'
7 import { cloneDeep } from 'lodash'
8 import { createVerify } from 'crypto'
9 import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils'
10 import * as bcrypt from 'bcrypt'
11
12 const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
13 const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
14 const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash)
15
16 const httpSignature = require('http-signature')
17
18 async function createPrivateAndPublicKeys () {
19   logger.info('Generating a RSA key...')
20
21   const { key } = await createPrivateKey(PRIVATE_RSA_KEY_SIZE)
22   const { publicKey } = await getPublicKey(key)
23
24   return { privateKey: key, publicKey }
25 }
26
27 // User password checks
28
29 function comparePassword (plainPassword: string, hashPassword: string) {
30   return bcryptComparePromise(plainPassword, hashPassword)
31 }
32
33 async function cryptPassword (password: string) {
34   const salt = await bcryptGenSaltPromise(BCRYPT_SALT_SIZE)
35
36   return bcryptHashPromise(password, salt)
37 }
38
39 // HTTP Signature
40
41 function isHTTPSignatureDigestValid (rawBody: Buffer, req: Request): boolean {
42   if (req.headers[HTTP_SIGNATURE.HEADER_NAME] && req.headers['digest']) {
43     return buildDigest(rawBody.toString()) === req.headers['digest']
44   }
45
46   return true
47 }
48
49 function isHTTPSignatureVerified (httpSignatureParsed: any, actor: ActorModel): boolean {
50   return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true
51 }
52
53 function parseHTTPSignature (req: Request, clockSkew?: number) {
54   return httpSignature.parse(req, { authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, clockSkew })
55 }
56
57 // JSONLD
58
59 async function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any): Promise<boolean> {
60   if (signedDocument.signature.type === 'RsaSignature2017') {
61     // Mastodon algorithm
62     const res = await isJsonLDRSA2017Verified(fromActor, signedDocument)
63     // Success? If no, try with our library
64     if (res === true) return true
65   }
66
67   const publicKeyObject = {
68     '@context': jsig.SECURITY_CONTEXT_URL,
69     id: fromActor.url,
70     type: 'CryptographicKey',
71     owner: fromActor.url,
72     publicKeyPem: fromActor.publicKey
73   }
74
75   const publicKeyOwnerObject = {
76     '@context': jsig.SECURITY_CONTEXT_URL,
77     id: fromActor.url,
78     publicKey: [ publicKeyObject ]
79   }
80
81   const options = {
82     publicKey: publicKeyObject,
83     publicKeyOwner: publicKeyOwnerObject
84   }
85
86   return jsig.promises
87              .verify(signedDocument, options)
88              .then((result: { verified: boolean }) => result.verified)
89              .catch(err => {
90                logger.error('Cannot check signature.', { err })
91                return false
92              })
93 }
94
95 // Backward compatibility with "other" implementations
96 async function isJsonLDRSA2017Verified (fromActor: ActorModel, signedDocument: any) {
97   function hash (obj: any): Promise<any> {
98     return jsonld.promises
99                  .normalize(obj, {
100                    algorithm: 'URDNA2015',
101                    format: 'application/n-quads'
102                  })
103                  .then(res => sha256(res))
104   }
105
106   const signatureCopy = cloneDeep(signedDocument.signature)
107   Object.assign(signatureCopy, {
108     '@context': [
109       'https://w3id.org/security/v1',
110       { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
111     ]
112   })
113   delete signatureCopy.type
114   delete signatureCopy.id
115   delete signatureCopy.signatureValue
116
117   const docWithoutSignature = cloneDeep(signedDocument)
118   delete docWithoutSignature.signature
119
120   const [ documentHash, optionsHash ] = await Promise.all([
121     hash(docWithoutSignature),
122     hash(signatureCopy)
123   ])
124
125   const toVerify = optionsHash + documentHash
126
127   const verify = createVerify('RSA-SHA256')
128   verify.update(toVerify, 'utf8')
129
130   return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64')
131 }
132
133 function signJsonLDObject (byActor: ActorModel, data: any) {
134   const options = {
135     privateKeyPem: byActor.privateKey,
136     creator: byActor.url,
137     algorithm: 'RsaSignature2017'
138   }
139
140   return jsig.promises.sign(data, options)
141 }
142
143 // ---------------------------------------------------------------------------
144
145 export {
146   isHTTPSignatureDigestValid,
147   parseHTTPSignature,
148   isHTTPSignatureVerified,
149   isJsonLDSignatureVerified,
150   comparePassword,
151   createPrivateAndPublicKeys,
152   cryptPassword,
153   signJsonLDObject
154 }
155
156 // ---------------------------------------------------------------------------