packetgen-plugin-ipsec 1.0.2 → 1.0.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd286b833bca903e5c88756962344db3e80d78d7c97d905547a4698903861fb4
4
- data.tar.gz: edb09dcf8b3ed16776da97748567d3b57ec911f2239b61b7afb97d2741d4db4d
3
+ metadata.gz: 6000090bef8439281eed6482a399f0e05b188e7594d3de3e92d32a9088e32b88
4
+ data.tar.gz: 0a753bf7266628b968ca9937f9d8f41c657c0bdd166b233b4c1e94eef4c71f89
5
5
  SHA512:
6
- metadata.gz: aa5b3e67ac032978a6a5f8618d48645ba5f750b300cd409e32d12d37de35ba25266cca409b677b7f90aabbf7737d8026398a702540435e1078bb12b86e3eb2ed
7
- data.tar.gz: 3ea743b743d19223c39ce245239f183c1029a4f2abf1293cce75ede601e2c60d6e8cba86e867c7d514f5554c51499cb3bc3355b1d45a4616ddba13cf56000ae9
6
+ metadata.gz: fdb0b0828199209621044b6c8a916018225cd025afd403a1f30d0137f5fad3f30bbcba0d58d924d1b2c6cef04690277d3b6de036c016ac20ced6d868b8576224
7
+ data.tar.gz: 2b76622be17f0fdb03b9703bc25eceddd9cafbc968b5d99991d387926d886b4fea3c6fc2437528b9ab662584997a374387a053991dc896fe164cd7cdf6b47f2b
@@ -0,0 +1,28 @@
1
+ name: Specs
2
+ on:
3
+ push:
4
+ branches: [ master ]
5
+ pull_request:
6
+ branches: [ master ]
7
+ jobs:
8
+ test:
9
+ strategy:
10
+ fail-fast: false
11
+ matrix:
12
+ os: [ubuntu-latest]
13
+ ruby: [2.4, 2.5, 2.6, 2.7]
14
+ runs-on: ${{ matrix.os }}
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+ - name: Install dependencies
18
+ run: sudo apt-get update -qq && sudo apt-get install libpcap-dev -qq
19
+ - name: Set up Ruby
20
+ uses: ruby/setup-ruby@v1
21
+ with:
22
+ ruby-version: ${{ matrix.ruby }}
23
+ - name: Run tests
24
+ run: |
25
+ bundle config set path 'vendor/bundle'
26
+ bundle config set --local without noci
27
+ bundle install
28
+ bundle exec rake
@@ -1,4 +1,9 @@
1
- TargetRubyVersion: 2.3
1
+ require:
2
+ - rubocop-performance
3
+ AllCops:
4
+ TargetRubyVersion: 2.4
5
+ Layout/LineLength:
6
+ Max: 150
2
7
  Layout/SpaceAroundEqualsInParameterDefault:
3
8
  EnforcedStyle: no_space
4
9
  Lint/EmptyWhen:
@@ -9,6 +14,8 @@ Metrics:
9
14
  Enabled: false
10
15
  Style/AsciiComments:
11
16
  Enabled: false
17
+ Style/ClassAndModuleChildren:
18
+ EnforcedStyle: compact
12
19
  Style/Encoding:
13
20
  Enabled: false
14
21
  Style/EvalWithLocation:
@@ -8,7 +8,7 @@ rvm:
8
8
  install:
9
9
  - sudo apt-get update -qq
10
10
  - sudo apt-get install libpcap-dev -qq
11
- - gem install bundler --version "~>1.17.3"
11
+ - gem install bundler
12
12
  - bundle install --path vendor/bundle --jobs=3 --retry=3
13
13
  script:
14
14
  - bundle exec rake
data/Gemfile CHANGED
@@ -1,3 +1,14 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
+
5
+ gem 'bundler', '>= 1.17', '< 3'
6
+ gem 'rake', '~> 12.3'
7
+ gem 'rspec', '~> 3.10'
8
+
9
+ group :noci do
10
+ gem 'rubocop', '~> 1.6'
11
+ gem 'rubocop-performance', '~> 1.9'
12
+ gem 'simplecov', '~> 0.18'
13
+ gem 'yard', '~> 0.9'
14
+ end
data/Rakefile CHANGED
@@ -1,13 +1,19 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  require 'bundler/gem_tasks'
3
4
  require 'rspec/core/rake_task'
4
- require 'yard'
5
5
 
6
6
  task default: :spec
7
7
 
8
8
  RSpec::Core::RakeTask.new
9
9
 
10
- YARD::Rake::YardocTask.new do |t|
11
- t.options = ['--no-private']
12
- t.files = ['lib/**/*.rb', '-', 'LICENSE']
10
+ begin
11
+ require 'yard'
12
+
13
+ YARD::Rake::YardocTask.new do |t|
14
+ t.options = ['--no-private']
15
+ t.files = ['lib/**/*.rb', '-', 'LICENSE']
16
+ end
17
+ rescue LoadError
18
+ # no yard, so no yard task
13
19
  end
