1
0

signature_verification_spec.rb 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  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 non-normalized URL' do
  103. before do
  104. get :success
  105. fake_request = Request.new(:get, 'http://test.host/subdir/../success')
  106. fake_request.on_behalf_of(author)
  107. request.headers.merge!(fake_request.headers)
  108. allow(controller).to receive(:actor_refresh_key!).and_return(author)
  109. end
  110. describe '#build_signed_string' do
  111. it 'includes the normalized request path' do
  112. expect(controller.send(:build_signed_string)).to start_with "(request-target): get /success\n"
  113. end
  114. end
  115. describe '#signed_request?' do
  116. it 'returns true' do
  117. expect(controller.signed_request?).to be true
  118. end
  119. end
  120. describe '#signed_request_actor' do
  121. it 'returns an account' do
  122. expect(controller.signed_request_account).to eq author
  123. end
  124. end
  125. end
  126. context 'with request with unparsable Date header' do
  127. before do
  128. get :success
  129. fake_request = Request.new(:get, request.url)
  130. fake_request.add_headers({ 'Date' => 'wrong date' })
  131. fake_request.on_behalf_of(author)
  132. request.headers.merge!(fake_request.headers)
  133. end
  134. describe '#signed_request?' do
  135. it 'returns true' do
  136. expect(controller.signed_request?).to be true
  137. end
  138. end
  139. describe '#signed_request_account' do
  140. it 'returns nil' do
  141. expect(controller.signed_request_account).to be_nil
  142. end
  143. end
  144. describe '#signature_verification_failure_reason' do
  145. it 'contains an error description' do
  146. controller.signed_request_account
  147. expect(controller.signature_verification_failure_reason[:error]).to eq 'Invalid Date header: not RFC 2616 compliant date: "wrong date"'
  148. end
  149. end
  150. end
  151. context 'with request older than a day' do
  152. before do
  153. get :success
  154. fake_request = Request.new(:get, request.url)
  155. fake_request.add_headers({ 'Date' => 2.days.ago.utc.httpdate })
  156. fake_request.on_behalf_of(author)
  157. request.headers.merge!(fake_request.headers)
  158. end
  159. describe '#signed_request?' do
  160. it 'returns true' do
  161. expect(controller.signed_request?).to be true
  162. end
  163. end
  164. describe '#signed_request_account' do
  165. it 'returns nil' do
  166. expect(controller.signed_request_account).to be_nil
  167. end
  168. end
  169. describe '#signature_verification_failure_reason' do
  170. it 'contains an error description' do
  171. controller.signed_request_account
  172. expect(controller.signature_verification_failure_reason[:error]).to eq 'Signed request date outside acceptable time window'
  173. end
  174. end
  175. end
  176. context 'with inaccessible key' do
  177. before do
  178. get :success
  179. author = Fabricate(:account, domain: 'localhost:5000', uri: 'http://localhost:5000/actor')
  180. fake_request = Request.new(:get, request.url)
  181. fake_request.on_behalf_of(author)
  182. author.destroy
  183. request.headers.merge!(fake_request.headers)
  184. stub_request(:get, 'http://localhost:5000/actor').to_raise(Mastodon::HostValidationError)
  185. end
  186. describe '#signed_request?' do
  187. it 'returns true' do
  188. expect(controller.signed_request?).to be true
  189. end
  190. end
  191. describe '#signed_request_account' do
  192. it 'returns nil' do
  193. expect(controller.signed_request_account).to be_nil
  194. end
  195. end
  196. end
  197. context 'with body' do
  198. before do
  199. allow(controller).to receive(:actor_refresh_key!).and_return(author)
  200. post :success, body: 'Hello world'
  201. fake_request = Request.new(:post, request.url, body: 'Hello world')
  202. fake_request.on_behalf_of(author)
  203. request.headers.merge!(fake_request.headers)
  204. end
  205. describe '#signed_request?' do
  206. it 'returns true' do
  207. expect(controller.signed_request?).to be true
  208. end
  209. end
  210. describe '#signed_request_account' do
  211. it 'returns an account' do
  212. expect(controller.signed_request_account).to eq author
  213. end
  214. end
  215. context 'when path does not match' do
  216. before do
  217. request.path = '/alternative-path'
  218. end
  219. describe '#signed_request_account' do
  220. it 'returns nil' do
  221. expect(controller.signed_request_account).to be_nil
  222. end
  223. end
  224. describe '#signature_verification_failure_reason' do
  225. it 'contains an error description' do
  226. controller.signed_request_account
  227. expect(controller.signature_verification_failure_reason[:error]).to include('using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)')
  228. expect(controller.signature_verification_failure_reason[:signed_string]).to include("(request-target): post /alternative-path\n")
  229. end
  230. end
  231. end
  232. context 'when method does not match' do
  233. before do
  234. get :success
  235. end
  236. describe '#signed_request_account' do
  237. it 'returns nil' do
  238. expect(controller.signed_request_account).to be_nil
  239. end
  240. end
  241. end
  242. context 'when body has been tampered' do
  243. before do
  244. post :success, body: 'doo doo doo'
  245. end
  246. describe '#signed_request_account' do
  247. it 'returns nil when body has been tampered' do
  248. expect(controller.signed_request_account).to be_nil
  249. end
  250. end
  251. end
  252. end
  253. end
  254. context 'when a signature is required' do
  255. before do
  256. get :signature_required
  257. end
  258. context 'without signature header' do
  259. it 'returns HTTP 401' do
  260. expect(response).to have_http_status(401)
  261. end
  262. it 'returns an error' do
  263. expect(Oj.load(response.body)['error']).to eq 'Request not signed'
  264. end
  265. end
  266. end
  267. end