packetgen-plugin-ipsec 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,413 +1,445 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of IPsec packetgen plugin.
2
4
  # See https://github.com/sdaubert/packetgen-plugin-ipsec for more informations
3
5
  # Copyright (c) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
4
6
  # This program is published under MIT license.
5
7
 
6
- # frozen_string_literal: true
7
-
8
8
  require_relative 'crypto'
9
9
 
10
- module PacketGen
11
- module Plugin
12
- # A ESP header consists of:
13
- # * a Security Parameters Index (#{spi}, {PacketGen::Types::Int32} type),
14
- # * a Sequence Number ({#sn}, +Int32+ type),
15
- # * a {#body} (variable length),
16
- # * an optional TFC padding ({#tfc}, variable length),
17
- # * an optional {#padding} (to align ESP on 32-bit boundary, variable length),
18
- # * a {#pad_length} ({PacketGen::Types::Int8}),
19
- # * a Next header field ({#next}, +Int8+),
20
- # * and an optional Integrity Check Value ({#icv}, variable length).
21
- #
22
- # == Create an ESP header
23
- # # standalone
24
- # esp = PacketGen::Plugin::ESP.new
25
- # # in a packet
26
- # pkt = PacketGen.gen('IP').add('ESP')
27
- # # access to ESP header
28
- # pkt.esp # => PacketGen::Plugin::ESP
29
- #
30
- # == Examples
31
- # === Create an enciphered UDP packet (ESP transport mode), using CBC mode
32
- # icmp = PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.2.1').
33
- # add('ESP', spi: 0xff456e01, sn: 12345678).
34
- # add('UDP', dport: 4567, sport: 45362, body 'abcdef')
35
- # cipher = OpenSSL::Cipher.new('aes-128-cbc')
36
- # cipher.encrypt
37
- # cipher.key = 16bytes_key
38
- # iv = 16bytes_iv
39
- # esp.esp.encrypt! cipher, iv
40
- #
41
- # === Create a ESP packet tunneling a UDP one, using GCM combined mode
42
- # # create inner UDP packet
43
- # icmp = PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.2.1').
44
- # add('UDP', dport: 4567, sport: 45362, body 'abcdef')
45
- #
46
- # # create outer ESP packet
47
- # esp = PacketGen.gen('IP', src '198.76.54.32', dst: '1.2.3.4').add('ESP')
48
- # esp.esp.spi = 0x87654321
49
- # esp.esp.sn = 0x123
50
- # esp.esp.icv_length = 16
51
- # # encapsulate ICMP packet in ESP one
52
- # esp.encapsulate icmp
53
- #
54
- # # encrypt ESP payload
55
- # cipher = OpenSSL::Cipher.new('aes-128-gcm')
56
- # cipher.encrypt
57
- # cipher.key = 16bytes_key
58
- # iv = 8bytes_iv
59
- # esp.esp.encrypt! cipher, iv, salt: 4bytes_gcm_salt
10
+ # rubocop:disable Metrics/ClassLength
11
+
12
+ module PacketGen::Plugin
13
+ # A ESP header consists of:
14
+ # * a Security Parameters Index (#{spi}, {BinStruct::Int32} type),
15
+ # * a Sequence Number ({#sn}, +Int32+ type),
16
+ # * a {#body} (variable length),
17
+ # * an optional TFC padding ({#tfc}, variable length),
18
+ # * an optional {#padding} (to align ESP on 32-bit boundary, variable length),
19
+ # * a {#pad_length} ({BinStruct::Int8}),
20
+ # * a Next header field ({#next}, +Int8+),
21
+ # * and an optional Integrity Check Value ({#icv}, variable length).
22
+ #
23
+ # == Create an ESP header
24
+ # # standalone
25
+ # esp = PacketGen::Plugin::ESP.new
26
+ # # in a packet
27
+ # pkt = PacketGen.gen('IP').add('ESP')
28
+ # # access to ESP header
29
+ # pkt.esp # => PacketGen::Plugin::ESP
30
+ #
31
+ # == Examples
32
+ # === Create an enciphered UDP packet (ESP transport mode), using CBC mode
33
+ # icmp = PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.2.1').
34
+ # add('ESP', spi: 0xff456e01, sn: 12345678).
35
+ # add('UDP', dport: 4567, sport: 45362, body 'abcdef')
36
+ # cipher = OpenSSL::Cipher.new('aes-128-cbc')
37
+ # cipher.encrypt
38
+ # cipher.key = 16bytes_key
39
+ # iv = 16bytes_iv
40
+ # esp.esp.encrypt! cipher, iv
41
+ #
42
+ # === Create a ESP packet tunneling a UDP one, using GCM combined mode
43
+ # # create inner UDP packet
44
+ # icmp = PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.2.1').
45
+ # add('UDP', dport: 4567, sport: 45362, body 'abcdef')
46
+ #
47
+ # # create outer ESP packet
48
+ # esp = PacketGen.gen('IP', src '198.76.54.32', dst: '1.2.3.4').add('ESP')
49
+ # esp.esp.spi = 0x87654321
50
+ # esp.esp.sn = 0x123
51
+ # esp.esp.icv_length = 16
52
+ # # encapsulate ICMP packet in ESP one
53
+ # esp.encapsulate icmp
54
+ #
55
+ # # encrypt ESP payload
56
+ # cipher = OpenSSL::Cipher.new('aes-128-gcm')
57
+ # cipher.encrypt
58
+ # cipher.key = 16bytes_key
59
+ # iv = 8bytes_iv
60
+ # esp.esp.encrypt! cipher, iv, salt: 4bytes_gcm_salt
61
+ #
62
+ # === Decrypt a ESP packet using CBC mode and HMAC-SHA-256
63
+ # cipher = OpenSSL::Cipher.new('aes-128-cbc')
64
+ # cipher.decrypt
65
+ # cipher.key = 16bytes_key
66
+ #
67
+ # hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
68
+ #
69
+ # pkt.esp.decrypt! cipher, intmode: hmac # => true if ICV check OK
70
+ # @author Sylvain Daubert
71
+ class ESP < PacketGen::Header::Base
72
+ include Crypto
73
+
74
+ # IP protocol number for ESP
75
+ IP_PROTOCOL = 50
76
+
77
+ # Well-known UDP port for ESP
78
+ UDP_PORT = 4500
79
+
80
+ # @!attribute spi
81
+ # 32-bit Security Parameter Index
82
+ # @return [Integer]
83
+ define_attr :spi, BinStruct::Int32
84
+ # @!attribute sn
85
+ # 32-bit Sequence Number
86
+ # @return [Integer]
87
+ define_attr :sn, BinStruct::Int32
88
+ # @!attribute body
89
+ # @return [BinStruct::String,PacketGen::Header::Base]
90
+ define_attr :body, BinStruct::String
91
+ # @!attribute tfc
92
+ # Traffic Flow Confidentiality padding
93
+ # @return [BinStruct::String,PacketGen::Header::Base]
94
+ define_attr :tfc, BinStruct::String
95
+ # @!attribute padding
96
+ # ESP padding
97
+ # @return [BinStruct::String,PacketGen::Header::Base]
98
+ define_attr :padding, BinStruct::String
99
+ # @!attribute pad_length
100
+ # 8-bit padding length
101
+ # @return [Integer]
102
+ define_attr :pad_length, BinStruct::Int8
103
+ # @!attribute next
104
+ # 8-bit next protocol value
105
+ # @return [Integer]
106
+ define_attr :next, BinStruct::Int8
107
+ # @!attribute icv
108
+ # Integrity Check Value
109
+ # @return [BinStruct::String,PacketGen::Header::Base]
110
+ define_attr :icv, BinStruct::String
111
+
112
+ # ICV (Integrity Check Value) length
113
+ # @return [Integer]
114
+ attr_accessor :icv_length
115
+
116
+ # @param [Hash] options
117
+ # @option options [Integer] :icv_length ICV length
118
+ # @option options [Integer] :spi Security Parameters Index
119
+ # @option options [Integer] :sn Sequence Number
120
+ # @option options [::String] :body ESP payload data
121
+ # @option options [::String] :tfc Traffic Flow Confidentiality, random padding
122
+ # up to MTU
123
+ # @option options [::String] :padding ESP padding to align ESP on 32-bit
124
+ # boundary
125
+ # @option options [Integer] :pad_length padding length
126
+ # @option options [Integer] :next Next Header field
127
+ # @option options [::String] :icv Integrity Check Value
128
+ def initialize(options={})
129
+ @icv_length = options[:icv_length] || 0
130
+ super
131
+ end
132
+
133
+ # Read a ESP packet from string.
60
134
  #