@@ -1,11 +1,11 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
3
+
2
4
  # This file is part of IPsec packetgen plugin.
3
5
  # See https://github.com/sdaubert/packetgen-plugin-ipsec for more informations
4
6
  # Copyright (c) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
5
7
  # This program is published under MIT license.
6
8
 
7
- # frozen_string_literal: true
8
-
9
9
  module PacketGen::Plugin
10
10
  # Mixin for cryptographic classes
11
11
  # @api private
@@ -22,6 +22,7 @@ module PacketGen::Plugin
22
22
  @conf = conf
23
23
  @intg = intg
24
24
  return unless conf.authenticated?
25
+
25
26
  # #auth_tag_len only supported from ruby 2.4.0
26
27
  @conf.auth_tag_len = @trunc if @conf.respond_to? :auth_tag_len
27
28
  end
@@ -31,6 +32,7 @@ module PacketGen::Plugin
31
32
  def confidentiality_mode
32
33
  mode = @conf.name.match(/-([^-]*)$/)[1]
33
34
  raise Error, 'unknown cipher mode' if mode.nil?
35
+
34
36
  mode.downcase
35
37
  end
36
38
 
@@ -59,7 +61,7 @@ module PacketGen::Plugin
59
61
  # @return [String] enciphered data
60
62
  def encipher(data)
61
63
  enciphered_data = @conf.update(data)
62
- @intg.update(enciphered_data) if @intg
64
+ @intg&.update(enciphered_data)
63
65
  enciphered_data
64
66
  end
65
67
 
@@ -67,7 +69,7 @@ module PacketGen::Plugin
67
69
  # @param [String] data
68
70
  # @return [String] deciphered data
69
71
  def decipher(data)
70
- @intg.update(data) if @intg
72
+ @intg&.update(data)
71
73
  @conf.update(data)
72
74
  end
73
75
  end
@@ -1,413 +1,416 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of IPsec packetgen plugin.
2
4
  # See https://github.com/sdaubert/packetgen-plugin-ipsec for more informations
3
5
  # Copyright (c) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
4
6
  # This program is published under MIT license.
5
7
 
6
- # frozen_string_literal: true
7
-
8
8
  require_relative 'crypto'
9
9
 
