linked_data_signature_spec.rb 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. # frozen_string_literal: true
  2. require 'rails_helper'
  3. RSpec.describe ActivityPub::LinkedDataSignature do
  4. include JsonLdHelper
  5. subject { described_class.new(json) }
  6. let!(:sender) { Fabricate(:account, uri: 'http://example.com/alice', domain: 'example.com') }
  7. let(:raw_json) do
  8. {
  9. '@context' => 'https://www.w3.org/ns/activitystreams',
  10. 'id' => 'http://example.com/hello-world',
  11. }
  12. end
  13. let(:json) { raw_json.merge('signature' => signature) }
  14. describe '#verify_actor!' do
  15. context 'when signature matches' do
  16. let(:raw_signature) do
  17. {
  18. 'creator' => 'http://example.com/alice',
  19. 'created' => '2017-09-23T20:21:34Z',
  20. }
  21. end
  22. let(:signature) { raw_signature.merge('type' => 'RsaSignature2017', 'signatureValue' => sign(sender, raw_signature, raw_json)) }
  23. it 'returns creator' do
  24. expect(subject.verify_actor!).to eq sender
  25. end
  26. end
  27. context 'when local account record is missing a public key' do
  28. let(:raw_signature) do
  29. {
  30. 'creator' => 'http://example.com/alice',
  31. 'created' => '2017-09-23T20:21:34Z',
  32. }
  33. end
  34. let(:signature) { raw_signature.merge('type' => 'RsaSignature2017', 'signatureValue' => sign(sender, raw_signature, raw_json)) }
  35. let(:service_stub) { instance_double(ActivityPub::FetchRemoteKeyService) }
  36. before do
  37. # Ensure signature is computed with the old key
  38. signature
  39. # Unset key
  40. old_key = sender.public_key
  41. sender.update!(private_key: '', public_key: '')
  42. allow(ActivityPub::FetchRemoteKeyService).to receive(:new).and_return(service_stub)
  43. allow(service_stub).to receive(:call).with('http://example.com/alice') do
  44. sender.update!(public_key: old_key)
  45. sender
  46. end
  47. end
  48. it 'fetches key and returns creator' do
  49. expect(subject.verify_actor!).to eq sender
  50. expect(service_stub).to have_received(:call).with('http://example.com/alice').once
  51. end
  52. end
  53. context 'when signature is missing' do
  54. let(:signature) { nil }
  55. it 'returns nil' do
  56. expect(subject.verify_actor!).to be_nil
  57. end
  58. end
  59. context 'when signature is tampered' do
  60. let(:raw_signature) do
  61. {
  62. 'creator' => 'http://example.com/alice',
  63. 'created' => '2017-09-23T20:21:34Z',
  64. }
  65. end
  66. let(:signature) { raw_signature.merge('type' => 'RsaSignature2017', 'signatureValue' => 's69F3mfddd99dGjmvjdjjs81e12jn121Gkm1') }
  67. it 'returns nil' do
  68. expect(subject.verify_actor!).to be_nil
  69. end
  70. end
  71. end
  72. describe '#sign!' do
  73. subject { described_class.new(raw_json).sign!(sender) }
  74. it 'returns a hash with a signature, the expected context, and the signature can be verified', :aggregate_failures do
  75. expect(subject).to be_a Hash
  76. expect(subject['signature']).to be_a Hash
  77. expect(subject['signature']['signatureValue']).to be_present
  78. expect(Array(subject['@context'])).to include('https://w3id.org/security/v1')
  79. expect(described_class.new(subject).verify_actor!).to eq sender
  80. end
  81. end
  82. def sign(from_actor, options, document)
  83. options_hash = Digest::SHA256.hexdigest(canonicalize(options.merge('@context' => ActivityPub::LinkedDataSignature::CONTEXT)))
  84. document_hash = Digest::SHA256.hexdigest(canonicalize(document))
  85. to_be_verified = options_hash + document_hash
  86. Base64.strict_encode64(from_actor.keypair.sign(OpenSSL::Digest.new('SHA256'), to_be_verified))
  87. end
  88. end