linzer 0.6.4 → 0.7.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/.standard.yml +1 -0
 - data/CHANGELOG.md +10 -0
 - data/README.md +28 -1
 - data/examples/sinatra/Gemfile +9 -0
 - data/examples/sinatra/config.ru +11 -0
 - data/examples/sinatra/http-signatures.yml +26 -0
 - data/examples/sinatra/myapp.rb +10 -0
 - data/lib/linzer/key/helper.rb +22 -8
 - data/lib/linzer/rsa.rb +1 -7
 - data/lib/linzer/rsa_pss.rb +40 -0
 - data/lib/linzer/version.rb +1 -1
 - data/lib/linzer.rb +2 -0
 - data/lib/rack/auth/signature.rb +206 -0
 - metadata +29 -7
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 9a19b05f41aa933779f504056b56b00fc1a8a6cda19bb26065dce56177caf0c2
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 25d7d8b4afbd60a3e6e872064d92f9be22670466292e23a6ba8010ca546cdf23
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 527fd9a73a1605b706fc7ca597ce14f4f5c95be099907bf61832aa698b11fe51dbf25a5eaf70d48d7c6715c91c27bfa391482a88a67f0e932c5f60ff8f3f16df
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 2b30454801734973711afe81559ad18f09f6d08f473796241d56e1b13d5619971a697945703b85012e18ae969059ca5a3718e677fc71716151bd46e795eac8a7
         
     | 
    
        data/.standard.yml
    CHANGED
    
    
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -1,5 +1,15 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ## [Unreleased]
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            ## [0.7.0.beta1] - 2025-04-12
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            - Introduce Rack::Auth::Signature middleware.
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            ## [0.6.5] - 2025-04-09
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            - Add support for RSA (RSASSA-PKCS1-V1_5) and improve RSASSA-PSS handling.
         
     | 
