packetgen 2.8.7 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +0 -1
- data/README.md +5 -4
- data/lib/packetgen.rb +6 -12
- data/lib/packetgen/capture.rb +43 -39
- data/lib/packetgen/config.rb +0 -1
- data/lib/packetgen/deprecation.rb +1 -1
- data/lib/packetgen/header.rb +9 -9
- data/lib/packetgen/header/asn1_base.rb +10 -10
- data/lib/packetgen/header/base.rb +42 -101
- data/lib/packetgen/header/dhcp/option.rb +5 -11
- data/lib/packetgen/header/dhcpv6/duid.rb +2 -0
- data/lib/packetgen/header/dhcpv6/option.rb +2 -19
- data/lib/packetgen/header/dhcpv6/options.rb +7 -0
- data/lib/packetgen/header/dns.rb +5 -23
- data/lib/packetgen/header/dns/name.rb +1 -0
- data/lib/packetgen/header/dns/qdsection.rb +1 -0
- data/lib/packetgen/header/dns/question.rb +3 -7
- data/lib/packetgen/header/dns/rr.rb +3 -0
- data/lib/packetgen/header/dns/rrsection.rb +1 -0
- data/lib/packetgen/header/dot11.rb +1 -17
- data/lib/packetgen/header/dot1x.rb +1 -0
- data/lib/packetgen/header/eap.rb +4 -7
- data/lib/packetgen/header/eth.rb +2 -0
- data/lib/packetgen/header/http/headers.rb +3 -0
- data/lib/packetgen/header/http/request.rb +5 -4
- data/lib/packetgen/header/http/response.rb +5 -4
- data/lib/packetgen/header/icmp.rb +6 -0
- data/lib/packetgen/header/icmpv6.rb +6 -0
- data/lib/packetgen/header/igmpv3/mq.rb +2 -0
- data/lib/packetgen/header/ip.rb +32 -30
- data/lib/packetgen/header/ip/addr.rb +1 -0
- data/lib/packetgen/header/ip/option.rb +23 -20
- data/lib/packetgen/header/ip/options.rb +11 -24
- data/lib/packetgen/header/ipv6.rb +45 -34
- data/lib/packetgen/header/ipv6/addr.rb +2 -0
- data/lib/packetgen/header/ipv6/hop_by_hop.rb +7 -31
- data/lib/packetgen/header/mdns.rb +1 -0
- data/lib/packetgen/header/mldv2/mlq.rb +2 -0
- data/lib/packetgen/header/ospfv2/lsa.rb +15 -25
- data/lib/packetgen/header/ospfv3/ipv6_prefix.rb +1 -1
- data/lib/packetgen/header/ospfv3/lsa.rb +8 -25
- data/lib/packetgen/header/snmp.rb +2 -0
- data/lib/packetgen/header/tcp.rb +23 -2
- data/lib/packetgen/header/tcp/option.rb +51 -52
- data/lib/packetgen/header/tcp/options.rb +17 -52
- data/lib/packetgen/header/tftp.rb +3 -0
- data/lib/packetgen/header/udp.rb +8 -0
- data/lib/packetgen/packet.rb +119 -102
- data/lib/packetgen/pcapng/block.rb +4 -10
- data/lib/packetgen/pcapng/epb.rb +4 -4
- data/lib/packetgen/pcapng/file.rb +7 -3
- data/lib/packetgen/pcapng/idb.rb +2 -2
- data/lib/packetgen/pcapng/shb.rb +3 -3
- data/lib/packetgen/pcapng/spb.rb +1 -8
- data/lib/packetgen/pcapng/unknown_block.rb +0 -7
- data/lib/packetgen/types.rb +1 -0
- data/lib/packetgen/types/array.rb +73 -71
- data/lib/packetgen/types/cstring.rb +1 -1
- data/lib/packetgen/types/enum.rb +3 -3
- data/lib/packetgen/types/fields.rb +66 -106
- data/lib/packetgen/types/int.rb +9 -5
- data/lib/packetgen/types/length_from.rb +45 -0
- data/lib/packetgen/types/oui.rb +2 -0
- data/lib/packetgen/types/string.rb +10 -16
- data/lib/packetgen/types/tlv.rb +7 -15
- data/lib/packetgen/utils.rb +8 -8
- data/lib/packetgen/utils/arp_spoofer.rb +1 -2
- data/lib/packetgen/version.rb +1 -1
- metadata +3 -21
- data/lib/packetgen/header/crypto.rb +0 -62
- data/lib/packetgen/header/esp.rb +0 -413
- data/lib/packetgen/header/ike.rb +0 -243
- data/lib/packetgen/header/ike/auth.rb +0 -165
- data/lib/packetgen/header/ike/cert.rb +0 -76
- data/lib/packetgen/header/ike/certreq.rb +0 -66
- data/lib/packetgen/header/ike/id.rb +0 -99
- data/lib/packetgen/header/ike/ke.rb +0 -79
- data/lib/packetgen/header/ike/nonce.rb +0 -40
- data/lib/packetgen/header/ike/notify.rb +0 -176
- data/lib/packetgen/header/ike/payload.rb +0 -315
- data/lib/packetgen/header/ike/sa.rb +0 -561
- data/lib/packetgen/header/ike/sk.rb +0 -261
- data/lib/packetgen/header/ike/ts.rb +0 -270
- data/lib/packetgen/header/ike/vendor_id.rb +0 -39
- data/lib/packetgen/header/netbios.rb +0 -20
- data/lib/packetgen/header/netbios/datagram.rb +0 -105
- data/lib/packetgen/header/netbios/name.rb +0 -67
- data/lib/packetgen/header/netbios/session.rb +0 -64
data/lib/packetgen/types/int.rb
CHANGED
@@ -38,23 +38,26 @@ module PacketGen
|
|
38
38
|
|
39
39
|
# @abstract
|
40
40
|
# Read an Int from a binary string or an integer
|
41
|
-
# @param [Integer,
|
41
|
+
# @param [Integer, #to_s] value
|
42
42
|
# @return [self]
|
43
|
+
# @raise [ParseError] when reading +#to_s+ objects with abstract Int class.
|
43
44
|
def read(value)
|
44
45
|
@value = if value.is_a?(Integer)
|
45
46
|
value.to_i
|
46
|
-
|
47
|
+
elsif defined? @packstr
|
47
48
|
value.to_s.unpack(@packstr[@endian]).first
|
49
|
+
else
|
50
|
+
raise ParseError, 'Int#read is abstract and cannot read'
|
48
51
|
end
|
49
52
|
self
|
50
53
|
end
|
51
54
|
|
52
55
|
# @abstract
|
53
56
|
# @return [::String]
|
54
|
-
# @raise [
|
57
|
+
# @raise [ParseError] This is an abstrat method and must be redefined
|
55
58
|
def to_s
|
56
59
|
unless defined? @packstr
|
57
|
-
raise
|
60
|
+
raise ParseError, 'PacketGen::Types::Int#to_s is an abstract method'
|
58
61
|
end
|
59
62
|
|
60
63
|
[to_i].pack(@packstr[@endian])
|
@@ -66,6 +69,7 @@ module PacketGen
|
|
66
69
|
@value || @default
|
67
70
|
end
|
68
71
|
alias to_human to_i
|
72
|
+
alias from_human value=
|
69
73
|
|
70
74
|
# Convert Int to Float
|
71
75
|
# @return [Float]
|
@@ -76,7 +80,7 @@ module PacketGen
|
|
76
80
|
# Give size in bytes of self
|
77
81
|
# @return [Integer]
|
78
82
|
def sz
|
79
|
-
|
83
|
+
width
|
80
84
|
end
|
81
85
|
end
|
82
86
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# This file is part of PacketGen
|
3
|
+
# See https://github.com/sdaubert/packetgen for more informations
|
4
|
+
# Copyright (C) 2016 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 Types
|
11
|
+
# This module is a mixin adding +length_from+ capacity to a type.
|
12
|
+
# +length_from+ capacity is the capacity, for a type, to gets its
|
13
|
+
# length from another object.
|
14
|
+
# @author Sylvain Daubert
|
15
|
+
# @since 3.0.0
|
16
|
+
module LengthFrom
|
17
|
+
# Initialize +length from+ capacity.
|
18
|
+
# Should be call by extensed object's initialize.
|
19
|
+
# @param [Hash] options
|
20
|
+
# @option options [Types::Int,Proc] :length_from object or proc from which
|
21
|
+
# takes length when reading
|
22
|
+
# @return [void]
|
23
|
+
def initialize_length_from(options)
|
24
|
+
@length_from = options[:length_from]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return a substring from +str+ of length given in another object.
|
28
|
+
# @param [#to_s] str
|
29
|
+
# @return [String]
|
30
|
+
def read_with_length_from(str)
|
31
|
+
s = PacketGen.force_binary(str.to_s)
|
32
|
+
str_end = case @length_from
|
33
|
+
when Types::Int
|
34
|
+
@length_from.to_i
|
35
|
+
when Proc
|
36
|
+
@length_from.call
|
37
|
+
else
|
38
|
+
s.size
|
39
|
+
end
|
40
|
+
str_end = 0 if str_end < 0
|
41
|
+
s[0, str_end]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/packetgen/types/oui.rb
CHANGED
@@ -29,8 +29,10 @@ module PacketGen
|
|
29
29
|
# @return [OUI] self
|
30
30
|
def from_human(str)
|
31
31
|
return self if str.nil?
|
32
|
+
|
32
33
|
bytes = str.split(/:/)
|
33
34
|
raise ArgumentError, 'not a OUI' unless bytes.size == 3
|
35
|
+
|
34
36
|
self[:b2].read(bytes[0].to_i(16))
|
35
37
|
self[:b1].read(bytes[1].to_i(16))
|
36
38
|
self[:b0].read(bytes[2].to_i(16))
|
@@ -12,39 +12,33 @@ module PacketGen
|
|
12
12
|
# to be compatible with others {Types}.
|
13
13
|
# @author Sylvain Daubert
|
14
14
|
class String < ::String
|
15
|
+
include LengthFrom
|
16
|
+
|
17
|
+
# @return [Integer]
|
18
|
+
attr_reader :static_length
|
19
|
+
|
15
20
|
# @param [Hash] options
|
16
21
|
# @option options [Types::Int,Proc] :length_from object or proc from which
|
17
22
|
# takes length when reading
|
18
23
|
# @option options [Integer] :static_length set a static length for this string
|
19
24
|
def initialize(options={})
|
20
25
|
super()
|
21
|
-
|
26
|
+
initialize_length_from(options)
|
22
27
|
@static_length = options[:static_length]
|
23
28
|
end
|
24
29
|
|
25
30
|
# @param [::String] str
|
26
31
|
# @return [String] self
|
27
32
|
def read(str)
|
28
|
-
s = str
|
29
|
-
|
30
|
-
|
31
|
-
@length_from.to_i
|
32
|
-
when Proc
|
33
|
-
@length_from.call
|
34
|
-
else
|
35
|
-
if @static_length.is_a? Integer
|
36
|
-
@static_length
|
37
|
-
else
|
38
|
-
s.size
|
39
|
-
end
|
40
|
-
end
|
41
|
-
str_end = 0 if str_end < 0
|
42
|
-
self.replace(s[0, str_end])
|
33
|
+
s = read_with_length_from(str)
|
34
|
+
s = s[0, static_length] if static_length
|
35
|
+
self.replace(s)
|
43
36
|
self
|
44
37
|
end
|
45
38
|
|
46
39
|
alias sz length
|
47
40
|
alias to_human to_s
|
41
|
+
alias from_human read
|
48
42
|
end
|
49
43
|
end
|
50
44
|
end
|
data/lib/packetgen/types/tlv.rb
CHANGED
@@ -75,6 +75,8 @@ module PacketGen
|
|
75
75
|
# @private
|
76
76
|
alias old_type= type=
|
77
77
|
|
78
|
+
undef type=, value=, value
|
79
|
+
|
78
80
|
# Set type
|
79
81
|
# @param [::String,Integer] val
|
80
82
|
# @return [Integer]
|
@@ -86,8 +88,10 @@ module PacketGen
|
|
86
88
|
self.old_type = val
|
87
89
|
else
|
88
90
|
raise TypeError, 'need an Integer' unless human_types?
|
91
|
+
|
89
92
|
new_val = self.class::TYPES.key(val.to_s)
|
90
93
|
raise ArgumentError, "unknown #{val} type" if new_val.nil?
|
94
|
+
|
91
95
|
self.old_type = new_val
|
92
96
|
end
|
93
97
|
end
|
@@ -96,27 +100,15 @@ module PacketGen
|
|
96
100
|
# @param [::String,Integer] val
|
97
101
|
# @return [::String,Integer]
|
98
102
|
def value=(val)
|
99
|
-
|
100
|
-
|
101
|
-
elsif self[:value].is_a? Types::Int
|
102
|
-
self[:value].value = val
|
103
|
-
else
|
104
|
-
self.length = val.length if val.is_a? ::String
|
105
|
-
self[:value].read val
|
106
|
-
end
|
103
|
+
self[:value].from_human val
|
104
|
+
self.length = self[:value].sz
|
107
105
|
val
|
108
106
|
end
|
109
107
|
|
110
108
|
# Get +value+
|
111
109
|
# @return [Object] depend on +value+ type
|
112
110
|
def value
|
113
|
-
|
114
|
-
self[:value].to_human
|
115
|
-
elsif self[:value].is_a? Types::Int
|
116
|
-
self[:value].to_i
|
117
|
-
else
|
118
|
-
self[:value]
|
119
|
-
end
|
111
|
+
self[:value].to_human
|
120
112
|
end
|
121
113
|
|
122
114
|
# Return human readable type, if TYPES is defined
|
data/lib/packetgen/utils.rb
CHANGED
@@ -42,6 +42,7 @@ module PacketGen
|
|
42
42
|
# @option options [Integer] :timeout timeout in seconds before stopping
|
43
43
|
# request. Default to 2.
|
44
44
|
# @return [String,nil]
|
45
|
+
# @raise [RuntimeError] user don't have permission to capture packets on network device.
|
45
46
|
def self.arp(ipaddr, options={})
|
46
47
|
unless options[:no_cache]
|
47
48
|
local_cache = self.arp_cache
|
@@ -65,6 +66,7 @@ module PacketGen
|
|
65
66
|
cap_thread.join
|
66
67
|
|
67
68
|
return if capture.packets.empty?
|
69
|
+
|
68
70
|
capture.packets.each do |pkt|
|
69
71
|
break pkt.arp.sha.to_s if pkt.arp.spa.to_s == ipaddr
|
70
72
|
end
|
@@ -85,6 +87,7 @@ module PacketGen
|
|
85
87
|
# @option options [String] :iface interface to use. Default to
|
86
88
|
# {PacketGen.default_iface}
|
87
89
|
# @return [void]
|
90
|
+
# @raise [RuntimeError] user don't have permission to capture packets on network device.
|
88
91
|
def self.arp_spoof(target_ip, spoofed_ip, options={})
|
89
92
|
interval = options[:interval] || 1.0
|
90
93
|
as = ARPSpoofer.new(timeout: options[:for_seconds], interval: interval,
|
@@ -119,6 +122,7 @@ module PacketGen
|
|
119
122
|
# pkt
|
120
123
|
# end
|
121
124
|
# @since 2.2.0
|
125
|
+
# @raise [RuntimeError] user don't have permission to capture packets on network device.
|
122
126
|
def self.mitm(target1, target2, options={})
|
123
127
|
options = { iface: PacketGen.default_iface }.merge(options)
|
124
128
|
|
@@ -144,14 +148,10 @@ module PacketGen
|
|
144
148
|
iph = modified_pkt.ip
|
145
149
|
l2 = modified_pkt.is?('Dot11') ? modified_pkt.dot11 : modified_pkt.eth
|
146
150
|
|
147
|
-
if (iph.
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
l2.dst = mac1
|
152
|
-
else
|
153
|
-
next
|
154
|
-
end
|
151
|
+
if (iph.src == target1) || (iph.dst == target2)
|
152
|
+
l2.dst = mac2
|
153
|
+
elsif (iph.src == target2) || (iph.dst == target1)
|
154
|
+
l2.dst = mac1
|
155
155
|
else
|
156
156
|
next
|
157
157
|
end
|
@@ -5,8 +5,6 @@
|
|
5
5
|
|
6
6
|
# frozen_string_literal: true
|
7
7
|
|
8
|
-
require 'thread'
|
9
|
-
|
10
8
|
module PacketGen
|
11
9
|
module Utils
|
12
10
|
# @note This class is provided for test purpose.
|
@@ -117,6 +115,7 @@ module PacketGen
|
|
117
115
|
# @return [Boolean,nil]
|
118
116
|
def active?(target_ip)
|
119
117
|
return unless @targets.key?(target_ip)
|
118
|
+
|
120
119
|
@targets[target_ip][:active]
|
121
120
|
end
|
122
121
|
|
data/lib/packetgen/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: packetgen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sylvain Daubert
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-10-
|
11
|
+
date: 2018-10-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: interfacez
|
@@ -153,7 +153,6 @@ files:
|
|
153
153
|
- lib/packetgen/header/asn1_base.rb
|
154
154
|
- lib/packetgen/header/base.rb
|
155
155
|
- lib/packetgen/header/bootp.rb
|
156
|
-
- lib/packetgen/header/crypto.rb
|
157
156
|
- lib/packetgen/header/dhcp.rb
|
158
157
|
- lib/packetgen/header/dhcp/option.rb
|
159
158
|
- lib/packetgen/header/dhcp/options.rb
|
@@ -183,7 +182,6 @@ files:
|
|
183
182
|
- lib/packetgen/header/eap/md5.rb
|
184
183
|
- lib/packetgen/header/eap/tls.rb
|
185
184
|
- lib/packetgen/header/eap/ttls.rb
|
186
|
-
- lib/packetgen/header/esp.rb
|
187
185
|
- lib/packetgen/header/eth.rb
|
188
186
|
- lib/packetgen/header/gre.rb
|
189
187
|
- lib/packetgen/header/http.rb
|
@@ -198,19 +196,6 @@ files:
|
|
198
196
|
- lib/packetgen/header/igmpv3/group_record.rb
|
199
197
|
- lib/packetgen/header/igmpv3/mq.rb
|
200
198
|
- lib/packetgen/header/igmpv3/mr.rb
|
201
|
-
- lib/packetgen/header/ike.rb
|
202
|
-
- lib/packetgen/header/ike/auth.rb
|
203
|
-
- lib/packetgen/header/ike/cert.rb
|
204
|
-
- lib/packetgen/header/ike/certreq.rb
|
205
|
-
- lib/packetgen/header/ike/id.rb
|
206
|
-
- lib/packetgen/header/ike/ke.rb
|
207
|
-
- lib/packetgen/header/ike/nonce.rb
|
208
|
-
- lib/packetgen/header/ike/notify.rb
|
209
|
-
- lib/packetgen/header/ike/payload.rb
|
210
|
-
- lib/packetgen/header/ike/sa.rb
|
211
|
-
- lib/packetgen/header/ike/sk.rb
|
212
|
-
- lib/packetgen/header/ike/ts.rb
|
213
|
-
- lib/packetgen/header/ike/vendor_id.rb
|
214
199
|
- lib/packetgen/header/ip.rb
|
215
200
|
- lib/packetgen/header/ip/addr.rb
|
216
201
|
- lib/packetgen/header/ip/option.rb
|
@@ -226,10 +211,6 @@ files:
|
|
226
211
|
- lib/packetgen/header/mldv2/mcast_address_record.rb
|
227
212
|
- lib/packetgen/header/mldv2/mlq.rb
|
228
213
|
- lib/packetgen/header/mldv2/mlr.rb
|
229
|
-
- lib/packetgen/header/netbios.rb
|
230
|
-
- lib/packetgen/header/netbios/datagram.rb
|
231
|
-
- lib/packetgen/header/netbios/name.rb
|
232
|
-
- lib/packetgen/header/netbios/session.rb
|
233
214
|
- lib/packetgen/header/ospfv2.rb
|
234
215
|
- lib/packetgen/header/ospfv2/db_description.rb
|
235
216
|
- lib/packetgen/header/ospfv2/hello.rb
|
@@ -271,6 +252,7 @@ files:
|
|
271
252
|
- lib/packetgen/types/fields.rb
|
272
253
|
- lib/packetgen/types/int.rb
|
273
254
|
- lib/packetgen/types/int_string.rb
|
255
|
+
- lib/packetgen/types/length_from.rb
|
274
256
|
- lib/packetgen/types/oui.rb
|
275
257
|
- lib/packetgen/types/string.rb
|
276
258
|
- lib/packetgen/types/tlv.rb
|
@@ -1,62 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module PacketGen
|
4
|
-
module Header
|
5
|
-
# Mixin for cryptographic classes
|
6
|
-
# @api private
|
7
|
-
# @author Sylvain Daubert
|
8
|
-
module Crypto
|
9
|
-
# Cryptographic error
|
10
|
-
class Error < PacketGen::Error; end
|
11
|
-
|
12
|
-
# Register cryptographic modes
|
13
|
-
# @param [OpenSSL::Cipher] conf
|
14
|
-
# @param [OpenSSL::HMAC] intg
|
15
|
-
# @return [void]
|
16
|
-
def set_crypto(conf, intg)
|
17
|
-
@conf = conf
|
18
|
-
@intg = intg
|
19
|
-
return unless conf.authenticated?
|
20
|
-
# #auth_tag_len only supported from ruby 2.4.0
|
21
|
-
@conf.auth_tag_len = @trunc if @conf.respond_to? :auth_tag_len
|
22
|
-
end
|
23
|
-
|
24
|
-
# Get confidentiality mode name
|
25
|
-
# @return [String]
|
26
|
-
def confidentiality_mode
|
27
|
-
mode = @conf.name.match(/-([^-]*)$/)[1]
|
28
|
-
raise Error, 'unknown cipher mode' if mode.nil?
|
29
|
-
mode.downcase
|
30
|
-
end
|
31
|
-
|
32
|
-
# Say if crypto modes permit authentication
|
33
|
-
# @return [Boolean]
|
34
|
-
def authenticated?
|
35
|
-
@conf.authenticated? || !@intg.nil?
|
36
|
-
end
|
37
|
-
|
38
|
-
def authenticate!
|
39
|
-
@conf.final
|
40
|
-
if @intg
|
41
|
-
@intg.update @esn.to_s if @esn
|
42
|
-
@intg.digest[0, @icv_length] == @icv
|
43
|
-
else
|
44
|
-
true
|
45
|
-
end
|
46
|
-
rescue OpenSSL::Cipher::CipherError
|
47
|
-
false
|
48
|
-
end
|
49
|
-
|
50
|
-
def encipher(data)
|
51
|
-
enciphered_data = @conf.update(data)
|
52
|
-
@intg.update(enciphered_data) if @intg
|
53
|
-
enciphered_data
|
54
|
-
end
|
55
|
-
|
56
|
-
def decipher(data)
|
57
|
-
@intg.update(data) if @intg
|
58
|
-
@conf.update(data)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
data/lib/packetgen/header/esp.rb
DELETED
@@ -1,413 +0,0 @@
|
|
1
|
-
# This file is part of PacketGen
|
2
|
-
# See https://github.com/sdaubert/packetgen for more informations
|
3
|
-
# Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
|
4
|
-
# This program is published under MIT license.
|
5
|
-
|
6
|
-
# frozen_string_literal: true
|
7
|
-
|
8
|
-
module PacketGen
|
9
|
-
module Header
|
10
|
-
# A ESP header consists of:
|
11
|
-
# * a Security Parameters Index (#{spi}, {Types::Int32} type),
|
12
|
-
# * a Sequence Number ({#sn}, +Int32+ type),
|
13
|
-
# * a {#body} (variable length),
|
14
|
-
# * an optional TFC padding ({#tfc}, variable length),
|
15
|
-
# * an optional {#padding} (to align ESP on 32-bit boundary, variable length),
|
16
|
-
# * a {#pad_length} ({Types::Int8}),
|
17
|
-
# * a Next header field ({#next}, +Int8+),
|
18
|
-
# * and an optional Integrity Check Value ({#icv}, variable length).
|
19
|
-
#
|
20
|
-
# == Create an ESP header
|
21
|
-
# # standalone
|
22
|
-
# esp = PacketGen::Header::ESP.new
|
23
|
-
# # in a packet
|
24
|
-
# pkt = PacketGen.gen('IP').add('ESP')
|
25
|
-
# # access to ESP header
|
26
|
-
# pkt.esp # => PacketGen::Header::ESP
|
27
|
-
#
|
28
|
-
# == Examples
|
29
|
-
# === Create an enciphered UDP packet (ESP transport mode), using CBC mode
|
30
|
-
# icmp = PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.2.1').
|
31
|
-
# add('ESP', spi: 0xff456e01, sn: 12345678).
|
32
|
-
# add('UDP', dport: 4567, sport: 45362, body 'abcdef')
|
33
|
-
# cipher = OpenSSL::Cipher.new('aes-128-cbc')
|
34
|
-
# cipher.encrypt
|
35
|
-
# cipher.key = 16bytes_key
|
36
|
-
# iv = 16bytes_iv
|
37
|
-
# esp.esp.encrypt! cipher, iv
|
38
|
-
#
|
39
|
-
# === Create a ESP packet tunneling a UDP one, using GCM combined mode
|
40
|
-
# # create inner UDP packet
|
41
|
-
# icmp = PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.2.1').
|
42
|
-
# add('UDP', dport: 4567, sport: 45362, body 'abcdef')
|
43
|
-
#
|
44
|
-
# # create outer ESP packet
|
45
|
-
# esp = PacketGen.gen('IP', src '198.76.54.32', dst: '1.2.3.4').add('ESP')
|
46
|
-
# esp.esp.spi = 0x87654321
|
47
|
-
# esp.esp.sn = 0x123
|
48
|
-
# esp.esp.icv_length = 16
|
49
|
-
# # encapsulate ICMP packet in ESP one
|
50
|
-
# esp.encapsulate icmp
|
51
|
-
#
|
52
|
-
# # encrypt ESP payload
|
53
|
-
# cipher = OpenSSL::Cipher.new('aes-128-gcm')
|
54
|
-
# cipher.encrypt
|
55
|
-
# cipher.key = 16bytes_key
|
56
|
-
# iv = 8bytes_iv
|
57
|
-
# esp.esp.encrypt! cipher, iv, salt: 4bytes_gcm_salt
|
58
|
-
#
|
59
|
-
# === Decrypt a ESP packet using CBC mode and HMAC-SHA-256
|
60
|
-
# cipher = OpenSSL::Cipher.new('aes-128-cbc')
|
61
|
-
# cipher.decrypt
|
62
|
-
# cipher.key = 16bytes_key
|
63
|
-
#
|
64
|
-
# hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
|
65
|
-
#
|
66
|
-
# pkt.esp.decrypt! cipher, intmode: hmac # => true if ICV check OK
|
67
|
-
# @author Sylvain Daubert
|
68
|
-
# @since 1.2.0
|
69
|
-
class ESP < Base
|
70
|
-
include Crypto
|
71
|
-
|
72
|
-
# IP protocol number for ESP
|
73
|
-
IP_PROTOCOL = 50
|
74
|
-
|
75
|
-
# Well-known UDP port for ESP
|
76
|
-
UDP_PORT = 4500
|
77
|
-
|
78
|
-
# @!attribute spi
|
79
|
-
# 32-bit Security Parameter Index
|
80
|
-
# @return [Integer]
|
81
|
-
define_field :spi, Types::Int32
|
82
|
-
# @!attribute sn
|
83
|
-
# 32-bit Sequence Number
|
84
|
-
# @return [Integer]
|
85
|
-
define_field :sn, Types::Int32
|
86
|
-
# @!attribute body
|
87
|
-
# @return [Types::String,Header::Base]
|
88
|
-
define_field :body, Types::String
|
89
|
-
# @!attribute tfc
|
90
|
-
# Traffic Flow Confidentiality padding
|
91
|
-
# @return [Types::String,Header::Base]
|
92
|
-
define_field :tfc, Types::String
|
93
|
-
# @!attribute padding
|
94
|
-
# ESP padding
|
95
|
-
# @return [Types::String,Header::Base]
|
96
|
-
define_field :padding, Types::String
|
97
|
-
# @!attribute pad_length
|
98
|
-
# 8-bit padding length
|
99
|
-
# @return [Integer]
|
100
|
-
define_field :pad_length, Types::Int8
|
101
|
-
# @!attribute next
|
102
|
-
# 8-bit next protocol value
|
103
|
-
# @return [Integer]
|
104
|
-
define_field :next, Types::Int8
|
105
|
-
# @!attribute icv
|
106
|
-
# Integrity Check Value
|
107
|
-
# @return [Types::String,Header::Base]
|
108
|
-
define_field :icv, Types::String
|
109
|
-
|
110
|
-
# ICV (Integrity Check Value) length
|
111
|
-
# @return [Integer]
|
112
|
-
attr_accessor :icv_length
|
113
|
-
|
114
|
-
# @param [Hash] options
|
115
|
-
# @option options [Integer] :icv_length ICV length
|
116
|
-
# @option options [Integer] :spi Security Parameters Index
|
117
|
-
# @option options [Integer] :sn Sequence Number
|
118
|
-
# @option options [::String] :body ESP payload data
|
119
|
-
# @option options [::String] :tfc Traffic Flow Confidentiality, random padding
|
120
|
-
# up to MTU
|
121
|
-
# @option options [::String] :padding ESP padding to align ESP on 32-bit
|
122
|
-
# boundary
|
123
|
-
# @option options [Integer] :pad_length padding length
|
124
|
-
# @option options [Integer] :next Next Header field
|
125
|
-
# @option options [::String] :icv Integrity Check Value
|
126
|
-
def initialize(options={})
|
127
|
-
@icv_length = options[:icv_length] || 0
|
128
|
-
super
|
129
|
-
end
|
130
|
-
|
131
|
-
# Read a ESP packet from string.
|
132
|
-
#
|
133
|
-
# {#padding} and {#tfc} are not set as they are enciphered (impossible
|
134
|
-
# to guess their respective size). {#pad_length} and {#next} are also
|
135
|
-
# enciphered.
|
136
|
-
# @param [String] str
|
137
|
-
# @return [self]
|
138
|
-
def read(str)
|
139
|
-
return self if str.nil?
|
140
|
-
force_binary str
|
141
|
-
self[:spi].read str[0, 4]
|
142
|
-
self[:sn].read str[4, 4]
|
143
|
-
self[:body].read str[8...-@icv_length - 2]
|
144
|
-
self[:tfc].read ''
|
145
|
-
self[:padding].read ''
|
146
|
-
self[:pad_length].read str[-@icv_length - 2, 1]
|
147
|
-
self[:next].read str[-@icv_length - 1, 1]
|
148
|
-
self[:icv].read str[-@icv_length, @icv_length] if @icv_length
|
149
|
-
self
|
150
|
-
end
|
151
|
-
|
152
|
-
# Encrypt in-place ESP payload and trailer.
|
153
|
-
#
|
154
|
-
# This method removes all data from +tfc+ and +padding+ fields, as their
|
155
|
-
# enciphered values are concatenated into +body+.
|
156
|
-
#
|
157
|
-
# It also removes headers under ESP from packet, as they are enciphered in
|
158
|
-
# ESP body, and then are no more accessible.
|
159
|
-
# @param [OpenSSL::Cipher] cipher keyed cipher.
|
160
|
-
# This cipher is confidentiality-only one, or AEAD one. To use a second
|
161
|
-
# cipher to add integrity, use +:intmode+ option.
|
162
|
-
# @param [String] iv full IV for encryption
|
163
|
-
# * CTR and GCM modes: +iv+ is 8-bytes long.
|
164
|
-
# @param [Hash] options
|
165
|
-
# @option options [String] :salt salt value for CTR and GCM modes
|
166
|
-
# @option options [Boolean] :tfc
|
167
|
-
# @option options [Fixnum] :tfc_size ESP body size used for TFC
|
168
|
-
# (default 1444, max size for a tunneled IPv4/ESP packet).
|
169
|
-
# This is the maximum size for ESP packet (without IP header
|
170
|
-
# nor Eth one).
|
171
|
-
# @option options [Fixnum] :esn 32 high-orber bits of ESN
|
172
|
-
# @option options [Fixnum] :pad_length set a padding length
|
173
|
-
# @option options [String] :padding set a padding. No check with
|
174
|
-
# +:pad_length+ is made. If +:pad_length+ is not set, +:padding+
|
175
|
-
# length is shortened to correct padding length
|
176
|
-
# @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
|
177
|
-
# confidentiality-only cipher. Only HMAC are supported.
|
178
|
-
# @return [self]
|
179
|
-
def encrypt!(cipher, iv, options={})
|
180
|
-
opt = { salt: '', tfc_size: 1444 }.merge(options)
|
181
|
-
|
182
|
-
set_crypto cipher, opt[:intmode]
|
183
|
-
|
184
|
-
real_iv = force_binary(opt[:salt]) + force_binary(iv)
|
185
|
-
real_iv += [1].pack('N') if confidentiality_mode == 'ctr'
|
186
|
-
cipher.iv = real_iv
|
187
|
-
|
188
|
-
authenticate_esp_header_if_needed options, iv
|
189
|
-
|
190
|
-
case confidentiality_mode
|
191
|
-
when 'cbc'
|
192
|
-
cipher_len = self.body.sz + 2
|
193
|
-
self.pad_length = (16 - (cipher_len % 16)) % 16
|
194
|
-
else
|
195
|
-
mod4 = to_s.size % 4
|
196
|
-
self.pad_length = 4 - mod4 if mod4 > 0
|
197
|
-
end
|
198
|
-
|
199
|
-
if opt[:pad_length]
|
200
|
-
self.pad_length = opt[:pad_length]
|
201
|
-
padding = force_binary(opt[:padding] || (1..self.pad_length).to_a.pack('C*'))
|
202
|
-
self[:padding].read padding
|
203
|
-
else
|
204
|
-
padding = force_binary(opt[:padding] || (1..self.pad_length).to_a.pack('C*'))
|
205
|
-
self[:padding].read padding[0...self.pad_length]
|
206
|
-
end
|
207
|
-
|
208
|
-
tfc = ''
|
209
|
-
if opt[:tfc]
|
210
|
-
tfc_size = opt[:tfc_size] - body.sz
|
211
|
-
if tfc_size > 0
|
212
|
-
tfc_size = case confidentiality_mode
|
213
|
-
when 'cbc'
|
214
|
-
(tfc_size / 16) * 16
|
215
|
-
else
|
216
|
-
(tfc_size / 4) * 4
|
217
|
-
end
|
218
|
-
tfc = force_binary("\0" * tfc_size)
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
msg = self.body.to_s + tfc
|
223
|
-
msg += self[:padding].to_s + self[:pad_length].to_s + self[:next].to_s
|
224
|
-
enc_msg = encipher(msg)
|
225
|
-
# as padding is used to pad for CBC mode, this is unused
|
226
|
-
cipher.final
|
227
|
-
|
228
|
-
self[:body] = Types::String.new.read(iv) << enc_msg[0..-3]
|
229
|
-
self[:pad_length].read enc_msg[-2]
|
230
|
-
self[:next].read enc_msg[-1]
|
231
|
-
|
232
|
-
# reset padding field as it has no sense in encrypted ESP
|
233
|
-
self[:padding].read ''
|
234
|
-
|
235
|
-
set_esp_icv_if_needed
|
236
|
-
|
237
|
-
# Remove enciphered headers from packet
|
238
|
-
id = header_id(self)
|
239
|
-
if id < packet.headers.size - 1
|
240
|
-
(packet.headers.size - 1).downto(id + 1) do |index|
|
241
|
-
packet.headers.delete_at index
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
|
-
self
|
246
|
-
end
|
247
|
-
|
248
|
-
# Decrypt in-place ESP payload and trailer.
|
249
|
-
# @param [OpenSSL::Cipher] cipher keyed cipher
|
250
|
-
# This cipher is confidentiality-only one, or AEAD one. To use a second
|
251
|
-
# cipher to add integrity, use +:intmode+ option.
|
252
|
-
# @param [Hash] options
|
253
|
-
# @option options [Boolean] :parse parse deciphered payload to retrieve
|
254
|
-
# headers (default: +true+)
|
255
|
-
# @option options [Fixnum] :icv_length ICV length for captured packets,
|
256
|
-
# or read from PCapNG files
|
257
|
-
# @option options [String] :salt salt value for CTR and GCM modes
|
258
|
-
# @option options [Fixnum] :esn 32 high-orber bits of ESN
|
259
|
-
# @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
|
260
|
-
# confidentiality-only cipher. Only HMAC are supported.
|
261
|
-
# @return [Boolean] +true+ if ESP packet is authenticated
|
262
|
-
def decrypt!(cipher, options={})
|
263
|
-
opt = { salt: '', parse: true }.merge(options)
|
264
|
-
|
265
|
-
set_crypto cipher, opt[:intmode]
|
266
|
-
|
267
|
-
case confidentiality_mode
|
268
|
-
when 'gcm'
|
269
|
-
iv = self.body.slice!(0, 8)
|
270
|
-
real_iv = opt[:salt] + iv
|
271
|
-
when 'cbc'
|
272
|
-
cipher.padding = 0
|
273
|
-
real_iv = iv = self.body.slice!(0, 16)
|
274
|
-
when 'ctr'
|
275
|
-
iv = self.body.slice!(0, 8)
|
276
|
-
real_iv = opt[:salt] + iv + [1].pack('N')
|
277
|
-
else
|
278
|
-
real_iv = iv = self.body.slice!(0, 16)
|
279
|
-
end
|
280
|
-
cipher.iv = real_iv
|
281
|
-
|
282
|
-
if authenticated? && (@icv_length.zero? || opt[:icv_length])
|
283
|
-
raise ParseError, 'unknown ICV size' unless opt[:icv_length]
|
284
|
-
@icv_length = opt[:icv_length].to_i
|
285
|
-
# reread ESP to handle new ICV size
|
286
|
-
msg = self.body.to_s + self[:pad_length].to_s
|
287
|
-
msg += self[:next].to_s
|
288
|
-
self[:icv].read msg.slice!(-@icv_length, @icv_length)
|
289
|
-
self[:body].read msg[0..-3]
|
290
|
-
self[:pad_length].read msg[-2]
|
291
|
-
self[:next].read msg[-1]
|
292
|
-
end
|
293
|
-
|
294
|
-
authenticate_esp_header_if_needed options, iv, self[:icv]
|
295
|
-
private_decrypt opt
|
296
|
-
end
|
297
|
-
|
298
|
-
private
|
299
|
-
|
300
|
-
def get_auth_data(opt)
|
301
|
-
ad = self[:spi].to_s
|
302
|
-
if opt[:esn]
|
303
|
-
@esn = Types::Int32.new(opt[:esn])
|
304
|
-
ad << @esn.to_s if @conf.authenticated?
|
305
|
-
end
|
306
|
-
ad << self[:sn].to_s
|
307
|
-
end
|
308
|
-
|
309
|
-
def authenticate_esp_header_if_needed(opt, iv, icv=nil)
|
310
|
-
if @conf.authenticated?
|
311
|
-
@conf.auth_tag = icv if icv
|
312
|
-
@conf.auth_data = get_auth_data(opt)
|
313
|
-
elsif @intg
|
314
|
-
@intg.reset
|
315
|
-
@intg.update get_auth_data(opt)
|
316
|
-
@intg.update iv
|
317
|
-
@icv = icv
|
318
|
-
else
|
319
|
-
@icv = nil
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
def set_esp_icv_if_needed
|
324
|
-
return unless authenticated?
|
325
|
-
if @conf.authenticated?
|
326
|
-
self[:icv].read @conf.auth_tag[0, @icv_length]
|
327
|
-
else
|
328
|
-
self[:icv].read @intg.digest[0, @icv_length]
|
329
|
-
end
|
330
|
-
end
|
331
|
-
|
332
|
-
def private_decrypt(options)
|
333
|
-
# decrypt
|
334
|
-
msg = self.body.to_s
|
335
|
-
msg += self[:padding].to_s + self[:pad_length].to_s + self[:next].to_s
|
336
|
-
plain_msg = decipher(msg)
|
337
|
-
|
338
|
-
# check authentication tag
|
339
|
-
if authenticated?
|
340
|
-
return false unless authenticate!
|
341
|
-
end
|
342
|
-
|
343
|
-
# Set ESP fields
|
344
|
-
self[:body].read plain_msg[0..-3]
|
345
|
-
self[:pad_length].read plain_msg[-2]
|
346
|
-
self[:next].read plain_msg[-1]
|
347
|
-
|
348
|
-
# Set padding
|
349
|
-
if self.pad_length > 0
|
350
|
-
len = self.pad_length
|
351
|
-
self[:padding].read self.body.slice!(-len, len)
|
352
|
-
end
|
353
|
-
|
354
|
-
# Set TFC padding
|
355
|
-
encap_length = 0
|
356
|
-
pkt = nil
|
357
|
-
case self.next
|
358
|
-
when 4 # IPv4
|
359
|
-
pkt = Packet.parse(body, first_header: 'IP')
|
360
|
-
encap_length = pkt.ip.length
|
361
|
-
when 41 # IPv6
|
362
|
-
pkt = Packet.parse(body, first_header: 'IPv6')
|
363
|
-
encap_length = pkt.ipv6.length + pkt.ipv6.sz
|
364
|
-
when ICMP::IP_PROTOCOL
|
365
|
-
pkt = Packet.parse(body, first_header: 'ICMP')
|
366
|
-
# no size field. cannot recover TFC padding
|
367
|
-
encap_length = body.sz
|
368
|
-
when UDP::IP_PROTOCOL
|
369
|
-
pkt = Packet.parse(body, first_header: 'UDP')
|
370
|
-
encap_length = pkt.udp.length
|
371
|
-
when TCP::IP_PROTOCOL
|
372
|
-
# No length in TCP header, so TFC may not be used.
|
373
|
-
# Or underlayer protocol should have a size information...
|
374
|
-
pkt = Packet.parse(body, first_header: 'TCP')
|
375
|
-
encap_length = pkt.sz
|
376
|
-
when ICMPv6::IP_PROTOCOL
|
377
|
-
pkt = Packet.parse(body, first_header: 'ICMPv6')
|
378
|
-
# no size field. cannot recover TFC padding
|
379
|
-
encap_length = body.sz
|
380
|
-
else
|
381
|
-
# Unmanaged encapsulated protocol
|
382
|
-
encap_length = body.sz
|
383
|
-
end
|
384
|
-
|
385
|
-
if encap_length < body.sz
|
386
|
-
tfc_len = body.sz - encap_length
|
387
|
-
self[:tfc].read self.body.slice!(encap_length, tfc_len)
|
388
|
-
end
|
389
|
-
|
390
|
-
if options[:parse]
|
391
|
-
packet.encapsulate pkt unless pkt.nil?
|
392
|
-
end
|
393
|
-
|
394
|
-
true
|
395
|
-
end
|
396
|
-
end
|
397
|
-
|
398
|
-
self.add_class ESP
|
399
|
-
|
400
|
-
IP.bind ESP, protocol: ESP::IP_PROTOCOL
|
401
|
-
IPv6.bind ESP, next: ESP::IP_PROTOCOL
|
402
|
-
UDP.bind ESP, procs: [->(f) { f.dport = f.sport = ESP::UDP_PORT },
|
403
|
-
->(f) { (f.dport == ESP::UDP_PORT ||
|
404
|
-
f.sport == ESP::UDP_PORT) &&
|
405
|
-
Types::Int32.new.read(f.body[0..3]).to_i > 0 }]
|
406
|
-
ESP.bind IP, next: 4
|
407
|
-
ESP.bind IPv6, next: 41
|
408
|
-
ESP.bind TCP, next: TCP::IP_PROTOCOL
|
409
|
-
ESP.bind UDP, next: TCP::IP_PROTOCOL
|
410
|
-
ESP.bind ICMP, next: ICMP::IP_PROTOCOL
|
411
|
-
ESP.bind ICMPv6, next: ICMPv6::IP_PROTOCOL
|
412
|
-
end
|
413
|
-
end
|