packetgen-plugin-ipsec 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,261 @@
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
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
67
+
68
+ # Payload type number
69
+ PAYLOAD_TYPE = 46
70
+
71
+ # ICV (Integrity Check Value) length
72
+ # @return [Integer]
73
+ attr_accessor :icv_length
74
+
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
81
+
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)
97
+
98
+ set_crypto cipher, opt[:intmode]
99
+
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
114
+
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)
121
+ end
122
+
123
+ authenticate_if_needed iv, icv
124
+ private_decrypt opt
125
+ end
126
+
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)
146
+
147
+ set_crypto cipher, opt[:intmode]
148
+
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
152
+
153
+ authenticate_if_needed iv
154
+
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
169
+
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)
179
+
180
+ # Remove plain payloads
181
+ self[:body] = PacketGen::Types::String.new
182
+
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
189
+ end
190
+
191
+ self.calc_length
192
+ self
193
+ end
194
+
195
+ private
196
+
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
209
+ end
210
+
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]
223
+ end
224
+
225
+ def private_decrypt(options)
226
+ # decrypt
227
+ plain_msg = decipher(content.to_s)
228
+ # Remove cipher text
229
+ self[:content].read ''
230
+
231
+ # check authentication tag
232
+ if authenticated?
233
+ return false unless authenticate!
234
+ end
235
+
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]
239
+
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
252
+ end
253
+
254
+ true
255
+ end
256
+ end
257
+ end
258
+
259
+ Header.add_class IKE::SK
260
+ end
261
+ end
@@ -0,0 +1,260 @@
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
10
+ module Plugin
11
+ class IKE
12
+ # TrafficSelector substructure, as defined in RFC 7296, §3.13.1:
13
+ # 1 2 3
14
+ # 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
15
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16
+ # | TS Type |IP Protocol ID*| Selector Length |
17
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
18
+ # | Start Port* | End Port* |
19
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20
+ # | |
21
+ # ~ Starting Address* ~
22
+ # | |
23
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
24
+ # | |
25
+ # ~ Ending Address* ~
26
+ # | |
27
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
28
+ # @author Sylvain Daubert
29
+ class TrafficSelector < PacketGen::Types::Fields
30
+ # IPv4 traffic selector type
31
+ TS_IPV4_ADDR_RANGE = 7
32
+ # IPv6 traffic selector type
33
+ TS_IPV6_ADDR_RANGE = 8
34
+
35
+ # @!attribute [r] type
36
+ # 8-bit TS type
37
+ # @return [Integer]
38
+ define_field :type, PacketGen::Types::Int8, default: 7
39
+ # @!attribute [r] protocol
40
+ # 8-bit protocol ID
41
+ # @return [Integer]
42
+ define_field :protocol, PacketGen::Types::Int8, default: 0
43
+ # @!attribute length
44
+ # 16-bit Selector Length
45
+ # @return [Integer]
46
+ define_field :length, PacketGen::Types::Int16
47
+ # @!attribute start_port
48
+ # 16-bit Start port
49
+ # @return [Integer]
50
+ define_field :start_port, PacketGen::Types::Int16, default: 0
51
+ # @!attribute end_port
52
+ # 16-bit End port
53
+ # @return [Integer]
54
+ define_field :end_port, PacketGen::Types::Int16, default: 65_535
55
+ # @!attribute start_addr
56
+ # starting address
57
+ # @return [IP::Addr, IPv6::Addr]
58
+ define_field :start_addr, PacketGen::Header::IP::Addr
59
+ # @!attribute end_addr
60
+ # starting address
61
+ # @return [IP::Addr, IPv6::Addr]
62
+ define_field :end_addr, PacketGen::Header::IP::Addr
63
+
64
+ # @param [Hash] options
65
+ # @option [Range] :ports port range
66
+ # @option [Integer] :start_port start port
67
+ # @option [Integer] :end_port end port
68
+ def initialize(options={})
69
+ super
70
+ select_addr options
71
+ self[:start_addr].from_human(options[:start_addr]) if options[:start_addr]
72
+ self[:end_addr].from_human(options[:end_addr]) if options[:end_addr]
73
+ self.type = options[:type] if options[:type]
74
+ self.protocol = options[:protocol] if options[:protocol]
75
+ self[:length].value = sz unless options[:length]
76
+
77
+ return unless options[:ports]
78
+ self.start_port = options[:ports].begin
79
+ self.end_port = options[:ports].end
80
+ end
81
+
82
+ # Populate object from a string
83
+ # @param [String] str
84
+ # @return [self]
85
+ def read(str)
86
+ super
87
+ select_addr_from_type type
88
+ super
89
+ end
90
+
91
+ undef type=, protocol=
92
+
93
+ # Set type
94
+ # @param [Integer,String] value
95
+ # @return [Integer]
96
+ def type=(value)
97
+ type = case value
98
+ when Integer
99
+ value
100
+ else
101
+ c = self.class.constants.grep(/TS_#{value.upcase}/).first
102
+ c ? self.class.const_get(c) : nil
103
+ end
104
+ raise ArgumentError, "unknown type #{value.inspect}" unless type
105
+ select_addr_from_type type
106
+ self[:type].value = type
107
+ end
108
+
109
+ # Set protocol
110
+ # @param [Integer,String] value
111
+ # @return [Integer]
112
+ def protocol=(value)
113
+ protocol = case value
114
+ when Integer
115
+ value
116
+ else
117
+ Proto.getprotobyname(value)
118
+ end
119
+ raise ArgumentError, "unknown protocol #{value.inspect}" unless protocol
120
+ self[:protocol].value = protocol
121
+ end
122
+
123
+ # Get a human readable string
124
+ # @return [String]
125
+ def to_human
126
+ h = start_addr << '-' << end_addr
127
+ unless human_protocol.empty?
128
+ h << "/#{human_protocol}"
129
+ h << "[#{start_port}-#{end_port}]" if (start_port..end_port) != (0..65_535)
130
+ end
131
+ h
132
+ end
133
+
134
+ # Get human readable protocol name. If protocol ID is 0, an empty string
135
+ # is returned.
136
+ # @return [String]
137
+ def human_protocol
138
+ if protocol.zero?
139
+ ''
140
+ else
141
+ Proto.getprotobynumber(protocol) || protocol.to_s
142
+ end
143
+ end
144
+
145
+ # Get human readable TS type
146
+ # @return [String]
147
+ def human_type
148
+ case type
149
+ when TS_IPV4_ADDR_RANGE
150
+ 'IPv4'
151
+ when TS_IPV6_ADDR_RANGE
152
+ 'IPv6'
153
+ else
154
+ "type #{type}"
155
+ end
156
+ end
157
+
158
+ private
159
+
160
+ def select_addr_from_type(type)
161
+ case type
162
+ when TS_IPV4_ADDR_RANGE, 'IPV4', 'IPv4', 'ipv4', nil
163
+ self[:start_addr] = PacketGen::Header::IP::Addr.new unless self[:start_addr].is_a?(PacketGen::Header::IP::Addr)
164
+ self[:end_addr] = PacketGen::Header::IP::Addr.new unless self[:end_addr].is_a?(PacketGen::Header::IP::Addr)
165
+ when TS_IPV6_ADDR_RANGE, 'IPV6', 'IPv6', 'ipv6'
166
+ self[:start_addr] = PacketGen::Header::IPv6::Addr.new unless self[:start_addr].is_a?(PacketGen::Header::IPv6::Addr)
167
+ self[:end_addr] = PacketGen::Header::IPv6::Addr.new unless self[:end_addr].is_a?(PacketGen::Header::IPv6::Addr)
168
+ else
169
+ raise ArgumentError, "unknown type #{type}"
170
+ end
171
+ end
172
+
173
+ def select_addr(options)
174
+ if options[:type]
175
+ select_addr_from_type options[:type]
176
+ elsif options[:start_addr]
177
+ ipv4 = IPAddr.new(options[:start_addr]).ipv4?
178
+ self.type = ipv4 ? TS_IPV4_ADDR_RANGE : TS_IPV6_ADDR_RANGE
179
+ elsif options[:end_addr]
180
+ ipv4 = IPAddr.new(options[:end_addr]).ipv4?
181
+ self.type = ipv4 ? TS_IPV4_ADDR_RANGE : TS_IPV6_ADDR_RANGE
182
+ end
183
+ end
184
+ end
185
+
186
+ # Set of {TrafficSelector}, used by {TSi} and {TSr}.
187
+ # @author Sylvain Daubert
188
+ class TrafficSelectors < PacketGen::Types::Array
189
+ set_of TrafficSelector
190
+ end
191
+
192
+ # This class handles Traffic Selector - Initiator payloads, denoted TSi.
193
+ #
194
+ # A TSi payload consists of the IKE generic payload Plugin (see {Payload})
195
+ # and some specific fields:
196
+ # 1 2 3
197
+ # 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
198
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
199
+ # | Next Payload |C| RESERVED | Payload Length |
200
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
201
+ # | Number of TSs | RESERVED |
202
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
203
+ # | |
204
+ # ~ <Traffic Selectors> ~
205
+ # | |
206
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
207
+ # These specific fields are:
208
+ # * {#num_ts},
209
+ # * {#rsv1},
210
+ # * {#rsv2},
211
+ # * and {#traffic_selectors}.
212
+ #
213
+ # == Create a TSi payload
214
+ # # Create a IKE packet with a TSi payload
215
+ # pkt = PacketGen.gen('IP').add('UDP').add('IKE').add('IKE::TSi')
216
+ # # add a traffic selector to this payload
217
+ # pkt.ike_tsi.traffic_selectors << { protocol: 'tcp', ports: 1..1024, start_addr: '20.0.0.1', end_addr: '21.255.255.254' }
218
+ # # add another traffic selector (IPv6, all protocols)
219
+ # pkt.ike_tsi.traffic_selectors << { start_addr: '2001::1', end_addr: '200a:ffff:ffff:ffff:ffff:ffff:ffff:ffff' }
220
+ # @author Sylvain Daubert
221
+ class TSi < Payload
222
+ # Payload type number
223
+ PAYLOAD_TYPE = 44
224
+
225
+ remove_field :content
226
+
227
+ # @!attribute num_ts
228
+ # 8-bit Number of TSs
229
+ # @return [Integer]
230
+ define_field_before :body, :num_ts, PacketGen::Types::Int8
231
+ # @!attribute rsv
232
+ # 24-bit RESERVED field
233
+ # @return [Integer]
234
+ define_field_before :body, :rsv, PacketGen::Types::Int24
235
+
236
+ # @!attribute traffic_selectors
237
+ # Set of {TrafficSelector}
238
+ # @return {TrafficSelectors}
239
+ define_field_before :body, :traffic_selectors, TrafficSelectors,
240
+ builder: ->(h, t) { t.new(counter: h[:num_ts]) }
241
+ alias selectors traffic_selectors
242
+
243
+ # Compute length and set {#length} field
244
+ # @return [Integer] new length
245
+ def calc_length
246
+ selectors.each(&:calc_length)
247
+ super
248
+ end
249
+ end
250
+
251
+ class TSr < TSi
252
+ # Payload type number
253
+ PAYLOAD_TYPE = 45
254
+ end
255
+ end
256
+
257
+ Header.add_class IKE::TSi
258
+ Header.add_class IKE::TSr
259
+ end
260
+ end