peertube-crypto.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import { Request } from 'express'
  2. import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants'
  3. import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils'
  4. import { jsonld } from './custom-jsonld-signature'
  5. import { logger } from './logger'
  6. import { cloneDeep } from 'lodash'
  7. import { createSign, createVerify } from 'crypto'
  8. import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils'
  9. import * as bcrypt from 'bcrypt'
  10. import { MActor } from '../typings/models'
  11. const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
  12. const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
  13. const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash)
  14. const httpSignature = require('http-signature')
  15. async function createPrivateAndPublicKeys () {
  16. logger.info('Generating a RSA key...')
  17. const { key } = await createPrivateKey(PRIVATE_RSA_KEY_SIZE)
  18. const { publicKey } = await getPublicKey(key)
  19. return { privateKey: key, publicKey }
  20. }
  21. // User password checks
  22. function comparePassword (plainPassword: string, hashPassword: string) {
  23. return bcryptComparePromise(plainPassword, hashPassword)
  24. }
  25. async function cryptPassword (password: string) {
  26. const salt = await bcryptGenSaltPromise(BCRYPT_SALT_SIZE)
  27. return bcryptHashPromise(password, salt)
  28. }
  29. // HTTP Signature
  30. function isHTTPSignatureDigestValid (rawBody: Buffer, req: Request): boolean {
  31. if (req.headers[HTTP_SIGNATURE.HEADER_NAME] && req.headers['digest']) {
  32. return buildDigest(rawBody.toString()) === req.headers['digest']
  33. }
  34. return true
  35. }
  36. function isHTTPSignatureVerified (httpSignatureParsed: any, actor: MActor): boolean {
  37. return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true
  38. }
  39. function parseHTTPSignature (req: Request, clockSkew?: number) {
  40. return httpSignature.parse(req, { clockSkew })
  41. }
  42. // JSONLD
  43. function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> {
  44. if (signedDocument.signature.type === 'RsaSignature2017') {
  45. return isJsonLDRSA2017Verified(fromActor, signedDocument)
  46. }
  47. logger.warn('Unknown JSON LD signature %s.', signedDocument.signature.type, signedDocument)
  48. return Promise.resolve(false)
  49. }
  50. // Backward compatibility with "other" implementations
  51. async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any) {
  52. const [ documentHash, optionsHash ] = await Promise.all([
  53. createDocWithoutSignatureHash(signedDocument),
  54. createSignatureHash(signedDocument.signature)
  55. ])
  56. const toVerify = optionsHash + documentHash
  57. const verify = createVerify('RSA-SHA256')
  58. verify.update(toVerify, 'utf8')
  59. return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64')
  60. }
  61. async function signJsonLDObject (byActor: MActor, data: any) {
  62. const signature = {
  63. type: 'RsaSignature2017',
  64. creator: byActor.url,
  65. created: new Date().toISOString()
  66. }
  67. const [ documentHash, optionsHash ] = await Promise.all([
  68. createDocWithoutSignatureHash(data),
  69. createSignatureHash(signature)
  70. ])
  71. const toSign = optionsHash + documentHash
  72. const sign = createSign('RSA-SHA256')
  73. sign.update(toSign, 'utf8')
  74. const signatureValue = sign.sign(byActor.privateKey, 'base64')
  75. Object.assign(signature, { signatureValue })
  76. return Object.assign(data, { signature })
  77. }
  78. // ---------------------------------------------------------------------------
  79. export {
  80. isHTTPSignatureDigestValid,
  81. parseHTTPSignature,
  82. isHTTPSignatureVerified,
  83. isJsonLDSignatureVerified,
  84. comparePassword,
  85. createPrivateAndPublicKeys,
  86. cryptPassword,
  87. signJsonLDObject
  88. }
  89. // ---------------------------------------------------------------------------
  90. function hash (obj: any): Promise<any> {
  91. return jsonld.promises
  92. .normalize(obj, {
  93. algorithm: 'URDNA2015',
  94. format: 'application/n-quads'
  95. })
  96. .then(res => sha256(res))
  97. }
  98. function createSignatureHash (signature: any) {
  99. const signatureCopy = cloneDeep(signature)
  100. Object.assign(signatureCopy, {
  101. '@context': [
  102. 'https://w3id.org/security/v1',
  103. { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
  104. ]
  105. })
  106. delete signatureCopy.type
  107. delete signatureCopy.id
  108. delete signatureCopy.signatureValue
  109. return hash(signatureCopy)
  110. }
  111. function createDocWithoutSignatureHash (doc: any) {
  112. const docWithoutSignature = cloneDeep(doc)
  113. delete docWithoutSignature.signature
  114. return hash(docWithoutSignature)
  115. }