File: gcm_cipher.rb

package info (click to toggle)
ruby-net-ssh 1%3A7.3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,804 kB
  • sloc: ruby: 16,999; makefile: 5
file content (207 lines) | stat: -rw-r--r-- 7,868 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
require 'net/ssh/loggable'

module Net
  module SSH
    module Transport
      ## Extension module for aes(128|256)gcm ciphers
      module GCMCipher
        # rubocop:disable Metrics/AbcSize
        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
        # rubocop:enable Metrics/BlockLength
      end
      # rubocop:enable Metrics/AbcSize
    end
  end
end