10
- module PacketGen
11
- module Plugin
12
- # A ESP header consists of:
13
- # * a Security Parameters Index (#{spi}, {PacketGen::Types::Int32} type),
14
- # * a Sequence Number ({#sn}, +Int32+ type),
15
- # * a {#body} (variable length),
16
- # * an optional TFC padding ({#tfc}, variable length),
17
- # * an optional {#padding} (to align ESP on 32-bit boundary, variable length),
18
- # * a {#pad_length} ({PacketGen::Types::Int8}),
19
- # * a Next header field ({#next}, +Int8+),
20
- # * and an optional Integrity Check Value ({#icv}, variable length).
21
- #
22
- # == Create an ESP header
23
- # # standalone
24
- # esp = PacketGen::Plugin::ESP.new
25
- # # in a packet
26
- # pkt = PacketGen.gen('IP').add('ESP')
27
- # # access to ESP header
28
- # pkt.esp # => PacketGen::Plugin::ESP
29
- #
30
- # == Examples
31
- # === Create an enciphered UDP packet (ESP transport mode), using CBC mode
32
- # icmp = PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.2.1').
33
- # add('ESP', spi: 0xff456e01, sn: 12345678).
34
- # add('UDP', dport: 4567, sport: 45362, body 'abcdef')
35
- # cipher = OpenSSL::Cipher.new('aes-128-cbc')
36
- # cipher.encrypt
37
- # cipher.key = 16bytes_key
38
- # iv = 16bytes_iv
39
- # esp.esp.encrypt! cipher, iv
40
- #
41
- # === Create a ESP packet tunneling a UDP one, using GCM combined mode
42
- # # create inner UDP packet
43
- # icmp = PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.2.1').
44
- # add('UDP', dport: 4567, sport: 45362, body 'abcdef')
45
- #
46
- # # create outer ESP packet
47
- # esp = PacketGen.gen('IP', src '198.76.54.32', dst: '1.2.3.4').add('ESP')
48
- # esp.esp.spi = 0x87654321
49
- # esp.esp.sn = 0x123
50
- # esp.esp.icv_length = 16
51
- # # encapsulate ICMP packet in ESP one
52
- # esp.encapsulate icmp
53
- #
54
- # # encrypt ESP payload
55
- # cipher = OpenSSL::Cipher.new('aes-128-gcm')
56
- # cipher.encrypt
57
- # cipher.key = 16bytes_key
58
- # iv = 8bytes_iv
59
- # esp.esp.encrypt! cipher, iv, salt: 4bytes_gcm_salt
10
+ module PacketGen::Plugin
11
+ # A ESP header consists of:
12
+ # * a Security Parameters Index (#{spi}, {PacketGen::Types::Int32} type),
13
+ # * a Sequence Number ({#sn}, +Int32+ type),
14
+ # * a {#body} (variable length),
15
+ # * an optional TFC padding ({#tfc}, variable length),
16
+ # * an optional {#padding} (to align ESP on 32-bit boundary, variable length),
17
+ # * a {#pad_length} ({PacketGen::Types::Int8}),
18
+ # * a Next header field ({#next}, +Int8+),
19
+ # * and an optional Integrity Check Value ({#icv}, variable length).
20
+ #
21
+ # == Create an ESP header
22
+ # # standalone
23
+ # esp = PacketGen::Plugin::ESP.new
24
+ # # in a packet
25
+ # pkt = PacketGen.gen('IP').add('ESP')
26
+ # # access to ESP header
27
+ # pkt.esp # => PacketGen::Plugin::ESP
28
+ #
29
+ # == Examples
30
+ # === Create an enciphered UDP packet (ESP transport mode), using CBC mode
31
+ # icmp = PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.2.1').
32
+ # add('ESP', spi: 0xff456e01, sn: 12345678).
33
+ # add('UDP', dport: 4567, sport: 45362, body 'abcdef')
34
+ # cipher = OpenSSL::Cipher.new('aes-128-cbc')
35
+ # cipher.encrypt
36
+ # cipher.key = 16bytes_key
37
+ # iv = 16bytes_iv
38
+ # esp.esp.encrypt! cipher, iv
39
+ #
40
+ # === Create a ESP packet tunneling a UDP one, using GCM combined mode
41
+ # # create inner UDP packet
42
+ # icmp = PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.2.1').
43
+ # add('UDP', dport: 4567, sport: 45362, body 'abcdef')
44
+ #
45
+ # # create outer ESP packet
46
+ # esp = PacketGen.gen('IP', src '198.76.54.32', dst: '1.2.3.4').add('ESP')
47
+ # esp.esp.spi = 0x87654321
48
+ # esp.esp.sn = 0x123
49
+ # esp.esp.icv_length = 16
50
+ # # encapsulate ICMP packet in ESP one
51
+ # esp.encapsulate icmp
52
+ #
53
+ # # encrypt ESP payload
54
+ # cipher = OpenSSL::Cipher.new('aes-128-gcm')
55
+ # cipher.encrypt
56
+ # cipher.key = 16bytes_key
57
+ # iv = 8bytes_iv
58
+ # esp.esp.encrypt! cipher, iv, salt: 4bytes_gcm_salt
59
+ #
60
+ # === Decrypt a ESP packet using CBC mode and HMAC-SHA-256
61
+ # cipher = OpenSSL::Cipher.new('aes-128-cbc')
62
+ # cipher.decrypt
63
+ # cipher.key = 16bytes_key
64
+ #
65
+ # hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
66
+ #
67
+ # pkt.esp.decrypt! cipher, intmode: hmac # => true if ICV check OK
68
+ # @author Sylvain Daubert
69
+ class ESP < PacketGen::Header::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, PacketGen::Types::Int32
82
+ # @!attribute sn
83
+ # 32-bit Sequence Number
84
+ # @return [Integer]
85
+ define_field :sn, PacketGen::Types::Int32
86
+ # @!attribute body
87
+ # @return [PacketGen::Types::String,PacketGen::Header::Base]
88
+ define_field :body, PacketGen::Types::String
89
+ # @!attribute tfc
90
+ # Traffic Flow Confidentiality padding
91
+ # @return [PacketGen::Types::String,PacketGen::Header::Base]
92
+ define_field :tfc, PacketGen::Types::String
93
+ # @!attribute padding
94
+ # ESP padding
95
+ # @return [PacketGen::Types::String,PacketGen::Header::Base]
96
+ define_field :padding, PacketGen::Types::String
97
+ # @!attribute pad_length
98
+ # 8-bit padding length
99
+ # @return [Integer]
100
+ define_field :pad_length, PacketGen::Types::Int8
101
+ # @!attribute next
102
+ # 8-bit next protocol value
103
+ # @return [Integer]
104
+ define_field :next, PacketGen::Types::Int8
105
+ # @!attribute icv
106
+ # Integrity Check Value
107
+ # @return [PacketGen::Types::String,PacketGen::Header::Base]
108
+ define_field :icv, PacketGen::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.
60
132
  #
61
- # === Decrypt a ESP packet using CBC mode and HMAC-SHA-256
62
- # cipher = OpenSSL::Cipher.new('aes-128-cbc')
63
- # cipher.decrypt
64
- # cipher.key = 16bytes_key
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
+
141
+ force_binary str
142
+ self[:spi].read str[0, 4]
143
+ self[:sn].read str[4, 4]
144
+ self[:body].read str[8...-@icv_length - 2]
145
+ self[:tfc].read ''
146
+ self[:padding].read ''
147
+ self[:pad_length].read str[-@icv_length - 2, 1]
148
+ self[:next].read str[-@icv_length - 1, 1]
149
+ self[:icv].read str[-@icv_length, @icv_length] if @icv_length
150
+ self
151
+ end
152
+
153
+ # Encrypt in-place ESP payload and trailer.
65
154
  #
