module PuTTY::Key::Util

Utility methods used internally by PuTTY::Key.

@private

Public Class Methods

ssh_pack(*values) click to toggle source

Encodes a list of values (String and OpenSSL::BN instances) according to RFC 4251 section 5 (as strings and mpints).

No encoding conversion is performed on String instances.

@param [Array] values An Array of String and OpenSSL::BN instances to be encoded.

@return [String] A binary String containing the encoded values.

@raise [NilValueError] If a value is nil.

# File lib/putty/key/util.rb, line 23
def self.ssh_pack(*values)
  return ''.b if values.empty?

  values.map do |value|
    raise NilValueError, 'values must not contain nil elements' unless value

    if value.kind_of?(::OpenSSL::BN)
      value = value.to_i
      if value == 0
        value = ''
      else
        bytes = []

        if value > 0
          begin
            bytes << (value & 0xff)
            value = value >> 8
          end until value == 0

          # 0 pad if necessary to resolve ambiguity with negative numbers
          # in two's complement representation.
          bytes << 0 if bytes.last & 0x80 != 0
        else
          begin
            bytes << (value & 0xff)
            value = value >> 8
          end until value == -1 && bytes.last & 0x80 != 0
        end

        value = bytes.reverse!.pack('C*')
      end
    else
      value = value.to_s.b
    end

    [value.bytesize].pack('N') + value
  end.join
end
ssh_unpack(encoded, *spec) click to toggle source

Decodes a string containing RFC 4251 section 5 encoded string and mpint values.

@param [String] encoded A binary String containing the encoded values. @param [Array<Symbol>] spec An array consisting of :string or :mpint elements describing the contents of encoded.

@return [Array] An array of decoded (binary) String and OpenSSL::BN instances.

@raise [ArgumentError] If encoded is nil. @raise [ArgumentError] If encoded does not use the ASCII_8BIT (binary) encoding. @raise [ArgumentError] If spec contains elements other than :mpint and :string. @raise [FormatError] If the encoded structure is malformed. @raise [FormatError] If spec contains more elements than are present within encoded.

# File lib/putty/key/util.rb, line 80
def self.ssh_unpack(encoded, *spec)
  raise ArgumentError, 'encoded must not be nil' unless encoded
  encoded = encoded.to_s
  raise ArgumentError, 'encoded must be a binary String' unless encoded.encoding == Encoding::ASCII_8BIT

  io = StringIO.new(encoded)

  spec.map do |type|
    length_bytes = io.read(4)
    raise FormatError, 'spec contains more elements than are contained within the encoded String' unless length_bytes
    raise FormatError, 'Truncated length encountered' unless length_bytes.bytesize == 4

    length = length_bytes.unpack('N').first

    if length > 0
      encoded_value = io.read(length)
      raise FormatError, 'Missing value encountered' unless encoded_value
      raise FormatError, 'Truncated value encountered' unless encoded_value.bytesize == length
    else
      encoded_value = nil
    end

    case type
    when :string
      encoded_value || String.new
    when :mpint
      value = 0

      if encoded_value
        bytes = encoded_value.unpack('C*')
        bytes.each {|b| value = (value << 8) | b }

        if bytes.first & 0x80 != 0
          # A negative value. Reinterpret the bytes as the two's
          # complement representation of the negative integer.
          mask = 0xff
          (bytes.length - 1).times { mask = mask << 8 | 0xff }
          value = -(-value & mask)
        end
      end

      ::OpenSSL::BN.new(value)
    else
      raise ArgumentError, 'spec must contain only :string and :mpint elements'
    end
  end
end