packetgen-plugin-ipsec 1.0.2 → 1.0.3
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 +28 -0
- data/.rubocop.yml +8 -1
- data/.travis.yml +1 -1
- data/Gemfile +11 -0
- data/Rakefile +10 -4
- data/lib/packetgen/plugin/crypto.rb +6 -4
- data/lib/packetgen/plugin/esp.rb +373 -370
- data/lib/packetgen/plugin/ike.rb +218 -217
- data/lib/packetgen/plugin/ike/auth.rb +141 -141
- 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 -80
- data/lib/packetgen/plugin/ike/ke.rb +64 -66
- data/lib/packetgen/plugin/ike/nonce.rb +29 -31
- data/lib/packetgen/plugin/ike/notify.rb +135 -139
- data/lib/packetgen/plugin/ike/payload.rb +58 -57
- data/lib/packetgen/plugin/ike/sa.rb +515 -452
- data/lib/packetgen/plugin/ike/sk.rb +219 -221
- data/lib/packetgen/plugin/ike/ts.rb +223 -223
- data/lib/packetgen/plugin/ike/vendor_id.rb +28 -30
- data/lib/packetgen/plugin/ipsec_version.rb +8 -1
- data/packetgen-plugin-ipsec.gemspec +3 -9
- metadata +8 -77
@@ -1,261 +1,259 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
# This file is part of IPsec packetgen plugin.
|
3
5
|
# See https://github.com/sdaubert/packetgen-plugin-ipsec for more informations
|
4
6
|
# Copyright (c) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
|
5
7
|
# This program is published under MIT license.
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
# @author Sylvain Daubert
|
65
|
-
class SK < Payload
|
66
|
-
include Crypto
|
9
|
+
module PacketGen::Plugin
|
10
|
+
class IKE
|
11
|
+
# This class handles encrypted payloads, denoted SK.
|
12
|
+
#
|
13
|
+
# The encrypted payload contains other payloads in encrypted form.
|
14
|
+
# The Encrypted payload consists of the IKE generic payload Plugin followed
|
15
|
+
# by individual fields as follows:
|
16
|
+
# 1 2 3
|
17
|
+
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
18
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
19
|
+
# | Next Payload |C| RESERVED | Payload Length |
|
20
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
21
|
+
# | Initialization Vector |
|
22
|
+
# | (length is block size for encryption algorithm) |
|
23
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
24
|
+
# ~ Encrypted IKE Payloads ~
|
25
|
+
# + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
26
|
+
# | | Padding (0-255 octets) |
|
27
|
+
# +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
28
|
+
# | | Pad Length |
|
29
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
30
|
+
# ~ Integrity Checksum Data ~
|
31
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
32
|
+
# Encrypted payloads are set in {#content} field, as a {PacketGen::Types::String}.
|
33
|
+
# All others fields are only set when decrypting a previously read SK
|
34
|
+
# payload. They also may be set manually to encrypt IKE payloads.
|
35
|
+
#
|
36
|
+
# == Read and decrypt a SK payload
|
37
|
+
# # Read a IKE packet
|
38
|
+
# pkt = PacketGen.read(str)
|
39
|
+
# # decrypt SK payload
|
40
|
+
# cipher = OpenSSL::Cipher.new('aes-128-ctr')
|
41
|
+
# cipher.decrypt
|
42
|
+
# cipher_key = aes_key
|
43
|
+
# hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
|
44
|
+
# pkt.ike_sk.decrypt! cipher, intmode: hmac, icv_length: 16 # => true if authentication is verified
|
45
|
+
# pkt.ike_sk.body # => kind of PacketGen::Plugin::IKE::Payload
|
46
|
+
#
|
47
|
+
# == Set and encrypt a SK payload
|
48
|
+
# # Create a IKE packet
|
49
|
+
# pkt = PacketGen.gen('IP').add('IP').add('UDP').add('IKE', init_spi: 0x123456789, resp_spi: 0x987654321, type: 'IKE_AUTH', message_id: 1)
|
50
|
+
# # Add SK payload
|
51
|
+
# pkt.add('IKE::SK', icv_length: 16)
|
52
|
+
# # Add others unencrypted payloads
|
53
|
+
# pkt.add('IKE::IDi').add('IKE::Auth').add('IKE::SA').add('IKE::TSi').add('IKE::TSr')
|
54
|
+
# # encrypt SK payload
|
55
|
+
# cipher = OpenSSL::Cipher.new('aes-128-ctr')
|
56
|
+
# cipher.encrypt
|
57
|
+
# cipher_key = aes_key
|
58
|
+
# hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
|
59
|
+
# pkt.ike_sk.encrypt! cipher, iv, salt: salt, intmode: hmac
|
60
|
+
# pkt.ike_sk.body # => String
|
61
|
+
# pkt.calc_length
|
62
|
+
#
|
63
|
+
# @author Sylvain Daubert
|
64
|
+
class SK < Payload
|
65
|
+
include Crypto
|
67
66
|
|
68
|
-
|
69
|
-
|
67
|
+
# Payload type number
|
68
|
+
PAYLOAD_TYPE = 46
|
70
69
|
|
71
|
-
|
72
|
-
|
73
|
-
|
70
|
+
# ICV (Integrity Check Value) length
|
71
|
+
# @return [Integer]
|
72
|
+
attr_accessor :icv_length
|
74
73
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
74
|
+
# @param [Hash] options
|
75
|
+
# @option options [Integer] :icv_length ICV length
|
76
|
+
def initialize(options={})
|
77
|
+
@icv_length = options[:icv_length] || 0
|
78
|
+
super
|
79
|
+
end
|
81
80
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
81
|
+
# Decrypt in-place SK payload.
|
82
|
+
# @param [OpenSSL::Cipher] cipher keyed cipher
|
83
|
+
# This cipher is confidentiality-only one, or AEAD one. To use a second
|
84
|
+
# cipher to add integrity, use +:intmode+ option.
|
85
|
+
# @param [Hash] options
|
86
|
+
# @option options [Boolean] :parse parse deciphered payload to retrieve
|
87
|
+
# Plugins (default: +true+)
|
88
|
+
# @option options [Fixnum] :icv_length ICV length for captured packets,
|
89
|
+
# or read from PCapNG files
|
90
|
+
# @option options [String] :salt salt value for CTR and GCM modes
|
91
|
+
# @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
|
92
|
+
# confidentiality-only cipher. Only HMAC are supported.
|
93
|
+
# @return [Boolean] +true+ if SK payload is authenticated
|
94
|
+
def decrypt!(cipher, options={})
|
95
|
+
opt = { salt: '', parse: true }.merge!(options)
|
97
96
|
|
98
|
-
|
97
|
+
set_crypto cipher, opt[:intmode]
|
99
98
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
99
|
+
case confidentiality_mode
|
100
|
+
when 'gcm'
|
101
|
+
iv = self[:content].slice!(0, 8)
|
102
|
+
real_iv = force_binary(opt[:salt]) + iv
|
103
|
+
when 'cbc'
|
104
|
+
cipher.padding = 0
|
105
|
+
real_iv = iv = self[:content].slice!(0, 16)
|
106
|
+
when 'ctr'
|
107
|
+
iv = self[:content].slice!(0, 8)
|
108
|
+
real_iv = force_binary(opt[:salt]) + iv + [1].pack('N')
|
109
|
+
else
|
110
|
+
real_iv = iv = self[:content].slice!(0, 16)
|
111
|
+
end
|
112
|
+
cipher.iv = real_iv
|
114
113
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
end
|
120
|
-
icv = self[:content].slice!(-@icv_length, @icv_length)
|
114
|
+
if authenticated?
|
115
|
+
if @icv_length.zero?
|
116
|
+
@icv_length = opt[:icv_length].to_i if opt[:icv_length]
|
117
|
+
raise PacketGen::ParseError, 'unknown ICV size' if @icv_length.zero?
|
121
118
|
end
|
122
|
-
|
123
|
-
authenticate_if_needed iv, icv
|
124
|
-
private_decrypt opt
|
119
|
+
icv = self[:content].slice!(-@icv_length, @icv_length)
|
125
120
|
end
|
126
121
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
# cipher to add integrity, use +:intmode+ option.
|
131
|
-
# @param [String] iv IV to encipher SK payload content
|
132
|
-
# * CTR and GCM modes: +iv+ is 8-bytes long.
|
133
|
-
# @param [Hash] options
|
134
|
-
# @option options [Fixnum] :icv_length ICV length for captured packets,
|
135
|
-
# or read from PCapNG files
|
136
|
-
# @option options [String] :salt salt value for CTR and GCM modes
|
137
|
-
# @option options [Fixnum] :pad_length set a padding length
|
138
|
-
# @option options [String] :padding set a padding. No check with
|
139
|
-
# +:pad_length+ is made. If +:pad_length+ is not set, +:padding+
|
140
|
-
# length is shortened to correct padding length
|
141
|
-
# @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
|
142
|
-
# confidentiality-only cipher. Only HMAC are supported.
|
143
|
-
# @return [self]
|
144
|
-
def encrypt!(cipher, iv, options={})
|
145
|
-
opt = { salt: '' }.merge!(options)
|
122
|
+
authenticate_if_needed iv, icv
|
123
|
+
private_decrypt opt
|
124
|
+
end
|
146
125
|
|
147
|
-
|
126
|
+
# Encrypt in-place SK payload.
|
127
|
+
# @param [OpenSSL::Cipher] cipher keyed cipher
|
128
|
+
# This cipher is confidentiality-only one, or AEAD one. To use a second
|
129
|
+
# cipher to add integrity, use +:intmode+ option.
|
130
|
+
# @param [String] iv IV to encipher SK payload content
|
131
|
+
# * CTR and GCM modes: +iv+ is 8-bytes long.
|
132
|
+
# @param [Hash] options
|
133
|
+
# @option options [Fixnum] :icv_length ICV length for captured packets,
|
134
|
+
# or read from PCapNG files
|
135
|
+
# @option options [String] :salt salt value for CTR and GCM modes
|
136
|
+
# @option options [Fixnum] :pad_length set a padding length
|
137
|
+
# @option options [String] :padding set a padding. No check with
|
138
|
+
# +:pad_length+ is made. If +:pad_length+ is not set, +:padding+
|
139
|
+
# length is shortened to correct padding length
|
140
|
+
# @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
|
141
|
+
# confidentiality-only cipher. Only HMAC are supported.
|
142
|
+
# @return [self]
|
143
|
+
def encrypt!(cipher, iv, options={})
|
144
|
+
opt = { salt: '' }.merge!(options)
|
148
145
|
|
149
|
-
|
150
|
-
real_iv += [1].pack('N') if confidentiality_mode == 'ctr'
|
151
|
-
cipher.iv = real_iv
|
146
|
+
set_crypto cipher, opt[:intmode]
|
152
147
|
|
153
|
-
|
148
|
+
real_iv = force_binary(opt[:salt]) + force_binary(iv)
|
149
|
+
real_iv += [1].pack('N') if confidentiality_mode == 'ctr'
|
150
|
+
cipher.iv = real_iv
|
154
151
|
|
155
|
-
|
156
|
-
pad_length = opt[:pad_length]
|
157
|
-
padding = force_binary(opt[:padding] || ([0] * pad_length).pack('C*'))
|
158
|
-
else
|
159
|
-
pad_length = cipher.block_size
|
160
|
-
pad_length = 16 if cipher.block_size == 1 # Some AES mode returns 1...
|
161
|
-
pad_length -= (self[:body].sz + iv.size + 1) % cipher.block_size
|
162
|
-
pad_length = 0 if pad_length == 16
|
163
|
-
padding = force_binary(opt[:padding] || ([0] * pad_length).pack('C*'))
|
164
|
-
padding = padding[0, pad_length]
|
165
|
-
end
|
166
|
-
msg = self[:body].to_s + padding + PacketGen::Types::Int8.new(pad_length).to_s
|
167
|
-
encrypted_msg = encipher(msg)
|
168
|
-
cipher.final # message is already padded. No need for mode padding
|
152
|
+
authenticate_if_needed iv
|
169
153
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
154
|
+
if opt[:pad_length]
|
155
|
+
pad_length = opt[:pad_length]
|
156
|
+
padding = force_binary(opt[:padding] || ([0] * pad_length).pack('C*'))
|
157
|
+
else
|
158
|
+
pad_length = cipher.block_size
|
159
|
+
pad_length = 16 if cipher.block_size == 1 # Some AES mode returns 1...
|
160
|
+
pad_length -= (self[:body].sz + iv.size + 1) % cipher.block_size
|
161
|
+
pad_length = 0 if pad_length == 16
|
162
|
+
padding = force_binary(opt[:padding] || ([0] * pad_length).pack('C*'))
|
163
|
+
padding = padding[0, pad_length]
|
164
|
+
end
|
165
|
+
msg = self[:body].to_s + padding + PacketGen::Types::Int8.new(pad_length).to_s
|
166
|
+
encrypted_msg = encipher(msg)
|
167
|
+
cipher.final # message is already padded. No need for mode padding
|
168
|
+
|
169
|
+
if authenticated?
|
170
|
+
@icv_length = opt[:icv_length] if opt[:icv_length]
|
171
|
+
encrypted_msg << if @conf.authenticated?
|
172
|
+
@conf.auth_tag[0, @icv_length]
|
173
|
+
else
|
174
|
+
@intg.digest[0, @icv_length]
|
175
|
+
end
|
176
|
+
end
|
177
|
+
self[:content].read(iv + encrypted_msg)
|
179
178
|
|
180
|
-
|
181
|
-
|
179
|
+
# Remove plain payloads
|
180
|
+
self[:body] = PacketGen::Types::String.new
|
182
181
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
end
|
182
|
+
# Remove enciphered payloads from packet
|
183
|
+
id = header_id(self)
|
184
|
+
if id < packet.headers.size - 1
|
185
|
+
(packet.headers.size - 1).downto(id + 1) do |index|
|
186
|
+
packet.headers.delete_at index
|
189
187
|
end
|
190
|
-
|
191
|
-
self.calc_length
|
192
|
-
self
|
193
188
|
end
|
194
189
|
|
195
|
-
|
190
|
+
self.calc_length
|
191
|
+
self
|
192
|
+
end
|
196
193
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
194
|
+
private
|
195
|
+
|
196
|
+
def authenticate_if_needed(iv, icv=nil)
|
197
|
+
if @conf.authenticated?
|
198
|
+
@conf.auth_tag = icv if icv
|
199
|
+
@conf.auth_data = get_ad
|
200
|
+
elsif @intg
|
201
|
+
@intg.reset
|
202
|
+
@intg.update get_ad
|
203
|
+
@intg.update iv
|
204
|
+
@icv = icv
|
205
|
+
else
|
206
|
+
@icv = nil
|
209
207
|
end
|
208
|
+
end
|
210
209
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
end
|
222
|
-
str << self.to_s[0, SK.new.sz]
|
210
|
+
# From RFC 7206, §5.1: The associated data MUST consist of the partial
|
211
|
+
# contents of the IKEv2 message, starting from the first octet of the
|
212
|
+
# Fixed IKE Plugin through the last octet of the Payload Plugin of the
|
213
|
+
# Encrypted Payload (i.e., the fourth octet of the Encrypted Payload).
|
214
|
+
def get_ad
|
215
|
+
str = packet.ike.to_s[0, IKE.new.sz]
|
216
|
+
current_payload = packet.ike[:body]
|
217
|
+
until current_payload.is_a? SK
|
218
|
+
str << current_payload.to_s[0, current_payload.to_s.length]
|
219
|
+
current_payload = current_payload[:body]
|
223
220
|
end
|
221
|
+
str << self.to_s[0, SK.new.sz]
|
222
|
+
end
|
224
223
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
224
|
+
def private_decrypt(options)
|
225
|
+
# decrypt
|
226
|
+
plain_msg = decipher(content.to_s)
|
227
|
+
# Remove cipher text
|
228
|
+
self[:content].read ''
|
230
229
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
230
|
+
# check authentication tag
|
231
|
+
if authenticated?
|
232
|
+
return false unless authenticate!
|
233
|
+
end
|
235
234
|
|
236
|
-
|
237
|
-
|
238
|
-
|
235
|
+
# remove padding
|
236
|
+
pad_len = PacketGen::Types::Int8.new.read(plain_msg[-1]).to_i
|
237
|
+
payloads = plain_msg[0, plain_msg.size - 1 - pad_len]
|
239
238
|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
end
|
246
|
-
klass = klass.nil? ? Payload : IKE.const_get(klass.first)
|
247
|
-
firsth = klass.protocol_name
|
248
|
-
pkt = Packet.parse(payloads, first_header: firsth)
|
249
|
-
packet.encapsulate(pkt, parsing: true) unless pkt.nil?
|
250
|
-
else
|
251
|
-
self[:body].read payloads
|
239
|
+
# parse IKE payloads
|
240
|
+
if options[:parse]
|
241
|
+
klass = IKE.constants.select do |c|
|
242
|
+
cst = IKE.const_get(c)
|
243
|
+
cst.is_a?(Class) && (cst < Payload) && (cst::PAYLOAD_TYPE == self.next)
|
252
244
|
end
|
253
|
-
|
254
|
-
|
245
|
+
klass = klass.nil? ? Payload : IKE.const_get(klass.first)
|
246
|
+
firsth = klass.protocol_name
|
247
|
+
pkt = PacketGen::Packet.parse(payloads, first_header: firsth)
|
248
|
+
packet.encapsulate(pkt, parsing: true) unless pkt.nil?
|
249
|
+
else
|
250
|
+
self[:body].read payloads
|
255
251
|
end
|
252
|
+
|
253
|
+
true
|
256
254
|
end
|
257
255
|
end
|
258
|
-
|
259
|
-
Header.add_class IKE::SK
|
260
256
|
end
|
257
|
+
|
258
|
+
PacketGen::Header.add_class IKE::SK
|
261
259
|
end
|