peertube-crypto.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  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 { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey, 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. const httpSignature = require('http-signature')
  11. async function createPrivateAndPublicKeys () {
  12. logger.info('Generating a RSA key...')
  13. const { key } = await createPrivateKey(PRIVATE_RSA_KEY_SIZE)
  14. const { publicKey } = await getPublicKey(key)
  15. return { privateKey: key, publicKey }
  16. }
  17. // User password checks
  18. function comparePassword (plainPassword: string, hashPassword: string) {
  19. return bcryptComparePromise(plainPassword, hashPassword)
  20. }
  21. async function cryptPassword (password: string) {
  22. const salt = await bcryptGenSaltPromise(BCRYPT_SALT_SIZE)
  23. return bcryptHashPromise(password, salt)
  24. }
  25. // HTTP Signature
  26. function isHTTPSignatureDigestValid (rawBody: Buffer, req: Request): boolean {
  27. if (req.headers[HTTP_SIGNATURE.HEADER_NAME] && req.headers['digest']) {
  28. return buildDigest(rawBody.toString()) === req.headers['digest']
  29. }
  30. return true
  31. }
  32. function isHTTPSignatureVerified (httpSignatureParsed: any, actor: ActorModel): boolean {
  33. return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true
  34. }
  35. function parseHTTPSignature (req: Request, clockSkew?: number) {
  36. return httpSignature.parse(req, { authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, clockSkew })
  37. }
  38. // JSONLD
  39. async function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any): Promise<boolean> {
  40. if (signedDocument.signature.type === 'RsaSignature2017') {
  41. // Mastodon algorithm
  42. const res = await isJsonLDRSA2017Verified(fromActor, signedDocument)
  43. // Success? If no, try with our library
  44. if (res === true) return true
  45. }
  46. const publicKeyObject = {
  47. '@context': jsig.SECURITY_CONTEXT_URL,
  48. id: fromActor.url,
  49. type: 'CryptographicKey',
  50. owner: fromActor.url,
  51. publicKeyPem: fromActor.publicKey
  52. }
  53. const publicKeyOwnerObject = {
  54. '@context': jsig.SECURITY_CONTEXT_URL,
  55. id: fromActor.url,
  56. publicKey: [ publicKeyObject ]
  57. }
  58. const options = {
  59. publicKey: publicKeyObject,
  60. publicKeyOwner: publicKeyOwnerObject
  61. }
  62. return jsig.promises
  63. .verify(signedDocument, options)
  64. .then((result: { verified: boolean }) => result.verified)
  65. .catch(err => {
  66. logger.error('Cannot check signature.', { err })
  67. return false
  68. })
  69. }
  70. // Backward compatibility with "other" implementations
  71. async function isJsonLDRSA2017Verified (fromActor: ActorModel, signedDocument: any) {
  72. function hash (obj: any): Promise<any> {
  73. return jsonld.promises
  74. .normalize(obj, {
  75. algorithm: 'URDNA2015',
  76. format: 'application/n-quads'
  77. })
  78. .then(res => sha256(res))
  79. }
  80. const signatureCopy = cloneDeep(signedDocument.signature)
  81. Object.assign(signatureCopy, {
  82. '@context': [
  83. 'https://w3id.org/security/v1',
  84. { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
  85. ]
  86. })
  87. delete signatureCopy.type
  88. delete signatureCopy.id
  89. delete signatureCopy.signatureValue
  90. const docWithoutSignature = cloneDeep(signedDocument)
  91. delete docWithoutSignature.signature
  92. const [ documentHash, optionsHash ] = await Promise.all([
  93. hash(docWithoutSignature),
  94. hash(signatureCopy)
  95. ])
  96. const toVerify = optionsHash + documentHash
  97. const verify = createVerify('RSA-SHA256')
  98. verify.update(toVerify, 'utf8')
  99. return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64')
  100. }
  101. function signJsonLDObject (byActor: ActorModel, data: any) {
  102. const options = {
  103. privateKeyPem: byActor.privateKey,
  104. creator: byActor.url,
  105. algorithm: 'RsaSignature2017'
  106. }
  107. return jsig.promises.sign(data, options)
  108. }
  109. // ---------------------------------------------------------------------------
  110. export {
  111. isHTTPSignatureDigestValid,
  112. parseHTTPSignature,
  113. isHTTPSignatureVerified,
  114. isJsonLDSignatureVerified,
  115. comparePassword,
  116. createPrivateAndPublicKeys,
  117. cryptPassword,
  118. signJsonLDObject
  119. }