packetgen-plugin-ipsec 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
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