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