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.
- checksums.yaml +4 -4
- data/.travis.yml +3 -4
- data/README.md +6 -6
- data/lib/packetgen.rb +0 -1
- data/lib/packetgen/capture.rb +15 -37
- data/lib/packetgen/header.rb +6 -2
- data/lib/packetgen/header/asn1_base.rb +86 -0
- data/lib/packetgen/header/base.rb +68 -44
- data/lib/packetgen/header/crypto.rb +62 -0
- data/lib/packetgen/header/dns.rb +3 -3
- data/lib/packetgen/header/dot11.rb +5 -0
- data/lib/packetgen/header/esp.rb +7 -43
- data/lib/packetgen/header/ike.rb +235 -0
- data/lib/packetgen/header/ike/auth.rb +197 -0
- data/lib/packetgen/header/ike/cert.rb +105 -0
- data/lib/packetgen/header/ike/certreq.rb +69 -0
- data/lib/packetgen/header/ike/id.rb +131 -0
- data/lib/packetgen/header/ike/ke.rb +74 -0
- data/lib/packetgen/header/ike/nonce.rb +35 -0
- data/lib/packetgen/header/ike/notify.rb +220 -0
- data/lib/packetgen/header/ike/payload.rb +307 -0
- data/lib/packetgen/header/ike/sa.rb +577 -0
- data/lib/packetgen/header/ike/sk.rb +255 -0
- data/lib/packetgen/header/ike/ts.rb +287 -0
- data/lib/packetgen/header/ike/vendor_id.rb +34 -0
- data/lib/packetgen/header/ip.rb +4 -4
- data/lib/packetgen/header/ipv6.rb +3 -3
- data/lib/packetgen/header/snmp.rb +283 -0
- data/lib/packetgen/header/tcp.rb +3 -3
- data/lib/packetgen/inspect.rb +35 -9
- data/lib/packetgen/packet.rb +23 -9
- data/lib/packetgen/types/fields.rb +11 -3
- data/lib/packetgen/version.rb +1 -1
- data/packetgen.gemspec +2 -0
- metadata +52 -2
data/lib/packetgen/header/dns.rb
CHANGED
@@ -248,13 +248,13 @@ module PacketGen
|
|
248
248
|
flags = [:qr, :aa, :tc, :rd, :ra].select! { |attr| send "#{attr}?" }.
|
249
249
|
map(&:to_s).join(',')
|
250
250
|
str << Inspect.shift_level(2)
|
251
|
-
str << Inspect::
|
251
|
+
str << Inspect::FMT_ATTR % ['Flags', 'flags', flags]
|
252
252
|
opcode = '%-10s (%u)' % [OPCODES.key(self.opcode), self.opcode]
|
253
253
|
str << Inspect.shift_level(2)
|
254
|
-
str << Inspect::
|
254
|
+
str << Inspect::FMT_ATTR % ['Integer', 'opcode', opcode]
|
255
255
|
rcode = '%-10s (%u)' % [RCODES.key(self.rcode), self.rcode]
|
256
256
|
str << Inspect.shift_level(2)
|
257
|
-
str << Inspect::
|
257
|
+
str << Inspect::FMT_ATTR % ['Integer', 'rcode', rcode]
|
258
258
|
else
|
259
259
|
str << Inspect.inspect_attribute(attr, value, 2)
|
260
260
|
end
|
@@ -133,6 +133,11 @@ module PacketGen
|
|
133
133
|
# * a {#ht_ctrl} ({Types::Int32}),
|
134
134
|
# * a {#body} (a {Types::String} or another {Base} class),
|
135
135
|
# * a Frame check sequence ({#fcs}, of type {Types::Int32le})
|
136
|
+
#
|
137
|
+
# == header accessors
|
138
|
+
# As Dot11 header types are defined under Dot11 namespace, Dot11 header accessors
|
139
|
+
# have a specific name. By example, to access to a {Dot11::Beacon} header,
|
140
|
+
# accessor is +#dot11_beacon+.
|
136
141
|
# @author Sylvain Daubert
|
137
142
|
class Dot11 < Base
|
138
143
|
|
data/lib/packetgen/header/esp.rb
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
module PacketGen
|
2
2
|
module Header
|
3
3
|
|
4
|
-
# Error about enciphering/deciphering was encountered
|
5
|
-
class CipherError < Error;end
|
6
|
-
|
7
4
|
# A ESP header consists of:
|
8
5
|
# * a Security Parameters Index (#{spi}, {Types::Int32} type),
|
9
6
|
# * a Sequence Number ({#sn}, +Int32+ type),
|
@@ -48,14 +45,14 @@ module PacketGen
|
|
48
45
|
#
|
49
46
|
# # encrypt ESP payload
|
50
47
|
# cipher = OpenSSL::Cipher.new('aes-128-gcm')
|
51
|
-
# cipher.encrypt
|
48
|
+
# cipher.encrypt
|
52
49
|
# cipher.key = 16bytes_key
|
53
50
|
# iv = 8bytes_iv
|
54
51
|
# esp.esp.encrypt! cipher, iv, salt: 4bytes_gcm_salt
|
55
52
|
#
|
56
53
|
# === Decrypt a ESP packet using CBC mode and HMAC-SHA-256
|
57
54
|
# cipher = OpenSSL::Cipher.new('aes-128-cbc')
|
58
|
-
# cipher.decrypt
|
55
|
+
# cipher.decrypt
|
59
56
|
# cipher.key = 16bytes_key
|
60
57
|
#
|
61
58
|
# hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
|
@@ -63,6 +60,7 @@ module PacketGen
|
|
63
60
|
# pkt.esp.decrypt! cipher, intmode: hmac # => true if ICV check OK
|
64
61
|
# @author Sylvain Daubert
|
65
62
|
class ESP < Base
|
63
|
+
include Crypto
|
66
64
|
|
67
65
|
# IP protocol number for ESP
|
68
66
|
IP_PROTOCOL = 50
|
@@ -292,43 +290,6 @@ module PacketGen
|
|
292
290
|
|
293
291
|
private
|
294
292
|
|
295
|
-
def set_crypto(conf, intg)
|
296
|
-
@conf, @intg = conf, intg
|
297
|
-
end
|
298
|
-
|
299
|
-
def confidentiality_mode
|
300
|
-
mode = @conf.name.match(/-([^-]*)$/)[1]
|
301
|
-
raise CipherError, 'unknown cipher mode' if mode.nil?
|
302
|
-
mode.downcase
|
303
|
-
end
|
304
|
-
|
305
|
-
def authenticated?
|
306
|
-
@conf.authenticated? or !!@intg
|
307
|
-
end
|
308
|
-
|
309
|
-
def authenticate!
|
310
|
-
@conf.final
|
311
|
-
if @intg
|
312
|
-
@intg.update @esn.to_s if @esn
|
313
|
-
@intg.digest[0, @icv_length] == @icv
|
314
|
-
else
|
315
|
-
true
|
316
|
-
end
|
317
|
-
rescue OpenSSL::Cipher::CipherError
|
318
|
-
false
|
319
|
-
end
|
320
|
-
|
321
|
-
def encipher(data)
|
322
|
-
enciphered_data = @conf.update(data)
|
323
|
-
@intg.update(enciphered_data) if @intg
|
324
|
-
enciphered_data
|
325
|
-
end
|
326
|
-
|
327
|
-
def decipher(data)
|
328
|
-
@intg.update(data) if @intg
|
329
|
-
@conf.update(data)
|
330
|
-
end
|
331
|
-
|
332
293
|
def get_auth_data(opt)
|
333
294
|
ad = self[:spi].to_s
|
334
295
|
if opt[:esn]
|
@@ -431,7 +392,10 @@ module PacketGen
|
|
431
392
|
|
432
393
|
IP.bind_header ESP, protocol: ESP::IP_PROTOCOL
|
433
394
|
IPv6.bind_header ESP, next: ESP::IP_PROTOCOL
|
434
|
-
UDP.bind_header ESP,
|
395
|
+
UDP.bind_header ESP, procs: [ ->(f) { f.dport = f.sport = ESP::UDP_PORT },
|
396
|
+
->(f) { (f.dport == ESP::UDP_PORT ||
|
397
|
+
f.sport == ESP::UDP_PORT) &&
|
398
|
+
Types::Int32.new.read(f.body[0..3]).to_i > 0 }]
|
435
399
|
ESP.bind_header IP, next: 4
|
436
400
|
ESP.bind_header IPv6, next: 41
|
437
401
|
ESP.bind_header TCP, next: TCP::IP_PROTOCOL
|
@@ -0,0 +1,235 @@
|
|
1
|
+
module PacketGen
|
2
|
+
module Header
|
3
|
+
|
4
|
+
# This class handles a pseudo-header used to differentiate ESP from IKE headers
|
5
|
+
# in a UDP datagram with port 4500.
|
6
|
+
# @author Sylvain Daubert
|
7
|
+
# @since 2.0.0
|
8
|
+
class NonESPMarker < Base
|
9
|
+
# @!attribute non_esp_marker
|
10
|
+
# 32-bit zero marker to differentiate IKE packet over UDP port 4500 from ESP ones
|
11
|
+
# @return [Integer]
|
12
|
+
define_field :non_esp_marker, Types::Int32, default: 0
|
13
|
+
# @!attribute body
|
14
|
+
# @return [Types::String,Header::Base]
|
15
|
+
define_field :body, Types::String
|
16
|
+
|
17
|
+
# Check non_esp_marker field
|
18
|
+
# @see [Base#parse?]
|
19
|
+
def parse?
|
20
|
+
non_esp_marker == 0
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# IKE is the Internet Key Exchange protocol (RFC 7296). Ony IKEv2 is supported.
|
25
|
+
#
|
26
|
+
# A IKE header consists of a header, and a set of payloads. This class
|
27
|
+
# handles IKE header. For payloads, see {IKE::Payload}.
|
28
|
+
#
|
29
|
+
# == IKE header
|
30
|
+
# The format of a IKE header is shown below:
|
31
|
+
# 1 2 3
|
32
|
+
# 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
|
33
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
34
|
+
# | IKE SA Initiator's SPI |
|
35
|
+
# | |
|
36
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
37
|
+
# | IKE SA Responder's SPI |
|
38
|
+
# | |
|
39
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
40
|
+
# | Next Payload | MjVer | MnVer | Exchange Type | Flags |
|
41
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
42
|
+
# | Message ID |
|
43
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
44
|
+
# | Length |
|
45
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
46
|
+
# A IKE header consists of:
|
47
|
+
# * a IKE SA initiator SPI ({#init_spi}, {Types::Int64} type),
|
48
|
+
# * a IKE SA responder SPI ({#resp_spi}, {Types::Int64} type),
|
49
|
+
# * a Next Payload field ({#next}, {Types::Int8} type),
|
50
|
+
# * a Version field ({#version}, {Types::Int8} type, with first 4-bit field
|
51
|
+
# as major number, and last 4-bit field as minor number),
|
52
|
+
# * a Exchange type ({#exchange_type}, {Types::Int8} type),
|
53
|
+
# * a {#flags} field ({Types::Int8} type),
|
54
|
+
# * a Message ID ({#message_id}, {Types::Int32} type),
|
55
|
+
# * and a {#length} ({Types::Int32} type).
|
56
|
+
#
|
57
|
+
# == Create a IKE header
|
58
|
+
# === Standalone
|
59
|
+
# ike = PacketGen::Header::IKE.new
|
60
|
+
# === Classical IKE packet
|
61
|
+
# pkt = PacketGen.gen('IP').add('UDP').add('IKE')
|
62
|
+
# # access to IKE header
|
63
|
+
# pkt.ike # => PacketGen::Header::IKE
|
64
|
+
# === NAT-T IKE packet
|
65
|
+
# # NonESPMarker is used to insert a 32-bit null field between UDP header
|
66
|
+
# # and IKE one to differentiate it from ESP-in-UDP (see RFC 3948)
|
67
|
+
# pkt = PacketGen.gen('IP').add('UDP').add('NonESPMarker').add('IKE)
|
68
|
+
# @author Sylvain Daubert
|
69
|
+
# @since 2.0.0
|
70
|
+
class IKE < Base
|
71
|
+
|
72
|
+
# Classical well-known UDP port for IKE
|
73
|
+
UDP_PORT1 = 500
|
74
|
+
# Well-known UDP port for IKE when NAT is detected
|
75
|
+
UDP_PORT2 = 4500
|
76
|
+
|
77
|
+
PROTO_IKE = 1
|
78
|
+
PROTO_AH = 2
|
79
|
+
PROTO_ESP = 3
|
80
|
+
|
81
|
+
TYPE_IKE_SA_INIT = 34
|
82
|
+
TYPE_IKE_AUTH = 35
|
83
|
+
TYPE_CREATE_CHILD_SA = 36
|
84
|
+
TYPE_INFORMATIONAL = 37
|
85
|
+
|
86
|
+
# @!attribute init_spi
|
87
|
+
# 64-bit initiator SPI
|
88
|
+
# @return [Integer]
|
89
|
+
define_field :init_spi, Types::Int64
|
90
|
+
# @!attribute resp_spi
|
91
|
+
# 64-bit responder SPI
|
92
|
+
# @return [Integer]
|
93
|
+
define_field :resp_spi, Types::Int64
|
94
|
+
# @!attribute next
|
95
|
+
# 8-bit next payload type
|
96
|
+
# @return [Integer]
|
97
|
+
define_field :next, Types::Int8
|
98
|
+
# @!attribute version
|
99
|
+
# 8-bit IKE version
|
100
|
+
# @return [Integer]
|
101
|
+
define_field :version, Types::Int8, default: 0x20
|
102
|
+
# @!attribute [r] exchange_type
|
103
|
+
# 8-bit exchange type
|
104
|
+
# @return [Integer]
|
105
|
+
define_field :exchange_type, Types::Int8
|
106
|
+
# @!attribute flags
|
107
|
+
# 8-bit flags
|
108
|
+
# @return [Integer]
|
109
|
+
define_field :flags, Types::Int8
|
110
|
+
# @!attribute message_id
|
111
|
+
# 32-bit message ID
|
112
|
+
# @return [Integer]
|
113
|
+
define_field :message_id, Types::Int32
|
114
|
+
# @!attribute length
|
115
|
+
# 32-bit length of total message (header + payloads)
|
116
|
+
# @return [Integer]
|
117
|
+
define_field :length, Types::Int32
|
118
|
+
|
119
|
+
# Defining a body permits using Packet#parse to parse IKE payloads.
|
120
|
+
# But this method is hidden as prefered way to access payloads is via #payloads
|
121
|
+
define_field :body, Types::String
|
122
|
+
|
123
|
+
# @!attribute mjver
|
124
|
+
# 4-bit major version value
|
125
|
+
# @return [Integer]
|
126
|
+
# @!attribute mnver
|
127
|
+
# 4-bit minor version value
|
128
|
+
# @return [Integer]
|
129
|
+
define_bit_fields_on :version, :mjver, 4, :mnver, 4
|
130
|
+
|
131
|
+
# @!attribute rsv1
|
132
|
+
# @return [Integer]
|
133
|
+
# @!attribute rsv2
|
134
|
+
# @return [Integer]
|
135
|
+
# @!attribute flag_i
|
136
|
+
# bit set in message sent by the original initiator
|
137
|
+
# @return [Boolean]
|
138
|
+
# @!attribute flag_r
|
139
|
+
# indicate this message is a response to a message containing the same Message ID
|
140
|
+
# @return [Boolean]
|
141
|
+
# @!attribute flag_v
|
142
|
+
# version flag. Ignored by IKEv2 peers, and should be set to 0
|
143
|
+
# @return [Boolean]
|
144
|
+
define_bit_fields_on :flags, :rsv1, 2, :flag_r, :flag_v, :flag_i, :rsv2, 3
|
145
|
+
|
146
|
+
# @param [Hash] options
|
147
|
+
# @see Base#initialize
|
148
|
+
def initialize(options={})
|
149
|
+
super
|
150
|
+
calc_length unless options[:length]
|
151
|
+
self.type = options[:type] if options[:type]
|
152
|
+
self.type = options[:exchange_type] if options[:exchange_type]
|
153
|
+
end
|
154
|
+
|
155
|
+
# Set exchange type
|
156
|
+
# @param [Integer,String] value
|
157
|
+
# @return [Integer]
|
158
|
+
def exchange_type=(value)
|
159
|
+
type = case value
|
160
|
+
when Integer
|
161
|
+
value
|
162
|
+
else
|
163
|
+
c = self.class.constants.grep(/TYPE_#{value}/).first
|
164
|
+
c ? self.class.const_get(c) : nil
|
165
|
+
end
|
166
|
+
raise ArgumentError, "unknown exchange type #{value.inspect}" unless type
|
167
|
+
self[:exchange_type].value = type
|
168
|
+
end
|
169
|
+
alias type exchange_type
|
170
|
+
alias type= exchange_type=
|
171
|
+
|
172
|
+
# Get exchange type name
|
173
|
+
# @return [String
|
174
|
+
def human_exchange_type
|
175
|
+
name = self.class.constants.grep(/TYPE_/).
|
176
|
+
select { |c| self.class.const_get(c) == type }.
|
177
|
+
first || "type #{type}"
|
178
|
+
name.to_s.sub(/TYPE_/, '')
|
179
|
+
end
|
180
|
+
alias human_type human_exchange_type
|
181
|
+
|
182
|
+
# Calculate length field
|
183
|
+
# @return [Integer]
|
184
|
+
def calc_length
|
185
|
+
self[:length].value = self.sz
|
186
|
+
end
|
187
|
+
|
188
|
+
# IKE payloads
|
189
|
+
# @return [Array<Payload>]
|
190
|
+
def payloads
|
191
|
+
payloads = []
|
192
|
+
body = self.body
|
193
|
+
while body.is_a?(Payload) do
|
194
|
+
payloads << body
|
195
|
+
body = body.body
|
196
|
+
end
|
197
|
+
payloads
|
198
|
+
end
|
199
|
+
|
200
|
+
# @return [String]
|
201
|
+
def inspect
|
202
|
+
str = Inspect.dashed_line(self.class, 2)
|
203
|
+
to_h.each do |attr, value|
|
204
|
+
next if attr == :body
|
205
|
+
case attr
|
206
|
+
when :flags
|
207
|
+
str_flags = ''
|
208
|
+
%w(r v i).each do |flag|
|
209
|
+
str_flags << (send("flag_#{flag}?") ? flag.upcase : '.')
|
210
|
+
end
|
211
|
+
str << Inspect.shift_level(2)
|
212
|
+
str << Inspect::FMT_ATTR % [value.class.to_s.sub(/.*::/, ''), attr,
|
213
|
+
str_flags]
|
214
|
+
when :exchange_type
|
215
|
+
str << Inspect.shift_level(2)
|
216
|
+
str << Inspect::FMT_ATTR % [value.class.to_s.sub(/.*::/, ''), attr,
|
217
|
+
human_exchange_type]
|
218
|
+
else
|
219
|
+
str << Inspect.inspect_attribute(attr, value, 2)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
str
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
self.add_class IKE
|
227
|
+
self.add_class NonESPMarker
|
228
|
+
|
229
|
+
UDP.bind_header IKE, dport: IKE::UDP_PORT1, sport: IKE::UDP_PORT1
|
230
|
+
UDP.bind_header NonESPMarker, dport: IKE::UDP_PORT2, sport: IKE::UDP_PORT2
|
231
|
+
NonESPMarker.bind_header IKE
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
require_relative 'ike/payload'
|
@@ -0,0 +1,197 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module PacketGen
|
3
|
+
module Header
|
4
|
+
class IKE
|
5
|
+
|
6
|
+
# This class handles Authentication payloads.
|
7
|
+
#
|
8
|
+
# A AUTH payload consists of the IKE generic payload header (see {Payload})
|
9
|
+
# and some specific fields:
|
10
|
+
# 1 2 3
|
11
|
+
# 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
|
12
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
13
|
+
# | Next Payload |C| RESERVED | Payload Length |
|
14
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
15
|
+
# | Auth Method | RESERVED |
|
16
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
17
|
+
# | |
|
18
|
+
# ~ Authentication Data ~
|
19
|
+
# | |
|
20
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
21
|
+
# These specific fields are:
|
22
|
+
# * {#type} (ID type),
|
23
|
+
# * {#reserved},
|
24
|
+
# * and {#content} (Identification Data).
|
25
|
+
#
|
26
|
+
# == Create a KE payload
|
27
|
+
# # create a IKE packet with a Auth payload
|
28
|
+
# pkt = PacketGen.gen('IP').add('UDP').add('IKE').add('IKE::Auth', method: 'SHARED_KEY')
|
29
|
+
# pkt.calc_length
|
30
|
+
# @author Sylvain Daubert
|
31
|
+
class Auth < Payload
|
32
|
+
|
33
|
+
# Payload type number
|
34
|
+
PAYLOAD_TYPE = 39
|
35
|
+
|
36
|
+
METHOD_RSA_SIGNATURE = 1
|
37
|
+
METHOD_SHARED_KEY = 2
|
38
|
+
METHOD_DSA_SIGNATURE = 3
|
39
|
+
METHOD_ECDSA256 = 9
|
40
|
+
METHOD_ECDSA384 = 10
|
41
|
+
METHOD_ECDSA512 = 11
|
42
|
+
METHOD_PASSWORD = 12
|
43
|
+
METHOD_NULL = 13
|
44
|
+
METHOD_DIGITAL_SIGNATURE = 14
|
45
|
+
|
46
|
+
# @attribute :u32
|
47
|
+
# 32-bit word including ID Type and RESERVED fields
|
48
|
+
# @return [Integer]
|
49
|
+
define_field_before :content, :u32, Types::Int32
|
50
|
+
# @attribute [r] method
|
51
|
+
# 8-bit Auth Method
|
52
|
+
# @return [Integer]
|
53
|
+
# @attribute reserved
|
54
|
+
# 24-bit reserved field
|
55
|
+
# @return [Integer]
|
56
|
+
define_bit_fields_on :u32, :method, 8, :reserved, 24
|
57
|
+
|
58
|
+
# Check authentication (see RFC 7296 §2.15)
|
59
|
+
# @param [Packet] init_msg first IKE message sent by peer
|
60
|
+
# @param [String] nonce my nonce, sent in first message
|
61
|
+
# @param [String] sk_p secret key used to compute prf(SK_px, IDx')
|
62
|
+
# @param [Integer] prf PRF type to use (see {Transform}+::PRF_*+ constants)
|
63
|
+
# @param [String] shared_secret shared secret to use as PSK (shared secret
|
64
|
+
# method only)
|
65
|
+
# @param [OpenSSL::X509::Certificate] cert certificate to check AUTH signature,
|
66
|
+
# if not embedded in IKE message
|
67
|
+
# @return [Boolean]
|
68
|
+
# @note For now, only NULL, SHARED_KEY and RSA, DSA and ECDSA signatures are
|
69
|
+
# supported.
|
70
|
+
# @note For certificates, only check AUTH authenticity with given (or guessed
|
71
|
+
# from packet) certificate, but certificate chain is not verified.
|
72
|
+
def check?(init_msg: nil, nonce: '', sk_p: '', prf: 1, shared_secret: '',
|
73
|
+
cert: nil)
|
74
|
+
raise TypeError, 'init_msg should be a Packet' unless init_msg.is_a?(Packet)
|
75
|
+
signed_octets = init_msg.ike.to_s
|
76
|
+
signed_octets << nonce
|
77
|
+
id = packet.ike.flag_i? ? packet.ike_idi : packet.ike_idr
|
78
|
+
signed_octets << prf(prf, sk_p, id.to_s[4, id.length - 4])
|
79
|
+
|
80
|
+
case method
|
81
|
+
when METHOD_SHARED_KEY
|
82
|
+
auth = prf(prf(shared_secret, 'Key Pad for IKEv2'), signed_octets)
|
83
|
+
auth == content
|
84
|
+
when METHOD_RSA_SIGNATURE, METHOD_ECDSA256, METHOD_ECDSA384, METHOD_ECDSA512
|
85
|
+
if packet.ike_cert
|
86
|
+
# FIXME: Expect a ENCODING_X509_CERT_SIG
|
87
|
+
# Others types not supported for now...
|
88
|
+
cert = OpenSSL::X509::Certificate.new(packet.ike_cert.content)
|
89
|
+
elsif cert.nil?
|
90
|
+
raise CryptoError, 'a certificate should be provided'
|
91
|
+
end
|
92
|
+
|
93
|
+
text = cert.to_text
|
94
|
+
m = text.match(/Public Key Algorithm: ([a-zA-Z0-9-]+)/)
|
95
|
+
digest = case m[1]
|
96
|
+
when 'id-ecPublicKey'
|
97
|
+
m2 = text.match(/Public-Key: \((\d+) bit\)/)
|
98
|
+
case m2[1]
|
99
|
+
when '256'
|
100
|
+
OpenSSL::Digest::SHA256.new
|
101
|
+
when '384'
|
102
|
+
OpenSSL::Digest::SHA384.new
|
103
|
+
when '521'
|
104
|
+
OpenSSL::Digest::SHA512.new
|
105
|
+
end
|
106
|
+
when /sha([235]\d+)/
|
107
|
+
OpenSSL::Digest.const_get("SHA#{$1}").new
|
108
|
+
when /sha1/, 'rsaEncryption'
|
109
|
+
OpenSSL::Digest::SHA1.new
|
110
|
+
end
|
111
|
+
signature = format_signature(cert.public_key, content.to_s)
|
112
|
+
cert.public_key.verify(digest, signature, signed_octets)
|
113
|
+
when METHOD_NULL
|
114
|
+
true
|
115
|
+
else
|
116
|
+
raise NotImplementedError, "unsupported method #{human_method}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Set Auth method
|
121
|
+
# @param [Integer,String] value
|
122
|
+
# @return [Integer]
|
123
|
+
def method=(value)
|
124
|
+
method = case value
|
125
|
+
when Integer
|
126
|
+
value
|
127
|
+
else
|
128
|
+
c = self.class.constants.grep(/METHOD_#{value}/).first
|
129
|
+
c ? self.class.const_get(c) : nil
|
130
|
+
end
|
131
|
+
raise ArgumentError, "unknown auth method #{value.inspect}" unless method
|
132
|
+
self[:u32].value = (self[:u32].to_i & 0xffffff) | (method << 24)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Get authentication method name
|
136
|
+
# @return [String]
|
137
|
+
def human_method
|
138
|
+
name = self.class.constants.grep(/METHOD_/).
|
139
|
+
select { |c| self.class.const_get(c) == method }.
|
140
|
+
first || "method #{method}"
|
141
|
+
name.to_s.sub(/METHOD_/, '')
|
142
|
+
end
|
143
|
+
|
144
|
+
# @return [String]
|
145
|
+
def inspect
|
146
|
+
str = Inspect.dashed_line(self.class, 2)
|
147
|
+
fields.each do |attr|
|
148
|
+
case attr
|
149
|
+
when :body
|
150
|
+
next
|
151
|
+
when :u32
|
152
|
+
str << Inspect.shift_level(2)
|
153
|
+
str << Inspect::FMT_ATTR % ['Int8', :method, human_method]
|
154
|
+
str << Inspect.inspect_attribute(:reserved, self.reserved, 2)
|
155
|
+
else
|
156
|
+
str << Inspect.inspect_attribute(attr, self[attr], 2)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
str
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def prf(type, key, msg)
|
165
|
+
case type
|
166
|
+
when Transform::PRF_HMAC_MD5, Transform::PRF_HMAC_SHA1,
|
167
|
+
Transform::PRF_HMAC_SHA2_256, Transform::PRF_HMAC_SHA2_384,
|
168
|
+
Transform::PRF_HMAC_SHA2_512
|
169
|
+
digestname = Transform.constants.grep(/PRF_/).
|
170
|
+
select { |c| Transform.const_get(c) == type }.first.
|
171
|
+
to_s.sub(/^PRF_HMAC_/, '').sub(/2_/, '')
|
172
|
+
digest = OpenSSL::Digest.const_get(digestname).new
|
173
|
+
else
|
174
|
+
raise NotImplementedError, 'for now, only HMAC-based PRF are supported'
|
175
|
+
end
|
176
|
+
hmac = OpenSSL::HMAC.new(key, digest)
|
177
|
+
hmac << msg
|
178
|
+
hmac.digest
|
179
|
+
end
|
180
|
+
|
181
|
+
def format_signature(pkey, sig)
|
182
|
+
if pkey.is_a?(OpenSSL::PKey::EC)
|
183
|
+
# PKey::EC need a signature as a DER string representing a sequence of
|
184
|
+
# 2 integers: r and s
|
185
|
+
r = OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(sig[0, sig.size / 2], 2).to_i)
|
186
|
+
s = OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(sig[sig.size / 2,
|
187
|
+
sig.size / 2], 2).to_i)
|
188
|
+
OpenSSL::ASN1::Sequence.new([r, s]).to_der
|
189
|
+
else
|
190
|
+
sig
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|