Class: SSHData::Signature

Inherits:
Object
  • Object
show all
Defined in:
lib/ssh_data/signature.rb

Constant Summary collapse

PEM_TYPE =
"SSH SIGNATURE"
SIGNATURE_PREAMBLE =
"SSHSIG"
MIN_SUPPORTED_VERSION =
1
MAX_SUPPORTED_VERSION =
1
SUPPORTED_HASH_ALGORITHMS =

Spec: no SHA1 or SHA384. In practice, OpenSSH is always going to use SHA512. Note the actual signing / verify primitive may use a different hash algorithm. github.com/openssh/openssh-portable/blob/b7ffbb17e37f59249c31f1ff59d6c5d80888f689/PROTOCOL.sshsig#L67

{
  "sha256" => OpenSSL::Digest::SHA256,
  "sha512" => OpenSSL::Digest::SHA512,
}
PERMITTED_RSA_SIGNATURE_ALGORITHMS =
[
  PublicKey::ALGO_RSA_SHA2_256,
  PublicKey::ALGO_RSA_SHA2_512,
]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(sigversion:, publickey:, namespace:, reserved:, hash_algorithm:, signature:) ⇒ Signature

Returns a new instance of Signature.

Raises:



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/ssh_data/signature.rb', line 50

def initialize(sigversion:, publickey:, namespace:, reserved:, hash_algorithm:, signature:)
  if sigversion > MAX_SUPPORTED_VERSION || sigversion < MIN_SUPPORTED_VERSION
    raise UnsupportedError, "Signature version is not supported"
  end

  unless SUPPORTED_HASH_ALGORITHMS.has_key?(hash_algorithm)
    raise UnsupportedError, "Hash algorithm #{hash_algorithm} is not supported."
  end

  # Spec: empty namespaces are not permitted.
  # https://github.com/openssh/openssh-portable/blob/b7ffbb17e37f59249c31f1ff59d6c5d80888f689/PROTOCOL.sshsig#L57
  raise UnsupportedError, "A namespace is required." if namespace.empty?

  # Spec: ignore 'reserved', don't need to validate that it is empty.

  @sigversion = sigversion
  @publickey = publickey
  @namespace = namespace
  @reserved = reserved
  @hash_algorithm = hash_algorithm
  @signature = signature
end

Instance Attribute Details

#hash_algorithmObject (readonly)

Returns the value of attribute hash_algorithm.



23
24
25
# File 'lib/ssh_data/signature.rb', line 23

def hash_algorithm
  @hash_algorithm
end

#namespaceObject (readonly)

Returns the value of attribute namespace.



23
24
25
# File 'lib/ssh_data/signature.rb', line 23

def namespace
  @namespace
end

#reservedObject (readonly)

Returns the value of attribute reserved.



23
24
25
# File 'lib/ssh_data/signature.rb', line 23

def reserved
  @reserved
end

#signatureObject (readonly)

Returns the value of attribute signature.



23
24
25
# File 'lib/ssh_data/signature.rb', line 23

def signature
  @signature
end

#sigversionObject (readonly)

Returns the value of attribute sigversion.



23
24
25
# File 'lib/ssh_data/signature.rb', line 23

def sigversion
  @sigversion
end

Class Method Details

.parse_blob(blob) ⇒ Object



40
41
42
43
44
45
46
47
48
# File 'lib/ssh_data/signature.rb', line 40

def self.parse_blob(blob)
  data, read = Encoding.decode_openssh_signature(blob)

  if read != blob.bytesize
    raise DecodeError, "unexpected trailing data"
  end

  new(**data)
end

.parse_pem(pem) ⇒ Object

Parses a PEM armored SSH signature. pem - A PEM encoded SSH signature.

Returns a Signature instance.



29
30
31
32
33
34
35
36
37
38
# File 'lib/ssh_data/signature.rb', line 29

def self.parse_pem(pem)
  pem_type = Encoding.pem_type(pem)

  if pem_type != PEM_TYPE
    raise DecodeError, "Mismatched PEM type. Expecting '#{PEM_TYPE}', actually '#{pem_type}'."
  end

  blob = Encoding.decode_pem(pem, pem_type)
  self.parse_blob(blob)
end

Instance Method Details

#public_keyObject

Gets the public key from the signature. If the signature was created from a certificate, this will be an SSHData::Certificate. Otherwise, this will be a PublicKey algorithm.



110
111
112
# File 'lib/ssh_data/signature.rb', line 110

def public_key
  @data_public_key ||= load_public_key
end

#verify(signed_data, **opts) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/ssh_data/signature.rb', line 73

def verify(signed_data, **opts)
  signing_key = public_key

  # Unwrap the signing key if this signature was created from a certificate.
  key = signing_key.is_a?(Certificate) ? signing_key.public_key : signing_key

  digest_algorithm = SUPPORTED_HASH_ALGORITHMS[@hash_algorithm]

  if key.is_a?(PublicKey::RSA)
    sig_algo, * = Encoding.decode_signature(@signature)

    # Spec: If the signature is an RSA signature, the legacy 'ssh-rsa'
    # identifer is not permitted.
    # https://github.com/openssh/openssh-portable/blob/b7ffbb17e37f59249c31f1ff59d6c5d80888f689/PROTOCOL.sshsig#L72
    unless PERMITTED_RSA_SIGNATURE_ALGORITHMS.include?(sig_algo)
      raise UnsupportedError, "RSA signature #{sig_algo} is not supported."
    end
  end

  message_digest = digest_algorithm.digest(signed_data)
  blob =
    SIGNATURE_PREAMBLE +
    Encoding.encode_string(@namespace) +
    Encoding.encode_string(@reserved || "") +
    Encoding.encode_string(@hash_algorithm) +
    Encoding.encode_string(message_digest)

  if key.class.include?(::SSHData::PublicKey::SecurityKey)
    key.verify(blob, @signature, **opts)
  else
    key.verify(blob, @signature)
  end
end