Class: SSHData::PublicKey::ECDSA

Inherits:
Base
  • Object
show all
Defined in:
lib/ssh_data/public_key/ecdsa.rb

Direct Known Subclasses

SKECDSA

Constant Summary collapse

NISTP256 =
"nistp256"
NISTP384 =
"nistp384"
NISTP521 =
"nistp521"
OPENSSL_CURVE_NAME_FOR_CURVE =
{
  NISTP256 => "prime256v1",
  NISTP384 => "secp384r1",
  NISTP521 => "secp521r1",
}
CURVE_FOR_OPENSSL_CURVE_NAME =
{
  "prime256v1" => NISTP256,
  "secp384r1"  => NISTP384,
  "secp521r1"  => NISTP521,
}
DIGEST_FOR_CURVE =
{
  NISTP256 => OpenSSL::Digest::SHA256,
  NISTP384 => OpenSSL::Digest::SHA384,
  NISTP521 => OpenSSL::Digest::SHA512,
}

Instance Attribute Summary collapse

Attributes inherited from Base

#algo

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#fingerprint, #openssh, #sign

Constructor Details

#initialize(algo:, curve:, public_key:) ⇒ ECDSA

Returns a new instance of ECDSA.



78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/ssh_data/public_key/ecdsa.rb', line 78

def initialize(algo:, curve:, public_key:)
  self.class.check_algorithm!(algo, curve)

  @curve = curve
  @public_key_bytes = public_key

  @openssl = begin
    OpenSSL::PKey::EC.new(asn1.to_der)
  rescue ArgumentError
    raise DecodeError, "bad key data"
  end

  super(algo: algo)
end

Instance Attribute Details

#curveObject (readonly)

Returns the value of attribute curve.



4
5
6
# File 'lib/ssh_data/public_key/ecdsa.rb', line 4

def curve
  @curve
end

#opensslObject (readonly)

Returns the value of attribute openssl.



4
5
6
# File 'lib/ssh_data/public_key/ecdsa.rb', line 4

def openssl
  @openssl
end

#public_key_bytesObject (readonly)

Returns the value of attribute public_key_bytes.



4
5
6
# File 'lib/ssh_data/public_key/ecdsa.rb', line 4

def public_key_bytes
  @public_key_bytes
end

Class Method Details

.check_algorithm!(algo, curve) ⇒ Object



68
69
70
71
72
73
74
75
76
# File 'lib/ssh_data/public_key/ecdsa.rb', line 68

def self.check_algorithm!(algo, curve)
  unless [ALGO_ECDSA256, ALGO_ECDSA384, ALGO_ECDSA521].include?(algo)
    raise DecodeError, "bad algorithm: #{algo.inspect}"
  end

  unless algo == "ecdsa-sha2-#{curve}"
    raise DecodeError, "bad curve: #{curve.inspect}"
  end
end

.openssl_signature(sig) ⇒ Object

Convert an SSH encoded ECDSA signature to DER encoding for verification with OpenSSL.

sig - A binary String signature from an SSH packet.

Returns a binary String signature, as expected by OpenSSL.



34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/ssh_data/public_key/ecdsa.rb', line 34

def self.openssl_signature(sig)
  r, rlen = Encoding.decode_mpint(sig, 0)
  s, slen = Encoding.decode_mpint(sig, rlen)

  if rlen + slen != sig.bytesize
    raise DecodeError, "unexpected trailing data"
  end

  OpenSSL::ASN1::Sequence.new([
    OpenSSL::ASN1::Integer.new(r),
    OpenSSL::ASN1::Integer.new(s)
  ]).to_der
end

.ssh_signature(sig) ⇒ Object

Convert an DER encoded ECDSA signature, as generated by OpenSSL to SSH encoding.

sig - A binary String signature, as generated by OpenSSL.

Returns a binary String signature, as found in an SSH packet.



54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/ssh_data/public_key/ecdsa.rb', line 54

def self.ssh_signature(sig)
  a1 = OpenSSL::ASN1.decode(sig)
  if a1.tag_class != :UNIVERSAL || a1.tag != OpenSSL::ASN1::SEQUENCE || a1.value.count != 2
    raise DecodeError, "bad asn1 signature"
  end

  r, s = a1.value
  if r.tag_class != :UNIVERSAL || r.tag != OpenSSL::ASN1::INTEGER || s.tag_class != :UNIVERSAL || s.tag != OpenSSL::ASN1::INTEGER
    raise DecodeError, "bad asn1 signature"
  end

  [Encoding.encode_mpint(r.value), Encoding.encode_mpint(s.value)].join
end

Instance Method Details

#==(other) ⇒ Object

Is this public key equal to another public key?

other - Another SSHData::PublicKey::Base instance to compare with.

Returns boolean.



127
128
129
# File 'lib/ssh_data/public_key/ecdsa.rb', line 127

def ==(other)
  super && other.curve == curve && other.public_key_bytes == public_key_bytes
end

#digestObject

The digest algorithm to use with this key’s curve.

Returns an OpenSSL::Digest.



134
135
136
# File 'lib/ssh_data/public_key/ecdsa.rb', line 134

def digest
  DIGEST_FOR_CURVE[curve]
end

#rfc4253Object

RFC4253 binary encoding of the public key.

Returns a binary String.



114
115
116
117
118
119
120
# File 'lib/ssh_data/public_key/ecdsa.rb', line 114

def rfc4253
  Encoding.encode_fields(
    [:string, algo],
    [:string, curve],
    [:string, public_key_bytes],
  )
end

#verify(signed_data, signature) ⇒ Object

Verify an SSH signature.

signed_data - The String message that the signature was calculated over. signature - The binary String signature with SSH encoding.

Returns boolean.



99
100
101
102
103
104
105
106
107
108
109
# File 'lib/ssh_data/public_key/ecdsa.rb', line 99

def verify(signed_data, signature)
  sig_algo, ssh_sig, _ = Encoding.decode_signature(signature)
  if sig_algo != "ecdsa-sha2-#{curve}"
    raise DecodeError, "bad signature algorithm: #{sig_algo.inspect}"
  end

  openssl_sig = self.class.openssl_signature(ssh_sig)
  digest = DIGEST_FOR_CURVE[curve]

  openssl.verify(digest.new, openssl_sig, signed_data)
end