packetgen 2.8.7 → 3.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.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -1
  3. data/README.md +5 -4
  4. data/lib/packetgen.rb +6 -12
  5. data/lib/packetgen/capture.rb +43 -39
  6. data/lib/packetgen/config.rb +0 -1
  7. data/lib/packetgen/deprecation.rb +1 -1
  8. data/lib/packetgen/header.rb +9 -9
  9. data/lib/packetgen/header/asn1_base.rb +10 -10
  10. data/lib/packetgen/header/base.rb +42 -101
  11. data/lib/packetgen/header/dhcp/option.rb +5 -11
  12. data/lib/packetgen/header/dhcpv6/duid.rb +2 -0
  13. data/lib/packetgen/header/dhcpv6/option.rb +2 -19
  14. data/lib/packetgen/header/dhcpv6/options.rb +7 -0
  15. data/lib/packetgen/header/dns.rb +5 -23
  16. data/lib/packetgen/header/dns/name.rb +1 -0
  17. data/lib/packetgen/header/dns/qdsection.rb +1 -0
  18. data/lib/packetgen/header/dns/question.rb +3 -7
  19. data/lib/packetgen/header/dns/rr.rb +3 -0
  20. data/lib/packetgen/header/dns/rrsection.rb +1 -0
  21. data/lib/packetgen/header/dot11.rb +1 -17
  22. data/lib/packetgen/header/dot1x.rb +1 -0
  23. data/lib/packetgen/header/eap.rb +4 -7
  24. data/lib/packetgen/header/eth.rb +2 -0
  25. data/lib/packetgen/header/http/headers.rb +3 -0
  26. data/lib/packetgen/header/http/request.rb +5 -4
  27. data/lib/packetgen/header/http/response.rb +5 -4
  28. data/lib/packetgen/header/icmp.rb +6 -0
  29. data/lib/packetgen/header/icmpv6.rb +6 -0
  30. data/lib/packetgen/header/igmpv3/mq.rb +2 -0
  31. data/lib/packetgen/header/ip.rb +32 -30
  32. data/lib/packetgen/header/ip/addr.rb +1 -0
  33. data/lib/packetgen/header/ip/option.rb +23 -20
  34. data/lib/packetgen/header/ip/options.rb +11 -24
  35. data/lib/packetgen/header/ipv6.rb +45 -34
  36. data/lib/packetgen/header/ipv6/addr.rb +2 -0
  37. data/lib/packetgen/header/ipv6/hop_by_hop.rb +7 -31
  38. data/lib/packetgen/header/mdns.rb +1 -0
  39. data/lib/packetgen/header/mldv2/mlq.rb +2 -0
  40. data/lib/packetgen/header/ospfv2/lsa.rb +15 -25
  41. data/lib/packetgen/header/ospfv3/ipv6_prefix.rb +1 -1
  42. data/lib/packetgen/header/ospfv3/lsa.rb +8 -25
  43. data/lib/packetgen/header/snmp.rb +2 -0
  44. data/lib/packetgen/header/tcp.rb +23 -2
  45. data/lib/packetgen/header/tcp/option.rb +51 -52
  46. data/lib/packetgen/header/tcp/options.rb +17 -52
  47. data/lib/packetgen/header/tftp.rb +3 -0
  48. data/lib/packetgen/header/udp.rb +8 -0
  49. data/lib/packetgen/packet.rb +119 -102
  50. data/lib/packetgen/pcapng/block.rb +4 -10
  51. data/lib/packetgen/pcapng/epb.rb +4 -4
  52. data/lib/packetgen/pcapng/file.rb +7 -3
  53. data/lib/packetgen/pcapng/idb.rb +2 -2
  54. data/lib/packetgen/pcapng/shb.rb +3 -3
  55. data/lib/packetgen/pcapng/spb.rb +1 -8
  56. data/lib/packetgen/pcapng/unknown_block.rb +0 -7
  57. data/lib/packetgen/types.rb +1 -0
  58. data/lib/packetgen/types/array.rb +73 -71
  59. data/lib/packetgen/types/cstring.rb +1 -1
  60. data/lib/packetgen/types/enum.rb +3 -3
  61. data/lib/packetgen/types/fields.rb +66 -106
  62. data/lib/packetgen/types/int.rb +9 -5
  63. data/lib/packetgen/types/length_from.rb +45 -0
  64. data/lib/packetgen/types/oui.rb +2 -0
  65. data/lib/packetgen/types/string.rb +10 -16
  66. data/lib/packetgen/types/tlv.rb +7 -15
  67. data/lib/packetgen/utils.rb +8 -8
  68. data/lib/packetgen/utils/arp_spoofer.rb +1 -2
  69. data/lib/packetgen/version.rb +1 -1
  70. metadata +3 -21
  71. data/lib/packetgen/header/crypto.rb +0 -62
  72. data/lib/packetgen/header/esp.rb +0 -413
  73. data/lib/packetgen/header/ike.rb +0 -243
  74. data/lib/packetgen/header/ike/auth.rb +0 -165
  75. data/lib/packetgen/header/ike/cert.rb +0 -76
  76. data/lib/packetgen/header/ike/certreq.rb +0 -66
  77. data/lib/packetgen/header/ike/id.rb +0 -99
  78. data/lib/packetgen/header/ike/ke.rb +0 -79
  79. data/lib/packetgen/header/ike/nonce.rb +0 -40
  80. data/lib/packetgen/header/ike/notify.rb +0 -176
  81. data/lib/packetgen/header/ike/payload.rb +0 -315
  82. data/lib/packetgen/header/ike/sa.rb +0 -561
  83. data/lib/packetgen/header/ike/sk.rb +0 -261
  84. data/lib/packetgen/header/ike/ts.rb +0 -270
  85. data/lib/packetgen/header/ike/vendor_id.rb +0 -39
  86. data/lib/packetgen/header/netbios.rb +0 -20
  87. data/lib/packetgen/header/netbios/datagram.rb +0 -105
  88. data/lib/packetgen/header/netbios/name.rb +0 -67
  89. data/lib/packetgen/header/netbios/session.rb +0 -64
@@ -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, String] value
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
- else
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 [StandardError] This is an abstrat method and must be redefined
57
+ # @raise [ParseError] This is an abstrat method and must be redefined
55
58
  def to_s
56
59
  unless defined? @packstr
57
- raise StandardError, 'PacketGen::Types::Int#to_s is an abstract method'
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
- to_s.size
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
@@ -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
- @length_from = options[:length_from]
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.to_s
29
- str_end = case @length_from
30
- when Types::Int
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
@@ -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
- if self[:value].respond_to? :from_human
100
- self[:value].from_human val
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
- if self[:value].respond_to? :to_human
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
@@ -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.dst != my_ip) && (iph.src != my_ip)
148
- if (iph.src == target1) || (iph.dst == target2)
149
- l2.dst = mac2
150
- elsif (iph.src == target2) ||(iph.dst == target1)
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
 
@@ -10,5 +10,5 @@
10
10
  # @author Sylvain Daubert
11
11
  module PacketGen
12
12
  # PacketGen version
13
- VERSION = '2.8.7'
13
+ VERSION = '3.0.0'
14
14
  end
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: 2.8.7
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-18 00:00:00.000000000 Z
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
@@ -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