packetgen-plugin-ipsec 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rubocop.yml +29 -0
- data/.travis.yml +12 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +97 -0
- data/Rakefile +13 -0
- data/lib/packetgen-plugin-ipsec.rb +4 -0
- data/lib/packetgen/plugin/crypto.rb +74 -0
- data/lib/packetgen/plugin/esp.rb +413 -0
- data/lib/packetgen/plugin/ike.rb +241 -0
- data/lib/packetgen/plugin/ike/auth.rb +165 -0
- data/lib/packetgen/plugin/ike/cert.rb +76 -0
- data/lib/packetgen/plugin/ike/certreq.rb +66 -0
- data/lib/packetgen/plugin/ike/id.rb +99 -0
- data/lib/packetgen/plugin/ike/ke.rb +79 -0
- data/lib/packetgen/plugin/ike/nonce.rb +40 -0
- data/lib/packetgen/plugin/ike/notify.rb +159 -0
- data/lib/packetgen/plugin/ike/payload.rb +304 -0
- data/lib/packetgen/plugin/ike/sa.rb +494 -0
- data/lib/packetgen/plugin/ike/sk.rb +261 -0
- data/lib/packetgen/plugin/ike/ts.rb +260 -0
- data/lib/packetgen/plugin/ike/vendor_id.rb +39 -0
- data/lib/packetgen/plugin/ipsec_version.rb +6 -0
- data/packetgen-plugin-ipsec.gemspec +29 -0
- metadata +152 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d2414b8c5da9828bc956ddac67bdb729bff3d4573f9254532d5b71b02c017165
|
4
|
+
data.tar.gz: 9bdf29105a2cf5ccbbd985126aea57c7a6bc40fbca15ec0ac7883f13ad06bacb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6609a9c5eac34a53a779cecefb1b30092ac0b68350fd697a0e1024ebb517b3b1b278c7bdbef5114a67f1e87b6398a60f23e0bc6a61f35003233ef047599933e8
|
7
|
+
data.tar.gz: fb8d634fb9b0b3b6d5740d780684b2bcdaecedc355c10a6f9bc5e8b9a4e1ddde9c7291e40b3198424d74bb1ceb4078d21571c125cfd15d1d1138a683a2e28551
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
TargetRubyVersion: 2.3
|
2
|
+
Layout/SpaceAroundEqualsInParameterDefault:
|
3
|
+
EnforcedStyle: no_space
|
4
|
+
Lint/EmptyWhen:
|
5
|
+
Enabled: false
|
6
|
+
Lint/Void:
|
7
|
+
Enabled: false
|
8
|
+
Metrics:
|
9
|
+
Enabled: false
|
10
|
+
Style/AsciiComments:
|
11
|
+
Enabled: false
|
12
|
+
Style/Encoding:
|
13
|
+
Enabled: false
|
14
|
+
Style/EvalWithLocation:
|
15
|
+
Enabled: false
|
16
|
+
Style/FormatString:
|
17
|
+
EnforcedStyle: percent
|
18
|
+
Style/FormatStringToken:
|
19
|
+
EnforcedStyle: unannotated
|
20
|
+
Style/PerlBackrefs:
|
21
|
+
Enabled: false
|
22
|
+
Style/RedundantSelf:
|
23
|
+
Enabled: false
|
24
|
+
Style/StructInheritance:
|
25
|
+
Enabled: false
|
26
|
+
Style/TrailingCommaInArrayLiteral:
|
27
|
+
Enabled: false
|
28
|
+
Style/TrailingCommaInHashLiteral:
|
29
|
+
Enabled: false
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Sylvain Daubert
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/packetgen-plugin-ipsec.svg)](https://badge.fury.io/rb/packetgen-plugin-ipsec)
|
2
|
+
[![Build Status](https://travis-ci.com/sdaubert/packetgen-plugin-ipsec.svg?branch=master)](https://travis-ci.com/sdaubert/packetgen-plugin-ipsec)
|
3
|
+
|
4
|
+
# packetgen-plugin-ipsec
|
5
|
+
|
6
|
+
**Warning:** this repository is a work-in-progress. It will be available with packetgen3.
|
7
|
+
|
8
|
+
This is a plugin for [PacketGen gem](https://github.com/sdaubert/packetgen). It adds two protocols:
|
9
|
+
|
10
|
+
* `PacketGen::Plugin::ESP`: IP Encapsulating Security Payload ([RFC 4303](https://tools.ietf.org/html/rfc4303)),
|
11
|
+
* `PacketGen::Plugin::IKE`: Internet Key Exchange v2 ([RFC 7296](https://tools.ietf.org/html/rfc7296)).
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem 'packetgen-plugin-ipsec'
|
19
|
+
```
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install packetgen-plugin-ipsec
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
First, you have to require packetgen-plugin-ipsec:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'packetgen-plugin-ipsec'
|
35
|
+
```
|
36
|
+
|
37
|
+
### Parse an ESP or IKE packet
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
pkt = PacketGen.parse(str)
|
41
|
+
```
|
42
|
+
|
43
|
+
### Read a PcapNG file containing ESP and/or IKE packets
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
pkts = PacketGen.read('ipsec.pcapng')
|
47
|
+
```
|
48
|
+
|
49
|
+
### Access to ESP and IKE headers
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
pkt.esp #=> PacketGen::Plugin::ESP
|
53
|
+
pkt.ike #=> PacketGen::Plugin::IKE
|
54
|
+
```
|
55
|
+
|
56
|
+
### Forge packets
|
57
|
+
|
58
|
+
#### ESP (transport mode)
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
pkt = PacketGen.gen('IP', src: '1.1.1.1', dst: '2.2.2.2').
|
62
|
+
add('ESP', spi: 0xff456e01, sn: 12345678).
|
63
|
+
add('UDP', dport: 4567, sport: 45362, body 'abcdef')
|
64
|
+
cipher = OpenSSL::Cipher.new('aes-128-cbc')
|
65
|
+
cipher.encrypt
|
66
|
+
cipher.key = 16bytes_key
|
67
|
+
iv = 16bytes_iv
|
68
|
+
pkt.esp.esp.encrypt! cipher, iv
|
69
|
+
pkt.to_w
|
70
|
+
```
|
71
|
+
|
72
|
+
#### IKE (IKE_SA_INIT)
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
pkt = PacketGen.gen('IP', src: '1.1.1.1', dst: '2.2.2.2').
|
76
|
+
add('UDP').
|
77
|
+
add('IKE', init_spi: spi, flags: 8).
|
78
|
+
add('IKE::SA').
|
79
|
+
add('IKE::KE', group: 'ECP256', content: key_ex_data).
|
80
|
+
add('IKE::Nonce', content: nonce_data)
|
81
|
+
pkt.ike_sa.proposals << { num: 1, protocol: 'IKE' }
|
82
|
+
pkt.ike_sa.proposals.first.transforms << { type: 'ENCR', id: 'AES_CTR' }
|
83
|
+
pkt.ike_sa.proposals[0].transforms[0].attributes << { type: 0x800e, value: 128 }
|
84
|
+
pkt.to_w
|
85
|
+
```
|
86
|
+
|
87
|
+
## See also
|
88
|
+
|
89
|
+
API documentation: http://www.rubydoc.info/gems/packetgen-plugin-ipsec
|
90
|
+
|
91
|
+
## License
|
92
|
+
|
93
|
+
MIT License (see [LICENSE](https://github.com/sdaubert/packetgen-plugin-ipsec/blob/master/LICENSE))
|
94
|
+
|
95
|
+
## Contributing
|
96
|
+
|
97
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/sdaubert/packetgen-plugin-ipsec.
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
require 'yard'
|
5
|
+
|
6
|
+
task default: :spec
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new
|
9
|
+
|
10
|
+
YARD::Rake::YardocTask.new do |t|
|
11
|
+
t.options = ['--no-private']
|
12
|
+
t.files = ['lib/**/*.rb', '-', 'LICENSE']
|
13
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# This file is part of IPsec packetgen plugin.
|
3
|
+
# See https://github.com/sdaubert/packetgen-plugin-ipsec for more informations
|
4
|
+
# Copyright (c) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
|
5
|
+
# This program is published under MIT license.
|
6
|
+
|
7
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
module PacketGen::Plugin
|
10
|
+
# Mixin for cryptographic classes
|
11
|
+
# @api private
|
12
|
+
# @author Sylvain Daubert
|
13
|
+
module Crypto
|
14
|
+
# Cryptographic error
|
15
|
+
class Error < PacketGen::Error; end
|
16
|
+
|
17
|
+
# Register cryptographic modes
|
18
|
+
# @param [OpenSSL::Cipher] conf
|
19
|
+
# @param [OpenSSL::HMAC] intg
|
20
|
+
# @return [void]
|
21
|
+
def set_crypto(conf, intg)
|
22
|
+
@conf = conf
|
23
|
+
@intg = intg
|
24
|
+
return unless conf.authenticated?
|
25
|
+
# #auth_tag_len only supported from ruby 2.4.0
|
26
|
+
@conf.auth_tag_len = @trunc if @conf.respond_to? :auth_tag_len
|
27
|
+
end
|
28
|
+
|
29
|
+
# Get confidentiality mode name
|
30
|
+
# @return [String]
|
31
|
+
def confidentiality_mode
|
32
|
+
mode = @conf.name.match(/-([^-]*)$/)[1]
|
33
|
+
raise Error, 'unknown cipher mode' if mode.nil?
|
34
|
+
mode.downcase
|
35
|
+
end
|
36
|
+
|
37
|
+
# Say if crypto modes permit authentication
|
38
|
+
# @return [Boolean]
|
39
|
+
def authenticated?
|
40
|
+
@conf.authenticated? || !@intg.nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
# Check authentication
|
44
|
+
# @return [Boolean]
|
45
|
+
def authenticate!
|
46
|
+
@conf.final
|
47
|
+
if @intg
|
48
|
+
@intg.update @esn.to_s if defined? @esn
|
49
|
+
@intg.digest[0, @icv_length] == @icv
|
50
|
+
else
|
51
|
+
true
|
52
|
+
end
|
53
|
+
rescue OpenSSL::Cipher::CipherError
|
54
|
+
false
|
55
|
+
end
|
56
|
+
|
57
|
+
# Encipher +data+
|
58
|
+
# @param [String] data
|
59
|
+
# @return [String] enciphered data
|
60
|
+
def encipher(data)
|
61
|
+
enciphered_data = @conf.update(data)
|
62
|
+
@intg.update(enciphered_data) if @intg
|
63
|
+
enciphered_data
|
64
|
+
end
|
65
|
+
|
66
|
+
# Decipher +data+
|
67
|
+
# @param [String] data
|
68
|
+
# @return [String] deciphered data
|
69
|
+
def decipher(data)
|
70
|
+
@intg.update(data) if @intg
|
71
|
+
@conf.update(data)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,413 @@
|
|
1
|
+
# This file is part of IPsec packetgen plugin.
|
2
|
+
# See https://github.com/sdaubert/packetgen-plugin-ipsec for more informations
|
3
|
+
# Copyright (c) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
|
4
|
+
# This program is published under MIT license.
|
5
|
+
|
6
|
+
# frozen_string_literal: true
|
7
|
+
|
8
|
+
require_relative 'crypto'
|
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
|
60
|
+
#
|
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
|
65
|
+
#
|
66
|
+
# hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
|
67
|
+
#
|
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
|
130
|
+
end
|
131
|
+
|
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
|
152
|
+
end
|
153
|
+
|
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
|
222
|
+
end
|
223
|
+
|
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
|
229
|
+
|
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]
|
233
|
+
|
234
|
+
# reset padding field as it has no sense in encrypted ESP
|
235
|
+
self[:padding].read ''
|
236
|
+
|
237
|
+
set_esp_icv_if_needed
|
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
|
245
|
+
end
|
246
|
+
|
247
|
+
self
|
248
|
+
end
|
249
|
+
|
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
|
295
|
+
|
296
|
+
authenticate_esp_header_if_needed options, iv, icv
|
297
|
+
private_decrypt opt
|
298
|
+
end
|
299
|
+
|
300
|
+
private
|
301
|
+
|
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
|
310
|
+
|
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
|
323
|
+
end
|
324
|
+
|
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
|
332
|
+
end
|
333
|
+
|
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)
|
339
|
+
|
340
|
+
# check authentication tag
|
341
|
+
return false if authenticated? && !authenticate!
|
342
|
+
|
343
|
+
# Set ESP fields
|
344
|
+
self[:body].read plain_msg[0..-3]
|
345
|
+
self[:pad_length].read plain_msg[-2]
|
346
|
+
self[:next].read plain_msg[-1]
|
347
|
+
|
348
|
+
# Set padding
|
349
|
+
if self.pad_length > 0
|
350
|
+
len = self.pad_length
|
351
|
+
self[:padding].read self[:body].slice!(-len, len)
|
352
|
+
end
|
353
|
+
|
354
|
+
# Set TFC padding
|
355
|
+
encap_length = 0
|
356
|
+
pkt = nil
|
357
|
+
case self.next
|
358
|
+
when 4 # IPv4
|
359
|
+
pkt = Packet.parse(body, first_header: 'IP')
|
360
|
+
encap_length = pkt.ip.length
|
361
|
+
when 41 # IPv6
|
362
|
+
pkt = Packet.parse(body, first_header: 'IPv6')
|
363
|
+
encap_length = pkt.ipv6.length + pkt.ipv6.sz
|
364
|
+
when 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
|
384
|
+
|
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
|
389
|
+
|
390
|
+
if options[:parse]
|
391
|
+
packet.encapsulate pkt unless pkt.nil?
|
392
|
+
end
|
393
|
+
|
394
|
+
true
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
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
|
412
|
+
end
|
413
|
+
end
|