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.
@@ -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
- # frozen_string_literal: true
8
-
9
- module PacketGen
10
- module Plugin
11
- class IKE
12
- # This class handles encrypted payloads, denoted SK.
13
- #
14
- # The encrypted payload contains other payloads in encrypted form.
15
- # The Encrypted payload consists of the IKE generic payload Plugin followed
16
- # by individual fields as follows:
17
- # 1 2 3
18
- # 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
19
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20
- # | Next Payload |C| RESERVED | Payload Length |
21
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
22
- # | Initialization Vector |
23
- # | (length is block size for encryption algorithm) |
24
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
25
- # ~ Encrypted IKE Payloads ~
26
- # + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
27
- # | | Padding (0-255 octets) |
28
- # +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
29
- # | | Pad Length |
30
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
31
- # ~ Integrity Checksum Data ~
32
- # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
33
- # Encrypted payloads are set in {#content} field, as a {PacketGen::Types::String}.
34
- # All others fields are only set when decrypting a previously read SK
35
- # payload. They also may be set manually to encrypt IKE payloads.
36
- #
37
- # == Read and decrypt a SK payload
38
- # # Read a IKE packet
39
- # pkt = PacketGen.read(str)
40
- # # decrypt SK payload
41
- # cipher = OpenSSL::Cipher.new('aes-128-ctr')
42
- # cipher.decrypt
43
- # cipher_key = aes_key
44
- # hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
45
- # pkt.ike_sk.decrypt! cipher, intmode: hmac, icv_length: 16 # => true if authentication is verified
46
- # pkt.ike_sk.body # => kind of PacketGen::Plugin::IKE::Payload
47
- #
48
- # == Set and encrypt a SK payload
49
- # # Create a IKE packet
50
- # pkt = PacketGen.gen('IP').add('IP').add('UDP').add('IKE', init_spi: 0x123456789, resp_spi: 0x987654321, type: 'IKE_AUTH', message_id: 1)
51
- # # Add SK payload
52
- # pkt.add('IKE::SK', icv_length: 16)
53
- # # Add others unencrypted payloads
54
- # pkt.add('IKE::IDi').add('IKE::Auth').add('IKE::SA').add('IKE::TSi').add('IKE::TSr')
55
- # # encrypt SK payload
56
- # cipher = OpenSSL::Cipher.new('aes-128-ctr')
57
- # cipher.encrypt
58
- # cipher_key = aes_key
59
- # hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
60
- # pkt.ike_sk.encrypt! cipher, iv, salt: salt, intmode: hmac
61
- # pkt.ike_sk.body # => String
62
- # pkt.calc_length
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
- # Payload type number
69
- PAYLOAD_TYPE = 46
67
+ # Payload type number
68
+ PAYLOAD_TYPE = 46
70
69
 
71
- # ICV (Integrity Check Value) length
72
- # @return [Integer]
73
- attr_accessor :icv_length
70
+ # ICV (Integrity Check Value) length
71
+ # @return [Integer]
72
+ attr_accessor :icv_length
74
73
 
75
- # @param [Hash] options
76
- # @option options [Integer] :icv_length ICV length
77
- def initialize(options={})
78
- @icv_length = options[:icv_length] || 0
79
- super
80
- end
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
- # Decrypt in-place SK payload.
83
- # @param [OpenSSL::Cipher] cipher keyed cipher
84
- # This cipher is confidentiality-only one, or AEAD one. To use a second
85
- # cipher to add integrity, use +:intmode+ option.
86
- # @param [Hash] options
87
- # @option options [Boolean] :parse parse deciphered payload to retrieve
88
- # Plugins (default: +true+)
89
- # @option options [Fixnum] :icv_length ICV length for captured packets,
90
- # or read from PCapNG files
91
- # @option options [String] :salt salt value for CTR and GCM modes
92
- # @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
93
- # confidentiality-only cipher. Only HMAC are supported.
94
- # @return [Boolean] +true+ if SK payload is authenticated
95
- def decrypt!(cipher, options={})
96
- opt = { salt: '', parse: true }.merge!(options)
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
- set_crypto cipher, opt[:intmode]
97
+ set_crypto cipher, opt[:intmode]
99
98
 