61
- # === Decrypt a ESP packet using CBC mode and HMAC-SHA-256
62
- # cipher = OpenSSL::Cipher.new('aes-128-cbc')
63
- # cipher.decrypt
64
- # cipher.key = 16bytes_key
135
+ # {#padding} and {#tfc} are not set as they are enciphered (impossible
136
+ # to guess their respective size). {#pad_length} and {#next} are also
137
+ # enciphered.
138
+ # @param [String] str
139
+ # @return [self]
140
+ def read(str)
141
+ return self if str.nil?
142
+
143
+ str = str.b
144
+ self[:spi].read(str[0, 4])
145
+ self[:sn].read(str[4, 4])
146
+ self[:tfc].read('')
147
+ self[:padding].read('')
148
+
149
+ read_icv_dependent_fields(str[8..])
150
+ read_icv(str)
151
+ self
152
+ end
153
+
154
+ # Encrypt in-place ESP payload and trailer.
65
155
  #
66
- # hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
156
+ # This method removes all data from +tfc+ and +padding+ fields, as their
157
+ # enciphered values are concatenated into +body+.
67
158
  #
68
- # pkt.esp.decrypt! cipher, intmode: hmac # => true if ICV check OK
69
- # @author Sylvain Daubert
70
- class ESP < PacketGen::Header::Base
71
- include Crypto
72
-
73
- # IP protocol number for ESP
74
- IP_PROTOCOL = 50
75
-
76
- # Well-known UDP port for ESP
77
- UDP_PORT = 4500
78
-
79
- # @!attribute spi
80
- # 32-bit Security Parameter Index
81
- # @return [Integer]
82
- define_field :spi, PacketGen::Types::Int32
83
- # @!attribute sn
84
- # 32-bit Sequence Number
85
- # @return [Integer]
86
- define_field :sn, PacketGen::Types::Int32
87
- # @!attribute body
88
- # @return [PacketGen::Types::String,PacketGen::Header::Base]
89
- define_field :body, PacketGen::Types::String
90
- # @!attribute tfc
91
- # Traffic Flow Confidentiality padding
92
- # @return [PacketGen::Types::String,PacketGen::Header::Base]
93
- define_field :tfc, PacketGen::Types::String
94
- # @!attribute padding
95
- # ESP padding
96
- # @return [PacketGen::Types::String,PacketGen::Header::Base]
97
- define_field :padding, PacketGen::Types::String
98
- # @!attribute pad_length
99
- # 8-bit padding length
100
- # @return [Integer]
101
- define_field :pad_length, PacketGen::Types::Int8
102
- # @!attribute next
103
- # 8-bit next protocol value
104
- # @return [Integer]
105
- define_field :next, PacketGen::Types::Int8
106
- # @!attribute icv
107
- # Integrity Check Value
108
- # @return [PacketGen::Types::String,PacketGen::Header::Base]
109
- define_field :icv, PacketGen::Types::String
110
-
111
- # ICV (Integrity Check Value) length
112
- # @return [Integer]
113
- attr_accessor :icv_length
114
-
115
- # @param [Hash] options
116
- # @option options [Integer] :icv_length ICV length
117
- # @option options [Integer] :spi Security Parameters Index
118
- # @option options [Integer] :sn Sequence Number
119
- # @option options [::String] :body ESP payload data
120
- # @option options [::String] :tfc Traffic Flow Confidentiality, random padding
121
- # up to MTU
122
- # @option options [::String] :padding ESP padding to align ESP on 32-bit
123
- # boundary
124
- # @option options [Integer] :pad_length padding length
125
- # @option options [Integer] :next Next Header field
126
- # @option options [::String] :icv Integrity Check Value
127
- def initialize(options={})
128
- @icv_length = options[:icv_length] || 0
129
- super
159
+ # It also removes headers under ESP from packet, as they are enciphered in
160
+ # ESP body, and then are no more accessible.
161
+ # @param [OpenSSL::Cipher] cipher keyed cipher.
162
+ # This cipher is confidentiality-only one, or AEAD one. To use a second
163
+ # cipher to add integrity, use +:intmode+ option.
164
+ # @param [String] iv full IV for encryption
165
+ # * CTR and GCM modes: +iv+ is 8-bytes long.
166
+ # @param [Hash] options
167
+ # @option options [String] :salt salt value for CTR and GCM modes
168
+ # @option options [Boolean] :tfc
169
+ # @option options [Fixnum] :tfc_size ESP body size used for TFC
170
+ # (default 1444, max size for a tunneled IPv4/ESP packet).
171
+ # This is the maximum size for ESP packet (without IP header
172
+ # nor Eth one).
173
+ # @option options [Fixnum] :esn 32 high-orber bits of ESN
174
+ # @option options [Fixnum] :pad_length set a padding length
175
+ # @option options [String] :padding set a padding. No check with
176
+ # +:pad_length+ is made. If +:pad_length+ is not set, +:padding+
177
+ # length is shortened to correct padding length
178
+ # @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
179
+ # confidentiality-only cipher. Only HMAC are supported.
180
+ # @return [self]
181
+ def encrypt!(cipher, iv, options={}) # rubocop:disable Naming/MethodParameterName
182
+ opt = { salt: '', tfc_size: 1444 }.merge(options)
183
+
184
+ set_crypto cipher, opt[:intmode]
185
+ compute_iv_for_encrypting iv, opt[:salt]
186
+
187
+ authenticate_esp_header_if_needed options, iv
188
+
189
+ encrypt_set_pad_length
190
+ encrypt_set_padding(opt)
191
+ encrypt_body(opt, iv)
192
+
193
+ set_esp_icv_if_needed
194
+ remove_enciphered_packets
195
+
196
+ self
197
+ end
198
+
199
+ # Decrypt in-place ESP payload and trailer.
200
+ # @param [OpenSSL::Cipher] cipher keyed cipher
201
+ # This cipher is confidentiality-only one, or AEAD one. To use a second
202
+ # cipher to add integrity, use +:intmode+ option.
203
+ # @param [Hash] options
204
+ # @option options [Boolean] :parse parse deciphered payload to retrieve
205
+ # headers (default: +true+)
206
+ # @option options [Fixnum] :icv_length ICV length for captured packets,
207
+ # or read from PCapNG files
208
+ # @option options [String] :salt salt value for CTR and GCM modes
209
+ # @option options [Fixnum] :esn 32 high-orber bits of ESN
210
+ # @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
211
+ # confidentiality-only cipher. Only HMAC are supported.
212
+ # @return [Boolean] +true+ if ESP packet is authenticated
213
+ def decrypt!(cipher, options={})
214
+ opt = { salt: '', parse: true }.merge(options)
215
+
216
+ set_crypto cipher, opt[:intmode]
217
+ iv = compute_iv_for_decrypting(opt[:salt], self[:body])
218
+ if authenticated? && (@icv_length.zero? || opt[:icv_length])
219
+ check_icv_length(opt)
220
+ decrypt_format_packet
130
221
  end
