packetgen-plugin-ipsec 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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