Module: OpenSSHKeyConverter

Defined in:
lib/zold/key.rb

Overview

Constant Summary collapse

RSA_COMPONENTS =

The components in a openssh .pub / known_host RSA public key.

['ssh-rsa', :e, :n].freeze
DSA_COMPONENTS =

The components in a openssh .pub / known_host DSA public key.

['ssh-dss', :p, :q, :g, :pub_key].freeze

Class Method Summary collapse

Class Method Details

.decode_mpi(mpi_str) ⇒ Object

Decodes an openssh-mpi-encoded integer.



146
147
148
# File 'lib/zold/key.rb', line 146

def self.decode_mpi(mpi_str)
  mpi_str.unpack('C*').inject(0) { |a, e| (a << 8) | e }
end

.decode_pubkey(string) ⇒ Object

Decodes an openssh public key from the format of .pub & known_hosts files.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/zold/key.rb', line 87

def self.decode_pubkey(string)
  components = unpack_pubkey_components Base64.decode64(string)
  case components.first
  when RSA_COMPONENTS.first
    ops = RSA_COMPONENTS.zip components
    key = OpenSSL::PKey::RSA.new
  when DSA_COMPONENTS.first
    ops = DSA_COMPONENTS.zip components
    key = OpenSSL::PKey::DSA.new
  else
    raise "Unsupported key type #{components.first}"
  end
  ops.each do |o|
    next unless o.first.is_a? Symbol
    key.send "#{o.first}=", decode_mpi(o.last)
  end
  key
end

.encode_mpi(n) ⇒ Object

Encodes an openssh-mpi-encoded integer.



151
152
153
154
155
156
157
# File 'lib/zold/key.rb', line 151

def self.encode_mpi(n)
  chars = []
  n = n.to_i
  chars << (n & 0xff) && n >>= 8 while n != 0
  chars << 0 if chars.empty? || chars.last >= 0x80
  chars.reverse.pack('C*')
end

.encode_pubkey(key) ⇒ Object

Encodes a key’s public part in the format found in .pub & known_hosts files.



72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/zold/key.rb', line 72

def self.encode_pubkey(key)
  case key
  when OpenSSL::PKey::RSA
    components = RSA_COMPONENTS
  when OpenSSL::PKey::DSA
    components = DSA_COMPONENTS
  else
    raise "Unsupported key type #{key.class.name}"
  end
  components.map! { |c| c.is_a?(Symbol) ? encode_mpi(key.send(c)) : c }
  # ruby tries to be helpful and adds new lines every 60 bytes :(
  [pack_pubkey_components(components)].pack('m').delete("\n")
end

.first_line(string) ⇒ Object

Extracts the first line of a string.



124
125
126
# File 'lib/zold/key.rb', line 124

def self.first_line(string)
  string[0, string.index(/\r|\n/) || string.len]
end

.key_from_string(serialized_key) ⇒ Object

Reads a serialized key from a string.



112
113
114
115
116
117
118
119
120
121
# File 'lib/zold/key.rb', line 112

def self.key_from_string(serialized_key)
  header = first_line serialized_key
  if header.index 'RSA'
    OpenSSL::PKey::RSA.new serialized_key
  elsif header.index 'DSA'
    OpenSSL::PKey::DSA.new serialized_key
  else
    raise 'Unknown key type'
  end
end

.load_key(io) ⇒ Object

Loads a serialized key from an IO instance (File, StringIO).



107
108
109
# File 'lib/zold/key.rb', line 107

def self.load_key(io)
  key_from_string io.read
end

.pack_pubkey_components(strings) ⇒ Object

Packs string components into an openssh-encoded pubkey.



141
142
143
# File 'lib/zold/key.rb', line 141

def self.pack_pubkey_components(strings)
  (strings.map { |s| [s.length].pack('N') }).zip(strings).flatten.join
end

.unpack_pubkey_components(str) ⇒ Object

Unpacks the string components in an openssh-encoded pubkey.



129
130
131
132
133
134
135
136
137
138
# File 'lib/zold/key.rb', line 129

def self.unpack_pubkey_components(str)
  cs = []
  i = 0
  while i < str.length
    len = str[i, 4].unpack('N').first
    cs << str[i + 4, len]
    i += 4 + len
  end
  cs
end