]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/helpers/peertube-crypto.ts
Suffix external auth username on conflict
[github/Chocobozzz/PeerTube.git] / server / helpers / peertube-crypto.ts
CommitLineData
41fb13c3
C
1import { compare, genSalt, hash } from 'bcrypt'
2import { createSign, createVerify } from 'crypto'
41f2ebae 3import { Request } from 'express'
41fb13c3 4import { cloneDeep } from 'lodash'
f304a158 5import { sha256 } from '@shared/extra-utils'
74dc3bca 6import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants'
41fb13c3 7import { MActor } from '../types/models'
5d7cb63e 8import { generateRSAKeyPairPromise, promisify1, promisify2 } from './core-utils'
ad513607 9import { jsonld } from './custom-jsonld-signature'
8d468a16 10import { logger } from './logger'
8d2be0ed 11
41fb13c3
C
12const bcryptComparePromise = promisify2<any, string, boolean>(compare)
13const bcryptGenSaltPromise = promisify1<number, string>(genSalt)
14const bcryptHashPromise = promisify2<any, string | number, string>(hash)
9f10b292 15
5842a854 16const httpSignature = require('@peertube/http-signature')
41f2ebae 17
5d7cb63e 18function createPrivateAndPublicKeys () {
e4f97bab 19 logger.info('Generating a RSA key...')
bdfbd4f1 20
5d7cb63e 21 return generateRSAKeyPairPromise(PRIVATE_RSA_KEY_SIZE)
9f10b292
C
22}
23
41f2ebae
C
24// User password checks
25
26function comparePassword (plainPassword: string, hashPassword: string) {
27 return bcryptComparePromise(plainPassword, hashPassword)
28}
29
30async function cryptPassword (password: string) {
31 const salt = await bcryptGenSaltPromise(BCRYPT_SALT_SIZE)
32
33 return bcryptHashPromise(password, salt)
34}
35
36// HTTP Signature
37
df66d815
C
38function 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
453e83ea 46function isHTTPSignatureVerified (httpSignatureParsed: any, actor: MActor): boolean {
41f2ebae
C
47 return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true
48}
49
df66d815 50function parseHTTPSignature (req: Request, clockSkew?: number) {
e08ec7a7
C
51 const requiredHeaders = req.method === 'POST'
52 ? [ '(request-target)', 'host', 'digest' ]
53 : [ '(request-target)', 'host' ]
797d05bd 54
e08ec7a7
C
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
41f2ebae
C
63}
64
65// JSONLD
66
ad513607 67function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> {
df66d815 68 if (signedDocument.signature.type === 'RsaSignature2017') {
ad513607 69 return isJsonLDRSA2017Verified(fromActor, signedDocument)
df66d815
C
70 }
71
ad513607 72 logger.warn('Unknown JSON LD signature %s.', signedDocument.signature.type, signedDocument)
bdfbd4f1 73
ad513607 74 return Promise.resolve(false)
26d7d31b
C
75}
76
df66d815 77// Backward compatibility with "other" implementations
453e83ea 78async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any) {
df66d815 79 const [ documentHash, optionsHash ] = await Promise.all([
ad513607
C
80 createDocWithoutSignatureHash(signedDocument),
81 createSignatureHash(signedDocument.signature)
df66d815
C
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
db4b15f2 92async function signJsonLDObject <T> (byActor: MActor, data: T) {
ad513607
C
93 const signature = {
94 type: 'RsaSignature2017',
ce33ee01 95 creator: byActor.url,
ad513607 96 created: new Date().toISOString()
f5028693 97 }
9f10b292 98
ad513607
C
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 })
e4f97bab
C
113}
114
8dc8a34e
C
115function buildDigest (body: any) {
116 const rawBody = typeof body === 'string' ? body : JSON.stringify(body)
117
118 return 'SHA-256=' + sha256(rawBody, 'base64')
119}
120
9f10b292 121// ---------------------------------------------------------------------------
dac0a531 122
65fcc311 123export {
df66d815 124 isHTTPSignatureDigestValid,
41f2ebae
C
125 parseHTTPSignature,
126 isHTTPSignatureVerified,
8dc8a34e 127 buildDigest,
41f2ebae 128 isJsonLDSignatureVerified,
65fcc311 129 comparePassword,
e4f97bab 130 createPrivateAndPublicKeys,
65fcc311 131 cryptPassword,
41f2ebae 132 signJsonLDObject
9f10b292 133}
8d2be0ed
C
134
135// ---------------------------------------------------------------------------
ad513607 136
41fb13c3 137function hashObject (obj: any): Promise<any> {
ad513607
C
138 return jsonld.promises
139 .normalize(obj, {
140 algorithm: 'URDNA2015',
141 format: 'application/n-quads'
142 })
143 .then(res => sha256(res))
144}
145
146function createSignatureHash (signature: any) {
147 const signatureCopy = cloneDeep(signature)
148 Object.assign(signatureCopy, {
149 '@context': [
150 'https://w3id.org/security/v1',
151 { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
152 ]
153 })
154
155 delete signatureCopy.type
156 delete signatureCopy.id
157 delete signatureCopy.signatureValue
158
41fb13c3 159 return hashObject(signatureCopy)
ad513607
C
160}
161
162function createDocWithoutSignatureHash (doc: any) {
163 const docWithoutSignature = cloneDeep(doc)
164 delete docWithoutSignature.signature
165
41fb13c3 166 return hashObject(docWithoutSignature)
ad513607 167}