packetgen-plugin-ipsec 1.0.2 → 1.0.3

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