222
+ authenticate_esp_header_if_needed options, iv, icv
223
+ private_decrypt opt
224
+ end
225
+
226
+ private
227
+
228
+ def read_icv_dependent_fields(str)
229
+ body_end = -@icv_length - 2
230
+ self[:body].read str[0...body_end]
231
+ self[:pad_length].read str[body_end, 1]
232
+ self[:next].read str[body_end + 1, 1]
233
+ end
234
+
235
+ def read_icv(str)
236
+ self[:icv].read str[-@icv_length, @icv_length] if @icv_length
237
+ end
131
238
 
132
- # Read a ESP packet from string.
133
- #
134
- # {#padding} and {#tfc} are not set as they are enciphered (impossible
135
- # to guess their respective size). {#pad_length} and {#next} are also
136
- # enciphered.
137
- # @param [String] str
138
- # @return [self]
139
- def read(str)
140
- return self if str.nil?
141
-
142
- force_binary str
143
- self[:spi].read str[0, 4]
144
- self[:sn].read str[4, 4]
145
- self[:body].read str[8...-@icv_length - 2]
146
- self[:tfc].read ''
147
- self[:padding].read ''
148
- self[:pad_length].read str[-@icv_length - 2, 1]
149
- self[:next].read str[-@icv_length - 1, 1]
150
- self[:icv].read str[-@icv_length, @icv_length] if @icv_length
151
- self
239
+ def get_auth_data(opt)
240
+ ad = self[:spi].to_s
241
+ if opt[:esn]
242
+ @esn = BinStruct::Int32.new(value: opt[:esn])
243
+ ad << @esn.to_s if @conf.authenticated?
152
244
  end
