Module: PQCrypto::JWT::JWK
- Defined in:
- lib/pq_crypto/jwt/jwk.rb
Constant Summary collapse
- KTY =
"AKP".freeze
- SEED_BYTES =
defined?(PQCrypto::PKCS8::ML_DSA_SEED_BYTES) ? PQCrypto::PKCS8::ML_DSA_SEED_BYTES : 32
- THUMBPRINT_URI_PREFIX =
"urn:ietf:params:oauth:jwk-thumbprint:sha-256:".freeze
Class Method Summary collapse
- .algorithm_from_jwk!(jwk) ⇒ Object
- .base64url(bytes) ⇒ Object
- .base64url_decode(value) ⇒ Object
- .from_public_key(public_key, kid: nil, use: nil, key_ops: nil) ⇒ Object
- .from_secret_key(secret_key, kid: nil, public_key: nil, use: nil, key_ops: nil) ⇒ Object
- .from_seed(seed, alg:, kid: nil, public_key: nil, use: nil, key_ops: nil, verify_public: false) ⇒ Object
- .normalize_hash!(hash) ⇒ Object
- .public_key_from_jwk(hash) ⇒ Object
- .secret_key_from_jwk(hash, verify_public: false) ⇒ Object
- .thumbprint(jwk_hash) ⇒ Object
- .thumbprint_uri(jwk_hash) ⇒ Object
Class Method Details
.algorithm_from_jwk!(jwk) ⇒ Object
104 105 106 107 108 |
# File 'lib/pq_crypto/jwt/jwk.rb', line 104 def algorithm_from_jwk!(jwk) raise PQCrypto::JWT::Error, "Unsupported JWK kty: #{jwk['kty'].inspect}" unless jwk["kty"] == KTY pq_algorithm_from_jose!(jwk["alg"]) end |
.base64url(bytes) ⇒ Object
87 88 89 |
# File 'lib/pq_crypto/jwt/jwk.rb', line 87 def base64url(bytes) Base64.urlsafe_encode64(String(bytes).b, padding: false) end |
.base64url_decode(value) ⇒ Object
91 92 93 94 95 96 |
# File 'lib/pq_crypto/jwt/jwk.rb', line 91 def base64url_decode(value) raw = String(value) Base64.urlsafe_decode64(raw + ("=" * ((4 - raw.bytesize % 4) % 4))) rescue ArgumentError => e raise PQCrypto::JWT::Error, "Invalid base64url value: #{e.}" end |
.from_public_key(public_key, kid: nil, use: nil, key_ops: nil) ⇒ Object
16 17 18 19 |
# File 'lib/pq_crypto/jwt/jwk.rb', line 16 def from_public_key(public_key, kid: nil, use: nil, key_ops: nil) validate_key!(public_key, PQCrypto::Signature::PublicKey) public_jwk(public_key.algorithm, public_key.to_bytes, kid: kid, use: use, key_ops: key_ops).freeze end |
.from_secret_key(secret_key, kid: nil, public_key: nil, use: nil, key_ops: nil) ⇒ Object
42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/pq_crypto/jwt/jwk.rb', line 42 def from_secret_key(secret_key, kid: nil, public_key: nil, use: nil, key_ops: nil) if secret_key.is_a?(PQCrypto::Signature::Keypair) public_key ||= secret_key.public_key secret_key = secret_key.secret_key end validate_key!(secret_key, PQCrypto::Signature::SecretKey) from_seed(seed_from_secret_key!(secret_key), alg: jose_alg_for!(secret_key.algorithm), kid: kid, public_key: public_key, use: use, key_ops: key_ops) end |
.from_seed(seed, alg:, kid: nil, public_key: nil, use: nil, key_ops: nil, verify_public: false) ⇒ Object
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/pq_crypto/jwt/jwk.rb', line 21 def from_seed(seed, alg:, kid: nil, public_key: nil, use: nil, key_ops: nil, verify_public: false) algorithm = pq_algorithm_from_jose!(alg) seed_bytes = validate_seed!(seed, algorithm) derived = derive_public_key(algorithm, seed_bytes) if public_key.nil? || verify_public public_key ||= derived unless public_key raise PQCrypto::JWT::UnsupportedFeature, "AKP JWK export from seed requires pq_crypto public_key_from_seed/keypair_from_seed or public_key:" end validate_key!(public_key, PQCrypto::Signature::PublicKey) unless public_key.algorithm == algorithm raise PQCrypto::JWT::KeyTypeError, "public_key algorithm mismatch: expected #{algorithm.inspect}, got #{public_key.algorithm.inspect}" end check_seed_matches_public!(public_key, derived) if verify_public public_jwk(algorithm, public_key.to_bytes, kid: kid, use: use, key_ops: key_ops) .merge!("priv" => base64url(seed_bytes)) .freeze end |
.normalize_hash!(hash) ⇒ Object
98 99 100 101 102 |
# File 'lib/pq_crypto/jwt/jwk.rb', line 98 def normalize_hash!(hash) raise PQCrypto::JWT::Error, "JWK must be a Hash-like object" unless hash.respond_to?(:to_hash) hash.to_hash.each_with_object({}) { |(key, value), out| out[String(key)] = value } end |
.public_key_from_jwk(hash) ⇒ Object
54 55 56 57 58 59 60 |
# File 'lib/pq_crypto/jwt/jwk.rb', line 54 def public_key_from_jwk(hash) jwk = normalize_hash!(hash) algorithm = algorithm_from_jwk!(jwk) PQCrypto::Signature.public_key_from_bytes(algorithm, decode_field!(jwk, "pub", algorithm)) rescue ArgumentError, PQCrypto::Error => e raise PQCrypto::JWT::Error, e. end |
.secret_key_from_jwk(hash, verify_public: false) ⇒ Object
62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/pq_crypto/jwt/jwk.rb', line 62 def secret_key_from_jwk(hash, verify_public: false) jwk = normalize_hash!(hash) algorithm = algorithm_from_jwk!(jwk) decode_field!(jwk, "pub", algorithm) seed = validate_seed!(base64url_decode(jwk.fetch("priv") { raise PQCrypto::JWT::Error, "JWK priv is required" }), algorithm) check_seed_matches_public!(public_key_from_jwk(jwk), derive_public_key(algorithm, seed)) if verify_public PQCrypto::Signature.secret_key_from_seed(algorithm, seed) rescue ArgumentError, PQCrypto::Error => e raise PQCrypto::JWT::Error, e. end |
.thumbprint(jwk_hash) ⇒ Object
74 75 76 77 78 79 80 81 |
# File 'lib/pq_crypto/jwt/jwk.rb', line 74 def thumbprint(jwk_hash) jwk = normalize_hash!(jwk_hash) algorithm = algorithm_from_jwk!(jwk) decode_field!(jwk, "pub", algorithm) canonical = JSON.generate("alg" => jwk.fetch("alg"), "kty" => KTY, "pub" => jwk.fetch("pub")) base64url(Digest::SHA256.digest(canonical.b)) end |
.thumbprint_uri(jwk_hash) ⇒ Object
83 84 85 |
# File 'lib/pq_crypto/jwt/jwk.rb', line 83 def thumbprint_uri(jwk_hash) "#{THUMBPRINT_URI_PREFIX}#{thumbprint(jwk_hash)}" end |