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 +4 -4
- data/.github/workflows/specs.yml +28 -0
- data/.rubocop.yml +8 -1
- data/.travis.yml +1 -1
- data/Gemfile +11 -0
- data/Rakefile +10 -4
- data/lib/packetgen/plugin/crypto.rb +6 -4
- data/lib/packetgen/plugin/esp.rb +373 -370
- data/lib/packetgen/plugin/ike.rb +218 -217
- data/lib/packetgen/plugin/ike/auth.rb +141 -141
- data/lib/packetgen/plugin/ike/cert.rb +61 -62
- data/lib/packetgen/plugin/ike/certreq.rb +51 -52
- data/lib/packetgen/plugin/ike/id.rb +80 -80
- data/lib/packetgen/plugin/ike/ke.rb +64 -66
- data/lib/packetgen/plugin/ike/nonce.rb +29 -31
- data/lib/packetgen/plugin/ike/notify.rb +135 -139
- data/lib/packetgen/plugin/ike/payload.rb +58 -57
- data/lib/packetgen/plugin/ike/sa.rb +515 -452
- data/lib/packetgen/plugin/ike/sk.rb +219 -221
- data/lib/packetgen/plugin/ike/ts.rb +223 -223
- data/lib/packetgen/plugin/ike/vendor_id.rb +28 -30
- data/lib/packetgen/plugin/ipsec_version.rb +8 -1
- data/packetgen-plugin-ipsec.gemspec +3 -9
- metadata +8 -77
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6000090bef8439281eed6482a399f0e05b188e7594d3de3e92d32a9088e32b88
|
4
|
+
data.tar.gz: 0a753bf7266628b968ca9937f9d8f41c657c0bdd166b233b4c1e94eef4c71f89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/.rubocop.yml
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
-
|
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:
|
data/.travis.yml
CHANGED
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
|
-
|
11
|
-
|
12
|
-
|
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
|
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
|
72
|
+
@intg&.update(data)
|
71
73
|
@conf.update(data)
|
72
74
|
end
|
73
75
|
end
|
data/lib/packetgen/plugin/esp.rb
CHANGED
@@ -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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
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
|
-
#
|
155
|
+
# This method removes all data from +tfc+ and +padding+ fields, as their
|
156
|
+
# enciphered values are concatenated into +body+.
|
67
157
|
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
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
|
-
|
231
|
-
|
232
|
-
|
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
|
-
|
235
|
-
|
234
|
+
# reset padding field as it has no sense in encrypted ESP
|
235
|
+
self[:padding].read ''
|
236
236
|
|
237
|
-
|
237
|
+
set_esp_icv_if_needed
|
238
238
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
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
|
-
|
251
|
-
|
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
|
-
|
297
|
-
|
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
|
-
|
297
|
+
authenticate_esp_header_if_needed options, iv, icv
|
298
|
+
private_decrypt opt
|
299
|
+
end
|
301
300
|
|
302
|
-
|
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
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
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
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
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
|
-
|
335
|
-
|
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
|
-
|
341
|
-
|
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
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
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
|
-
|
349
|
-
|
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
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
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
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
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
|
-
|
391
|
-
|
392
|
-
|
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
|
-
|
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
|
-
|
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
|