peertube-jsonld.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import { sha256 } from '@peertube/peertube-node-utils'
  2. import { createSign, createVerify } from 'crypto'
  3. import cloneDeep from 'lodash-es/cloneDeep.js'
  4. import { MActor } from '../types/models/index.js'
  5. import { logger } from './logger.js'
  6. import { assertIsInWorkerThread } from './threads.js'
  7. import { jsonld } from './custom-jsonld-signature.js'
  8. export function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> {
  9. if (signedDocument.signature.type === 'RsaSignature2017') {
  10. return isJsonLDRSA2017Verified(fromActor, signedDocument)
  11. }
  12. logger.warn('Unknown JSON LD signature %s.', signedDocument.signature.type, signedDocument)
  13. return Promise.resolve(false)
  14. }
  15. // Backward compatibility with "other" implementations
  16. export async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any) {
  17. const [ documentHash, optionsHash ] = await Promise.all([
  18. createDocWithoutSignatureHash(signedDocument),
  19. createSignatureHash(signedDocument.signature)
  20. ])
  21. const toVerify = optionsHash + documentHash
  22. const verify = createVerify('RSA-SHA256')
  23. verify.update(toVerify, 'utf8')
  24. return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64')
  25. }
  26. export async function signJsonLDObject <T> (options: {
  27. byActor: { url: string, privateKey: string }
  28. data: T
  29. disableWorkerThreadAssertion?: boolean
  30. }) {
  31. const { byActor, data, disableWorkerThreadAssertion = false } = options
  32. if (!disableWorkerThreadAssertion) assertIsInWorkerThread()
  33. const signature = {
  34. type: 'RsaSignature2017',
  35. creator: byActor.url,
  36. created: new Date().toISOString()
  37. }
  38. const [ documentHash, optionsHash ] = await Promise.all([
  39. createDocWithoutSignatureHash(data),
  40. createSignatureHash(signature)
  41. ])
  42. const toSign = optionsHash + documentHash
  43. const sign = createSign('RSA-SHA256')
  44. sign.update(toSign, 'utf8')
  45. const signatureValue = sign.sign(byActor.privateKey, 'base64')
  46. Object.assign(signature, { signatureValue })
  47. return Object.assign(data, { signature })
  48. }
  49. // ---------------------------------------------------------------------------
  50. // Private
  51. // ---------------------------------------------------------------------------
  52. async function hashObject (obj: any): Promise<any> {
  53. const res = await (jsonld as any).promises.normalize(obj, {
  54. safe: false,
  55. algorithm: 'URDNA2015',
  56. format: 'application/n-quads'
  57. })
  58. return sha256(res)
  59. }
  60. function createSignatureHash (signature: any) {
  61. const signatureCopy = cloneDeep(signature)
  62. Object.assign(signatureCopy, {
  63. '@context': [
  64. 'https://w3id.org/security/v1',
  65. { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
  66. ]
  67. })
  68. delete signatureCopy.type
  69. delete signatureCopy.id
  70. delete signatureCopy.signatureValue
  71. return hashObject(signatureCopy)
  72. }
  73. function createDocWithoutSignatureHash (doc: any) {
  74. const docWithoutSignature = cloneDeep(doc)
  75. delete docWithoutSignature.signature
  76. return hashObject(docWithoutSignature)
  77. }