activitypub.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import { NextFunction, Request, Response } from 'express'
  2. import { isActorDeleteActivityValid } from '@server/helpers/custom-validators/activitypub/actor'
  3. import { getAPId } from '@server/lib/activitypub/activity'
  4. import { ActivityDelete, ActivityPubSignature, HttpStatusCode } from '@shared/models'
  5. import { logger } from '../helpers/logger'
  6. import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto'
  7. import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants'
  8. import { getOrCreateAPActor, loadActorUrlOrGetFromWebfinger } from '../lib/activitypub/actors'
  9. async function checkSignature (req: Request, res: Response, next: NextFunction) {
  10. try {
  11. const httpSignatureChecked = await checkHttpSignature(req, res)
  12. if (httpSignatureChecked !== true) return
  13. const actor = res.locals.signature.actor
  14. // Forwarded activity
  15. const bodyActor = req.body.actor
  16. const bodyActorId = getAPId(bodyActor)
  17. if (bodyActorId && bodyActorId !== actor.url) {
  18. const jsonLDSignatureChecked = await checkJsonLDSignature(req, res)
  19. if (jsonLDSignatureChecked !== true) return
  20. }
  21. return next()
  22. } catch (err) {
  23. const activity: ActivityDelete = req.body
  24. if (isActorDeleteActivityValid(activity) && activity.object === activity.actor) {
  25. logger.debug('Handling signature error on actor delete activity', { err })
  26. return res.status(HttpStatusCode.NO_CONTENT_204).end()
  27. }
  28. logger.warn('Error in ActivityPub signature checker.', { err })
  29. return res.fail({
  30. status: HttpStatusCode.FORBIDDEN_403,
  31. message: 'ActivityPub signature could not be checked'
  32. })
  33. }
  34. }
  35. function executeIfActivityPub (req: Request, res: Response, next: NextFunction) {
  36. const accepted = req.accepts(ACCEPT_HEADERS)
  37. if (accepted === false || ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS.includes(accepted) === false) {
  38. // Bypass this route
  39. return next('route')
  40. }
  41. logger.debug('ActivityPub request for %s.', req.url)
  42. return next()
  43. }
  44. // ---------------------------------------------------------------------------
  45. export {
  46. checkSignature,
  47. executeIfActivityPub,
  48. checkHttpSignature
  49. }
  50. // ---------------------------------------------------------------------------
  51. async function checkHttpSignature (req: Request, res: Response) {
  52. // FIXME: compatibility with http-signature < v1.3
  53. const sig = req.headers[HTTP_SIGNATURE.HEADER_NAME] as string
  54. if (sig && sig.startsWith('Signature ') === true) req.headers[HTTP_SIGNATURE.HEADER_NAME] = sig.replace(/^Signature /, '')
  55. let parsed: any
  56. try {
  57. parsed = parseHTTPSignature(req, HTTP_SIGNATURE.CLOCK_SKEW_SECONDS)
  58. } catch (err) {
  59. logger.warn('Invalid signature because of exception in signature parser', { reqBody: req.body, err })
  60. res.fail({
  61. status: HttpStatusCode.FORBIDDEN_403,
  62. message: err.message
  63. })
  64. return false
  65. }
  66. const keyId = parsed.keyId
  67. if (!keyId) {
  68. res.fail({
  69. status: HttpStatusCode.FORBIDDEN_403,
  70. message: 'Invalid key ID',
  71. data: {
  72. keyId
  73. }
  74. })
  75. return false
  76. }
  77. logger.debug('Checking HTTP signature of actor %s...', keyId)
  78. let [ actorUrl ] = keyId.split('#')
  79. if (actorUrl.startsWith('acct:')) {
  80. actorUrl = await loadActorUrlOrGetFromWebfinger(actorUrl.replace(/^acct:/, ''))
  81. }
  82. const actor = await getOrCreateAPActor(actorUrl)
  83. const verified = isHTTPSignatureVerified(parsed, actor)
  84. if (verified !== true) {
  85. logger.warn('Signature from %s is invalid', actorUrl, { parsed })
  86. res.fail({
  87. status: HttpStatusCode.FORBIDDEN_403,
  88. message: 'Invalid signature',
  89. data: {
  90. actorUrl
  91. }
  92. })
  93. return false
  94. }
  95. res.locals.signature = { actor }
  96. return true
  97. }
  98. async function checkJsonLDSignature (req: Request, res: Response) {
  99. const signatureObject: ActivityPubSignature = req.body.signature
  100. if (!signatureObject || !signatureObject.creator) {
  101. res.fail({
  102. status: HttpStatusCode.FORBIDDEN_403,
  103. message: 'Object and creator signature do not match'
  104. })
  105. return false
  106. }
  107. const [ creator ] = signatureObject.creator.split('#')
  108. logger.debug('Checking JsonLD signature of actor %s...', creator)
  109. const actor = await getOrCreateAPActor(creator)
  110. const verified = await isJsonLDSignatureVerified(actor, req.body)
  111. if (verified !== true) {
  112. logger.warn('Signature not verified.', req.body)
  113. res.fail({
  114. status: HttpStatusCode.FORBIDDEN_403,
  115. message: 'Signature could not be verified'
  116. })
  117. return false
  118. }
  119. res.locals.signature = { actor }
  120. return true
  121. }