66
- # hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
155
+ # This method removes all data from +tfc+ and +padding+ fields, as their
156
+ # enciphered values are concatenated into +body+.
67
157
  #
68
- # pkt.esp.decrypt! cipher, intmode: hmac # => true if ICV check OK
69
- # @author Sylvain Daubert
70
- class ESP < PacketGen::Header::Base
71
- include Crypto
72
-
73
- # IP protocol number for ESP
74
- IP_PROTOCOL = 50
75
-
76
- # Well-known UDP port for ESP
77
- UDP_PORT = 4500
78
-
79
- # @!attribute spi
80
- # 32-bit Security Parameter Index
81
- # @return [Integer]
82
- define_field :spi, PacketGen::Types::Int32
83
- # @!attribute sn
84
- # 32-bit Sequence Number
85
- # @return [Integer]
86
- define_field :sn, PacketGen::Types::Int32
87
- # @!attribute body
88
- # @return [PacketGen::Types::String,PacketGen::Header::Base]
89
- define_field :body, PacketGen::Types::String
90
- # @!attribute tfc
91
- # Traffic Flow Confidentiality padding
92
- # @return [PacketGen::Types::String,PacketGen::Header::Base]
93
- define_field :tfc, PacketGen::Types::String
94
- # @!attribute padding
95
- # ESP padding
96
- # @return [PacketGen::Types::String,PacketGen::Header::Base]
97
- define_field :padding, PacketGen::Types::String
98
- # @!attribute pad_length
99
- # 8-bit padding length
100
- # @return [Integer]
101
- define_field :pad_length, PacketGen::Types::Int8
102
- # @!attribute next
103
- # 8-bit next protocol value
104
- # @return [Integer]
105
- define_field :next, PacketGen::Types::Int8
106
- # @!attribute icv
107
- # Integrity Check Value
108
- # @return [PacketGen::Types::String,PacketGen::Header::Base]
109
- define_field :icv, PacketGen::Types::String
110
-
111
- # ICV (Integrity Check Value) length
112
- # @return [Integer]
113
- attr_accessor :icv_length
114
-
115
- # @param [Hash] options
116
- # @option options [Integer] :icv_length ICV length
117
- # @option options [Integer] :spi Security Parameters Index
118
- # @option options [Integer] :sn Sequence Number
119
- # @option options [::String] :body ESP payload data
120
- # @option options [::String] :tfc Traffic Flow Confidentiality, random padding
121
- # up to MTU
122
- # @option options [::String] :padding ESP padding to align ESP on 32-bit
123
- # boundary
124
- # @option options [Integer] :pad_length padding length
125
- # @option options [Integer] :next Next Header field
126
- # @option options [::String] :icv Integrity Check Value
127
- def initialize(options={})
128
- @icv_length = options[:icv_length] || 0
129
- super
158
+ # It also removes headers under ESP from packet, as they are enciphered in
159
+ # ESP body, and then are no more accessible.
160
+ # @param [OpenSSL::Cipher] cipher keyed cipher.
161
+ # This cipher is confidentiality-only one, or AEAD one. To use a second
162
+ # cipher to add integrity, use +:intmode+ option.
163
+ # @param [String] iv full IV for encryption
164
+ # * CTR and GCM modes: +iv+ is 8-bytes long.
165
+ # @param [Hash] options
166
+ # @option options [String] :salt salt value for CTR and GCM modes
167
+ # @option options [Boolean] :tfc
168
+ # @option options [Fixnum] :tfc_size ESP body size used for TFC
169
+ # (default 1444, max size for a tunneled IPv4/ESP packet).
170
+ # This is the maximum size for ESP packet (without IP header
171
+ # nor Eth one).
172
+ # @option options [Fixnum] :esn 32 high-orber bits of ESN
173
+ # @option options [Fixnum] :pad_length set a padding length
174
+ # @option options [String] :padding set a padding. No check with
175
+ # +:pad_length+ is made. If +:pad_length+ is not set, +:padding+
176
+ # length is shortened to correct padding length
177
+ # @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
178
+ # confidentiality-only cipher. Only HMAC are supported.
179
+ # @return [self]
180
+ def encrypt!(cipher, iv, options={})
181
+ opt = { salt: '', tfc_size: 1444 }.merge(options)
182
+
183
+ set_crypto cipher, opt[:intmode]
184
+
185
+ real_iv = force_binary(opt[:salt]) + force_binary(iv)
186
+ real_iv += [1].pack('N') if confidentiality_mode == 'ctr'
187
+ cipher.iv = real_iv
188
+
189
+ authenticate_esp_header_if_needed options, iv
190
+
191
+ case confidentiality_mode
192
+ when 'cbc'
193
+ cipher_len = self[:body].sz + 2
194
+ self.pad_length = (16 - (cipher_len % 16)) % 16
195
+ else
196
+ mod4 = to_s.size % 4
197
+ self.pad_length = 4 - mod4 if mod4.positive?
130
198
  end
131
199
 