100
- case confidentiality_mode
101
- when 'gcm'
102
- iv = self[:content].slice!(0, 8)
103
- real_iv = force_binary(opt[:salt]) + iv
104
- when 'cbc'
105
- cipher.padding = 0
106
- real_iv = iv = self[:content].slice!(0, 16)
107
- when 'ctr'
108
- iv = self[:content].slice!(0, 8)
109
- real_iv = force_binary(opt[:salt]) + iv + [1].pack('N')
110
- else
111
- real_iv = iv = self[:content].slice!(0, 16)
112
- end
113
- cipher.iv = real_iv
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
- if authenticated?
116
- if @icv_length.zero?
117
- @icv_length = opt[:icv_length].to_i if opt[:icv_length]
118
- raise ParseError, 'unknown ICV size' if @icv_length.zero?
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
- # Encrypt in-place SK payload.
128
- # @param [OpenSSL::Cipher] cipher keyed cipher
129
- # This cipher is confidentiality-only one, or AEAD one. To use a second
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
- set_crypto cipher, opt[:intmode]
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
- real_iv = force_binary(opt[:salt]) + force_binary(iv)
150
- real_iv += [1].pack('N') if confidentiality_mode == 'ctr'
151
- cipher.iv = real_iv
146
+ set_crypto cipher, opt[:intmode]
152
147
 
153
- authenticate_if_needed iv
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
- if opt[:pad_length]
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
- if authenticated?
171
- @icv_length = opt[:icv_length] if opt[:icv_length]
172
- encrypted_msg << if @conf.authenticated?
173
- @conf.auth_tag[0, @icv_length]
174
- else
175
- @intg.digest[0, @icv_length]
176
- end
177
- end
178
- self[:content].read(iv + encrypted_msg)
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
- # Remove plain payloads
181
- self[:body] = PacketGen::Types::String.new
179
+ # Remove plain payloads
180
+ self[:body] = PacketGen::Types::String.new
182
181
 
183
- # Remove enciphered payloads from packet
184
- id = header_id(self)
185
- if id < packet.headers.size - 1
186
- (packet.headers.size - 1).downto(id + 1) do |index|
187
- packet.headers.delete_at index
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
- private
190
+ self.calc_length
191
+ self
192
+ end
196
193
 
197
- def authenticate_if_needed(iv, icv=nil)
198
- if @conf.authenticated?
199
- @conf.auth_tag = icv if icv
200
- @conf.auth_data = get_ad
201
- elsif @intg
202
- @intg.reset
203
- @intg.update get_ad
204
- @intg.update iv
205
- @icv = icv
206
- else
207
- @icv = nil
208
- end
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
- # From RFC 7206, §5.1: The associated data MUST consist of the partial
212
- # contents of the IKEv2 message, starting from the first octet of the
213
- # Fixed IKE Plugin through the last octet of the Payload Plugin of the
214
- # Encrypted Payload (i.e., the fourth octet of the Encrypted Payload).
215
- def get_ad
216
- str = packet.ike.to_s[0, IKE.new.sz]
217
- current_payload = packet.ike[:body]
218
- until current_payload.is_a? SK
219
- str << current_payload.to_s[0, current_payload.to_s.length]
220
- current_payload = current_payload[:body]
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
- def private_decrypt(options)
226
- # decrypt
227
- plain_msg = decipher(content.to_s)
228
- # Remove cipher text
229
- self[:content].read ''
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
- # check authentication tag
232
- if authenticated?
233
- return false unless authenticate!
234
- end
230
+ # check authentication tag
231
+ if authenticated?
232
+ return false unless authenticate!
233
+ end
235
234
 
236
- # remove padding
237
- pad_len = PacketGen::Types::Int8.new.read(plain_msg[-1]).to_i
238
- payloads = plain_msg[0, plain_msg.size - 1 - pad_len]
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
- # parse IKE payloads
241
- if options[:parse]
242
- klass = IKE.constants.select do |c|
243
- cst = IKE.const_get(c)
244
- cst.is_a?(Class) && (cst < Payload) && (cst::PAYLOAD_TYPE == self.next)
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
- true
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