Sha256 Seal 🔏

A small library allowing to sign documents, and to check their integrity.

Status

Version Yard documentation Ruby RuboCop License

Installation

Add this line to your application's Gemfile:

gem "sha256_seal"

And then execute:

bundle install

Or install it yourself as:

gem install sha256_seal

Usage

Sign information and verify their signature.

Example

In the context of a Web application, CSRF tokens could be embedded in URLs.

SECRET = "secret".freeze

document_string = "/.__SIGNATURE_HERE__/accounts/42?editable=false"
signature_field = "__SIGNATURE_HERE__"

builder = Sha256Seal::Builder.new(document_string, SECRET, signature_field)
builder.signed_value? # => false
builder.signed_value  # => "/.a31c3936f236684a8ebc51dcfef168ce124450d71ae1ec404552ec9e0090a8db/accounts/42?editable=false"

document_string = "/.a31c3936f236684a8ebc51dcfef168ce124450d71ae1ec404552ec9e0090a8db/accounts/42?editable=false"
signature_field = "a31c3936f236684a8ebc51dcfef168ce124450d71ae1ec404552ec9e0090a8db"

builder = Sha256Seal::Builder.new(document_string, SECRET, signature_field)
builder.signed_value? # => true
builder.signed_value  # => "/.a31c3936f236684a8ebc51dcfef168ce124450d71ae1ec404552ec9e0090a8db/accounts/42?editable=false"

document_string = "/.a31c3936f236684a8ebc51dcfef168ce124450d71ae1ec404552ec9e0090a8db/accounts/42?editable=true"
signature_field = "a31c3936f236684a8ebc51dcfef168ce124450d71ae1ec404552ec9e0090a8db"

builder = Sha256Seal::Builder.new(document_string, SECRET, signature_field)
builder.signed_value? # => false
builder.signed_value  # => "/.babd3a90b6bc2a4c0c7536a0c4804e5430a5a6df27d223c0f0102edb231de590/accounts/42?editable=true"

Rails integration example

Environment variable:

CSRF_SECRET_KEY=secret

Route:

# config/routes.rb
Rails.application.routes.draw do
  scope module: :verified_requests, path: ".:csrf", as: "verified_request" do
    get "/accounts/:id", to: "accounts#show", as: "account"
  end
end

Controller:

# app/controllers/verified_requests/base_controller.rb
module VerifiedRequests
  class BaseController < ::ApplicationController
    def signed_url(route_method, **options)
      url_route_method  = "#{route_method}_url".to_sym
      incorrect_csrf    = "__CSRF_SECRET_KEY__"
      url_route_string  = public_send(url_route_method, csrf: incorrect_csrf, **options)

      replace_incorrect_csrf_by_correct_csrf(url_route_string, incorrect_csrf:)
    end
    helper_method :signed_url

    private

    def replace_incorrect_csrf_by_correct_csrf(value, incorrect_csrf:)
      secret  = ::ENV.fetch("CSRF_SECRET_KEY")
      field   = incorrect_csrf
      builder = ::Sha256Seal::Builder.new(value, secret, field)
      value   = builder.signed_value
      field   = builder.send(:signature)

      builder = ::Sha256Seal::Builder.new(value, secret, field)
      builder.signed_value
    end

    # @see https://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html#method-i-verified_request-3F
    # @see https://github.com/rails/rails/blob/8015c2c2cf5c8718449677570f372ceb01318a32/actionpack/lib/action_controller/metal/request_forgery_protection.rb#L333-L341
    def verified_request?
      secret          = ::ENV.fetch("CSRF_SECRET_KEY")
      document_string = request.original_url.force_encoding("utf-8")
      signature_field = request.path_parameters.fetch(:csrf)

      builder = ::Sha256Seal::Builder.new(document_string, secret, signature_field)
      builder.signed_value? || ::Rails.env.test?
    end
  end
end

View:

# app/views/verified_requests/accounts/show.html.erb

signed_url(:verified_request_account, id: "bob", admin: true)
# => "http://0.0.0.0:5000/.405d7c8f14389c9ae7f1d97ff66699093bf2d89d13b4f4280a35d62f9e616259/accounts/bob?admin=true"

Versioning

Sha256Seal uses Semantic Versioning 2.0.0

License

The gem is available as open source under the terms of the MIT License.