132
- # Read a ESP packet from string.
133
- #
134
- # {#padding} and {#tfc} are not set as they are enciphered (impossible
135
- # to guess their respective size). {#pad_length} and {#next} are also
136
- # enciphered.
137
- # @param [String] str
138
- # @return [self]
139
- def read(str)
140
- return self if str.nil?
141
-
142
- force_binary str
143
- self[:spi].read str[0, 4]
144
- self[:sn].read str[4, 4]
145
- self[:body].read str[8...-@icv_length - 2]
146
- self[:tfc].read ''
147
- self[:padding].read ''
148
- self[:pad_length].read str[-@icv_length - 2, 1]
149
- self[:next].read str[-@icv_length - 1, 1]
150
- self[:icv].read str[-@icv_length, @icv_length] if @icv_length
151
- self
200
+ if opt[:pad_length]
201
+ self.pad_length = opt[:pad_length]
202
+ padding = force_binary(opt[:padding] || (1..self.pad_length).to_a.pack('C*'))
203
+ self[:padding].read padding
204
+ else
205
+ padding = force_binary(opt[:padding] || (1..self.pad_length).to_a.pack('C*'))
206
+ self[:padding].read padding[0...self.pad_length]
152
207
  end
153
208
 
154
- # Encrypt in-place ESP payload and trailer.
155
- #
156
- # This method removes all data from +tfc+ and +padding+ fields, as their
157
- # enciphered values are concatenated into +body+.
158
- #
159
- # It also removes headers under ESP from packet, as they are enciphered in
160
- # ESP body, and then are no more accessible.
161
- # @param [OpenSSL::Cipher] cipher keyed cipher.
162
- # This cipher is confidentiality-only one, or AEAD one. To use a second
163
- # cipher to add integrity, use +:intmode+ option.
164
- # @param [String] iv full IV for encryption
165
- # * CTR and GCM modes: +iv+ is 8-bytes long.
166
- # @param [Hash] options
167
- # @option options [String] :salt salt value for CTR and GCM modes
168
- # @option options [Boolean] :tfc
169
- # @option options [Fixnum] :tfc_size ESP body size used for TFC
170
- # (default 1444, max size for a tunneled IPv4/ESP packet).
171
- # This is the maximum size for ESP packet (without IP header
172
- # nor Eth one).
173
- # @option options [Fixnum] :esn 32 high-orber bits of ESN
174
- # @option options [Fixnum] :pad_length set a padding length
175
- # @option options [String] :padding set a padding. No check with
176
- # +:pad_length+ is made. If +:pad_length+ is not set, +:padding+
177
- # length is shortened to correct padding length
178
- # @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
179
- # confidentiality-only cipher. Only HMAC are supported.
180
- # @return [self]
181
- def encrypt!(cipher, iv, options={})
182
- opt = { salt: '', tfc_size: 1444 }.merge(options)
183
-
184
- set_crypto cipher, opt[:intmode]
185
-
186
- real_iv = force_binary(opt[:salt]) + force_binary(iv)
187
- real_iv += [1].pack('N') if confidentiality_mode == 'ctr'
188
- cipher.iv = real_iv
189
-
190
- authenticate_esp_header_if_needed options, iv
191
-
192
- case confidentiality_mode
193
- when 'cbc'
194
- cipher_len = self[:body].sz + 2
195
- self.pad_length = (16 - (cipher_len % 16)) % 16
196
- else
197
- mod4 = to_s.size % 4
198
- self.pad_length = 4 - mod4 if mod4 > 0
199
- end
200
-
201
- if opt[:pad_length]
202
- self.pad_length = opt[:pad_length]
203
- padding = force_binary(opt[:padding] || (1..self.pad_length).to_a.pack('C*'))
204
- self[:padding].read padding
205
- else
206
- padding = force_binary(opt[:padding] || (1..self.pad_length).to_a.pack('C*'))
207
- self[:padding].read padding[0...self.pad_length]
208
- end
209
-
210
- tfc = ''
211
- if opt[:tfc]
212
- tfc_size = opt[:tfc_size] - self[:body].sz
213
- if tfc_size > 0
214
- tfc_size = case confidentiality_mode
215
- when 'cbc'
216
- (tfc_size / 16) * 16
217
- else
218
- (tfc_size / 4) * 4
219
- end
220
- tfc = force_binary("\0" * tfc_size)
221
- end
209
+ tfc = ''
210
+ if opt[:tfc]
211
+ tfc_size = opt[:tfc_size] - self[:body].sz
212
+ if tfc_size.positive?
213
+ tfc_size = case confidentiality_mode
214
+ when 'cbc'
215
+ (tfc_size / 16) * 16
216
+ else
217
+ (tfc_size / 4) * 4
218
+ end
219
+ tfc = force_binary("\0" * tfc_size)
222
220
  end
221
+ end
223
222
 
