Class: Sandal::Sig::ES

Inherits:
Object
  • Object
show all
Defined in:
lib/sandal/sig/es.rb

Overview

Base implementation of the ECDSA-SHA family of signature algorithms.

Direct Known Subclasses

ES256, ES384, ES512

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(sha_size, prime_size, key) ⇒ ES

Creates a new instance; it’s probably easier to use one of the subclass constructors.

Parameters:

  • sha_size (Integer)

    The size of the SHA algorithm.

  • prime_size (Integer)

    The size of the ECDSA primes.

  • key (OpenSSL::PKey::EC)

    The key to use for signing (private) or validation (public).



17
18
19
20
21
22
# File 'lib/sandal/sig/es.rb', line 17

def initialize(sha_size, prime_size, key)
  @name = "ES#{sha_size}"
  @digest = OpenSSL::Digest.new("sha#{sha_size}")
  @prime_size = prime_size
  @key = key
end

Instance Attribute Details

#nameString (readonly)

Returns The JWA name of the algorithm.

Returns:

  • (String)

    The JWA name of the algorithm.



10
11
12
# File 'lib/sandal/sig/es.rb', line 10

def name
  @name
end

Class Method Details

.decode_asn1_signature(signature) ⇒ OpenSSL::BN

Decodes an ASN.1 signature into a pair of BNs.

Parameters:

  • signature (String)

    The ASN.1 signature.

Returns:

  • (OpenSSL::BN, OpenSSL::BN)

    A pair of BNs.



51
52
53
54
# File 'lib/sandal/sig/es.rb', line 51

def self.decode_asn1_signature(signature)
  asn_seq = OpenSSL::ASN1.decode(signature)
  return asn_seq.value[0].value, asn_seq.value[1].value
end

.decode_jws_signature(signature) ⇒ OpenSSL::BN

Decodes a JWS signature into a pair of BNs.

Parameters:

  • signature (String)

    The ASN.1 signature.

Returns:

  • (OpenSSL::BN, OpenSSL::BN)

    A pair of BNs.



70
71
72
73
74
75
76
# File 'lib/sandal/sig/es.rb', line 70

def self.decode_jws_signature(signature)
  n_length = signature.length / 2
  s_to_n = -> s { OpenSSL::BN.new(s.unpack('H*')[0], 16) }
  r = s_to_n.call(signature[0..(n_length - 1)])
  s = s_to_n.call(signature[n_length..-1])
  return r, s
end

.encode_asn1_signature(r, s) ⇒ String

Encodes a pair of BNs into an ASN.1 signature.

Parameters:

  • r (OpenSSL::BN)

    The ‘r’ value.

  • s (OpenSSL::BN)

    The ‘s’ value.

Returns:

  • (String)

    The ASN.1 signature.



61
62
63
64
# File 'lib/sandal/sig/es.rb', line 61

def self.encode_asn1_signature(r, s)
  items = [OpenSSL::ASN1::Integer.new(r), OpenSSL::ASN1::Integer.new(s)]
  OpenSSL::ASN1::Sequence.new(items).to_der
end

.encode_jws_signature(r, s, prime_size) ⇒ String

Encodes a pair of BNs into a JWS signature.

Parameters:

  • r (OpenSSL::BN)

    The ‘r’ value.

  • s (OpenSSL::BN)

    The ‘s’ value.

  • prime_size (Integer)

    The size of the ECDSA primes.

Returns:

  • (String)

    The ASN.1 signature.



84
85
86
87
88
# File 'lib/sandal/sig/es.rb', line 84

def self.encode_jws_signature(r, s, prime_size)
  byte_count = (prime_size / 8.0).ceil
  n_to_s = -> n { [n.to_s(16)].pack('H*').rjust(byte_count, "\0") }
  n_to_s.call(r) + n_to_s.call(s)
end

Instance Method Details

#ensure_curve(key, curve_name) ⇒ void (protected)

This method returns an undefined value.

Ensures that a key has a specified curve name.

Parameters:

  • key (OpenSSL::PKey::EC)

    The key.

  • curve_name (String)

    The curve name.

Raises:

  • (ArgumentError)

    The key has a different curve name.



98
99
100
# File 'lib/sandal/sig/es.rb', line 98

def ensure_curve(key, curve_name)
  raise ArgumentError, "The key must be in the #{curve_name} group." unless key.group.curve_name == curve_name
end

#sign(payload) ⇒ String

Signs a payload and returns the signature.

Parameters:

  • payload (String)

    The payload of the token to sign.

Returns:

  • (String)

    The signature.



28
29
30
31
32
33
# File 'lib/sandal/sig/es.rb', line 28

def sign(payload)
  hash = @digest.digest(payload)
  asn1_sig = @key.dsa_sign_asn1(hash)
  r, s = self.class.decode_asn1_signature(asn1_sig)
  self.class.encode_jws_signature(r, s, @prime_size)
end

#valid?(signature, payload) ⇒ Boolean

Validates a payload signature and returns whether the signature matches.

Parameters:

  • signature (String)

    The signature to verify.

  • payload (String)

    The payload of the token.

Returns:

  • (Boolean)

    true if the signature is correct; otherwise false.



40
41
42
43
44
45
# File 'lib/sandal/sig/es.rb', line 40

def valid?(signature, payload)
  hash = @digest.digest(payload)
  r, s = self.class.decode_jws_signature(signature)
  asn1_sig = self.class.encode_asn1_signature(r, s)
  @key.dsa_verify_asn1(hash, asn1_sig)
end