245
+ ad << self[:sn].to_s
246
+ end
153
247
 
154
- # Encrypt in-place ESP payload and trailer.
155
- #
156
- # This method removes all data from +tfc+ and +padding+ fields, as their
157
- # enciphered values are concatenated into +body+.
158
- #
159
- # It also removes headers under ESP from packet, as they are enciphered in
160
- # ESP body, and then are no more accessible.
161
- # @param [OpenSSL::Cipher] cipher keyed cipher.
162
- # This cipher is confidentiality-only one, or AEAD one. To use a second
163
- # cipher to add integrity, use +:intmode+ option.
164
- # @param [String] iv full IV for encryption
165
- # * CTR and GCM modes: +iv+ is 8-bytes long.
166
- # @param [Hash] options
167
- # @option options [String] :salt salt value for CTR and GCM modes
168
- # @option options [Boolean] :tfc
169
- # @option options [Fixnum] :tfc_size ESP body size used for TFC
170
- # (default 1444, max size for a tunneled IPv4/ESP packet).
171
- # This is the maximum size for ESP packet (without IP header
172
- # nor Eth one).
173
- # @option options [Fixnum] :esn 32 high-orber bits of ESN
174
- # @option options [Fixnum] :pad_length set a padding length
175
- # @option options [String] :padding set a padding. No check with
176
- # +:pad_length+ is made. If +:pad_length+ is not set, +:padding+
177
- # length is shortened to correct padding length
178
- # @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
179
- # confidentiality-only cipher. Only HMAC are supported.
180
- # @return [self]
181
- def encrypt!(cipher, iv, options={})
182
- opt = { salt: '', tfc_size: 1444 }.merge(options)
183
-
184
- set_crypto cipher, opt[:intmode]
185
-
186
- real_iv = force_binary(opt[:salt]) + force_binary(iv)
187
- real_iv += [1].pack('N') if confidentiality_mode == 'ctr'
188
- cipher.iv = real_iv
189
-
190
- authenticate_esp_header_if_needed options, iv
191
-
192
- case confidentiality_mode
193
- when 'cbc'
194
- cipher_len = self[:body].sz + 2
195
- self.pad_length = (16 - (cipher_len % 16)) % 16
196
- else
197
- mod4 = to_s.size % 4
198
- self.pad_length = 4 - mod4 if mod4 > 0
199
- end
200
-
201
- if opt[:pad_length]
202
- self.pad_length = opt[:pad_length]
203
- padding = force_binary(opt[:padding] || (1..self.pad_length).to_a.pack('C*'))
204
- self[:padding].read padding
205
- else
206
- padding = force_binary(opt[:padding] || (1..self.pad_length).to_a.pack('C*'))
207
- self[:padding].read padding[0...self.pad_length]
208
- end
209
-
210
- tfc = ''
211
- if opt[:tfc]
212
- tfc_size = opt[:tfc_size] - self[:body].sz
213
- if tfc_size > 0
214
- tfc_size = case confidentiality_mode
215
- when 'cbc'
216
- (tfc_size / 16) * 16
217
- else
218
- (tfc_size / 4) * 4
219
- end
220
- tfc = force_binary("\0" * tfc_size)
221
- end
222
- end
223
-
224
- msg = self[:body].to_s + tfc
225
- msg += self[:padding].to_s + self[:pad_length].to_s + self[:next].to_s
226
- enc_msg = encipher(msg)
227
- # as padding is used to pad for CBC mode, this is unused
228
- cipher.final
229
-
230
- self[:body] = PacketGen::Types::String.new.read(iv) << enc_msg[0..-3]
231
- self[:pad_length].read enc_msg[-2]
232
- self[:next].read enc_msg[-1]
233
-
234
- # reset padding field as it has no sense in encrypted ESP
235
- self[:padding].read ''
236
-
237
- set_esp_icv_if_needed
238
-
239
- # Remove enciphered headers from packet
240
- id = header_id(self)
241
- if id < packet.headers.size - 1
242
- (packet.headers.size - 1).downto(id + 1) do |index|
243
- packet.headers.delete_at index
244
- end
245
- end
246
-
247
- self
248
+ def authenticate_esp_header_if_needed(opt, iv, icv=nil) # rubocop:disable Naming/MethodParameterName
249
+ if @conf.authenticated?
250
+ @conf.auth_tag = icv if icv
251
+ @conf.auth_data = get_auth_data(opt)
252
+ elsif @intg
253
+ @intg.reset
254
+ @intg.update get_auth_data(opt)
255
+ @intg.update iv
256
+ @icv = icv
257
+ else
258
+ @icv = nil
248
259
  end
