Module: Tapyrus::JWS

Defined in:
lib/tapyrus/jws.rb

Defined Under Namespace

Classes: DecodeError

Constant Summary collapse

ALGO =
"ES256K"
CURVE_NAME =
"secp256k1"

Class Method Summary collapse

Class Method Details

.decode(jws) ⇒ Array[JSON]

Decode JWS to JSON object

Parameters:

  • jws (String)

    The JWS formatted data to be decoded

Returns:

  • (Array[JSON])

    JSON objects representing JWS header and payload.

Raises:



50
51
52
53
54
55
56
57
# File 'lib/tapyrus/jws.rb', line 50

def decode(jws)
  jwt_claims, header = JWT.decode(jws, nil, false, { algorithm: ALGO })
  jwks_hash = header.dig("jwk", "keys")
  raise Tapyrus::JWS::DecodeError, "No jwk key found in header" unless jwks_hash
  validate_header!(jwks_hash)
  jwks = JWT::JWK::Set.new(jwks_hash)
  JWT.decode(jws, nil, true, { algorithm: ALGO, jwks: jwks, allow_nil_kid: true })
end

.encode(payload, private_key_hex) ⇒ String

Encode data as JWS format.

Parameters:

  • payload (Object)

    The data to be encoded as JWS.

  • private_key_hex (String)

    The private key as hex string

Returns:

  • (String)

    JWS signed with the specified private key



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/tapyrus/jws.rb', line 16

def encode(payload, private_key_hex)
  parameters = { use: "sig", alg: ALGO }

  # see https://github.com/nov/json-jwt/blob/413848c/lib/json/jwk.rb#L162-L172
  # see https://www.rfc-editor.org/rfc/rfc5915.html#page-6
  sequence =
    OpenSSL::ASN1.Sequence(
      [
        OpenSSL::ASN1.Integer(1),
        OpenSSL::ASN1.OctetString(OpenSSL::BN.new(private_key_hex, 16).to_s(2)),
        OpenSSL::ASN1.ObjectId(CURVE_NAME, 0, :EXPLICIT),
        OpenSSL::ASN1.BitString(
          ECDSA::Format::PointOctetString.encode(point_for(private_key_hex), compression: false),
          1,
          :EXPLICIT
        )
      ]
    )
  ec_key = OpenSSL::PKey::EC.new(sequence.to_der)
  jwk = JWT::JWK.new(ec_key, parameters)
  jwks_hash = JWT::JWK::Set.new(jwk).export(include_private: false)
  JWT.encode(payload, jwk.signing_key, jwk[:alg], { typ: "JWT", algo: ALGO, jwk: jwks_hash })
end

.point_for(r_hex) ⇒ ECDSA::Point

Return point object that represents rG

Parameters:

  • r_hex (String)

    r value as hex string

Returns:



72
73
74
# File 'lib/tapyrus/jws.rb', line 72

def point_for(r_hex)
  Tapyrus::Key.new(priv_key: r_hex).to_point
end

.validate_header!(jwks) ⇒ Object



59
60
61
62
63
64
65
66
# File 'lib/tapyrus/jws.rb', line 59

def validate_header!(jwks)
  jwk = jwks.first
  raise Tapyrus::JWS::DecodeError, "No jwk key found in header" unless jwk
  raise Tapyrus::JWS::DecodeError, 'kty must be "EC"' if jwk["kty"] && jwk["kty"] != "EC"
  raise Tapyrus::JWS::DecodeError, 'crv must be "P-256K"' if jwk["crv"] && jwk["crv"] != "P-256K"
  raise Tapyrus::JWS::DecodeError, 'use must be "sig"' if jwk["use"] && jwk["use"] != "sig"
  raise Tapyrus::JWS::DecodeError, 'alg must be "ES256K"' if jwk["alg"] && jwk["alg"] != "ES256K"
end