packetgen 1.4.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,255 @@
1
+ # coding: utf-8
2
+ module PacketGen
3
+ module Header
4
+ class IKE
5
+
6
+ # This class handles encrypted payloads, denoted SK.
7
+ #
8
+ # The encrypted payload contains other payloads in encrypted form.
9
+ # The Encrypted payload consists of the IKE generic payload header followed
10
+ # by individual fields as follows:
11
+ # 1 2 3
12
+ # 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
13
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
14
+ # | Next Payload |C| RESERVED | Payload Length |
15
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16
+ # | Initialization Vector |
17
+ # | (length is block size for encryption algorithm) |
18
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
19
+ # ~ Encrypted IKE Payloads ~
20
+ # + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
21
+ # | | Padding (0-255 octets) |
22
+ # +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
23
+ # | | Pad Length |
24
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
25
+ # ~ Integrity Checksum Data ~
26
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
27
+ # Encrypted payloads are set in {#content} field, as a {Types::String}.
28
+ # All others fields are only set when decrypting a previously read SK
29
+ # payload. They also may be set manually to encrypt IKE payloads.
30
+ #
31
+ # == Read and decrypt a SK payload
32
+ # # Read a IKE packet
33
+ # pkt = PacketGen.read(str)
34
+ # # decrypt SK payload
35
+ # cipher = OpenSSL::Cipher.new('aes-128-ctr')
36
+ # cipher.decrypt
37
+ # cipher_key = aes_key
38
+ # hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
39
+ # pkt.ike_sk.decrypt! cipher, intmode: hmac, icv_length: 16 # => true if authentication is verified
40
+ # pkt.ike_sk.body # => kind of PacketGen::Header::IKE::Payload
41
+ #
42
+ # == Set and encrypt a SK payload
43
+ # # Create a IKE packet
44
+ # pkt = PacketGen.gen('IP').add('IP').add('UDP').add('IKE', init_spi: 0x123456789, resp_spi: 0x987654321, type: 'IKE_AUTH', message_id: 1)
45
+ # # Add SK payload
46
+ # pkt.add('IKE::SK', icv_length: 16)
47
+ # # Add others unencrypted payloads
48
+ # pkt.add('IKE::IDi').add('IKE::Auth').add('IKE::SA').add('IKE::TSi').add('IKE::TSr')
49
+ # # encrypt SK payload
50
+ # cipher = OpenSSL::Cipher.new('aes-128-ctr')
51
+ # cipher.encrypt
52
+ # cipher_key = aes_key
53
+ # hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
54
+ # pkt.ike_sk.encrypt! cipher, iv, salt: salt, intmode: hmac
55
+ # pkt.ike_sk.body # => String
56
+ # pkt.calc_length
57
+ #
58
+ # @author Sylvain Daubert
59
+ class SK < Payload
60
+ include Crypto
61
+
62
+ # Payload type number
63
+ PAYLOAD_TYPE = 46
64
+
65
+ # ICV (Integrity Check Value) length
66
+ # @return [Integer]
67
+ attr_accessor :icv_length
68
+
69
+ # @param [Hash] options
70
+ # @option options [Integer] :icv_length ICV length
71
+ def initialize(options={})
72
+ @icv_length = options[:icv_length] || 0
73
+ super
74
+ end
75
+
76
+ # Decrypt in-place SK payload.
77
+ # @param [OpenSSL::Cipher] cipher keyed cipher
78
+ # This cipher is confidentiality-only one, or AEAD one. To use a second
79
+ # cipher to add integrity, use +:intmode+ option.
80
+ # @param [Hash] options
81
+ # @option options [Boolean] :parse parse deciphered payload to retrieve
82
+ # headers (default: +true+)
83
+ # @option options [Fixnum] :icv_length ICV length for captured packets,
84
+ # or read from PCapNG files
85
+ # @option options [String] :salt salt value for CTR and GCM modes
86
+ # @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
87
+ # confidentiality-only cipher. Only HMAC are supported.
88
+ # @return [Boolean] +true+ if SK payload is authenticated
89
+ def decrypt!(cipher, options={})
90
+ opt = { salt: '', parse: true }.merge!(options)
91
+
92
+ set_crypto cipher, opt[:intmode]
93
+
94
+ case confidentiality_mode
95
+ when 'gcm'
96
+ iv = self.content.slice!(0, 8)
97
+ real_iv = force_binary(opt[:salt]) + iv
98
+ when 'cbc'
99
+ cipher.padding = 0
100
+ real_iv = iv = self.content.slice!(0, 16)
101
+ when 'ctr'
102
+ iv = self.content.slice!(0, 8)
103
+ real_iv = force_binary(opt[:salt]) + iv + [1].pack('N')
104
+ else
105
+ real_iv = iv = self.content.slice!(0, 16)
106
+ end
107
+ cipher.iv = real_iv
108
+
109
+ if authenticated?
110
+ if @icv_length == 0
111
+ @icv_length = opt[:icv_length].to_i if opt[:icv_length]
112
+ raise ParseError, 'unknown ICV size' if @icv_length == 0
113
+ end
114
+ icv = self.content.slice!(-@icv_length, @icv_length)
115
+ end
116
+
117
+ authenticate_if_needed opt, iv, icv
118
+ private_decrypt cipher, opt
119
+ end
120
+
121
+ # Encrypt in-place SK payload.
122
+ # @param [OpenSSL::Cipher] cipher keyed cipher
123
+ # This cipher is confidentiality-only one, or AEAD one. To use a second
124
+ # cipher to add integrity, use +:intmode+ option.
125
+ # @param [String] iv IV to encipher SK payload content
126
+ # * CTR and GCM modes: +iv+ is 8-bytes long.
127
+ # @param [Hash] options
128
+ # @option options [Fixnum] :icv_length ICV length for captured packets,
129
+ # or read from PCapNG files
130
+ # @option options [String] :salt salt value for CTR and GCM modes
131
+ # @option options [Fixnum] :pad_length set a padding length
132
+ # @option options [String] :padding set a padding. No check with
133
+ # +:pad_length+ is made. If +:pad_length+ is not set, +:padding+
134
+ # length is shortened to correct padding length
135
+ # @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
136
+ # confidentiality-only cipher. Only HMAC are supported.
137
+ # @return [self]
138
+ def encrypt!(cipher, iv, options={})
139
+ opt = { salt: '' }.merge!(options)
140
+
141
+ set_crypto cipher, opt[:intmode]
142
+
143
+ real_iv = force_binary(opt[:salt]) + force_binary(iv)
144
+ real_iv += [1].pack('N') if confidentiality_mode == 'ctr'
145
+ cipher.iv = real_iv
146
+
147
+ authenticate_if_needed options, iv
148
+
149
+ if opt[:pad_length]
150
+ pad_length = opt[:pad_length]
151
+ padding = force_binary(opt[:padding] || ([0] * pad_length).pack('C*'))
152
+ else
153
+ pad_length = cipher.block_size
154
+ pad_length = 16 if cipher.block_size == 1 # Some AES mode returns 1...
155
+ pad_length -= (self.body.sz + iv.size + 1) % cipher.block_size
156
+ pad_length = 0 if pad_length == 16
157
+ padding = force_binary(opt[:padding] || ([0] * pad_length).pack('C*'))
158
+ padding = padding[0, pad_length]
159
+ end
160
+ msg = self.body.to_s + padding + Types::Int8.new(pad_length).to_s
161
+ encrypted_msg = encipher(msg)
162
+ cipher.final # message is already padded. No need for mode padding
163
+
164
+ if authenticated?
165
+ @icv_length = opt[:icv_length] if opt[:icv_length]
166
+ if @conf.authenticated?
167
+ encrypted_msg << @conf.auth_tag[0, @icv_length]
168
+ else
169
+ encrypted_msg << @intg.digest[0, @icv_length]
170
+ end
171
+ end
172
+ self[:content].read (iv + encrypted_msg)
173
+
174
+ # Remove plain payloads
175
+ self[:body] = Types::String.new
176
+
177
+ # Remove enciphered payloads from packet
178
+ id = header_id(self)
179
+ if id < packet.headers.size - 1
180
+ (packet.headers.size-1).downto(id+1) do |index|
181
+ packet.headers.delete_at index
182
+ end
183
+ end
184
+
185
+ self.calc_length
186
+ self
187
+ end
188
+
189
+ private
190
+
191
+ def authenticate_if_needed(options, iv, icv=nil)
192
+ if @conf.authenticated?
193
+ @conf.auth_tag = icv if icv
194
+ @conf.auth_data = get_ad
195
+ elsif @intg
196
+ @intg.reset
197
+ @intg.update get_ad
198
+ @intg.update iv
199
+ @icv = icv
200
+ else
201
+ @icv = nil
202
+ end
203
+ end
204
+
205
+ # From RFC 7206, §5.1: The associated data MUST consist of the partial
206
+ # contents of the IKEv2 message, starting from the first octet of the
207
+ # Fixed IKE Header through the last octet of the Payload Header of the
208
+ # Encrypted Payload (i.e., the fourth octet of the Encrypted Payload).
209
+ def get_ad
210
+ str = packet.ike.to_s[0, IKE.new.sz]
211
+ current_payload = packet.ike.body
212
+ until current_payload.is_a? SK do
213
+ str << current_payload.to_s[0, current_payload.length]
214
+ current_payload = current_payload.body
215
+ end
216
+ str << self.to_s[0, SK.new.sz]
217
+ end
218
+
219
+ def private_decrypt(cipher, options)
220
+ # decrypt
221
+ plain_msg = decipher(content.to_s)
222
+ # Remove cipher text
223
+ self.content.read ''
224
+
225
+ # check authentication tag
226
+ if authenticated?
227
+ return false unless authenticate!
228
+ end
229
+
230
+ # remove padding
231
+ pad_len = Types::Int8.new.read(plain_msg[-1]).to_i
232
+ payloads = plain_msg[0, plain_msg.size - 1 - pad_len]
233
+
234
+ # parse IKE payloads
235
+ if options[:parse]
236
+ klass = IKE.constants.select do |c|
237
+ cst = IKE.const_get(c)
238
+ cst.is_a?(Class) && (cst < Payload) && (cst::PAYLOAD_TYPE == self.next)
239
+ end
240
+ klass = klass.nil? ? Payload : IKE.const_get(klass.first)
241
+ firsth = klass.protocol_name
242
+ pkt = Packet.parse(payloads, first_header: firsth)
243
+ packet.encapsulate(pkt, parsing: true) unless pkt.nil?
244
+ else
245
+ self[:body].read payloads
246
+ end
247
+
248
+ true
249
+ end
250
+ end
251
+ end
252
+
253
+ self.add_class IKE::SK
254
+ end
255
+ end
@@ -0,0 +1,287 @@
1
+ # coding: utf-8
2
+ require 'net-proto'
3
+
4
+ module PacketGen
5
+ module Header
6
+ class IKE
7
+
8
+ # TrafficSelector substructure, as defined in RFC 7296, §3.13.1:
9
+ # 1 2 3
10
+ # 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
11
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
12
+ # | TS Type |IP Protocol ID*| Selector Length |
13
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
14
+ # | Start Port* | End Port* |
15
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16
+ # | |
17
+ # ~ Starting Address* ~
18
+ # | |
19
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20
+ # | |
21
+ # ~ Ending Address* ~
22
+ # | |
23
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
24
+ # @author Sylvain Daubert
25
+ class TrafficSelector < Types::Fields
26
+ # IPv4 traffic selector type
27
+ TS_IPV4_ADDR_RANGE = 7
28
+ # IPv6 traffic selector type
29
+ TS_IPV6_ADDR_RANGE = 8
30
+
31
+ # @!attribute [r] type
32
+ # 8-bit TS type
33
+ # @return [Integer]
34
+ define_field :type, Types::Int8, default: 7
35
+ # @!attribute [r] protocol
36
+ # 8-bit protocol ID
37
+ # @return [Integer]
38
+ define_field :protocol, Types::Int8, default: 0
39
+ # @!attribute length
40
+ # 16-bit Selector Length
41
+ # @return [Integer]
42
+ define_field :length, Types::Int16
43
+ # @!attribute start_port
44
+ # 16-bit Start port
45
+ # @return [Integer]
46
+ define_field :start_port, Types::Int16, default: 0
47
+ # @!attribute end_port
48
+ # 16-bit End port
49
+ # @return [Integer]
50
+ define_field :end_port, Types::Int16, default: 65535
51
+ # @!attribute start_addr
52
+ # starting address
53
+ # @return [IP::Addr, IPv6::Addr]
54
+ define_field :start_addr, IP::Addr
55
+ # @!attribute end_addr
56
+ # starting address
57
+ # @return [IP::Addr, IPv6::Addr]
58
+ define_field :end_addr, IP::Addr
59
+
60
+ # @param [Hash] options
61
+ # @option [Range] :ports port range
62
+ # @option [Integer] :start_port start port
63
+ # @option [Integer] :end_port end port
64
+ def initialize(options={})
65
+ super
66
+ select_addr options
67
+ self[:start_addr].from_human(options[:start_addr]) if options[:start_addr]
68
+ self[:end_addr].from_human(options[:end_addr]) if options[:end_addr]
69
+ self[:length].value = sz unless options[:length]
70
+ self.type = options[:type] if options[:type]
71
+ self.protocol = options[:protocol] if options[:protocol]
72
+ if options[:ports]
73
+ self.start_port = options[:ports].begin
74
+ self.end_port = options[:ports].end
75
+ end
76
+ end
77
+
78
+ # Populate object from a string
79
+ # @param [String] str
80
+ # @return [self]
81
+ def read(str)
82
+ super
83
+ select_addr_from_type type
84
+ super
85
+ end
86
+
87
+ # Set type
88
+ # @param [Integer,String] value
89
+ # @return [Integer]
90
+ def type=(value)
91
+ type = case value
92
+ when Integer
93
+ value
94
+ else
95
+ c = self.class.constants.grep(/TS_#{value.upcase}/).first
96
+ c ? self.class.const_get(c) : nil
97
+ end
98
+ raise ArgumentError, "unknown type #{value.inspect}" unless type
99
+ select_addr_from_type type
100
+ self[:type].value = type
101
+ end
102
+
103
+ # Set protocol
104
+ # @param [Integer,String] value
105
+ # @return [Integer]
106
+ def protocol=(value)
107
+ protocol = case value
108
+ when Integer
109
+ value
110
+ else
111
+ Net::Proto.getprotobyname(value)
112
+ end
113
+ raise ArgumentError, "unknown protocol #{value.inspect}" unless protocol
114
+ self[:protocol].value = protocol
115
+ end
116
+
117
+ # Get a human readable string
118
+ # @return [String]
119
+ def to_human
120
+ h = start_addr << '-' << end_addr
121
+ unless human_protocol.empty?
122
+ h << "/#{human_protocol}"
123
+ h << "[#{start_port}-#{end_port}]" if (start_port..end_port) != (0..65535)
124
+ end
125
+ h
126
+ end
127
+
128
+ # Get human readable protocol name. If protocol ID is 0, an empty string
129
+ # is returned.
130
+ # @return [String]
131
+ def human_protocol
132
+ if protocol == 0
133
+ ''
134
+ else
135
+ Net::Proto.getprotobynumber(protocol) || "#{protocol}"
136
+ end
137
+ end
138
+
139
+ # Get human readable TS type
140
+ # @return [String]
141
+ def human_type
142
+ case type
143
+ when TS_IPV4_ADDR_RANGE
144
+ 'IPv4'
145
+ when TS_IPV6_ADDR_RANGE
146
+ 'IPv6'
147
+ else
148
+ "type #{type}"
149
+ end
150
+ end
151
+
152
+ private
153
+
154
+ def select_addr_from_type(type)
155
+ case type
156
+ when TS_IPV4_ADDR_RANGE, 'IPV4', 'IPv4', 'ipv4', nil
157
+ self[:start_addr] = IP::Addr.new unless self[:start_addr].is_a?(IP::Addr)
158
+ self[:end_addr] = IP::Addr.new unless self[:end_addr].is_a?(IP::Addr)
159
+ when TS_IPV6_ADDR_RANGE, 'IPV6', 'IPv6', 'ipv6'
160
+ self[:start_addr] = IPv6::Addr.new unless self[:start_addr].is_a?(IPv6::Addr)
161
+ self[:end_addr] = IPv6::Addr.new unless self[:end_addr].is_a?(IPv6::Addr)
162
+ else
163
+ raise ArgumentError, "unknown type #{type}"
164
+ end
165
+ end
166
+
167
+ def select_addr(options)
168
+ if options[:type]
169
+ select_addr_from_type options[:type]
170
+ elsif options[:start_addr]
171
+ ipv4 = IPAddr.new(options[:start_addr]).ipv4?
172
+ self.type = ipv4 ? TS_IPV4_ADDR_RANGE : TS_IPV6_ADDR_RANGE
173
+ elsif options[:end_addr]
174
+ ipv4 = IPAddr.new(options[:end_addr]).ipv4?
175
+ self.type = ipv4 ? TS_IPV4_ADDR_RANGE : TS_IPV6_ADDR_RANGE
176
+ end
177
+ end
178
+ end
179
+
180
+ # Set of {TrafficSelector}, used by {TSi} and {TSr}.
181
+ # @author Sylvain Daubert
182
+ class TrafficSelectors < Types::Array
183
+ set_of TrafficSelector
184
+ end
185
+
186
+ # This class handles Traffic Selector - Initiator payloads, denoted TSi.
187
+ #
188
+ # A TSi payload consists of the IKE generic payload header (see {Payload})
189
+ # and some specific fields:
190
+ # 1 2 3
191
+ # 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
192
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
193
+ # | Next Payload |C| RESERVED | Payload Length |
194
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
195
+ # | Number of TSs | RESERVED |
196
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
197
+ # | |
198
+ # ~ <Traffic Selectors> ~
199
+ # | |
200
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
201
+ # These specific fields are:
202
+ # * {#num_ts},
203
+ # * {#reserved},
204
+ # * and {#traffic_selectors}.
205
+ #
206
+ # == Create a TSi payload
207
+ # # Create a IKE packet with a TSi payload
208
+ # pkt = PacketGen.gen('IP').add('UDP').add('IKE').add('IKE::TSi')
209
+ # # add a traffic selector to this payload
210
+ # pkt.ike_tsi.traffic_selectors << { protocol: 'tcp', ports: 1..1024, start_addr: '20.0.0.1', end_addr: '21.255.255.254' }
211
+ # # add another traffic selector (IPv6, all protocols)
212
+ # pkt.ike_tsi.traffic_selectors << { start_addr: '2001::1', end_addr: '200a:ffff:ffff:ffff:ffff:ffff:ffff:ffff' }
213
+ # @author Sylvain Daubert
214
+ class TSi < Payload
215
+
216
+ # Payload type number
217
+ PAYLOAD_TYPE = 44
218
+
219
+ delete_field :content
220
+ # @!attribute num_ts
221
+ # 8-bit Number of TSs
222
+ # @return [Integer]
223
+ define_field_before :body, :num_ts, Types::Int8
224
+ # @!attribute rsv1
225
+ # First 8-bit RESERVED field
226
+ # @return [Integer]
227
+ define_field_before :body, :rsv1, Types::Int8
228
+ # @!attribute rsv1
229
+ # Last 16-bit RESERVED field
230
+ # @return [Integer]
231
+ define_field_before :body, :rsv2, Types::Int16
232
+
233
+ # @!attribute traffic_selectors
234
+ # Set of {TrafficSelector}
235
+ # @return {TrafficSelectors}
236
+ define_field_before :body, :traffic_selectors, TrafficSelectors,
237
+ builder: ->(ts) { TrafficSelectors.new(counter: ts[:num_ts]) }
238
+ alias :selectors :traffic_selectors
239
+
240
+ # Populate object from a string
241
+ # @param [String] str
242
+ # @return [self]
243
+ def read(str)
244
+ super(str[0, 8])
245
+ hlen = self.class.new.sz
246
+ tslen = length - hlen
247
+ selectors.read str[hlen, tslen]
248
+ body.read str[hlen+tslen..-1]
249
+ self
250
+ end
251
+
252
+ # Compute length and set {#length} field
253
+ # @return [Integer] new length
254
+ def calc_length
255
+ selectors.each { |p| p.calc_length }
256
+ super
257
+ end
258
+
259
+ # @return [String]
260
+ def inspect
261
+ str = Inspect.dashed_line(self.class, 2)
262
+ fields.each do |attr|
263
+ case attr
264
+ when :body, :rsv2
265
+ next
266
+ when :rsv1
267
+ str << Inspect.shift_level(2)
268
+ str << Inspect::FMT_ATTR % ['Int24', 'reserved',
269
+ (rsv1 << 16) | rsv2 ]
270
+ else
271
+ str << Inspect.inspect_attribute(attr, self[attr], 2)
272
+ end
273
+ end
274
+ str
275
+ end
276
+ end
277
+
278
+ class TSr < TSi
279
+ # Payload type number
280
+ PAYLOAD_TYPE = 45
281
+ end
282
+ end
283
+
284
+ self.add_class IKE::TSi
285
+ self.add_class IKE::TSr
286
+ end
287
+ end