260
+ end
249
261
 
250
- # Decrypt in-place ESP payload and trailer.
251
- # @param [OpenSSL::Cipher] cipher keyed cipher
252
- # This cipher is confidentiality-only one, or AEAD one. To use a second
253
- # cipher to add integrity, use +:intmode+ option.
254
- # @param [Hash] options
255
- # @option options [Boolean] :parse parse deciphered payload to retrieve
256
- # headers (default: +true+)
257
- # @option options [Fixnum] :icv_length ICV length for captured packets,
258
- # or read from PCapNG files
259
- # @option options [String] :salt salt value for CTR and GCM modes
260
- # @option options [Fixnum] :esn 32 high-orber bits of ESN
261
- # @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
262
- # confidentiality-only cipher. Only HMAC are supported.
263
- # @return [Boolean] +true+ if ESP packet is authenticated
264
- def decrypt!(cipher, options={})
265
- opt = { salt: '', parse: true }.merge(options)
266
-
267
- set_crypto cipher, opt[:intmode]
268
-
269
- case confidentiality_mode
270
- when 'gcm'
271
- iv = self[:body].slice!(0, 8)
272
- real_iv = opt[:salt] + iv
273
- when 'cbc'
274
- cipher.padding = 0
275
- real_iv = iv = self[:body].slice!(0, 16)
276
- when 'ctr'
277
- iv = self[:body].slice!(0, 8)
278
- real_iv = opt[:salt] + iv + [1].pack('N')
279
- else
280
- real_iv = iv = self[:body].slice!(0, 16)
281
- end
282
- cipher.iv = real_iv
283
-
284
- if authenticated? && (@icv_length.zero? || opt[:icv_length])
285
- raise ParseError, 'unknown ICV size' unless opt[:icv_length]
286
- @icv_length = opt[:icv_length].to_i
287
- # reread ESP to handle new ICV size
288
- msg = self[:body].to_s + self[:pad_length].to_s
289
- msg += self[:next].to_s
290
- self[:icv].read msg.slice!(-@icv_length, @icv_length)
291
- self[:body].read msg[0..-3]
292
- self[:pad_length].read msg[-2]
293
- self[:next].read msg[-1]
294
- end
295
-
296
- authenticate_esp_header_if_needed options, iv, icv
297
- private_decrypt opt
262
+ def encrypt_set_pad_length
263
+ case confidentiality_mode
264
+ when 'cbc'
265
+ cipher_len = self[:body].sz + 2
266
+ self.pad_length = (16 - (cipher_len % 16)) % 16
267
+ else
268
+ mod4 = to_s.size % 4
269
+ self.pad_length = 4 - mod4 if mod4.positive?
298
270
  end