| 
      
 10 
     | 
    
         
            +
              Pull request [#10](https://github.com/nomadium/linzer/pull/10)
         
     | 
| 
      
 11 
     | 
    
         
            +
              by [oneiros](https://github.com/oneiros).
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
       3 
13 
     | 
    
         
             
            ## [0.6.4] - 2025-04-04
         
     | 
| 
       4 
14 
     | 
    
         | 
| 
       5 
15 
     | 
    
         
             
            - Allow validating the `created` parameter to mitigate the
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -21,6 +21,33 @@ Or just `gem install linzer`. 
     | 
|
| 
       21 
21 
     | 
    
         | 
| 
       22 
22 
     | 
    
         
             
            ## Usage
         
     | 
| 
       23 
23 
     | 
    
         | 
| 
      
 24 
     | 
    
         
            +
            ### TL;DR: I just want to protect my application!!
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            Add the following middleware to you run Rack application, e.g.:
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 29 
     | 
    
         
            +
            # config.ru
         
     | 
| 
      
 30 
     | 
    
         
            +
            use Rack::Auth::Signature, except: "/login",
         
     | 
| 
      
 31 
     | 
    
         
            +
              default_key: {"key" => IO.read("app/config/pubkey.pem"), "alg" => "ed25519"}
         
     | 
| 
      
 32 
     | 
    
         
            +
            ```
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            or on more complex scenarios:
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 37 
     | 
    
         
            +
            # config.ru
         
     | 
| 
      
 38 
     | 
    
         
            +
            use Rack::Auth::Signature, except: "/login",
         
     | 
| 
      
 39 
     | 
    
         
            +
              config_path: "app/configuration/http-signatures.yml"
         
     | 
| 
      
 40 
     | 
    
         
            +
            ```
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
            And that's it, all routes in the example app (except `/login`) above will
         
     | 
| 
      
 43 
     | 
    
         
            +
            require a valid signature created with the respective private key held by a
         
     | 
| 
      
 44 
     | 
    
         
            +
            client. For more details on what configuration options are available, take a
         
     | 
| 
      
 45 
     | 
    
         
            +
            look at
         
     | 
| 
      
 46 
     | 
    
         
            +
            [examples/sinatra/http-signatures.yml](https://github.com/nomadium/linzer/tree/master/examples/sinatra/http-signatures.yml) to get started and/or
         
     | 
| 
      
 47 
     | 
    
         
            +
            [lib/rack/auth/signature.rb](https://github.com/nomadium/linzer/tree/master/lib/rack/auth/signature.rb) for full implementation details.
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
            To learn about more specific scenarios or use cases, keep reading on below.
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
       24 
51 
     | 
    
         
             
            ### To sign a HTTP message:
         
     | 
| 
       25 
52 
     | 
    
         | 
| 
       26 
53 
     | 
    
         
             
            ```ruby
         
     | 
| 
         @@ -160,7 +187,7 @@ pp signature.to_h 
     | 
|
| 
       160 
187 
     | 
    
         | 
| 
       161 
188 
     | 
    
         
             
            For now, to consult additional details just take a look at source code and/or the unit tests.
         
     | 
| 
       162 
189 
     | 
    
         | 
| 
       163 
     | 
    
         
            -
            Please note that is still early days and extensive testing is still ongoing. For now  
     | 
| 
      
 190 
     | 
    
         
            +
            Please note that is still early days and extensive testing is still ongoing. For now the following algorithms are supported: RSASSA-PSS using SHA-512, RSASSA-PKCS1-v1_5 using SHA-256, HMAC-SHA256, Ed25519 and ECDSA (P-256 and P-384 curves). JSON Web Signature (JWS) algorithms mentioned in the RFC are not supported yet.
         
     | 
| 
       164 
191 
     | 
    
         | 
| 
       165 
192 
     | 
    
         
             
            I'll be expanding the library to cover more functionality specified in the RFC
         
     | 
| 
       166 
193 
     | 
    
         
             
            in subsequent releases.
         
     | 
| 
         @@ -0,0 +1,26 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ---
         
     | 
| 
      
 2 
     | 
    
         
            +
            no_older_than: 6000
         
     | 
| 
      
 3 
     | 
    
         
            +
            created_required: true
         
     | 
| 
      
 4 
     | 
    
         
            +
            keyid_required: true
         
     | 
| 
      
 5 
     | 
    
         
            +
            # nonce_required: false
         
     | 
| 
      
 6 
     | 
    
         
            +
            # alg_required: false
         
     | 
| 
      
 7 
     | 
    
         
            +
            # tag_required: false
         
     | 
| 
      
 8 
     | 
    
         
            +
            # expires_required: false
         
     | 
| 
      
 9 
     | 
    
         
            +
            covered_components:
         
     | 
| 
      
 10 
     | 
    
         
            +
            - "@method"
         
     | 
| 
      
 11 
     | 
    
         
            +
            - "@request-target"
         
     | 
| 
      
 12 
     | 
    
         
            +
            - date
         
     | 
| 
      
 13 
     | 
    
         
            +
            known_keys:
         
     | 
| 
      
 14 
     | 
    
         
            +
              foo:
         
     | 
| 
      
 15 
     | 
    
         
            +
                alg: ed25519
         
     | 
| 
      
 16 
     | 
    
         
            +
                key: |
         
     | 
| 
      
 17 
     | 
    
         
            +
                  -----BEGIN PUBLIC KEY-----
         
     | 
| 
      
 18 
     | 
    
         
            +
                  MCowBQYDK2VwAyEAMEH9bSanwgAWE5qxUEaXjK6qei8z2hiHT0nlr7ljG0Y=
         
     | 
| 
      
 19 
     | 
    
         
            +
                  -----END PUBLIC KEY-----
         
     | 
| 
      
 20 
     | 
    
         
            +
              bar:
         
     | 
| 
      
 21 
     | 
    
         
            +
                alg: hmac-sha256
         
     | 
| 
      
 22 
     | 
    
         
            +
                key: !binary |-
         
     | 
| 
      
 23 
     | 
    
         
            +
                  KuOR/Q8U1Crgp0WsFqW+5ZuC+KbN41dIlXWp71UhPEeJcRfuxZEy6XAUE9nf0yl4jt55yRASBnD0kQKucO6SfA==
         
     | 
| 
      
 24 
     | 
    
         
            +
              baz:
         
     | 
| 
      
 25 
     | 
    
         
            +
                alg: rsa-pss-sha512
         
     | 
| 
      
 26 
     | 
    
         
            +
                path: rsa.pem
         
     | 
    
        data/lib/linzer/key/helper.rb
    CHANGED
    
    | 
         @@ -4,24 +4,38 @@ module Linzer 
     | 
|
| 
       4 
4 
     | 
    
         
             
              class Key
         
     | 
| 
       5 
5 
     | 
    
         
             
                module Helper
         
     | 
| 
       6 
6 
     | 
    
         
             
                  def generate_rsa_pss_sha512_key(size, key_id = nil)
         
     | 
| 
       7 
     | 
    
         
            -
                    material = OpenSSL::PKey 
     | 
| 
       8 
     | 
    
         
            -
                    Linzer:: 
     | 
| 
      
 7 
     | 
    
         
            +
                    material = OpenSSL::PKey.generate_key("RSASSA-PSS")
         
     | 
| 
      
 8 
     | 
    
         
            +
                    Linzer::RSAPSS::Key.new(material, id: key_id, digest: "SHA512")
         
     | 
| 
       9 
9 
     | 
    
         
             
                  end
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
       11 
11 
     | 
    
         
             
                  def new_rsa_pss_sha512_key(material, key_id = nil)
         
     | 
| 
       12 
12 
     | 
    
         
             
                    key = OpenSSL::PKey.read(material)
         
     | 
| 
       13 
     | 
    
         
            -
                    Linzer:: 
     | 
| 
      
 13 
     | 
    
         
            +
                    Linzer::RSAPSS::Key.new(key, id: key_id, digest: "SHA512")
         
     | 
| 
       14 
14 
     | 
    
         
             
                  end
         
     | 
| 
       15 
15 
     | 
    
         | 
| 
      
 16 
     | 
    
         
            +
                  # XXX: investigate: was this method made redundant after:
         
     | 
| 
      
 17 
     | 
    
         
            +
                  # https://github.com/nomadium/linzer/pull/10
         
     | 
| 
       16 
18 
     | 
    
         
             
                  def new_rsa_pss_sha512_public_key(material, key_id = nil)
         
     | 
| 
       17 
19 
     | 
    
         
             
                    key = OpenSSL::PKey::RSA.new(material)
         
     | 
| 
       18 
     | 
    
         
            -
                    Linzer:: 
     | 
| 
      
 20 
     | 
    
         
            +
                    Linzer::RSAPSS::Key.new(key, id: key_id, digest: "SHA512")
         
     | 
| 
       19 
21 
     | 
    
         
             
                  end
         
     | 
| 
       20 
22 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
                   
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
                   
     | 
| 
      
 23 
     | 
    
         
            +
                  def generate_rsa_v1_5_sha256_key(size, key_id = nil)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    material = OpenSSL::PKey::RSA.generate(size)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    Linzer::RSA::Key.new(material, id: key_id, digest: "SHA256")
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  def new_rsa_v1_5_sha256_key(material, key_id = nil)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    key = OpenSSL::PKey.read(material)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    Linzer::RSA::Key.new(key, id: key_id, digest: "SHA256")
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  # XXX: investigate: was this method made redundant after:
         
     | 
| 
      
 34 
     | 
    
         
            +
                  # https://github.com/nomadium/linzer/pull/10
         
     | 
| 
      
 35 
     | 
    
         
            +
                  def new_rsa_v1_5_sha256_public_key(material, key_id = nil)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    key = OpenSSL::PKey.read(material)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    Linzer::RSA::Key.new(key, id: key_id, digest: "SHA256")
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
       25 
39 
     | 
    
         | 
| 
       26 
40 
     | 
    
         
             
                  def generate_hmac_sha256_key(key_id = nil)
         
     | 
| 
       27 
41 
     | 
    
         
             
                    material = OpenSSL::Random.random_bytes(64)
         
     | 
    
        data/lib/linzer/rsa.rb
    CHANGED
    
    | 
         @@ -15,13 +15,7 @@ module Linzer 
     | 
|
| 
       15 
15 
     | 
    
         | 
| 
       16 
16 
     | 
    
         
             
                  def verify(signature, data)
         
     | 
| 
       17 
17 
     | 
    
         
             
                    # XXX: should check if the key is usable for verifying
         
     | 
| 
       18 
     | 
    
         
            -
                    return true if @material. 
     | 
| 
       19 
     | 
    
         
            -
                      @params[:digest],
         
     | 
| 
       20 
     | 
    
         
            -
                      signature,
         
     | 
| 
       21 
     | 
    
         
            -
                      data,
         
     | 
| 
       22 
     | 
    
         
            -
                      salt_length: @params[:salt_length] || :auto,
         
     | 
| 
       23 
     | 
    
         
            -
                      mgf1_hash:   @params[:digest]
         
     | 
| 
       24 
     | 
    
         
            -
                    )
         
     | 
| 
      
 18 
     | 
    
         
            +
                    return true if @material.verify(@params[:digest], signature, data)
         
     | 
| 
       25 
19 
     | 
    
         
             
                    false
         
     | 
| 
       26 
20 
     | 
    
         
             
                  end
         
     | 
| 
       27 
21 
     | 
    
         
             
                end
         
     | 
| 
         @@ -0,0 +1,40 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Linzer
         
     | 
| 
      
 4 
     | 
    
         
            +
              module RSAPSS
         
     | 
| 
      
 5 
     | 
    
         
            +
                SALT_LENGTH = 64
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                class Key < Linzer::Key
         
     | 
| 
      
 8 
     | 
    
         
            +
                  def validate
         
     | 
| 
      
 9 
     | 
    
         
            +
                    super
         
     | 
| 
      
 10 
     | 
    
         
            +
                    validate_digest
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  def sign(data)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    # XXX: should check if the key is usable for signing
         
     | 
| 
      
 15 
     | 
    
         
            +
                    @material.sign(@params[:digest], data, signature_options)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  def verify(signature, data)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    # XXX: should check if the key is usable for verifying
         
     | 
| 
      
 20 
     | 
    
         
            +
                    return true if @material.verify(
         
     | 
| 
      
 21 
     | 
    
         
            +
                      @params[:digest],
         
     | 
| 
      
 22 
     | 
    
         
            +
                      signature,
         
     | 
| 
      
 23 
     | 
    
         
            +
                      data,
         
     | 
| 
      
 24 
     | 
    
         
            +
                      signature_options
         
     | 
| 
      
 25 
     | 
    
         
            +
                    )
         
     | 
| 
      
 26 
     | 
    
         
            +
                    false
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  private
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  def signature_options
         
     | 
| 
      
 32 
     | 
    
         
            +
                    {
         
     | 
| 
      
 33 
     | 
    
         
            +
                      rsa_padding_mode: "pss",
         
     | 
| 
      
 34 
     | 
    
         
            +
                      rsa_pss_saltlen: @params[:salt_length] || SALT_LENGTH,
         
     | 
| 
      
 35 
     | 
    
         
            +
                      rsa_mgf1_md:   @params[:digest]
         
     | 
| 
      
 36 
     | 
    
         
            +
                    }
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
              end
         
     | 
| 
      
 40 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/linzer/version.rb
    CHANGED
    
    
    
        data/lib/linzer.rb
    CHANGED
    
    | 
         @@ -14,12 +14,14 @@ require_relative "linzer/message" 
     | 
|
| 
       14 
14 
     | 
    
         
             
            require_relative "linzer/signature"
         
     | 
| 
       15 
15 
     | 
    
         
             
            require_relative "linzer/key"
         
     | 
| 
       16 
16 
     | 
    
         
             
            require_relative "linzer/rsa"
         
     | 
| 
      
 17 
     | 
    
         
            +
            require_relative "linzer/rsa_pss"
         
     | 
| 
       17 
18 
     | 
    
         
             
            require_relative "linzer/hmac"
         
     | 
| 
       18 
19 
     | 
    
         
             
            require_relative "linzer/ed25519"
         
     | 
| 
       19 
20 
     | 
    
         
             
            require_relative "linzer/ecdsa"
         
     | 
| 
       20 
21 
     | 
    
         
             
            require_relative "linzer/key/helper"
         
     | 
| 
       21 
22 
     | 
    
         
             
            require_relative "linzer/signer"
         
     | 
| 
       22 
23 
     | 
    
         
             
            require_relative "linzer/verifier"
         
     | 
| 
      
 24 
     | 
    
         
            +
            require_relative "rack/auth/signature"
         
     | 
| 
       23 
25 
     | 
    
         | 
| 
       24 
26 
     | 
    
         
             
            module Linzer
         
     | 
| 
       25 
27 
     | 
    
         
             
              class Error < StandardError; end
         
     | 
| 
         @@ -0,0 +1,206 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "linzer"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "logger"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "yaml"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            # Rack::Auth::Signature implements HTTP Message Signatures, as per RFC 9421.
         
     | 
| 
      
 6 
     | 
    
         
            +
            #
         
     | 
| 
      
 7 
     | 
    
         
            +
            # Initialize with the Rack application that you want protecting.
         
     | 
| 
      
 8 
     | 
    
         
            +
            # A hash with options and a block can be passed to customize, enhance
         
     | 
| 
      
 9 
     | 
    
         
            +
            # or disable security checks applied to incoming requests.
         
     | 
| 
      
 10 
     | 
    
         
            +
            #
         
     | 
| 
      
 11 
     | 
    
         
            +
            module Rack
         
     | 
| 
      
 12 
     | 
    
         
            +
              module Auth
         
     | 
| 
      
 13 
     | 
    
         
            +
                class Signature
         
     | 
| 
      
 14 
     | 
    
         
            +
                  def initialize(app, options = {}, &block)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    @app = app
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @options = load_options(options)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    instance_eval(&block) if block
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  def call(env)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    @request = Rack::Request.new(env)
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                    if excluded? || allowed?
         
     | 
| 
      
 24 
     | 
    
         
            +
                      @app.call(env)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    else
         
     | 
| 
      
 26 
     | 
    
         
            +
                      response = options[:error_response].values
         
     | 
| 
      
 27 
     | 
    
         
            +
                      Rack::Response.new(*response).finish
         
     | 
| 
      
 28 
     | 
    
         
            +
                    end
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  private
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  def excluded?
         
     | 
| 
      
 34 
     | 
    
         
            +
                    return false if !request
         
     | 
| 
      
 35 
     | 
    
         
            +
                    Array(options[:except]).include?(request.path_info)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  def allowed?
         
     | 
| 
      
 39 
     | 
    
         
            +
                    has_signature? && acceptable? && verifiable?
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  DEFAULT_OPTIONS = {
         
     | 
| 
      
 43 
     | 
    
         
            +
                    no_older_than: 900,
         
     | 
| 
      
 44 
     | 
    
         
            +
                    created_required: true,
         
     | 
| 
      
 45 
     | 
    
         
            +
                    nonce_required:   false,
         
     | 
| 
      
 46 
     | 
    
         
            +
                    alg_required:     false,
         
     | 
| 
      
 47 
     | 
    
         
            +
                    tag_required:     false,
         
     | 
| 
      
 48 
     | 
    
         
            +
                    expires_required: false,
         
     | 
| 
      
 49 
     | 
    
         
            +
                    keyid_required:   false,
         
     | 
| 
      
 50 
     | 
    
         
            +
                    known_keys:       {},
         
     | 
| 
      
 51 
     | 
    
         
            +
                    covered_components: %w[@method @request-target @authority date],
         
     | 
| 
      
 52 
     | 
    
         
            +
                    error_response: {body: [], status: 401, headers: {}}
         
     | 
| 
      
 53 
     | 
    
         
            +
                  }
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                  private_constant :DEFAULT_OPTIONS
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  attr_reader :request, :options
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  def params
         
     | 
| 
      
 60 
     | 
    
         
            +
                    @signature.parameters || {}
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  def logger
         
     | 
| 
      
 64 
     | 
    
         
            +
                    @logger ||= request.logger || ::Logger.new($stderr)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                  def has_signature?
         
     | 
| 
      
 68 
     | 
    
         
            +
                    @message = Linzer::Message.new(request)
         
     | 
| 
      
 69 
     | 
    
         
            +
                    @signature = Linzer::Signature.build(@message.headers)
         
     | 
| 
      
 70 
     | 
    
         
            +
                    request.env["rack.signature"] = @signature
         
     | 
| 
      
 71 
     | 
    
         
            +
                    (@signature.to_h.keys & %w[signature signature-input]).size == 2
         
     | 
| 
      
 72 
     | 
    
         
            +
                  rescue => ex
         
     | 
| 
      
 73 
     | 
    
         
            +
                    logger.error ex.message
         
     | 
| 
      
 74 
     | 
    
         
            +
                    false
         
     | 
| 
      
 75 
     | 
    
         
            +
                  end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                  def created?
         
     | 
| 
      
 78 
     | 
    
         
            +
                    required = options[:created_required]
         
     | 
| 
      
 79 
     | 
    
         
            +
                    if required
         
     | 
| 
      
 80 
     | 
    
         
            +
                      params.key?("created") && Integer(params["created"])
         
     | 
| 
      
 81 
     | 
    
         
            +
                    else
         
     | 
| 
      
 82 
     | 
    
         
            +
                      !required
         
     | 
| 
      
 83 
     | 
    
         
            +
                    end
         
     | 
| 
      
 84 
     | 
    
         
            +
                  end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                  def expires?
         
     | 
| 
      
 87 
     | 
    
         
            +
                    required = options[:expires_required]
         
     | 
| 
      
 88 
     | 
    
         
            +
                    if required
         
     | 
| 
      
 89 
     | 
    
         
            +
                      params.key?("expires") && Integer(params["expires"]) > Time.now.to_i
         
     | 
| 
      
 90 
     | 
    
         
            +
                    else
         
     | 
| 
      
 91 
     | 
    
         
            +
                      !required
         
     | 
| 
      
 92 
     | 
    
         
            +
                    end
         
     | 
| 
      
 93 
     | 
    
         
            +
                  end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                  def keyid?
         
     | 
| 
      
 96 
     | 
    
         
            +
                    required = options[:keyid_required]
         
     | 
| 
      
 97 
     | 
    
         
            +
                    required ? params.key?("keyid") : !required
         
     | 
| 
      
 98 
     | 
    
         
            +
                  end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                  def nonce?
         
     | 
| 
      
 101 
     | 
    
         
            +
                    required = options[:nonce_required]
         
     | 
| 
      
 102 
     | 
    
         
            +
                    required ? params.key?("nonce") : !required
         
     | 
| 
      
 103 
     | 
    
         
            +
                  end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                  def alg?
         
     | 
| 
      
 106 
     | 
    
         
            +
                    required = options[:alg_required]
         
     | 
| 
      
 107 
     | 
    
         
            +
                    required ? params.key?("alg") : !required
         
     | 
| 
      
 108 
     | 
    
         
            +
                  end
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                  def tag?
         
     | 
| 
      
 111 
     | 
    
         
            +
                    required = options[:tag_required]
         
     | 
| 
      
 112 
     | 
    
         
            +
                    required ? params.key?("tag") : !required
         
     | 
| 
      
 113 
     | 
    
         
            +
                  end
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                  def has_required_params?
         
     | 
| 
      
 116 
     | 
    
         
            +
                    created? && expires? && keyid? && nonce? && alg? && tag?
         
     | 
| 
      
 117 
     | 
    
         
            +
                  rescue => ex
         
     | 
| 
      
 118 
     | 
    
         
            +
                    logger.error ex.message
         
     | 
| 
      
 119 
     | 
    
         
            +
                    false
         
     | 
| 
      
 120 
     | 
    
         
            +
                  end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                  def has_required_components?
         
     | 
| 
      
 123 
     | 
    
         
            +
                    components         = @signature.components || []
         
     | 
| 
      
 124 
     | 
    
         
            +
                    covered_components = options[:covered_components]
         
     | 
| 
      
 125 
     | 
    
         
            +
                    warning = "Insufficient coverage by signature. Consult S 7.2.1. in RFC"
         
     | 
| 
      
 126 
     | 
    
         
            +
                    logger.warn warning if covered_components.empty?
         
     | 
| 
      
 127 
     | 
    
         
            +
                    (covered_components || []).all? { |c| components.include?(c) }
         
     | 
| 
      
 128 
     | 
    
         
            +
                  end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                  def acceptable?
         
     | 
| 
      
 131 
     | 
    
         
            +
                    has_required_params? && has_required_components?
         
     | 
| 
      
 132 
     | 
    
         
            +
                  end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                  def verifiable?
         
     | 
| 
      
 135 
     | 
    
         
            +
                    verify_opts = {}
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                    warning = "Risk of signature replay! (Consult S 7.2.2. in RFC)"
         
     | 
| 
      
 138 
     | 
    
         
            +
                    logger.warn warning unless options[:no_older_than]
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
                    if options[:no_older_than]
         
     | 
| 
      
 141 
     | 
    
         
            +
                      age = Integer(options[:no_older_than])
         
     | 
| 
      
 142 
     | 
    
         
            +
                      verify_opts[:no_older_than] = age
         
     | 
| 
      
 143 
     | 
    
         
            +
                    end
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                    Linzer.verify(key, @message, @signature, **verify_opts)
         
     | 
| 
      
 146 
     | 
    
         
            +
                  rescue => ex
         
     | 
| 
      
 147 
     | 
    
         
            +
                    logger.error ex.message
         
     | 
| 
      
 148 
     | 
    
         
            +
                    false
         
     | 
| 
      
 149 
     | 
    
         
            +
                  end
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
                  def key
         
     | 
| 
      
 152 
     | 
    
         
            +
                    build_key(params["keyid"] || :default)
         
     | 
| 
      
 153 
     | 
    
         
            +
                  end
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
                  def build_key(keyid)
         
     | 
| 
      
 156 
     | 
    
         
            +
                    material = if keyid == :default
         
     | 
| 
      
 157 
     | 
    
         
            +
                      options[:default_key]
         
     | 
| 
      
 158 
     | 
    
         
            +
                    else
         
     | 
| 
      
 159 
     | 
    
         
            +
                      key_data = options[:known_keys][keyid] || {}
         
     | 
| 
      
 160 
     | 
    
         
            +
                      if !key_data.key?("key") && key_data.key?("path")
         
     | 
| 
      
 161 
     | 
    
         
            +
                        key_data["key"] = IO.read(key_data["path"]) rescue nil
         
     | 
| 
      
 162 
     | 
    
         
            +
                      end
         
     | 
| 
      
 163 
     | 
    
         
            +
                      key_data
         
     | 
| 
      
 164 
     | 
    
         
            +
                    end
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
                    key_not_found = "Key not found. Signature cannot be verified."
         
     | 
| 
      
 167 
     | 
    
         
            +
                    raise Linzer::Error.new key_not_found if !material || !material["key"]
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
                    alg = @signature.parameters["alg"] || material["alg"] || :unknown
         
     | 
| 
      
 170 
     | 
    
         
            +
                    instantiate_key(keyid, alg, material)
         
     | 
| 
      
 171 
     | 
    
         
            +
                  end
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                  def instantiate_key(keyid, alg, material)
         
     | 
| 
      
 174 
     | 
    
         
            +
                    key_methods = {
         
     | 
| 
      
 175 
     | 
    
         
            +
                      "rsa-pss-sha512"    => :new_rsa_pss_sha512_key,
         
     | 
| 
      
 176 
     | 
    
         
            +
                      "rsa-v1_5-sha256"   => :new_rsa_v1_5_sha256_key,
         
     | 
| 
      
 177 
     | 
    
         
            +
                      "hmac-sha256"       => :new_hmac_sha256_key,
         
     | 
| 
      
 178 
     | 
    
         
            +
                      "ecdsa-p256-sha256" => :new_ecdsa_p256_sha256_key,
         
     | 
| 
      
 179 
     | 
    
         
            +
                      "ecdsa-p384-sha384" => :new_ecdsa_p384_sha384_key,
         
     | 
| 
      
 180 
     | 
    
         
            +
                      "ed25519"           => :new_ed25519_public_key
         
     | 
| 
      
 181 
     | 
    
         
            +
                    }
         
     | 
| 
      
 182 
     | 
    
         
            +
                    method = key_methods[alg]
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
                    alg_error = "Unsupported or unknown signature algorithm"
         
     | 
| 
      
 185 
     | 
    
         
            +
                    raise Linzer::Error.new alg_error unless method
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
                    Linzer.public_send(method, material["key"], keyid.to_s)
         
     | 
| 
      
 188 
     | 
    
         
            +
                  end
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
                  def load_options(options)
         
     | 
| 
      
 191 
     | 
    
         
            +
                    DEFAULT_OPTIONS
         
     | 
| 
      
 192 
     | 
    
         
            +
                      .merge(load_options_from_config_file(options))
         
     | 
| 
      
 193 
     | 
    
         
            +
                      .merge(Hash(options)) || {}
         
     | 
| 
      
 194 
     | 
    
         
            +
                  end
         
     | 
| 
      
 195 
     | 
    
         
            +
             
     | 
| 
      
 196 
     | 
    
         
            +
                  def load_options_from_config_file(options)
         
     | 
| 
      
 197 
     | 
    
         
            +
                    config_path = options[:config_path]
         
     | 
| 
      
 198 
     | 
    
         
            +
                    YAML
         
     | 
| 
      
 199 
     | 
    
         
            +
                      .safe_load_file(config_path)
         
     | 
| 
      
 200 
     | 
    
         
            +
                      .transform_keys(&:to_sym)
         
     | 
| 
      
 201 
     | 
    
         
            +
                  rescue
         
     | 
| 
      
 202 
     | 
    
         
            +
                    {}
         
     | 
| 
      
 203 
     | 
    
         
            +
                  end
         
     | 
| 
      
 204 
     | 
    
         
            +
                end
         
     | 
| 
      
 205 
     | 
    
         
            +
              end
         
     | 
| 
      
 206 
     | 
    
         
            +
            end
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,13 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: linzer
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.7.0.beta1
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Miguel Landaeta
         
     | 
| 
       8 
     | 
    
         
            -
            autorequire:
         
     | 
| 
       9 
8 
     | 
    
         
             
            bindir: exe
         
     | 
| 
       10 
9 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date:  
     | 
| 
      
 10 
     | 
    
         
            +
            date: 1980-01-02 00:00:00.000000000 Z
         
     | 
| 
       12 
11 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
12 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
13 
     | 
    
         
             
              name: openssl
         
     | 
| 
         @@ -104,7 +103,26 @@ dependencies: 
     | 
|
| 
       104 
103 
     | 
    
         
             
                - - ">="
         
     | 
| 
       105 
104 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       106 
105 
     | 
    
         
             
                    version: 3.1.2
         
     | 
| 
       107 
     | 
    
         
            -
             
     | 
| 
      
 106 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 107 
     | 
    
         
            +
              name: logger
         
     | 
| 
      
 108 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 109 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 110 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 111 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 112 
     | 
    
         
            +
                    version: '1.7'
         
     | 
| 
      
 113 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 114 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 115 
     | 
    
         
            +
                    version: 1.7.0
         
     | 
| 
      
 116 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 117 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 118 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 119 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 120 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 121 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 122 
     | 
    
         
            +
                    version: '1.7'
         
     | 
| 
      
 123 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 124 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 125 
     | 
    
         
            +
                    version: 1.7.0
         
     | 
| 
       108 
126 
     | 
    
         
             
            email:
         
     | 
| 
       109 
127 
     | 
    
         
             
            - miguel@miguel.cc
         
     | 
| 
       110 
128 
     | 
    
         
             
            executables: []
         
     | 
| 
         @@ -118,6 +136,10 @@ files: 
     | 
|
| 
       118 
136 
     | 
    
         
             
            - LICENSE.txt
         
     | 
| 
       119 
137 
     | 
    
         
             
            - README.md
         
     | 
| 
       120 
138 
     | 
    
         
             
            - Rakefile
         
     | 
| 
      
 139 
     | 
    
         
            +
            - examples/sinatra/Gemfile
         
     | 
| 
      
 140 
     | 
    
         
            +
            - examples/sinatra/config.ru
         
     | 
| 
      
 141 
     | 
    
         
            +
            - examples/sinatra/http-signatures.yml
         
     | 
| 
      
 142 
     | 
    
         
            +
            - examples/sinatra/myapp.rb
         
     | 
| 
       121 
143 
     | 
    
         
             
            - lib/linzer.rb
         
     | 
| 
       122 
144 
     | 
    
         
             
            - lib/linzer/common.rb
         
     | 
| 
       123 
145 
     | 
    
         
             
            - lib/linzer/ecdsa.rb
         
     | 
| 
         @@ -129,10 +151,12 @@ files: 
     | 
|
| 
       129 
151 
     | 
    
         
             
            - lib/linzer/request.rb
         
     | 
| 
       130 
152 
     | 
    
         
             
            - lib/linzer/response.rb
         
     | 
| 
       131 
153 
     | 
    
         
             
            - lib/linzer/rsa.rb
         
     | 
| 
      
 154 
     | 
    
         
            +
            - lib/linzer/rsa_pss.rb
         
     | 
| 
       132 
155 
     | 
    
         
             
            - lib/linzer/signature.rb
         
     | 
| 
       133 
156 
     | 
    
         
             
            - lib/linzer/signer.rb
         
     | 
| 
       134 
157 
     | 
    
         
             
            - lib/linzer/verifier.rb
         
     | 
| 
       135 
158 
     | 
    
         
             
            - lib/linzer/version.rb
         
     | 
| 
      
 159 
     | 
    
         
            +
            - lib/rack/auth/signature.rb
         
     | 
| 
       136 
160 
     | 
    
         
             
            homepage: https://github.com/nomadium/linzer
         
     | 
| 
       137 
161 
     | 
    
         
             
            licenses:
         
     | 
| 
       138 
162 
     | 
    
         
             
            - MIT
         
     | 
| 
         @@ -140,7 +164,6 @@ metadata: 
     | 
|
| 
       140 
164 
     | 
    
         
             
              homepage_uri: https://github.com/nomadium/linzer
         
     | 
| 
       141 
165 
     | 
    
         
             
              source_code_uri: https://github.com/nomadium/linzer
         
     | 
| 
       142 
166 
     | 
    
         
             
              changelog_uri: https://github.com/nomadium/linzer/blob/master/CHANGELOG.md
         
     | 
| 
       143 
     | 
    
         
            -
            post_install_message:
         
     | 
| 
       144 
167 
     | 
    
         
             
            rdoc_options: []
         
     | 
| 
       145 
168 
     | 
    
         
             
            require_paths:
         
     | 
| 
       146 
169 
     | 
    
         
             
            - lib
         
     | 
| 
         @@ -155,8 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       155 
178 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       156 
179 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       157 
180 
     | 
    
         
             
            requirements: []
         
     | 
| 
       158 
     | 
    
         
            -
            rubygems_version: 3. 
     | 
| 
       159 
     | 
    
         
            -
            signing_key:
         
     | 
| 
      
 181 
     | 
    
         
            +
            rubygems_version: 3.6.7
         
     | 
| 
       160 
182 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       161 
183 
     | 
    
         
             
            summary: An implementation of HTTP Messages Signatures (RFC9421)
         
     | 
| 
       162 
184 
     | 
    
         
             
            test_files: []
         
     |