Class: Authentic::KeyManager

Inherits:
Object
  • Object
show all
Defined in:
lib/authentic/key_manager.rb

Overview

Internal: manages JWK retrieval, caching, and validation.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(max_age) ⇒ KeyManager

Returns a new instance of KeyManager.



12
13
14
15
# File 'lib/authentic/key_manager.rb', line 12

def initialize(max_age)
  @store = KeyStore.new(max_age)
  @well_known = '/.well-known/openid-configuration'
end

Instance Attribute Details

#storeObject (readonly)

Returns the value of attribute store.



10
11
12
# File 'lib/authentic/key_manager.rb', line 10

def store
  @store
end

#well_knownObject (readonly)

Returns the value of attribute well_known.



10
11
12
# File 'lib/authentic/key_manager.rb', line 10

def well_known
  @well_known
end

Instance Method Details

#get(jwt) ⇒ Object

Public: retrieves JWK.

jwt - JSON::JWT.

Returns JSON::JWK.



22
23
24
25
26
27
28
29
30
31
32
# File 'lib/authentic/key_manager.rb', line 22

def get(jwt)
  iss = jwt.fetch(:iss)

  result = store.get(iss, jwt.kid)

  return result unless result.nil?

  # Refresh all keys for an issuer while I have the updated data on hand
  hydrate_iss_keys iss
  store.get(iss, jwt.kid)
end

#hydrate_iss_keys(iss) ⇒ Object

Internal: hydrates JWK cache.

iss - issuer URI.

Returns nothing.

Raises:



73
74
75
76
77
78
79
80
81
82
# File 'lib/authentic/key_manager.rb', line 73

def hydrate_iss_keys(iss)
  uri = iss.sub(%r{[\/]+$}, '') + well_known
  json = json_req uri.to_s
  body = json_req json['jwks_uri']

  raise InvalidKey, "no valid JWK found, #{json['jwks_uri']}" if body['keys']&.blank?

  keys = body['keys'].select { |key| valid_key(key) }
  hydrate_store(keys, iss)
end

#hydrate_store(keys, iss) ⇒ Object

Internal: hydrates key store.

keys - array of keys hash. iss - JWT issuer endpoint.

Returns nothing.



90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/authentic/key_manager.rb', line 90

def hydrate_store(keys, iss)
  keys.each do |key|
    store.set(
      iss, key['kid'],
      JSON::JWK.new(
        kty: key['kty'],
        e: key['e'],
        n: key['n'],
        kid: key['kid']
      )
    )
  end
end

#json_req(uri) ⇒ Object

Internal: performs JSON request.

uri - endpoint to request.

Returns JSON.



57
58
59
60
61
62
63
64
65
66
# File 'lib/authentic/key_manager.rb', line 57

def json_req(uri)
  begin
    resp = RestClient.get(uri, accept: :json)
  rescue RestClient::ExceptionWithResponse => e
    code = e.response.code
    raise RequestError.new("failed to retrieve JWK, status #{code}", code)
  end

  JSON.parse resp.body
end

#valid_key(key) ⇒ Object

Internal: performs JSON request.

key - hash with JWK data.

Returns boolean.



48
49
50
# File 'lib/authentic/key_manager.rb', line 48

def valid_key(key)
  valid_rsa_key(key) && (key['x5c']&.length || (key['n'] && key['e']))
end

#valid_rsa_key(key) ⇒ Object

Internal: validates RSA key.

key - hash with key data.

Returns boolean.



39
40
41
# File 'lib/authentic/key_manager.rb', line 39

def valid_rsa_key(key)
  key['use'] == 'sig' && key['kty'] == 'RSA' && key['kid']
end