271
+ end
299
272
 
300
- private
273
+ def encrypt_set_padding(opt)
274
+ if opt[:pad_length]
275
+ self.pad_length = opt[:pad_length]
276
+ padding = opt[:padding] || (1..self.pad_length).to_a.pack('C*')
277
+ else
278
+ padding = opt[:padding] || (1..self.pad_length).to_a.pack('C*')
279
+ padding = padding[0...self.pad_length]
280
+ end
281
+ self[:padding].read(padding)
282
+ end
301
283
 
302
- def get_auth_data(opt)
303
- ad = self[:spi].to_s
304
- if opt[:esn]
305
- @esn = PacketGen::Types::Int32.new(opt[:esn])
306
- ad << @esn.to_s if @conf.authenticated?
307
- end
308
- ad << self[:sn].to_s
284
+ def generate_tfc(opt)
285
+ tfc = ''
286
+ return tfc unless opt[:tfc]
287
+
288
+ tfc_size = opt[:tfc_size] - self[:body].sz
289
+ if tfc_size.positive?
290
+ tfc_size = case confidentiality_mode
291
+ when 'cbc'
292
+ (tfc_size / 16) * 16
293
+ else
294
+ (tfc_size / 4) * 4
295
+ end
296
+ tfc = "\0".b * tfc_size
309
297
  end
298
+ tfc
299
+ end
310
300
 
311
- def authenticate_esp_header_if_needed(opt, iv, icv=nil)
312
- if @conf.authenticated?
313
- @conf.auth_tag = icv if icv
314
- @conf.auth_data = get_auth_data(opt)
315
- elsif @intg
316
- @intg.reset
317
- @intg.update get_auth_data(opt)
318
- @intg.update iv
319
- @icv = icv
320
- else
321
- @icv = nil
322
- end
301
+ def encrypt_body(opt, iv) # rubocop:disable Naming/MethodParameterName
302
+ msg = self[:body].to_s + generate_tfc(opt)
303
+ msg += self[:padding].to_s + self[:pad_length].to_s + self[:next].to_s
304
+ enc_msg = encipher(msg)
305
+ # as padding is used to pad for CBC mode, this is unused
306
+ @conf.final
307
+
308
+ encrypt_set_encrypted_fields(enc_msg, iv)
309
+ end
310
+
311
+ def encrypt_set_encrypted_fields(msg, iv) # rubocop:disable Naming/MethodParameterName
312
+ self[:body] = BinStruct::String.new.read(iv)
313
+ self[:body] << msg[0..-3]
314
+ self[:pad_length].read msg[-2]
315
+ self[:next].read msg[-1]
316
+
317
+ # reset padding field as it has no sense in encrypted ESP
318
+ self[:padding].read ''
319
+ end
320
+
321
+ def set_esp_icv_if_needed
322
+ return unless authenticated?
323
+
324
+ if @conf.authenticated?
325
+ self[:icv].read @conf.auth_tag[0, @icv_length]
326
+ else
327
+ self[:icv].read @intg.digest[0, @icv_length]
323
328
  end
329
+ end
330
+
331
+ def remove_enciphered_packets
332
+ id = header_id(self)
333
+ return if id >= packet.headers.size - 1
324
334
 
