parse RSA private keys
This commit is contained in:
Родитель
0f2ed098b1
Коммит
fe36498374
|
@ -31,4 +31,5 @@ require "ssh_data/version"
|
||||||
require "ssh_data/error"
|
require "ssh_data/error"
|
||||||
require "ssh_data/certificate"
|
require "ssh_data/certificate"
|
||||||
require "ssh_data/public_key"
|
require "ssh_data/public_key"
|
||||||
|
require "ssh_data/private_key"
|
||||||
require "ssh_data/encoding"
|
require "ssh_data/encoding"
|
||||||
|
|
|
@ -1,5 +1,25 @@
|
||||||
module SSHData
|
module SSHData
|
||||||
module Encoding
|
module Encoding
|
||||||
|
# Fields in an OpenSSL private key
|
||||||
|
# https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
|
||||||
|
OPENSSH_PRIVATE_KEY_MAGIC = "openssh-key-v1\x00"
|
||||||
|
OPENSSH_PRIVATE_KEY_FIELDS = [
|
||||||
|
[:ciphername, :string],
|
||||||
|
[:kdfname, :string],
|
||||||
|
[:kdfoptions, :string],
|
||||||
|
[:nkeys, :uint32],
|
||||||
|
]
|
||||||
|
|
||||||
|
# Fields in an RSA private key
|
||||||
|
RSA_PRIVATE_KEY_FIELDS = [
|
||||||
|
[:n, :mpint],
|
||||||
|
[:e, :mpint],
|
||||||
|
[:d, :mpint],
|
||||||
|
[:iqmp, :mpint],
|
||||||
|
[:p, :mpint],
|
||||||
|
[:q, :mpint],
|
||||||
|
]
|
||||||
|
|
||||||
# Fields in an RSA public key
|
# Fields in an RSA public key
|
||||||
RSA_KEY_FIELDS = [
|
RSA_KEY_FIELDS = [
|
||||||
[:e, :mpint],
|
[:e, :mpint],
|
||||||
|
@ -43,6 +63,97 @@ module SSHData
|
||||||
PublicKey::ALGO_ED25519 => ED25519_KEY_FIELDS,
|
PublicKey::ALGO_ED25519 => ED25519_KEY_FIELDS,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KEY_FIELDS_BY_PRIVATE_KEY_ALGO = {
|
||||||
|
PublicKey::ALGO_RSA => RSA_PRIVATE_KEY_FIELDS,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the raw data from a PEM encoded blob.
|
||||||
|
#
|
||||||
|
# pem - The PEM encoded String to decode.
|
||||||
|
# type - The String PEM type we're expecting.
|
||||||
|
#
|
||||||
|
# Returns the decoded String.
|
||||||
|
def decode_pem(pem, type)
|
||||||
|
lines = pem.split("\n")
|
||||||
|
|
||||||
|
unless lines.shift == "-----BEGIN #{type}-----"
|
||||||
|
raise DecodeError, "bad PEM header"
|
||||||
|
end
|
||||||
|
|
||||||
|
unless lines.pop == "-----END #{type}-----"
|
||||||
|
raise DecodeError, "bad PEM footer"
|
||||||
|
end
|
||||||
|
|
||||||
|
Base64.strict_decode64(lines.join)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Decode an OpenSSH private key.
|
||||||
|
#
|
||||||
|
# raw - The binary String private key.
|
||||||
|
#
|
||||||
|
# Returns an Array containing a Hash describing the private key and the
|
||||||
|
# Integer number of bytes read.
|
||||||
|
def decode_openssh_private_key(raw)
|
||||||
|
total_read = 0
|
||||||
|
|
||||||
|
magic = raw.byteslice(0, total_read + OPENSSH_PRIVATE_KEY_MAGIC.bytesize)
|
||||||
|
total_read += OPENSSH_PRIVATE_KEY_MAGIC.bytesize
|
||||||
|
unless magic == OPENSSH_PRIVATE_KEY_MAGIC
|
||||||
|
raise DecodeError, "bad OpenSSH private key"
|
||||||
|
end
|
||||||
|
|
||||||
|
data, read = decode_fields(raw, OPENSSH_PRIVATE_KEY_FIELDS, total_read)
|
||||||
|
total_read += read
|
||||||
|
|
||||||
|
# TODO: add support for encrypted private keys
|
||||||
|
unless data[:ciphername] == "none" && data[:kdfname] == "none"
|
||||||
|
raise DecryptError, "cannot decode encrypted private keys"
|
||||||
|
end
|
||||||
|
|
||||||
|
data[:public_keys], read = decode_n_strings(raw, data[:nkeys], total_read)
|
||||||
|
total_read += read
|
||||||
|
|
||||||
|
privs, read = decode_string(raw, total_read)
|
||||||
|
total_read += read
|
||||||
|
|
||||||
|
privs_read = 0
|
||||||
|
|
||||||
|
checkint1, read = decode_uint32(privs, privs_read)
|
||||||
|
privs_read += read
|
||||||
|
|
||||||
|
checkint2, read = decode_uint32(privs, privs_read)
|
||||||
|
privs_read += read
|
||||||
|
|
||||||
|
unless checkint1 == checkint2
|
||||||
|
raise DecryptError, "bad private key checksum"
|
||||||
|
end
|
||||||
|
|
||||||
|
data[:private_keys] = data[:nkeys].times.map do
|
||||||
|
algo, read = decode_string(privs, privs_read)
|
||||||
|
privs_read += read
|
||||||
|
|
||||||
|
unless fields = KEY_FIELDS_BY_PRIVATE_KEY_ALGO[algo]
|
||||||
|
raise AlgorithmError, "unknown algorithm: #{algo.inspect}"
|
||||||
|
end
|
||||||
|
|
||||||
|
priv_data, read = decode_fields(privs, fields, privs_read)
|
||||||
|
privs_read += read
|
||||||
|
|
||||||
|
comment, read = decode_string(privs, privs_read)
|
||||||
|
privs_read += read
|
||||||
|
|
||||||
|
priv_data.merge(algo: algo, comment: comment)
|
||||||
|
end
|
||||||
|
|
||||||
|
# padding at end is bytes 1, 2, 3, 4, etc...
|
||||||
|
padding = privs.byteslice(privs_read..-1)
|
||||||
|
unless padding.bytes.each_with_index.all? { |b, i| b == (i + 1) % 255 }
|
||||||
|
raise DecodeError, "bad padding: #{padding.inspect}"
|
||||||
|
end
|
||||||
|
|
||||||
|
[data, total_read]
|
||||||
|
end
|
||||||
|
|
||||||
# Decode the signature.
|
# Decode the signature.
|
||||||
#
|
#
|
||||||
# raw - The binary String signature as described by RFC4253 section 6.6.
|
# raw - The binary String signature as described by RFC4253 section 6.6.
|
||||||
|
@ -51,7 +162,7 @@ module SSHData
|
||||||
#
|
#
|
||||||
# Returns an Array containing the decoded algorithm String, the decoded binary
|
# Returns an Array containing the decoded algorithm String, the decoded binary
|
||||||
# signature String, and the Integer number of bytes read.
|
# signature String, and the Integer number of bytes read.
|
||||||
def self.decode_signature(raw, offset=0)
|
def decode_signature(raw, offset=0)
|
||||||
total_read = 0
|
total_read = 0
|
||||||
|
|
||||||
algo, read = decode_string(raw, offset + total_read)
|
algo, read = decode_string(raw, offset + total_read)
|
||||||
|
@ -82,7 +193,7 @@ module SSHData
|
||||||
#
|
#
|
||||||
# Returns an Array containing a Hash describing the public key and the
|
# Returns an Array containing a Hash describing the public key and the
|
||||||
# Integer number of bytes read.
|
# Integer number of bytes read.
|
||||||
def self.decode_public_key(raw, algo=nil, offset=0)
|
def decode_public_key(raw, algo=nil, offset=0)
|
||||||
total_read = 0
|
total_read = 0
|
||||||
|
|
||||||
if algo.nil?
|
if algo.nil?
|
||||||
|
@ -91,7 +202,7 @@ module SSHData
|
||||||
end
|
end
|
||||||
|
|
||||||
unless fields = KEY_FIELDS_BY_PUBLIC_KEY_ALGO[algo]
|
unless fields = KEY_FIELDS_BY_PUBLIC_KEY_ALGO[algo]
|
||||||
raise AlgorithmError
|
raise AlgorithmError, "unknown algorithm: #{algo}"
|
||||||
end
|
end
|
||||||
|
|
||||||
data, read = decode_fields(raw, fields, offset + total_read)
|
data, read = decode_fields(raw, fields, offset + total_read)
|
||||||
|
@ -110,7 +221,7 @@ module SSHData
|
||||||
#
|
#
|
||||||
# Returns an Array containing a Hash describing the certificate and the
|
# Returns an Array containing a Hash describing the certificate and the
|
||||||
# Integer number of bytes read.
|
# Integer number of bytes read.
|
||||||
def self.decode_certificate(raw, offset=0)
|
def decode_certificate(raw, offset=0)
|
||||||
total_read = 0
|
total_read = 0
|
||||||
|
|
||||||
data, read = decode_fields(raw, [
|
data, read = decode_fields(raw, [
|
||||||
|
@ -144,30 +255,30 @@ module SSHData
|
||||||
[data.merge(trailer), total_read]
|
[data.merge(trailer), total_read]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Decode all of the given fields from data.
|
# Decode all of the given fields from raw.
|
||||||
#
|
#
|
||||||
# data - A binary String.
|
# raw - A binary String.
|
||||||
# fields - An Array of Arrays, each containing a symbol describing the field
|
# fields - An Array of Arrays, each containing a symbol describing the field
|
||||||
# and a Symbol describing the type of the field (:mpint, :string,
|
# and a Symbol describing the type of the field (:mpint, :string,
|
||||||
# :uint64, or :uint32).
|
# :uint64, or :uint32).
|
||||||
# offset - The offset into data at which to read (default 0).
|
# offset - The offset into raw at which to read (default 0).
|
||||||
#
|
#
|
||||||
# Returns an Array containing a Hash mapping the provided field keys to the
|
# Returns an Array containing a Hash mapping the provided field keys to the
|
||||||
# decoded values and the Integer number of bytes read.
|
# decoded values and the Integer number of bytes read.
|
||||||
def decode_fields(data, fields, offset=0)
|
def decode_fields(raw, fields, offset=0)
|
||||||
hash = {}
|
hash = {}
|
||||||
total_read = 0
|
total_read = 0
|
||||||
|
|
||||||
fields.each do |key, type|
|
fields.each do |key, type|
|
||||||
value, read = case type
|
value, read = case type
|
||||||
when :string
|
when :string
|
||||||
decode_string(data, offset + total_read)
|
decode_string(raw, offset + total_read)
|
||||||
when :mpint
|
when :mpint
|
||||||
decode_mpint(data, offset + total_read)
|
decode_mpint(raw, offset + total_read)
|
||||||
when :uint64
|
when :uint64
|
||||||
decode_uint64(data, offset + total_read)
|
decode_uint64(raw, offset + total_read)
|
||||||
when :uint32
|
when :uint32
|
||||||
decode_uint32(data, offset + total_read)
|
decode_uint32(raw, offset + total_read)
|
||||||
else
|
else
|
||||||
raise DecodeError
|
raise DecodeError
|
||||||
end
|
end
|
||||||
|
@ -179,27 +290,27 @@ module SSHData
|
||||||
[hash, total_read]
|
[hash, total_read]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Read a string out of the provided data.
|
# Read a string out of the provided raw data.
|
||||||
#
|
#
|
||||||
# data - A binary String.
|
# raw - A binary String.
|
||||||
# offset - The offset into data at which to read (default 0).
|
# offset - The offset into raw at which to read (default 0).
|
||||||
#
|
#
|
||||||
# Returns an Array including the decoded String and the Integer number of
|
# Returns an Array including the decoded String and the Integer number of
|
||||||
# bytes read.
|
# bytes read.
|
||||||
def decode_string(data, offset=0)
|
def decode_string(raw, offset=0)
|
||||||
if data.bytesize < offset + 4
|
if raw.bytesize < offset + 4
|
||||||
raise DecodeError, "data too short"
|
raise DecodeError, "data too short"
|
||||||
end
|
end
|
||||||
|
|
||||||
size_s = data.byteslice(offset, 4)
|
size_s = raw.byteslice(offset, 4)
|
||||||
|
|
||||||
size = size_s.unpack("L>").first
|
size = size_s.unpack("L>").first
|
||||||
|
|
||||||
if data.bytesize < offset + 4 + size
|
if raw.bytesize < offset + 4 + size
|
||||||
raise DecodeError, "data too short"
|
raise DecodeError, "data too short"
|
||||||
end
|
end
|
||||||
|
|
||||||
string = data.byteslice(offset + 4, size)
|
string = raw.byteslice(offset + 4, size)
|
||||||
|
|
||||||
[string, 4 + size]
|
[string, 4 + size]
|
||||||
end
|
end
|
||||||
|
@ -213,18 +324,19 @@ module SSHData
|
||||||
[string.bytesize, string].pack("L>A*")
|
[string.bytesize, string].pack("L>A*")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Read a series of strings out of the provided data.
|
# Read a series of strings out of the provided raw data.
|
||||||
#
|
#
|
||||||
# data - A binary String.
|
# raw - A binary String.
|
||||||
|
# offset - The offset into raw at which to read (default 0).
|
||||||
#
|
#
|
||||||
# Returns an Array including the Array of decoded Strings and the Integer
|
# Returns an Array including the Array of decoded Strings and the Integer
|
||||||
# number of bytes read.
|
# number of bytes read.
|
||||||
def decode_strings(data)
|
def decode_strings(raw, offset=0)
|
||||||
total_read = 0
|
total_read = 0
|
||||||
strs = []
|
strs = []
|
||||||
|
|
||||||
while data.bytesize > total_read
|
while raw.bytesize > offset + total_read
|
||||||
str, read = decode_string(data, total_read)
|
str, read = decode_string(raw, offset + total_read)
|
||||||
strs << str
|
strs << str
|
||||||
total_read += read
|
total_read += read
|
||||||
end
|
end
|
||||||
|
@ -232,26 +344,46 @@ module SSHData
|
||||||
[strs, total_read]
|
[strs, total_read]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Read a series of key/value pairs out of the provided data.
|
# Read the specified number of strings out of the provided raw data.
|
||||||
#
|
#
|
||||||
# data - A binary String.
|
# raw - A binary String.
|
||||||
|
# n - The Integer number of Strings to read.
|
||||||
|
# offset - The offset into raw at which to read (default 0).
|
||||||
|
#
|
||||||
|
# Returns an Array including the Array of decoded Strings and the Integer
|
||||||
|
# number of bytes read.
|
||||||
|
def decode_n_strings(raw, n, offset=0)
|
||||||
|
total_read = 0
|
||||||
|
strs = []
|
||||||
|
|
||||||
|
n.times do |i|
|
||||||
|
strs[i], read = decode_string(raw, offset + total_read)
|
||||||
|
total_read += read
|
||||||
|
end
|
||||||
|
|
||||||
|
[strs, total_read]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Read a series of key/value pairs out of the provided raw data.
|
||||||
|
#
|
||||||
|
# raw - A binary String.
|
||||||
#
|
#
|
||||||
# Returns an Array including the Hash of decoded keys/values and the Integer
|
# Returns an Array including the Hash of decoded keys/values and the Integer
|
||||||
# number of bytes read.
|
# number of bytes read.
|
||||||
def decode_options(data)
|
def decode_options(raw)
|
||||||
total_read = 0
|
total_read = 0
|
||||||
opts = {}
|
opts = {}
|
||||||
|
|
||||||
while data.bytesize > total_read
|
while raw.bytesize > total_read
|
||||||
key, read = decode_string(data, total_read)
|
key, read = decode_string(raw, total_read)
|
||||||
total_read += read
|
total_read += read
|
||||||
|
|
||||||
value_data, read = decode_string(data, total_read)
|
value_raw, read = decode_string(raw, total_read)
|
||||||
total_read += read
|
total_read += read
|
||||||
|
|
||||||
if value_data.bytesize > 0
|
if value_raw.bytesize > 0
|
||||||
opts[key], read = decode_string(value_data)
|
opts[key], read = decode_string(value_raw)
|
||||||
if read != value_data.bytesize
|
if read != value_raw.bytesize
|
||||||
raise DecodeError, "bad options data"
|
raise DecodeError, "bad options data"
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
@ -262,27 +394,27 @@ module SSHData
|
||||||
[opts, total_read]
|
[opts, total_read]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Read a multi-precision integer from the provided data.
|
# Read a multi-precision integer from the provided raw data.
|
||||||
#
|
#
|
||||||
# data - A binary String.
|
# raw - A binary String.
|
||||||
# offset - The offset into data at which to read (default 0).
|
# offset - The offset into raw at which to read (default 0).
|
||||||
#
|
#
|
||||||
# Returns an Array including the decoded mpint as an OpenSSL::BN and the
|
# Returns an Array including the decoded mpint as an OpenSSL::BN and the
|
||||||
# Integer number of bytes read.
|
# Integer number of bytes read.
|
||||||
def decode_mpint(data, offset=0)
|
def decode_mpint(raw, offset=0)
|
||||||
if data.bytesize < offset + 4
|
if raw.bytesize < offset + 4
|
||||||
raise DecodeError, "data too short"
|
raise DecodeError, "data too short"
|
||||||
end
|
end
|
||||||
|
|
||||||
str_size_s = data.byteslice(offset, 4)
|
str_size_s = raw.byteslice(offset, 4)
|
||||||
str_size = str_size_s.unpack("L>").first
|
str_size = str_size_s.unpack("L>").first
|
||||||
mpi_size = str_size + 4
|
mpi_size = str_size + 4
|
||||||
|
|
||||||
if data.bytesize < offset + mpi_size
|
if raw.bytesize < offset + mpi_size
|
||||||
raise DecodeError, "data too short"
|
raise DecodeError, "data too short"
|
||||||
end
|
end
|
||||||
|
|
||||||
mpi_s = data.slice(offset, mpi_size)
|
mpi_s = raw.slice(offset, mpi_size)
|
||||||
|
|
||||||
# This calls OpenSSL's BN_mpi2bn() function. As far as I can tell, this
|
# This calls OpenSSL's BN_mpi2bn() function. As far as I can tell, this
|
||||||
# matches up with with MPI type defined in RFC4251 Section 5 with the
|
# matches up with with MPI type defined in RFC4251 Section 5 with the
|
||||||
|
@ -303,36 +435,36 @@ module SSHData
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# Read a uint64 from the provided data.
|
# Read a uint64 from the provided raw data.
|
||||||
#
|
#
|
||||||
# data - A binary String.
|
# raw - A binary String.
|
||||||
# offset - The offset into data at which to read (default 0).
|
# offset - The offset into raw at which to read (default 0).
|
||||||
#
|
#
|
||||||
# Returns an Array including the decoded uint64 as an Integer and the
|
# Returns an Array including the decoded uint64 as an Integer and the
|
||||||
# Integer number of bytes read.
|
# Integer number of bytes read.
|
||||||
def decode_uint64(data, offset=0)
|
def decode_uint64(raw, offset=0)
|
||||||
if data.bytesize < offset + 8
|
if raw.bytesize < offset + 8
|
||||||
raise DecodeError, "data too short"
|
raise DecodeError, "data too short"
|
||||||
end
|
end
|
||||||
|
|
||||||
uint64 = data.byteslice(offset, 8).unpack("Q>").first
|
uint64 = raw.byteslice(offset, 8).unpack("Q>").first
|
||||||
|
|
||||||
[uint64, 8]
|
[uint64, 8]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Read a uint32 from the provided data.
|
# Read a uint32 from the provided raw data.
|
||||||
#
|
#
|
||||||
# data - A binary String.
|
# raw - A binary String.
|
||||||
# offset - The offset into data at which to read (default 0).
|
# offset - The offset into raw at which to read (default 0).
|
||||||
#
|
#
|
||||||
# Returns an Array including the decoded uint32 as an Integer and the
|
# Returns an Array including the decoded uint32 as an Integer and the
|
||||||
# Integer number of bytes read.
|
# Integer number of bytes read.
|
||||||
def decode_uint32(data, offset=0)
|
def decode_uint32(raw, offset=0)
|
||||||
if data.bytesize < offset + 4
|
if raw.bytesize < offset + 4
|
||||||
raise DecodeError, "data too short"
|
raise DecodeError, "data too short"
|
||||||
end
|
end
|
||||||
|
|
||||||
uint32 = data.byteslice(offset, 4).unpack("L>").first
|
uint32 = raw.byteslice(offset, 4).unpack("L>").first
|
||||||
|
|
||||||
[uint32, 4]
|
[uint32, 4]
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,4 +3,5 @@ module SSHData
|
||||||
DecodeError = Class.new(Error)
|
DecodeError = Class.new(Error)
|
||||||
VerifyError = Class.new(Error)
|
VerifyError = Class.new(Error)
|
||||||
AlgorithmError = Class.new(Error)
|
AlgorithmError = Class.new(Error)
|
||||||
|
DecryptError = Class.new(Error)
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
module SSHData
|
||||||
|
module PrivateKey
|
||||||
|
PEM_TYPE = "OPENSSH PRIVATE KEY"
|
||||||
|
|
||||||
|
# Parse an SSH public key.
|
||||||
|
#
|
||||||
|
# key - An SSH formatted public key, including algo, encoded key and optional
|
||||||
|
# user/host names.
|
||||||
|
#
|
||||||
|
# Returns a PublicKey::Base subclass instance.
|
||||||
|
def self.parse(key)
|
||||||
|
raw = Encoding.decode_pem(key, PEM_TYPE)
|
||||||
|
|
||||||
|
data, read = Encoding.decode_openssh_private_key(raw)
|
||||||
|
unless read == raw.bytesize
|
||||||
|
raise DecodeError, "unexpected trailing data"
|
||||||
|
end
|
||||||
|
|
||||||
|
from_data(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.from_data(data)
|
||||||
|
case data[:algo]
|
||||||
|
when PublicKey::ALGO_RSA
|
||||||
|
RSA.new(**data)
|
||||||
|
else
|
||||||
|
raise DecodeError, "unkown algo: #{data[:algo].inspect}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
require "ssh_data/private_key/base"
|
||||||
|
require "ssh_data/private_key/rsa"
|
|
@ -0,0 +1,12 @@
|
||||||
|
module SSHData
|
||||||
|
module PrivateKey
|
||||||
|
class Base
|
||||||
|
attr_reader :algo, :comment
|
||||||
|
|
||||||
|
def initialize(**kwargs)
|
||||||
|
@algo = kwargs[:algo]
|
||||||
|
@comment = kwargs[:comment]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,63 @@
|
||||||
|
module SSHData
|
||||||
|
module PrivateKey
|
||||||
|
class RSA < Base
|
||||||
|
attr_reader :n, :e, :d, :iqmp, :p, :q
|
||||||
|
|
||||||
|
def initialize(algo:, n:, e:, d:, iqmp:, p:, q:, comment:)
|
||||||
|
unless algo == PublicKey::ALGO_RSA
|
||||||
|
raise DecodeError, "bad algorithm: #{algo.inspect}"
|
||||||
|
end
|
||||||
|
|
||||||
|
@n = n
|
||||||
|
@e = e
|
||||||
|
@d = d
|
||||||
|
@iqmp = iqmp
|
||||||
|
@p = p
|
||||||
|
@q = q
|
||||||
|
|
||||||
|
super(algo: algo, comment: comment)
|
||||||
|
end
|
||||||
|
|
||||||
|
def public_key
|
||||||
|
PublicKey::RSA.new(algo: algo, e: e, n: n)
|
||||||
|
end
|
||||||
|
|
||||||
|
def openssl
|
||||||
|
OpenSSL::PKey::RSA.new(asn1.to_der)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# CRT coefficient for faster RSA operations. Used by OpenSSL, but not
|
||||||
|
# OpenSSH.
|
||||||
|
#
|
||||||
|
# Returns an OpenSSL::BN instance.
|
||||||
|
def dmp1
|
||||||
|
d % (p - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
# CRT coefficient for faster RSA operations. Used by OpenSSL, but not
|
||||||
|
# OpenSSH.
|
||||||
|
#
|
||||||
|
# Returns an OpenSSL::BN instance.
|
||||||
|
def dmq1
|
||||||
|
d % (q - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def asn1
|
||||||
|
OpenSSL::ASN1::Sequence.new([
|
||||||
|
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(0)),
|
||||||
|
OpenSSL::ASN1::Integer.new(n),
|
||||||
|
OpenSSL::ASN1::Integer.new(e),
|
||||||
|
OpenSSL::ASN1::Integer.new(d),
|
||||||
|
OpenSSL::ASN1::Integer.new(p),
|
||||||
|
OpenSSL::ASN1::Integer.new(q),
|
||||||
|
OpenSSL::ASN1::Integer.new(dmp1),
|
||||||
|
OpenSSL::ASN1::Integer.new(dmq1),
|
||||||
|
OpenSSL::ASN1::Integer.new(iqmp),
|
||||||
|
])
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,7 +4,7 @@ module SSHData
|
||||||
attr_reader :algo
|
attr_reader :algo
|
||||||
|
|
||||||
def initialize(**kwargs)
|
def initialize(**kwargs)
|
||||||
raise "implement me"
|
@algo = kwargs[:algo]
|
||||||
end
|
end
|
||||||
|
|
||||||
def verify(signed_data, signature)
|
def verify(signed_data, signature)
|
||||||
|
|
|
@ -54,13 +54,14 @@ module SSHData
|
||||||
raise DecodeError, "bad algorithm: #{algo.inspect}"
|
raise DecodeError, "bad algorithm: #{algo.inspect}"
|
||||||
end
|
end
|
||||||
|
|
||||||
@algo = algo
|
|
||||||
@p = p
|
@p = p
|
||||||
@q = q
|
@q = q
|
||||||
@g = g
|
@g = g
|
||||||
@y = y
|
@y = y
|
||||||
|
|
||||||
@openssl = OpenSSL::PKey::DSA.new(asn1.to_der)
|
@openssl = OpenSSL::PKey::DSA.new(asn1.to_der)
|
||||||
|
|
||||||
|
super(algo: algo)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Verify an SSH signature.
|
# Verify an SSH signature.
|
||||||
|
|
|
@ -64,7 +64,6 @@ module SSHData
|
||||||
raise DecodeError, "bad curve: #{curve.inspect}"
|
raise DecodeError, "bad curve: #{curve.inspect}"
|
||||||
end
|
end
|
||||||
|
|
||||||
@algo = algo
|
|
||||||
@curve = curve
|
@curve = curve
|
||||||
@public_key = public_key
|
@public_key = public_key
|
||||||
|
|
||||||
|
@ -73,6 +72,8 @@ module SSHData
|
||||||
rescue ArgumentError
|
rescue ArgumentError
|
||||||
raise DecodeError, "bad key data"
|
raise DecodeError, "bad key data"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
super(algo: algo)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Verify an SSH signature.
|
# Verify an SSH signature.
|
||||||
|
|
|
@ -14,12 +14,13 @@ module SSHData
|
||||||
raise DecodeError, "bad algorithm: #{algo.inspect}"
|
raise DecodeError, "bad algorithm: #{algo.inspect}"
|
||||||
end
|
end
|
||||||
|
|
||||||
@algo = algo
|
|
||||||
@pk = pk
|
@pk = pk
|
||||||
|
|
||||||
if self.class.enabled?
|
if self.class.enabled?
|
||||||
@ed25519_key = Ed25519::VerifyKey.new(pk)
|
@ed25519_key = Ed25519::VerifyKey.new(pk)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
super(algo: algo)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Verify an SSH signature.
|
# Verify an SSH signature.
|
||||||
|
|
|
@ -13,6 +13,8 @@ module SSHData
|
||||||
@n = n
|
@n = n
|
||||||
|
|
||||||
@openssl = OpenSSL::PKey::RSA.new(asn1.to_der)
|
@openssl = OpenSSL::PKey::RSA.new(asn1.to_der)
|
||||||
|
|
||||||
|
super(algo: algo)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Verify an SSH signature.
|
# Verify an SSH signature.
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
ssh-keygen -trsa -N "passw0rd" -f ./encrypted_rsa
|
||||||
|
|
||||||
ssh-keygen -trsa -N "" -f ./rsa_ca
|
ssh-keygen -trsa -N "" -f ./rsa_ca
|
||||||
ssh-keygen -tdsa -N "" -f ./dsa_ca
|
ssh-keygen -tdsa -N "" -f ./dsa_ca
|
||||||
ssh-keygen -tecdsa -N "" -f ./ecdsa_ca
|
ssh-keygen -tecdsa -N "" -f ./ecdsa_ca
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
require_relative "../spec_helper"
|
||||||
|
|
||||||
|
describe SSHData::PrivateKey::RSA do
|
||||||
|
let(:private_key) { OpenSSL::PKey::RSA.generate(2048) }
|
||||||
|
let(:public_key) { private_key.public_key }
|
||||||
|
let(:params) { private_key.params }
|
||||||
|
|
||||||
|
let(:openssh_key) { SSHData::PublicKey.parse(fixture("rsa_leaf_for_rsa_ca.pub")) }
|
||||||
|
|
||||||
|
let(:comment) { "asdf" }
|
||||||
|
|
||||||
|
subject do
|
||||||
|
described_class.new(
|
||||||
|
algo: SSHData::PublicKey::ALGO_RSA,
|
||||||
|
n: params["n"],
|
||||||
|
e: params["e"],
|
||||||
|
d: params["d"],
|
||||||
|
iqmp: params["iqmp"],
|
||||||
|
p: params["p"],
|
||||||
|
q: params["q"],
|
||||||
|
comment: comment,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has an algo" do
|
||||||
|
expect(subject.algo).to eq(SSHData::PublicKey::ALGO_RSA)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has params" do
|
||||||
|
expect(subject.n).to eq(params["n"])
|
||||||
|
expect(subject.e).to eq(params["e"])
|
||||||
|
expect(subject.d).to eq(params["d"])
|
||||||
|
expect(subject.iqmp).to eq(params["iqmp"])
|
||||||
|
expect(subject.p).to eq(params["p"])
|
||||||
|
expect(subject.q).to eq(params["q"])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has a comment" do
|
||||||
|
expect(subject.comment).to eq(comment)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has an openssl representation" do
|
||||||
|
expect(subject.openssl).to be_a(OpenSSL::PKey::RSA)
|
||||||
|
expect(subject.openssl.to_der).to eq(private_key.to_der)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has a public key" do
|
||||||
|
expect(subject.public_key).to be_a(SSHData::PublicKey::RSA)
|
||||||
|
expect(subject.public_key.openssl.to_der).to eq(public_key.to_der)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can parse openssh-generate keys" do
|
||||||
|
expect { openssh_key }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
Загрузка…
Ссылка в новой задаче