224
- msg = self[:body].to_s + tfc
225
- msg += self[:padding].to_s + self[:pad_length].to_s + self[:next].to_s
226
- enc_msg = encipher(msg)
227
- # as padding is used to pad for CBC mode, this is unused
228
- cipher.final
223
+ msg = self[:body].to_s + tfc
224
+ msg += self[:padding].to_s + self[:pad_length].to_s + self[:next].to_s
225
+ enc_msg = encipher(msg)
226
+ # as padding is used to pad for CBC mode, this is unused
227
+ cipher.final
229
228
 
230
- self[:body] = PacketGen::Types::String.new.read(iv) << enc_msg[0..-3]
231
- self[:pad_length].read enc_msg[-2]
232
- self[:next].read enc_msg[-1]
229
+ self[:body] = PacketGen::Types::String.new.read(iv)
230
+ self[:body] << enc_msg[0..-3]
231
+ self[:pad_length].read enc_msg[-2]
232
+ self[:next].read enc_msg[-1]
233
233
 
234
- # reset padding field as it has no sense in encrypted ESP
235
- self[:padding].read ''
234
+ # reset padding field as it has no sense in encrypted ESP
235
+ self[:padding].read ''
236
236
 
237
- set_esp_icv_if_needed
237
+ set_esp_icv_if_needed
238
238
 
239
- # Remove enciphered headers from packet
240
- id = header_id(self)
241
- if id < packet.headers.size - 1
242
- (packet.headers.size - 1).downto(id + 1) do |index|
243
- packet.headers.delete_at index
244
- end
239
+ # Remove enciphered headers from packet
240
+ id = header_id(self)
241
+ if id < packet.headers.size - 1
242
+ (packet.headers.size - 1).downto(id + 1) do |index|
243
+ packet.headers.delete_at index
245
244
  end
246
-
247
- self
248
245
  end
249
246
 
250
- # Decrypt in-place ESP payload and trailer.
251
- # @param [OpenSSL::Cipher] cipher keyed cipher
252
- # This cipher is confidentiality-only one, or AEAD one. To use a second
253
- # cipher to add integrity, use +:intmode+ option.
254
- # @param [Hash] options
255
- # @option options [Boolean] :parse parse deciphered payload to retrieve
256
- # headers (default: +true+)
257
- # @option options [Fixnum] :icv_length ICV length for captured packets,
258
- # or read from PCapNG files
259
- # @option options [String] :salt salt value for CTR and GCM modes
260
- # @option options [Fixnum] :esn 32 high-orber bits of ESN
261
- # @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
262
- # confidentiality-only cipher. Only HMAC are supported.
263
- # @return [Boolean] +true+ if ESP packet is authenticated
264
- def decrypt!(cipher, options={})
265
- opt = { salt: '', parse: true }.merge(options)
266
-
267
- set_crypto cipher, opt[:intmode]
268
-
269
- case confidentiality_mode
270
- when 'gcm'
271
- iv = self[:body].slice!(0, 8)
272
- real_iv = opt[:salt] + iv
273
- when 'cbc'
274
- cipher.padding = 0
275
- real_iv = iv = self[:body].slice!(0, 16)
276
- when 'ctr'
277
- iv = self[:body].slice!(0, 8)
278
- real_iv = opt[:salt] + iv + [1].pack('N')
279
- else
280
- real_iv = iv = self[:body].slice!(0, 16)
281
- end
282
- cipher.iv = real_iv
283
-
284
- if authenticated? && (@icv_length.zero? || opt[:icv_length])
285
- raise ParseError, 'unknown ICV size' unless opt[:icv_length]
286
- @icv_length = opt[:icv_length].to_i
287
- # reread ESP to handle new ICV size
288
- msg = self[:body].to_s + self[:pad_length].to_s
289
- msg += self[:next].to_s
290
- self[:icv].read msg.slice!(-@icv_length, @icv_length)
291
- self[:body].read msg[0..-3]
292
- self[:pad_length].read msg[-2]
293
- self[:next].read msg[-1]
294
- end
247
+ self
248
+ end
295
249
 
296
- authenticate_esp_header_if_needed options, iv, icv
297
- private_decrypt opt
250
+ # Decrypt in-place ESP payload and trailer.
251
+ # @param [OpenSSL::Cipher] cipher keyed cipher
252
+ # This cipher is confidentiality-only one, or AEAD one. To use a second
253
+ # cipher to add integrity, use +:intmode+ option.
254
+ # @param [Hash] options
255
+ # @option options [Boolean] :parse parse deciphered payload to retrieve
256
+ # headers (default: +true+)
257
+ # @option options [Fixnum] :icv_length ICV length for captured packets,
258
+ # or read from PCapNG files
259
+ # @option options [String] :salt salt value for CTR and GCM modes
260
+ # @option options [Fixnum] :esn 32 high-orber bits of ESN
261
+ # @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
262
+ # confidentiality-only cipher. Only HMAC are supported.
263
+ # @return [Boolean] +true+ if ESP packet is authenticated
264
+ def decrypt!(cipher, options={})
265
+ opt = { salt: '', parse: true }.merge(options)
266
+
267
+ set_crypto cipher, opt[:intmode]
268
+
269
+ case confidentiality_mode
270
+ when 'gcm'
271
+ iv = self[:body].slice!(0, 8)
272
+ real_iv = opt[:salt] + iv
273
+ when 'cbc'
274
+ cipher.padding = 0
275
+ real_iv = iv = self[:body].slice!(0, 16)
276
+ when 'ctr'
277
+ iv = self[:body].slice!(0, 8)
278
+ real_iv = opt[:salt] + iv + [1].pack('N')
279
+ else
280
+ real_iv = iv = self[:body].slice!(0, 16)
281
+ end
282
+ cipher.iv = real_iv
283
+
284
+ if authenticated? && (@icv_length.zero? || opt[:icv_length])
285
+ raise PacketGen::ParseError, 'unknown ICV size' unless opt[:icv_length]
286
+
287
+ @icv_length = opt[:icv_length].to_i
288
+ # reread ESP to handle new ICV size
289
+ msg = self[:body].to_s + self[:pad_length].to_s
290
+ msg += self[:next].to_s
291
+ self[:icv].read msg.slice!(-@icv_length, @icv_length)
292
+ self[:body].read msg[0..-3]
293
+ self[:pad_length].read msg[-2]
294
+ self[:next].read msg[-1]
298
295
  end
