packetgen-plugin-ipsec 1.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d2414b8c5da9828bc956ddac67bdb729bff3d4573f9254532d5b71b02c017165
4
+ data.tar.gz: 9bdf29105a2cf5ccbbd985126aea57c7a6bc40fbca15ec0ac7883f13ad06bacb
5
+ SHA512:
6
+ metadata.gz: 6609a9c5eac34a53a779cecefb1b30092ac0b68350fd697a0e1024ebb517b3b1b278c7bdbef5114a67f1e87b6398a60f23e0bc6a61f35003233ef047599933e8
7
+ data.tar.gz: fb8d634fb9b0b3b6d5740d780684b2bcdaecedc355c10a6f9bc5e8b9a4e1ddde9c7291e40b3198424d74bb1ceb4078d21571c125cfd15d1d1138a683a2e28551
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /vendor/
10
+ Gemfile.lock
data/.rubocop.yml ADDED
@@ -0,0 +1,29 @@
1
+ TargetRubyVersion: 2.3
2
+ Layout/SpaceAroundEqualsInParameterDefault:
3
+ EnforcedStyle: no_space
4
+ Lint/EmptyWhen:
5
+ Enabled: false
6
+ Lint/Void:
7
+ Enabled: false
8
+ Metrics:
9
+ Enabled: false
10
+ Style/AsciiComments:
11
+ Enabled: false
12
+ Style/Encoding:
13
+ Enabled: false
14
+ Style/EvalWithLocation:
15
+ Enabled: false
16
+ Style/FormatString:
17
+ EnforcedStyle: percent
18
+ Style/FormatStringToken:
19
+ EnforcedStyle: unannotated
20
+ Style/PerlBackrefs:
21
+ Enabled: false
22
+ Style/RedundantSelf:
23
+ Enabled: false
24
+ Style/StructInheritance:
25
+ Enabled: false
26
+ Style/TrailingCommaInArrayLiteral:
27
+ Enabled: false
28
+ Style/TrailingCommaInHashLiteral:
29
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3
4
+ - 2.4
5
+ - 2.5
6
+
7
+ install:
8
+ - sudo apt-get update -qq
9
+ - sudo apt-get install libpcap-dev -qq
10
+ - bundle install --path vendor/bundle --jobs=3 --retry=3
11
+ script:
12
+ - bundle exec rake
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Sylvain Daubert
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,97 @@
1
+ [![Gem Version](https://badge.fury.io/rb/packetgen-plugin-ipsec.svg)](https://badge.fury.io/rb/packetgen-plugin-ipsec)
2
+ [![Build Status](https://travis-ci.com/sdaubert/packetgen-plugin-ipsec.svg?branch=master)](https://travis-ci.com/sdaubert/packetgen-plugin-ipsec)
3
+
4
+ # packetgen-plugin-ipsec
5
+
6
+ **Warning:** this repository is a work-in-progress. It will be available with packetgen3.
7
+
8
+ This is a plugin for [PacketGen gem](https://github.com/sdaubert/packetgen). It adds two protocols:
9
+
10
+ * `PacketGen::Plugin::ESP`: IP Encapsulating Security Payload ([RFC 4303](https://tools.ietf.org/html/rfc4303)),
11
+ * `PacketGen::Plugin::IKE`: Internet Key Exchange v2 ([RFC 7296](https://tools.ietf.org/html/rfc7296)).
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ ```ruby
18
+ gem 'packetgen-plugin-ipsec'
19
+ ```
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+ Or install it yourself as:
26
+
27
+ $ gem install packetgen-plugin-ipsec
28
+
29
+ ## Usage
30
+
31
+ First, you have to require packetgen-plugin-ipsec:
32
+
33
+ ```ruby
34
+ require 'packetgen-plugin-ipsec'
35
+ ```
36
+
37
+ ### Parse an ESP or IKE packet
38
+
39
+ ```ruby
40
+ pkt = PacketGen.parse(str)
41
+ ```
42
+
43
+ ### Read a PcapNG file containing ESP and/or IKE packets
44
+
45
+ ```ruby
46
+ pkts = PacketGen.read('ipsec.pcapng')
47
+ ```
48
+
49
+ ### Access to ESP and IKE headers
50
+
51
+ ```ruby
52
+ pkt.esp #=> PacketGen::Plugin::ESP
53
+ pkt.ike #=> PacketGen::Plugin::IKE
54
+ ```
55
+
56
+ ### Forge packets
57
+
58
+ #### ESP (transport mode)
59
+
60
+ ```ruby
61
+ pkt = PacketGen.gen('IP', src: '1.1.1.1', dst: '2.2.2.2').
62
+ add('ESP', spi: 0xff456e01, sn: 12345678).
63
+ add('UDP', dport: 4567, sport: 45362, body 'abcdef')
64
+ cipher = OpenSSL::Cipher.new('aes-128-cbc')
65
+ cipher.encrypt
66
+ cipher.key = 16bytes_key
67
+ iv = 16bytes_iv
68
+ pkt.esp.esp.encrypt! cipher, iv
69
+ pkt.to_w
70
+ ```
71
+
72
+ #### IKE (IKE_SA_INIT)
73
+
74
+ ```ruby
75
+ pkt = PacketGen.gen('IP', src: '1.1.1.1', dst: '2.2.2.2').
76
+ add('UDP').
77
+ add('IKE', init_spi: spi, flags: 8).
78
+ add('IKE::SA').
79
+ add('IKE::KE', group: 'ECP256', content: key_ex_data).
80
+ add('IKE::Nonce', content: nonce_data)
81
+ pkt.ike_sa.proposals << { num: 1, protocol: 'IKE' }
82
+ pkt.ike_sa.proposals.first.transforms << { type: 'ENCR', id: 'AES_CTR' }
83
+ pkt.ike_sa.proposals[0].transforms[0].attributes << { type: 0x800e, value: 128 }
84
+ pkt.to_w
85
+ ```
86
+
87
+ ## See also
88
+
89
+ API documentation: http://www.rubydoc.info/gems/packetgen-plugin-ipsec
90
+
91
+ ## License
92
+
93
+ MIT License (see [LICENSE](https://github.com/sdaubert/packetgen-plugin-ipsec/blob/master/LICENSE))
94
+
95
+ ## Contributing
96
+
97
+ Bug reports and pull requests are welcome on GitHub at https://github.com/sdaubert/packetgen-plugin-ipsec.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+ require 'yard'
5
+
6
+ task default: :spec
7
+
8
+ RSpec::Core::RakeTask.new
9
+
10
+ YARD::Rake::YardocTask.new do |t|
11
+ t.options = ['--no-private']
12
+ t.files = ['lib/**/*.rb', '-', 'LICENSE']
13
+ end
@@ -0,0 +1,4 @@
1
+ require 'packetgen'
2
+ require_relative 'packetgen/plugin/ipsec_version'
3
+ require_relative 'packetgen/plugin/esp'
4
+ require_relative 'packetgen/plugin/ike'
@@ -0,0 +1,74 @@
1
+ # coding: utf-8
2
+ # This file is part of IPsec packetgen plugin.
3
+ # See https://github.com/sdaubert/packetgen-plugin-ipsec for more informations
4
+ # Copyright (c) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
5
+ # This program is published under MIT license.
6
+
7
+ # frozen_string_literal: true
8
+
9
+ module PacketGen::Plugin
10
+ # Mixin for cryptographic classes
11
+ # @api private
12
+ # @author Sylvain Daubert
13
+ module Crypto
14
+ # Cryptographic error
15
+ class Error < PacketGen::Error; end
16
+
17
+ # Register cryptographic modes
18
+ # @param [OpenSSL::Cipher] conf
19
+ # @param [OpenSSL::HMAC] intg
20
+ # @return [void]
21
+ def set_crypto(conf, intg)
22
+ @conf = conf
23
+ @intg = intg
24
+ return unless conf.authenticated?
25
+ # #auth_tag_len only supported from ruby 2.4.0
26
+ @conf.auth_tag_len = @trunc if @conf.respond_to? :auth_tag_len
27
+ end
28
+
29
+ # Get confidentiality mode name
30
+ # @return [String]
31
+ def confidentiality_mode
32
+ mode = @conf.name.match(/-([^-]*)$/)[1]
33
+ raise Error, 'unknown cipher mode' if mode.nil?
34
+ mode.downcase
35
+ end
36
+
37
+ # Say if crypto modes permit authentication
38
+ # @return [Boolean]
39
+ def authenticated?
40
+ @conf.authenticated? || !@intg.nil?
41
+ end
42
+
43
+ # Check authentication
44
+ # @return [Boolean]
45
+ def authenticate!
46
+ @conf.final
47
+ if @intg
48
+ @intg.update @esn.to_s if defined? @esn
49
+ @intg.digest[0, @icv_length] == @icv
50
+ else
51
+ true
52
+ end
53
+ rescue OpenSSL::Cipher::CipherError
54
+ false
55
+ end
56
+
57
+ # Encipher +data+
58
+ # @param [String] data
59
+ # @return [String] enciphered data
60
+ def encipher(data)
61
+ enciphered_data = @conf.update(data)
62
+ @intg.update(enciphered_data) if @intg
63
+ enciphered_data
64
+ end
65
+
66
+ # Decipher +data+
67
+ # @param [String] data
68
+ # @return [String] deciphered data
69
+ def decipher(data)
70
+ @intg.update(data) if @intg
71
+ @conf.update(data)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,413 @@
1
+ # This file is part of IPsec packetgen plugin.
2
+ # See https://github.com/sdaubert/packetgen-plugin-ipsec for more informations
3
+ # Copyright (c) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
4
+ # This program is published under MIT license.
5
+
6
+ # frozen_string_literal: true
7
+
8
+ require_relative 'crypto'
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
60
+ #
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
65
+ #
66
+ # hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
67
+ #
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
130
+ end
131
+
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
152
+ end
153
+
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
+ end
249
+
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
298
+ end
299
+
300
+ private
301
+
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
309
+ end
310
+
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
323
+ end
324
+
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
332
+ end
333
+
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)
339
+
340
+ # check authentication tag
341
+ return false if authenticated? && !authenticate!
342
+
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]
347
+
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
353
+
354
+ # Set TFC padding
355
+ encap_length = 0
356
+ 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
395
+ end
396
+ end
397
+
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
412
+ end
413
+ end