325
- def set_esp_icv_if_needed
326
- return unless authenticated?
327
- if @conf.authenticated?
328
- self[:icv].read @conf.auth_tag[0, @icv_length]
329
- else
330
- self[:icv].read @intg.digest[0, @icv_length]
331
- end
335
+ (packet.headers.size - 1).downto(id + 1) do |index|
336
+ packet.headers.delete_at index
332
337
  end
338
+ end
339
+
340
+ def check_icv_length(opt)
341
+ raise PacketGen::ParseError, 'unknown ICV size' unless opt[:icv_length]
342
+
343
+ @icv_length = opt[:icv_length].to_i
344
+ end
333
345
 
334
- def private_decrypt(options)
335
- # decrypt
336
- msg = self.body.to_s
337
- msg += self.padding + self[:pad_length].to_s + self[:next].to_s
338
- plain_msg = decipher(msg)
346
+ def decrypt_format_packet
347
+ # reread ESP to handle new ICV size
348
+ msg = self[:body].to_s + self[:pad_length].to_s
349
+ msg << self[:next].to_s
350
+ read_icv_dependent_fields(msg)
351
+ read_icv(msg)
352
+ end
353
+
354
+ def private_decrypt(options)
355
+ plain_msg = decrypt_body
356
+ # check authentication tag
357
+ return false if authenticated? && !authenticate!
339
358
 
340
- # check authentication tag
341
- return false if authenticated? && !authenticate!
359
+ new_pkt = fill_decrypted_fields_and_generate_plain_packet(plain_msg)
360
+ packet.encapsulate new_pkt if options[:parse] && !new_pkt.nil?
361
+ true
362
+ end
342
363
 
343
- # Set ESP fields
344
- self[:body].read plain_msg[0..-3]
345
- self[:pad_length].read plain_msg[-2]
346
- self[:next].read plain_msg[-1]
364
+ def decrypt_body
365
+ msg = self.body.to_s
366
+ msg += self.padding + self[:pad_length].to_s + self[:next].to_s
367
+ decipher(msg)
368
+ end
369
+
370
+ def fill_decrypted_fields_and_generate_plain_packet(plain_msg)
371
+ self[:body].read plain_msg[0..-3]
372
+ self[:pad_length].read plain_msg[-2]
373
+ self[:next].read plain_msg[-1]
374
+
375
+ fill_padding_field
376
+ generate_plain_pkt
377
+ end
347
378
 
348
- # Set padding
349
- if self.pad_length > 0
350
- len = self.pad_length
351
- self[:padding].read self[:body].slice!(-len, len)
352
- end
379
+ def fill_padding_field
380
+ return unless self.pad_length.positive?
353
381
 
354
- # Set TFC padding
355
- encap_length = 0
382
+ len = self.pad_length
383
+ self[:padding].read self[:body].slice!(-len, len)
384
+ end
385
+
386
+ def generate_plain_pkt # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
387
+ case self.next
388
+ when 4 # IPv4
389
+ pkt = PacketGen::Packet.parse(body, first_header: 'IP')
390
+ encap_length = pkt.ip.length
391
+ when 41 # IPv6
392
+ pkt = PacketGen::Packet.parse(body, first_header: 'IPv6')
393
+ encap_length = pkt.ipv6.length + pkt.ipv6.sz
394
+ when PacketGen::Header::ICMP::IP_PROTOCOL
395
+ pkt = PacketGen::Packet.parse(body, first_header: 'ICMP')
396
+ # no size field. cannot recover TFC padding
397
+ encap_length = self[:body].sz
398
+ when PacketGen::Header::UDP::IP_PROTOCOL
399
+ pkt = PacketGen::Packet.parse(body, first_header: 'UDP')
400
+ encap_length = pkt.udp.length
401
+ when PacketGen::Header::TCP::IP_PROTOCOL
402
+ # No length in TCP header, so TFC may not be used.
403
+ # Or underlayer protocol should have a size information...
404
+ pkt = PacketGen::Packet.parse(body, first_header: 'TCP')
405
+ encap_length = pkt.sz
406
+ when PacketGen::Header::ICMPv6::IP_PROTOCOL
407
+ pkt = PacketGen::Packet.parse(body, first_header: 'ICMPv6')
408
+ # no size field. cannot recover TFC padding
409
+ encap_length = self[:body].sz
410
+ else
411
+ # Unmanaged encapsulated protocol
356
412
  pkt = nil
