module Net::SSH::Transport::GCMCipher
def self.extended(orig)
def self.extended(orig) # rubocop:disable Metrics/BlockLength orig.class_eval do include Net::SSH::Loggable attr_reader :cipher attr_reader :key attr_accessor :nonce # # Semantically gcm cipher supplies the OpenSSL iv interface with a nonce # as it is not randomly generated due to being supplied from a counter. # The RFC's use IV and nonce interchangeably. # def initialize(encrypt:, key:) @cipher = OpenSSL::Cipher.new(algo_name) @key = key key_len = @cipher.key_len if key.size != key_len error_message = "#{cipher_name}: keylength does not match" error { error_message } raise error_message end encrypt ? @cipher.encrypt : @cipher.decrypt @cipher.key = key @nonce = { fixed: nil, invocation_counter: 0 } end def update_cipher_mac(payload, _sequence_number) # # --- RFC 5647 7.3 --- # When using AES-GCM with secure shell, the packet_length field is to # be treated as additional authenticated data, not as plaintext. # length_data = [payload.bytesize].pack('N') cipher.auth_data = length_data encrypted_data = cipher.update(payload) << cipher.final mac = cipher.auth_tag incr_nonce length_data + encrypted_data + mac end # # --- RFC 5647 --- # uint32 packet_length; // 0 <= packet_length < 2^32 # def read_length(data, _sequence_number) data.unpack1('N') end # # --- RFC 5647 --- # In AES-GCM secure shell, the inputs to the authenticated encryption # are: # PT (Plain Text) # byte padding_length; // 4 <= padding_length < 256 # byte[n1] payload; // n1 = packet_length-padding_length-1 # byte[n2] random_padding; // n2 = padding_length # AAD (Additional Authenticated Data) # uint32 packet_length; // 0 <= packet_length < 2^32 # IV (Initialization Vector) # As described in section 7.1. # BK (Block Cipher Key) # The appropriate Encryption Key formed during the Key Exchange. # def read_and_mac(data, mac, _sequence_number) # The authentication tag will be placed in the MAC field at the end of the packet # OpenSSL does not verify auth tag length # GCM mode allows arbitrary sizes for the auth_tag up to 128 bytes and a single # byte allows authentication to pass. If single byte auth tags are possible # an attacker would require no more than 256 attempts to forge a valid tag. # raise 'incorrect auth_tag length' unless mac.to_s.length == mac_length packet_length = data.unpack1('N') cipher.auth_tag = mac.to_s cipher.auth_data = [packet_length].pack('N') result = cipher.update(data[4...]) << cipher.final incr_nonce result end def mac_length 16 end def block_size 16 end def self.block_size 16 end # # --- RFC 5647 --- # N_MIN minimum nonce (IV) length 12 octets # N_MAX maximum nonce (IV) length 12 octets # def iv_len 12 end # # --- RFC 5288 --- # Each value of the nonce_explicit MUST be distinct for each distinct # invocation of the GCM encrypt function for any fixed key. Failure to # meet this uniqueness requirement can significantly degrade security. # The nonce_explicit MAY be the 64-bit sequence number. # # --- RFC 5116 --- # (2.1) Applications that can generate distinct nonces SHOULD use the nonce # formation method defined in Section 3.2, and MAY use any # other method that meets the uniqueness requirement. # # (3.2) The following method to construct nonces is RECOMMENDED. # # <- variable -> <- variable -> # - - - - - - - - - - - - - - # | fixed | counter | # # Initial octets consist of a fixed field and final octets consist of a # Counter field. Implementations SHOULD support 12-octet nonces in which # the Counter field is four octets long. # The Counter fields of successive nonces form a monotonically increasing # sequence, when those fields are regarded as unsignd integers in network # byte order. # The Counter part SHOULD be equal to zero for the first nonce and increment # by one for each successive nonce that is generated. # The Fixed field MUST remain constant for all nonces that are generated for # a given encryption device. # # --- RFC 5647 --- # The invocation field is treated as a 64-bit integer and is increment after # each invocation of AES-GCM to process a binary packet. # AES-GCM produces a keystream in blocks of 16-octets that is used to # encrypt the plaintext. This keystream is produced by encrypting the # following 16-octet data structure: # # uint32 fixed; // 4 octets # uint64 invocation_counter; // 8 octets # unit32 block_counter; // 4 octets # # The block_counter is initially set to one (1) and increment as each block # of key is produced. # # The reader is reminded that SSH requires that the data to be encrypted # MUST be padded out to a multiple of the block size (16-octets for AES-GCM). # def incr_nonce return if nonce[:fixed].nil? nonce[:invocation_counter] = [nonce[:invocation_counter].to_s.unpack1('B*').to_i(2) + 1].pack('Q>*') apply_nonce end def nonce=(iv_s) return if nonce[:fixed] nonce[:fixed] = iv_s[0...4] nonce[:invocation_counter] = iv_s[4...12] apply_nonce end def apply_nonce cipher.iv = "#{nonce[:fixed]}#{nonce[:invocation_counter]}" end # # --- RFC 5647 --- # If AES-GCM is selected as the encryption algorithm for a given # tunnel, AES-GCM MUST also be selected as the Message Authentication # Code (MAC) algorithm. Conversely, if AES-GCM is selected as the MAC # algorithm, it MUST also be selected as the encryption algorithm. # def implicit_mac? true end end end