signature_verification_spec.rb 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. # frozen_string_literal: true
  2. require 'rails_helper'
  3. describe SignatureVerification do
  4. let(:wrapped_actor_class) do
  5. Class.new do
  6. attr_reader :wrapped_account
  7. def initialize(wrapped_account)
  8. @wrapped_account = wrapped_account
  9. end
  10. delegate :uri, :keypair, to: :wrapped_account
  11. end
  12. end
  13. controller(ApplicationController) do
  14. include SignatureVerification
  15. before_action :require_actor_signature!, only: [:signature_required]
  16. def success
  17. head 200
  18. end
  19. def alternative_success
  20. head 200
  21. end
  22. def signature_required
  23. head 200
  24. end
  25. end
  26. before do
  27. routes.draw do
  28. match :via => [:get, :post], 'success' => 'anonymous#success'
  29. match :via => [:get, :post], 'signature_required' => 'anonymous#signature_required'
  30. end
  31. end
  32. context 'without signature header' do
  33. before do
  34. get :success
  35. end
  36. describe '#signed_request?' do
  37. it 'returns false' do
  38. expect(controller.signed_request?).to be false
  39. end
  40. end
  41. describe '#signed_request_account' do
  42. it 'returns nil' do
  43. expect(controller.signed_request_account).to be_nil
  44. end
  45. end
  46. end
  47. context 'with signature header' do
  48. let!(:author) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/actor') }
  49. context 'without body' do
  50. before do
  51. get :success
  52. fake_request = Request.new(:get, request.url)
  53. fake_request.on_behalf_of(author)
  54. request.headers.merge!(fake_request.headers)
  55. end
  56. describe '#signed_request?' do
  57. it 'returns true' do
  58. expect(controller.signed_request?).to be true
  59. end
  60. end
  61. describe '#signed_request_account' do
  62. it 'returns an account' do
  63. expect(controller.signed_request_account).to eq author
  64. end
  65. it 'returns nil when path does not match' do
  66. request.path = '/alternative-path'
  67. expect(controller.signed_request_account).to be_nil
  68. end
  69. it 'returns nil when method does not match' do
  70. post :success
  71. expect(controller.signed_request_account).to be_nil
  72. end
  73. end
  74. end
  75. context 'with a valid actor that is not an Account' do
  76. let(:actor) { wrapped_actor_class.new(author) }
  77. before do
  78. get :success
  79. fake_request = Request.new(:get, request.url)
  80. fake_request.on_behalf_of(author)
  81. request.headers.merge!(fake_request.headers)
  82. allow(ActivityPub::TagManager.instance).to receive(:uri_to_actor).with(anything) do
  83. actor
  84. end
  85. end
  86. describe '#signed_request?' do
  87. it 'returns true' do
  88. expect(controller.signed_request?).to be true
  89. end
  90. end
  91. describe '#signed_request_account' do
  92. it 'returns nil' do
  93. expect(controller.signed_request_account).to be_nil
  94. end
  95. end
  96. describe '#signed_request_actor' do
  97. it 'returns the expected actor' do
  98. expect(controller.signed_request_actor).to eq actor
  99. end
  100. end
  101. end
  102. context 'with request with unparsable Date header' do
  103. before do
  104. get :success
  105. fake_request = Request.new(:get, request.url)
  106. fake_request.add_headers({ 'Date' => 'wrong date' })
  107. fake_request.on_behalf_of(author)
  108. request.headers.merge!(fake_request.headers)
  109. end
  110. describe '#signed_request?' do
  111. it 'returns true' do
  112. expect(controller.signed_request?).to be true
  113. end
  114. end
  115. describe '#signed_request_account' do
  116. it 'returns nil' do
  117. expect(controller.signed_request_account).to be_nil
  118. end
  119. end
  120. describe '#signature_verification_failure_reason' do
  121. it 'contains an error description' do
  122. controller.signed_request_account
  123. expect(controller.signature_verification_failure_reason[:error]).to eq 'Invalid Date header: not RFC 2616 compliant date: "wrong date"'
  124. end
  125. end
  126. end
  127. context 'with request older than a day' do
  128. before do
  129. get :success
  130. fake_request = Request.new(:get, request.url)
  131. fake_request.add_headers({ 'Date' => 2.days.ago.utc.httpdate })
  132. fake_request.on_behalf_of(author)
  133. request.headers.merge!(fake_request.headers)
  134. end
  135. describe '#signed_request?' do
  136. it 'returns true' do
  137. expect(controller.signed_request?).to be true
  138. end
  139. end
  140. describe '#signed_request_account' do
  141. it 'returns nil' do
  142. expect(controller.signed_request_account).to be_nil
  143. end
  144. end
  145. describe '#signature_verification_failure_reason' do
  146. it 'contains an error description' do
  147. controller.signed_request_account
  148. expect(controller.signature_verification_failure_reason[:error]).to eq 'Signed request date outside acceptable time window'
  149. end
  150. end
  151. end
  152. context 'with inaccessible key' do
  153. before do
  154. get :success
  155. author = Fabricate(:account, domain: 'localhost:5000', uri: 'http://localhost:5000/actor')
  156. fake_request = Request.new(:get, request.url)
  157. fake_request.on_behalf_of(author)
  158. author.destroy
  159. request.headers.merge!(fake_request.headers)
  160. stub_request(:get, 'http://localhost:5000/actor#main-key').to_raise(Mastodon::HostValidationError)
  161. end
  162. describe '#signed_request?' do
  163. it 'returns true' do
  164. expect(controller.signed_request?).to be true
  165. end
  166. end
  167. describe '#signed_request_account' do
  168. it 'returns nil' do
  169. expect(controller.signed_request_account).to be_nil
  170. end
  171. end
  172. end
  173. context 'with body' do
  174. before do
  175. allow(controller).to receive(:actor_refresh_key!).and_return(author)
  176. post :success, body: 'Hello world'
  177. fake_request = Request.new(:post, request.url, body: 'Hello world')
  178. fake_request.on_behalf_of(author)
  179. request.headers.merge!(fake_request.headers)
  180. end
  181. describe '#signed_request?' do
  182. it 'returns true' do
  183. expect(controller.signed_request?).to be true
  184. end
  185. end
  186. describe '#signed_request_account' do
  187. it 'returns an account' do
  188. expect(controller.signed_request_account).to eq author
  189. end
  190. end
  191. context 'when path does not match' do
  192. before do
  193. request.path = '/alternative-path'
  194. end
  195. describe '#signed_request_account' do
  196. it 'returns nil' do
  197. expect(controller.signed_request_account).to be_nil
  198. end
  199. end
  200. describe '#signature_verification_failure_reason' do
  201. it 'contains an error description' do
  202. controller.signed_request_account
  203. expect(controller.signature_verification_failure_reason[:error]).to include('using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)')
  204. expect(controller.signature_verification_failure_reason[:signed_string]).to include("(request-target): post /alternative-path\n")
  205. end
  206. end
  207. end
  208. context 'when method does not match' do
  209. before do
  210. get :success
  211. end
  212. describe '#signed_request_account' do
  213. it 'returns nil' do
  214. expect(controller.signed_request_account).to be_nil
  215. end
  216. end
  217. end
  218. context 'when body has been tampered' do
  219. before do
  220. post :success, body: 'doo doo doo'
  221. end
  222. describe '#signed_request_account' do
  223. it 'returns nil when body has been tampered' do
  224. expect(controller.signed_request_account).to be_nil
  225. end
  226. end
  227. end
  228. end
  229. end
  230. context 'when a signature is required' do
  231. before do
  232. get :signature_required
  233. end
  234. context 'without signature header' do
  235. it 'returns HTTP 401' do
  236. expect(response).to have_http_status(401)
  237. end
  238. it 'returns an error' do
  239. expect(Oj.load(response.body)['error']).to eq 'Request not signed'
  240. end
  241. end
  242. end
  243. end