10 Matching Annotations
  1. Jan 2023
    1. The code above is somewhat simplified and missing some checks that I would advise implementing in a serious production application. For example:The request contains a Date header. Compare it with current date and time within a reasonable time window to prevent replay attacks.It is advisable that requests with payloads in the body also send a Digest header, and that header be signed along in the signature. If it’s present, it should be checked as another special case within the comparison string: Instead of taking the digest value from the received header, recompute it from the received body.While this proves the request comes from an actor, what if the payload contains an attribution to someone else? In reality you’d want to check that both are the same, otherwise one actor could forge messages from other people.
    2. We need to read the Signature header, split it into its parts (keyId, headers and signature), fetch the public key linked from keyId, create a comparison string from the plaintext headers we got in the same order as was given in the signature header, and then verify that string using the public key and the original signature.

      ```ruby require 'json' require 'http'

      post '/inbox' do signature_header = request.headers['Signature'].split(',').map do |pair| pair.split('=').map do |value| value.gsub(/\A"/, '').gsub(/"\z/, '') # "foo" -> foo end end.to_h

      key_id = signature_header['keyId'] headers = signature_header['headers'] signature = Base64.decode64(signature_header['signature'])

      actor = JSON.parse(HTTP.get(key_id).to_s) key = OpenSSL::PKey::RSA.new(actor['publicKey']['publicKeyPem'])

      comparison_string = headers.split(' ').map do |signed_header_name| if signed_header_name == '(request-target)' '(request-target): post /inbox' else "#{signed_header_name}: #{request.headers[signed_header_name.capitalize]}" end end

      if key.verify(OpenSSL::Digest::SHA256.new, signature, comparison_string) request.body.rewind INBOX << request.body.read [200, 'OK'] else [401, 'Request signature could not be verified'] end end ```