299
296
 
300
- private
297
+ authenticate_esp_header_if_needed options, iv, icv
298
+ private_decrypt opt
299
+ end
301
300
 
302
- def get_auth_data(opt)
303
- ad = self[:spi].to_s
304
- if opt[:esn]
305
- @esn = PacketGen::Types::Int32.new(opt[:esn])
306
- ad << @esn.to_s if @conf.authenticated?
307
- end
308
- ad << self[:sn].to_s
309
- end
301
+ private
310
302
 
311
- def authenticate_esp_header_if_needed(opt, iv, icv=nil)
312
- if @conf.authenticated?
313
- @conf.auth_tag = icv if icv
314
- @conf.auth_data = get_auth_data(opt)
315
- elsif @intg
316
- @intg.reset
317
- @intg.update get_auth_data(opt)
318
- @intg.update iv
319
- @icv = icv
320
- else
321
- @icv = nil
322
- end
303
+ def get_auth_data(opt)
304
+ ad = self[:spi].to_s
305
+ if opt[:esn]
306
+ @esn = PacketGen::Types::Int32.new(opt[:esn])
307
+ ad << @esn.to_s if @conf.authenticated?
323
308
  end
309
+ ad << self[:sn].to_s
310
+ end
324
311
 
325
- def set_esp_icv_if_needed
326
- return unless authenticated?
327
- if @conf.authenticated?
328
- self[:icv].read @conf.auth_tag[0, @icv_length]
329
- else
330
- self[:icv].read @intg.digest[0, @icv_length]
331
- end
312
+ def authenticate_esp_header_if_needed(opt, iv, icv=nil)
313
+ if @conf.authenticated?
314
+ @conf.auth_tag = icv if icv
315
+ @conf.auth_data = get_auth_data(opt)
316
+ elsif @intg
317
+ @intg.reset
318
+ @intg.update get_auth_data(opt)
319
+ @intg.update iv
320
+ @icv = icv
321
+ else
322
+ @icv = nil
332
323
  end
324
+ end
333
325
 
334
- def private_decrypt(options)
335
- # decrypt
336
- msg = self.body.to_s
337
- msg += self.padding + self[:pad_length].to_s + self[:next].to_s
338
- plain_msg = decipher(msg)
326
+ def set_esp_icv_if_needed
327
+ return unless authenticated?
339
328
 
340
- # check authentication tag
341
- return false if authenticated? && !authenticate!
329
+ if @conf.authenticated?
330
+ self[:icv].read @conf.auth_tag[0, @icv_length]
331
+ else
332
+ self[:icv].read @intg.digest[0, @icv_length]
333
+ end
334
+ end
342
335
 
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]
336
+ def private_decrypt(options)
337
+ # decrypt
338
+ msg = self.body.to_s
339
+ msg += self.padding + self[:pad_length].to_s + self[:next].to_s
340
+ plain_msg = decipher(msg)
347
341
 
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
342
+ # check authentication tag
343
+ return false if authenticated? && !authenticate!
353
344
 
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 PacketGen::Header::ICMP::IP_PROTOCOL
365
- pkt = Packet.parse(body, first_header: 'ICMP')
366
- # no size field. cannot recover TFC padding
367
- encap_length = self[:body].sz
368
- when PacketGen::Header::UDP::IP_PROTOCOL
369
- pkt = Packet.parse(body, first_header: 'UDP')
370
- encap_length = pkt.udp.length
371
- when PacketGen::Header::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 PacketGen::Header::ICMPv6::IP_PROTOCOL
377
- pkt = Packet.parse(body, first_header: 'ICMPv6')
378
- # no size field. cannot recover TFC padding
379
- encap_length = self[:body].sz
380
- else
381
- # Unmanaged encapsulated protocol
382
- encap_length = self[:body].sz
383
- end
345
+ # Set ESP fields
346
+ self[:body].read plain_msg[0..-3]
347
+ self[:pad_length].read plain_msg[-2]
348
+ self[:next].read plain_msg[-1]
384
349
 
