linked_data_signature.rb 1.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
  1. # frozen_string_literal: true
  2. class ActivityPub::LinkedDataSignature
  3. include JsonLdHelper
  4. CONTEXT = 'https://w3id.org/identity/v1'
  5. def initialize(json)
  6. @json = json.with_indifferent_access
  7. end
  8. def verify_account!
  9. return unless @json['signature'].is_a?(Hash)
  10. type = @json['signature']['type']
  11. creator_uri = @json['signature']['creator']
  12. signature = @json['signature']['signatureValue']
  13. return unless type == 'RsaSignature2017'
  14. creator = ActivityPub::TagManager.instance.uri_to_resource(creator_uri, Account)
  15. creator ||= ActivityPub::FetchRemoteKeyService.new.call(creator_uri, id: false)
  16. return if creator.nil?
  17. options_hash = hash(@json['signature'].without('type', 'id', 'signatureValue').merge('@context' => CONTEXT))
  18. document_hash = hash(@json.without('signature'))
  19. to_be_verified = options_hash + document_hash
  20. if creator.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), Base64.decode64(signature), to_be_verified)
  21. creator
  22. end
  23. end
  24. def sign!(creator, sign_with: nil)
  25. options = {
  26. 'type' => 'RsaSignature2017',
  27. 'creator' => [ActivityPub::TagManager.instance.uri_for(creator), '#main-key'].join,
  28. 'created' => Time.now.utc.iso8601,
  29. }
  30. options_hash = hash(options.without('type', 'id', 'signatureValue').merge('@context' => CONTEXT))
  31. document_hash = hash(@json.without('signature'))
  32. to_be_signed = options_hash + document_hash
  33. keypair = sign_with.present? ? OpenSSL::PKey::RSA.new(sign_with) : creator.keypair
  34. signature = Base64.strict_encode64(keypair.sign(OpenSSL::Digest.new('SHA256'), to_be_signed))
  35. @json.merge('signature' => options.merge('signatureValue' => signature))
  36. end
  37. private
  38. def hash(obj)
  39. Digest::SHA256.hexdigest(canonicalize(obj))
  40. end
  41. end