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 +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
|