class SshSig::Blob
Attributes
Public Class Methods
# File lib/ssh_sig/blob.rb, line 31 def self.from_armor(armor) from_bytes(armor_to_blob(armor)) end
decode_blob parses the binary signature data as described in github.com/openssh/openssh-portable/blob/e665ed2d0c24fe11d5470ce72fa1e187377d3fc4/PROTOCOL.sshsig
byte MAGIC_PREAMBLE uint32 SIG_VERSION string publickey string namespace string reserved string hash_algorithm
string signature
# File lib/ssh_sig/blob.rb, line 45 def self.from_bytes(blob) buf = ::Net::SSH::Buffer.new(blob) preamble = buf.read!(6) raise DecodeError, 'Invalid magic preamble' unless preamble == MAGIC_PREAMBLE version = read_uint64(buf) raise DecodeError, 'Unsupported signature version' unless version == SIG_VERSION public_key = buf.read_key raise DecodeError, 'Signature is missing public key' if public_key.nil? namespace = buf.read_string raise DecodeError, 'Signature is missing namespace' if namespace.nil? # Read past the reserved value and ignore it. buf.read_string hash_algorithm = buf.read_string raise DecodeError, 'Signature is missing hash algorithm' if hash_algorithm.nil? raise DecodeError, 'Hash algorithm is not supported' unless hash_algorithm_allowed?(hash_algorithm) signature_raw = buf.read_string raise DecodeError, 'Signature is missing signed data' if signature_raw.nil? signature = Signature.from_bytes(signature_raw) raise DecodeError, 'Signature algorithm is not supported' \ unless signature_algorithm_allowed?(signature.algorithm) Blob.new( public_key: public_key, namespace: namespace, hash_algorithm: hash_algorithm, signature: signature ) end
# File lib/ssh_sig/blob.rb, line 13 def initialize( public_key:, namespace:, hash_algorithm:, signature: ) @public_key = public_key @namespace = namespace @hash_algorithm = hash_algorithm @signature = signature end
Private Class Methods
# File lib/ssh_sig/blob.rb, line 123 def self.armor_to_blob(armor) # Remove starting and ending whitespace for header checks. armor = armor.strip raise DecodeError, "Couldn't parse signature: missing header" unless armor.start_with?(BEGIN_SIGNATURE) raise DecodeError, "Couldn't parse signature: missing footer" unless armor.end_with?(END_SIGNATURE) b64 = armor .delete_prefix(BEGIN_SIGNATURE) .delete_suffix(END_SIGNATURE) .gsub(/\s+/, '') # Remove all remaining whitespace to ensure valid Base64 begin Base64.strict_decode64(b64) rescue ArgumentError => e raise DecodeError, "Couldn't decode armor body: #{e.message}" end end
# File lib/ssh_sig/blob.rb, line 115 def self.read_uint64(buf) b = buf.read(8) return nil unless b b.unpack1("N") end
Public Instance Methods
public_key is parsed from the signature data and is untrusted We make this clear using accessor naming
# File lib/ssh_sig/blob.rb, line 27 def public_key_untrusted @public_key end
signature_data
creates the “message” passed to the signing function as described in section 3 of github.com/openssh/openssh-portable/blob/b7ffbb17e37f59249c31f1ff59d6c5d80888f689/PROTOCOL.sshsig
Despite the documentation’s use of the word “concatenated”, this data must use the same DER-like encoding as the signature blob.
byte MAGIC_PREAMBLE string namespace string reserved string hash_algorithm
string H(message)
# File lib/ssh_sig/blob.rb, line 101 def signature_data(message) buf = ::Net::SSH::Buffer.new buf.write(MAGIC_PREAMBLE) buf.write_string(namespace) buf.write_string('') # reserved buf.write_string(hash_algorithm) buf.write_string(hash(message)) buf.to_s end
Private Instance Methods
# File lib/ssh_sig/blob.rb, line 143 def hash(data) case hash_algorithm when "sha512" ::Digest::SHA2.new(512).digest(data) when "sha256" ::Digest::SHA2.new(256).digest(data) else raise VerifyError, "Hash algorithm #{hash_algorithm} is not supported" end end