357
- case self.next
358
- when 4 # IPv4
359
- pkt = Packet.parse(body, first_header: 'IP')
360
- encap_length = pkt.ip.length
361
- when 41 # IPv6
362
- pkt = Packet.parse(body, first_header: 'IPv6')
363
- encap_length = pkt.ipv6.length + pkt.ipv6.sz
364
- when PacketGen::Header::ICMP::IP_PROTOCOL
365
- pkt = Packet.parse(body, first_header: 'ICMP')
366
- # no size field. cannot recover TFC padding
367
- encap_length = self[:body].sz
368
- when PacketGen::Header::UDP::IP_PROTOCOL
369
- pkt = Packet.parse(body, first_header: 'UDP')
370
- encap_length = pkt.udp.length
371
- when PacketGen::Header::TCP::IP_PROTOCOL
372
- # No length in TCP header, so TFC may not be used.
373
- # Or underlayer protocol should have a size information...
374
- pkt = Packet.parse(body, first_header: 'TCP')
375
- encap_length = pkt.sz
376
- when PacketGen::Header::ICMPv6::IP_PROTOCOL
377
- pkt = Packet.parse(body, first_header: 'ICMPv6')
378
- # no size field. cannot recover TFC padding
379
- encap_length = self[:body].sz
380
- else
381
- # Unmanaged encapsulated protocol
382
- encap_length = self[:body].sz
383
- end
384
-
385
- if encap_length < self[:body].sz
386
- tfc_len = self[:body].sz - encap_length
387
- self[:tfc].read self[:body].slice!(encap_length, tfc_len)
388
- end
389
-
390
- if options[:parse]
391
- packet.encapsulate pkt unless pkt.nil?
392
- end
393
-
394
- true
413
+ encap_length = self[:body].sz
395
414
  end
415
+
416
+ remove_tfc_if_needed(encap_length)
417
+ pkt
396
418
  end
397
419
 
398
- Header.add_class ESP
399
-
400
- PacketGen::Header::IP.bind ESP, protocol: ESP::IP_PROTOCOL
401
- PacketGen::Header::IPv6.bind ESP, next: ESP::IP_PROTOCOL
402
- PacketGen::Header::UDP.bind ESP, procs: [->(f) { f.dport = f.sport = ESP::UDP_PORT },
403
- ->(f) { (f.dport == ESP::UDP_PORT ||
404
- f.sport == ESP::UDP_PORT) &&
405
- PacketGen::Types::Int32.new.read(f.body[0..3]).to_i > 0 }]
406
- ESP.bind PacketGen::Header::IP, next: 4
407
- ESP.bind PacketGen::Header::IPv6, next: 41
408
- ESP.bind PacketGen::Header::TCP, next: PacketGen::Header::TCP::IP_PROTOCOL
409
- ESP.bind PacketGen::Header::UDP, next: PacketGen::Header::TCP::IP_PROTOCOL
410
- ESP.bind PacketGen::Header::ICMP, next: PacketGen::Header::ICMP::IP_PROTOCOL
411
- ESP.bind PacketGen::Header::ICMPv6, next: PacketGen::Header::ICMPv6::IP_PROTOCOL
420
+ def remove_tfc_if_needed(real_length)
421
+ return if real_length == self[:body].sz
422
+
423
+ tfc_len = self[:body].sz - real_length
424
+ self[:tfc].read self[:body].slice!(real_length, tfc_len)
425
+ end
412
426
  end
427
+
428
+ PacketGen::Header.add_class ESP
429
+
430
+ PacketGen::Header::IP.bind ESP, protocol: ESP::IP_PROTOCOL
431
+ PacketGen::Header::IPv6.bind ESP, next: ESP::IP_PROTOCOL
432
+ PacketGen::Header::UDP.bind ESP, procs: [->(f) { f.dport = f.sport = ESP::UDP_PORT },
433
+ lambda { |f|
434
+ (f.dport == ESP::UDP_PORT ||
435
+ f.sport == ESP::UDP_PORT) &&
436
+ BinStruct::Int32.new.read(f.body[0..3]).to_i.positive?
437
+ }]
438
+ ESP.bind PacketGen::Header::IP, next: 4
439
+ ESP.bind PacketGen::Header::IPv6, next: 41
440
+ ESP.bind PacketGen::Header::TCP, next: PacketGen::Header::TCP::IP_PROTOCOL
441
+ ESP.bind PacketGen::Header::UDP, next: PacketGen::Header::TCP::IP_PROTOCOL
442
+ ESP.bind PacketGen::Header::ICMP, next: PacketGen::Header::ICMP::IP_PROTOCOL
443
+ ESP.bind PacketGen::Header::ICMPv6, next: PacketGen::Header::ICMPv6::IP_PROTOCOL
413
444
  end
445
+ # rubocop:enable Metrics/ClassLength