packetgen-plugin-ipsec 1.0.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.
@@ -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