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.
- checksums.yaml +4 -4
- data/.github/workflows/specs.yml +32 -0
- data/.rubocop.yml +28 -3
- data/Gemfile +18 -0
- data/README.md +12 -6
- data/Rakefile +10 -4
- data/lib/packetgen/plugin/crypto.rb +38 -4
- data/lib/packetgen/plugin/esp.rb +410 -378
- data/lib/packetgen/plugin/ike/auth.rb +153 -140
- data/lib/packetgen/plugin/ike/cert.rb +61 -62
- data/lib/packetgen/plugin/ike/certreq.rb +51 -52
- data/lib/packetgen/plugin/ike/id.rb +80 -81
- data/lib/packetgen/plugin/ike/ke.rb +64 -65
- data/lib/packetgen/plugin/ike/nonce.rb +29 -31
- data/lib/packetgen/plugin/ike/notify.rb +134 -139
- data/lib/packetgen/plugin/ike/payload.rb +75 -76
- data/lib/packetgen/plugin/ike/sa.rb +515 -452
- data/lib/packetgen/plugin/ike/sk.rb +221 -221
- data/lib/packetgen/plugin/ike/ts.rb +226 -223
- data/lib/packetgen/plugin/ike/vendor_id.rb +28 -30
- data/lib/packetgen/plugin/ike.rb +213 -217
- data/lib/packetgen/plugin/ipsec_version.rb +8 -1
- data/lib/packetgen-plugin-ipsec.rb +2 -0
- data/packetgen-plugin-ipsec.gemspec +6 -11
- metadata +11 -88
- data/.travis.yml +0 -14
data/lib/packetgen/plugin/esp.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
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
|
-
#
|
156
|
+
# This method removes all data from +tfc+ and +padding+ fields, as their
|
157
|
+
# enciphered values are concatenated into +body+.
|
67
158
|
#
|
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
|
-
|
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
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
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
|
-
|
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
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
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
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
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
|
-
|
326
|
-
|
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
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
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
|
-
|
341
|
-
|
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
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
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
|
-
|
349
|
-
|
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
|
-
|
355
|
-
|
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
|
-
|
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
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
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
|