385
- if encap_length < self[:body].sz
386
- tfc_len = self[:body].sz - encap_length
387
- self[:tfc].read self[:body].slice!(encap_length, tfc_len)
388
- end
350
+ # Set padding
351
+ if self.pad_length.positive?
352
+ len = self.pad_length
353
+ self[:padding].read self[:body].slice!(-len, len)
354
+ end
389
355
 
390
- if options[:parse]
391
- packet.encapsulate pkt unless pkt.nil?
392
- end
356
+ # Set TFC padding
357
+ encap_length = 0
358
+ pkt = nil
359
+ case self.next
360
+ when 4 # IPv4
361
+ pkt = PacketGen::Packet.parse(body, first_header: 'IP')
362
+ encap_length = pkt.ip.length
363
+ when 41 # IPv6
364
+ pkt = PacketGen::Packet.parse(body, first_header: 'IPv6')
365
+ encap_length = pkt.ipv6.length + pkt.ipv6.sz
366
+ when PacketGen::Header::ICMP::IP_PROTOCOL
367
+ pkt = PacketGen::Packet.parse(body, first_header: 'ICMP')
368
+ # no size field. cannot recover TFC padding
369
+ encap_length = self[:body].sz
370
+ when PacketGen::Header::UDP::IP_PROTOCOL
371
+ pkt = PacketGen::Packet.parse(body, first_header: 'UDP')
372
+ encap_length = pkt.udp.length
373
+ when PacketGen::Header::TCP::IP_PROTOCOL
374
+ # No length in TCP header, so TFC may not be used.
375
+ # Or underlayer protocol should have a size information...
376
+ pkt = PacketGen::Packet.parse(body, first_header: 'TCP')
377
+ encap_length = pkt.sz
378
+ when PacketGen::Header::ICMPv6::IP_PROTOCOL
379
+ pkt = PacketGen::Packet.parse(body, first_header: 'ICMPv6')
380
+ # no size field. cannot recover TFC padding
381
+ encap_length = self[:body].sz
382
+ else
383
+ # Unmanaged encapsulated protocol
384
+ encap_length = self[:body].sz
385
+ end
393
386
 
394
- true
387
+ if encap_length < self[:body].sz
388
+ tfc_len = self[:body].sz - encap_length
389
+ self[:tfc].read self[:body].slice!(encap_length, tfc_len)
390
+ end
391
+
392
+ if options[:parse]
393
+ packet.encapsulate pkt unless pkt.nil?
395
394
  end
396
- end
397
395
 
398
- Header.add_class ESP
399
-
400
- PacketGen::Header::IP.bind ESP, protocol: ESP::IP_PROTOCOL
401
- PacketGen::Header::IPv6.bind ESP, next: ESP::IP_PROTOCOL
402
- PacketGen::Header::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
- PacketGen::Types::Int32.new.read(f.body[0..3]).to_i > 0 }]
406
- ESP.bind PacketGen::Header::IP, next: 4
407
- ESP.bind PacketGen::Header::IPv6, next: 41
408
- ESP.bind PacketGen::Header::TCP, next: PacketGen::Header::TCP::IP_PROTOCOL
409
- ESP.bind PacketGen::Header::UDP, next: PacketGen::Header::TCP::IP_PROTOCOL
410
- ESP.bind PacketGen::Header::ICMP, next: PacketGen::Header::ICMP::IP_PROTOCOL
411
- ESP.bind PacketGen::Header::ICMPv6, next: PacketGen::Header::ICMPv6::IP_PROTOCOL
396
+ true
397
+ end
412
398
  end
399
+
400
+ PacketGen::Header.add_class ESP
401
+
402
+ PacketGen::Header::IP.bind ESP, protocol: ESP::IP_PROTOCOL
403
+ PacketGen::Header::IPv6.bind ESP, next: ESP::IP_PROTOCOL
404
+ PacketGen::Header::UDP.bind ESP, procs: [->(f) { f.dport = f.sport = ESP::UDP_PORT },
405
+ lambda { |f|
406
+ (f.dport == ESP::UDP_PORT ||
407
+ f.sport == ESP::UDP_PORT) &&
408
+ PacketGen::Types::Int32.new.read(f.body[0..3]).to_i.positive?
409
+ }]
410
+ ESP.bind PacketGen::Header::IP, next: 4
411
+ ESP.bind PacketGen::Header::IPv6, next: 41
412
+ ESP.bind PacketGen::Header::TCP, next: PacketGen::Header::TCP::IP_PROTOCOL
413
+ ESP.bind PacketGen::Header::UDP, next: PacketGen::Header::TCP::IP_PROTOCOL
414
+ ESP.bind PacketGen::Header::ICMP, next: PacketGen::Header::ICMP::IP_PROTOCOL
415
+ ESP.bind PacketGen::Header::ICMPv6, next: PacketGen::Header::ICMPv6::IP_PROTOCOL
413
416
  end