simplicity_client 0.2.0 → 0.2.1
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/lib/fido2_client.rb +91 -0
- metadata +2 -1
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 3283578b786c0a520eeaee6b46290f1459e9075925f8fdf172534ac533270e33
         | 
| 4 | 
            +
              data.tar.gz: cee5f3f5ac077c4cea0bc246d7f1a4b2ea03a23db7ad21795485730685ba3959
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 2ddd8ee90c05b00ba4ed232846f5c2e546e087c93c7e2c3a58965c74480640fe04823bea6d94c9b3b0f58e64f41cd738b8b157420b71d5864846cb74e0600185
         | 
| 7 | 
            +
              data.tar.gz: fbf5d020b63bae7fd804154b34513d95e9991afa39023877905072e69df66879b4afd3e7028251204e314720c4ca472860dc9e81d86d4a90e521202791dd7bbe
         | 
    
        data/lib/fido2_client.rb
    ADDED
    
    | @@ -0,0 +1,91 @@ | |
| 1 | 
            +
            require "json"
         | 
| 2 | 
            +
            require "digest"
         | 
| 3 | 
            +
            require "openssl"
         | 
| 4 | 
            +
            require "base64"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Fido2Client
         | 
| 7 | 
            +
              Passkey = Data.define(:credentialId, :keyAlgorithm, :keyCurve, :keyValue, :userHandle)
         | 
| 8 | 
            +
              Assertion = Data.define(:authenticator_data, :client_data_json, :credential_id, :user_handle, :signature)
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              class Client
         | 
| 11 | 
            +
                def initialize
         | 
| 12 | 
            +
                  @origin = "https://app.simplicity.kiwi"
         | 
| 13 | 
            +
                  @rp_id = "simplicity.kiwi"
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def get_assertion(passkey, challenge)
         | 
| 17 | 
            +
                  collected_client_data = {
         | 
| 18 | 
            +
                    type: "webauthn.get",
         | 
| 19 | 
            +
                    challenge: challenge,
         | 
| 20 | 
            +
                    origin: @origin,
         | 
| 21 | 
            +
                    crossOrigin: false,
         | 
| 22 | 
            +
                  }
         | 
| 23 | 
            +
                  client_data_json = JSON.dump(collected_client_data)
         | 
| 24 | 
            +
                  client_data_hash = Digest::SHA256.digest(client_data_json)
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  # Assertion
         | 
| 27 | 
            +
                  auth_data = generate_auth_data
         | 
| 28 | 
            +
                  private_key = parse_private_key(passkey.keyAlgorithm, passkey.keyCurve, passkey.keyValue)
         | 
| 29 | 
            +
                  signature = generate_signature(auth_data, client_data_hash, private_key)
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  Assertion.new(
         | 
| 32 | 
            +
                    authenticator_data: Base64.urlsafe_encode64(auth_data.pack("c*"), padding: false),
         | 
| 33 | 
            +
                    client_data_json: Base64.urlsafe_encode64(client_data_json, padding: false),
         | 
| 34 | 
            +
                    credential_id: Base64.urlsafe_encode64(guid_to_raw_format(passkey.credentialId), padding: false),
         | 
| 35 | 
            +
                    user_handle: passkey.userHandle,
         | 
| 36 | 
            +
                    signature: Base64.urlsafe_encode64(signature, padding: false),
         | 
| 37 | 
            +
                  )
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                private
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def box(tag, lines)
         | 
| 43 | 
            +
                  lines.unshift "-----BEGIN #{tag}-----"
         | 
| 44 | 
            +
                  lines.push "-----END #{tag}-----"
         | 
| 45 | 
            +
                  lines.join("\n")
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def der_to_pem(tag, der)
         | 
| 49 | 
            +
                  box tag, Base64.strict_encode64(der).scan(/.{1,64}/)
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                def parse_private_key(key_algorithm, key_curve, key_value)
         | 
| 53 | 
            +
                  raise "Unsupported key algorithm: #{key_algorithm}" unless key_algorithm == "ECDSA"
         | 
| 54 | 
            +
                  raise "Unsupported key curve: #{key_curve}" unless key_curve == "P-256"
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  # Decode the Base64 key value
         | 
| 57 | 
            +
                  key_value_bin = Base64.urlsafe_decode64(key_value)
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  pem = der_to_pem("PRIVATE KEY", key_value_bin)
         | 
| 60 | 
            +
                  OpenSSL::PKey::EC.new(pem)
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                def generate_signature(auth_data, client_data_hash, private_key)
         | 
| 64 | 
            +
                  sig_base = [*auth_data, *client_data_hash.bytes]
         | 
| 65 | 
            +
                  digest = OpenSSL::Digest.new("SHA256")
         | 
| 66 | 
            +
                  private_key.sign(digest, sig_base.pack("c*"))
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                def generate_auth_data
         | 
| 70 | 
            +
                  auth_data = []
         | 
| 71 | 
            +
                  rp_id_hash = Digest::SHA256.digest(@rp_id)
         | 
| 72 | 
            +
                  auth_data += rp_id_hash.bytes
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  # Flags asserted: Backup eligibility, Backup State, User Verification, User Presence
         | 
| 75 | 
            +
                  flags = 0x1D
         | 
| 76 | 
            +
                  auth_data.push(flags)
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  # Counter
         | 
| 79 | 
            +
                  auth_data += [0, 0, 0, 0]
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  auth_data
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                def guid_to_raw_format(guid)
         | 
| 85 | 
            +
                  raise TypeError, "GUID parameter is invalid" unless guid.match?(/\A[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\z/)
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  # Remove the hyphens and pack the string into raw binary
         | 
| 88 | 
            +
                  [guid.delete("-")].pack("H*")
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
              end
         | 
| 91 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: simplicity_client
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.2. | 
| 4 | 
            +
              version: 0.2.1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - George Dewar
         | 
| @@ -101,6 +101,7 @@ executables: [] | |
| 101 101 | 
             
            extensions: []
         | 
| 102 102 | 
             
            extra_rdoc_files: []
         | 
| 103 103 | 
             
            files:
         | 
| 104 | 
            +
            - lib/fido2_client.rb
         | 
| 104 105 | 
             
            - lib/simplicity_client.rb
         | 
| 105 106 | 
             
            homepage: https://github.com/GeorgeDewar/simplicity_client
         | 
| 106 107 | 
             
            licenses:
         |