packetgen 1.4.3 → 2.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,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