Module: PQCrypto::JWT::JWK

Defined in:
lib/pq_crypto/jwt/jwk.rb

Constant Summary collapse

KTY =
"AKP".freeze

Class Method Summary collapse

Class Method Details

.alg_for_algorithm!(algorithm) ⇒ Object



90
91
92
93
94
95
# File 'lib/pq_crypto/jwt/jwk.rb', line 90

def alg_for_algorithm!(algorithm)
  match = PQCrypto::JWT.signing_algorithms.find { |candidate| candidate.pq_crypto_algorithm == algorithm }
  raise PQCrypto::JWT::UnsupportedAlgorithm, "Unsupported pq_crypto signature algorithm: #{algorithm.inspect}" unless match

  match.alg
end

.algorithm_from_jwk!(jwk) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/pq_crypto/jwt/jwk.rb', line 77

def algorithm_from_jwk!(jwk)
  unless jwk.fetch("kty", nil) == KTY
    raise PQCrypto::JWT::Error, "Unsupported JWK kty: #{jwk.fetch('kty', nil).inspect}"
  end

  alg = jwk.fetch("alg", nil)
  algorithm = PQCrypto::JWT.algorithm_for(alg)
  raise PQCrypto::JWT::UnsupportedAlgorithm, "Unsupported JWK alg: #{alg.inspect}" unless algorithm
  raise PQCrypto::JWT::UnsupportedAlgorithm, "Unsupported JWK alg: #{alg.inspect}" unless algorithm.key_kind == :signature

  algorithm.pq_crypto_algorithm
end

.base64url(bytes) ⇒ Object



49
50
51
# File 'lib/pq_crypto/jwt/jwk.rb', line 49

def base64url(bytes)
  Base64.urlsafe_encode64(String(bytes).b, padding: false)
end

.base64url_decode(value) ⇒ Object



53
54
55
56
57
# File 'lib/pq_crypto/jwt/jwk.rb', line 53

def base64url_decode(value)
  Base64.urlsafe_decode64(String(value))
rescue ArgumentError => e
  raise PQCrypto::JWT::Error, "Invalid base64url value: #{e.message}"
end

.from_public_key(public_key, kid: nil) ⇒ Object



14
15
16
17
# File 'lib/pq_crypto/jwt/jwk.rb', line 14

def from_public_key(public_key, kid: nil)
  validate_public_key!(public_key)
  base_public_jwk(public_key.algorithm, public_key.to_bytes, kid: kid).freeze
end

.from_secret_keyObject



19
20
21
22
# File 'lib/pq_crypto/jwt/jwk.rb', line 19

def from_secret_key(*, **)
  raise PQCrypto::JWT::UnsupportedAlgorithm,
        "Private AKP JWK export is not supported in the first release; use PEM/PKCS#8 for signing keys"
end

.normalize_hash!(hash) ⇒ Object



59
60
61
62
63
64
65
66
67
# File 'lib/pq_crypto/jwt/jwk.rb', line 59

def normalize_hash!(hash)
  unless hash.respond_to?(:to_hash)
    raise PQCrypto::JWT::Error, "JWK must be a Hash-like object"
  end

  hash.to_hash.each_with_object({}) do |(key, value), normalized|
    normalized[String(key)] = value
  end
end

.public_key_from_jwk(hash) ⇒ Object



24
25
26
27
28
29
30
31
32
# File 'lib/pq_crypto/jwt/jwk.rb', line 24

def public_key_from_jwk(hash)
  jwk = normalize_hash!(hash)
  reject_private_material!(jwk)
  algorithm = algorithm_from_jwk!(jwk)
  public_bytes = decode_required_key_bytes!(jwk, "pub", algorithm, :public_key_bytes)
  PQCrypto::Signature.public_key_from_bytes(algorithm, public_bytes)
rescue ArgumentError => e
  raise PQCrypto::JWT::Error, e.message
end

.secret_key_from_jwkObject



34
35
36
37
# File 'lib/pq_crypto/jwt/jwk.rb', line 34

def secret_key_from_jwk(*)
  raise PQCrypto::JWT::UnsupportedAlgorithm,
        "Private AKP JWK import is not supported in the first release; use PEM/PKCS#8 for signing keys"
end

.thumbprint(jwk_hash) ⇒ Object



39
40
41
42
43
44
45
46
47
# File 'lib/pq_crypto/jwt/jwk.rb', line 39

def thumbprint(jwk_hash)
  jwk = normalize_hash!(jwk_hash)
  reject_private_material!(jwk)
  algorithm_from_jwk!(jwk)
  raise PQCrypto::JWT::Error, "JWK pub is required" unless jwk.key?("pub")

  canonical = JSON.generate({ "alg" => jwk.fetch("alg"), "kty" => KTY, "pub" => jwk.fetch("pub") })
  base64url(Digest::SHA256.digest(canonical.b))
end