Class: PacketGen::Plugin::ESP

Inherits:
Header::Base
  • Object
show all
Includes:
Crypto
Defined in:
lib/packetgen/plugin/esp.rb

Overview

A ESP header consists of:

  • a Security Parameters Index (##spi, Types::Int32 type),

  • a Sequence Number (#sn, Int32 type),

  • a #body (variable length),

  • an optional TFC padding (#tfc, variable length),

  • an optional #padding (to align ESP on 32-bit boundary, variable length),

  • a #pad_length (Types::Int8),

  • a Next header field (#next, Int8),

  • and an optional Integrity Check Value (#icv, variable length).

Create an ESP header

# standalone
esp = PacketGen::Plugin::ESP.new
# in a packet
pkt = PacketGen.gen('IP').add('ESP')
# access to ESP header
pkt.esp   # => PacketGen::Plugin::ESP

Examples

Create an enciphered UDP packet (ESP transport mode), using CBC mode

icmp = PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.2.1').
                 add('ESP', spi: 0xff456e01, sn: 12345678).
                 add('UDP', dport: 4567, sport: 45362, body 'abcdef')
cipher = OpenSSL::Cipher.new('aes-128-cbc')
cipher.encrypt
cipher.key = 16bytes_key
iv = 16bytes_iv
esp.esp.encrypt! cipher, iv

Create a ESP packet tunneling a UDP one, using GCM combined mode

# create inner UDP packet
icmp = PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.2.1').
                 add('UDP', dport: 4567, sport: 45362, body 'abcdef')

# create outer ESP packet
esp = PacketGen.gen('IP', src '198.76.54.32', dst: '1.2.3.4').add('ESP')
esp.esp.spi = 0x87654321
esp.esp.sn  = 0x123
esp.esp.icv_length = 16
# encapsulate ICMP packet in ESP one
esp.encapsulate icmp

# encrypt ESP payload
cipher = OpenSSL::Cipher.new('aes-128-gcm')
cipher.encrypt
cipher.key = 16bytes_key
iv = 8bytes_iv
esp.esp.encrypt! cipher, iv, salt: 4bytes_gcm_salt

Decrypt a ESP packet using CBC mode and HMAC-SHA-256

cipher = OpenSSL::Cipher.new('aes-128-cbc')
cipher.decrypt
cipher.key = 16bytes_key

hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)

pkt.esp.decrypt! cipher, intmode: hmac    # => true if ICV check OK

Author:

  • Sylvain Daubert

Constant Summary collapse

IP_PROTOCOL =

IP protocol number for ESP

50
UDP_PORT =

Well-known UDP port for ESP

4500

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Crypto

#authenticate!, #authenticated?, #confidentiality_mode, #decipher, #encipher, #set_crypto

Constructor Details

#initialize(options = {}) ⇒ ESP

Returns a new instance of ESP.

Parameters:

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :icv_length (Integer)

    ICV length

  • :spi (Integer)

    Security Parameters Index

  • :sn (Integer)

    Sequence Number

  • :body (::String)

    ESP payload data

  • :tfc (::String)

    Traffic Flow Confidentiality, random padding up to MTU

  • :padding (::String)

    ESP padding to align ESP on 32-bit boundary

  • :pad_length (Integer)

    padding length

  • :next (Integer)

    Next Header field

  • :icv (::String)

    Integrity Check Value



126
127
128
129
# File 'lib/packetgen/plugin/esp.rb', line 126

def initialize(options={})
  @icv_length = options[:icv_length] || 0
  super
end

Instance Attribute Details

#bodyPacketGen::Types::String, PacketGen::Header::Base

Returns:

  • (PacketGen::Types::String, PacketGen::Header::Base)


88
# File 'lib/packetgen/plugin/esp.rb', line 88

define_field :body, PacketGen::Types::String

#icvPacketGen::Types::String, PacketGen::Header::Base

Integrity Check Value

Returns:

  • (PacketGen::Types::String, PacketGen::Header::Base)


108
# File 'lib/packetgen/plugin/esp.rb', line 108

define_field :icv, PacketGen::Types::String

#icv_lengthInteger

ICV (Integrity Check Value) length

Returns:

  • (Integer)


112
113
114
# File 'lib/packetgen/plugin/esp.rb', line 112

def icv_length
  @icv_length
end

#nextInteger

8-bit next protocol value

Returns:

  • (Integer)


104
# File 'lib/packetgen/plugin/esp.rb', line 104

define_field :next, PacketGen::Types::Int8

#pad_lengthInteger

8-bit padding length

Returns:

  • (Integer)


100
# File 'lib/packetgen/plugin/esp.rb', line 100

define_field :pad_length, PacketGen::Types::Int8

#paddingPacketGen::Types::String, PacketGen::Header::Base

ESP padding

Returns:

  • (PacketGen::Types::String, PacketGen::Header::Base)


96
# File 'lib/packetgen/plugin/esp.rb', line 96

define_field :padding, PacketGen::Types::String

#snInteger

32-bit Sequence Number

Returns:

  • (Integer)


85
# File 'lib/packetgen/plugin/esp.rb', line 85

define_field :sn, PacketGen::Types::Int32

#spiInteger

32-bit Security Parameter Index

Returns:

  • (Integer)


81
# File 'lib/packetgen/plugin/esp.rb', line 81

define_field :spi, PacketGen::Types::Int32

#tfcPacketGen::Types::String, PacketGen::Header::Base

Traffic Flow Confidentiality padding

Returns:

  • (PacketGen::Types::String, PacketGen::Header::Base)


92
# File 'lib/packetgen/plugin/esp.rb', line 92

define_field :tfc, PacketGen::Types::String

Instance Method Details

#decrypt!(cipher, options = {}) ⇒ Boolean

Decrypt in-place ESP payload and trailer.

Parameters:

  • cipher (OpenSSL::Cipher)

    keyed cipher This cipher is confidentiality-only one, or AEAD one. To use a second cipher to add integrity, use :intmode option.

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :parse (Boolean)

    parse deciphered payload to retrieve headers (default: true)

  • :icv_length (Fixnum)

    ICV length for captured packets, or read from PCapNG files

  • :salt (String)

    salt value for CTR and GCM modes

  • :esn (Fixnum)

    32 high-orber bits of ESN

  • :intmode (OpenSSL::HMAC)

    integrity mode to use with a confidentiality-only cipher. Only HMAC are supported.

Returns:

  • (Boolean)

    true if ESP packet is authenticated



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/packetgen/plugin/esp.rb', line 264

def decrypt!(cipher, options={})
  opt = { salt: '', parse: true }.merge(options)

  set_crypto cipher, opt[:intmode]

  case confidentiality_mode
  when 'gcm'
    iv = self[:body].slice!(0, 8)
    real_iv = opt[:salt] + iv
  when 'cbc'
    cipher.padding = 0
    real_iv = iv = self[:body].slice!(0, 16)
  when 'ctr'
    iv = self[:body].slice!(0, 8)
    real_iv = opt[:salt] + iv + [1].pack('N')
  else
    real_iv = iv = self[:body].slice!(0, 16)
  end
  cipher.iv = real_iv

  if authenticated? && (@icv_length.zero? || opt[:icv_length])
    raise PacketGen::ParseError, 'unknown ICV size' unless opt[:icv_length]

    @icv_length = opt[:icv_length].to_i
    # reread ESP to handle new ICV size
    msg = self[:body].to_s + self[:pad_length].to_s
    msg += self[:next].to_s
    self[:icv].read msg.slice!(-@icv_length, @icv_length)
    self[:body].read msg[0..-3]
    self[:pad_length].read msg[-2]
    self[:next].read msg[-1]
  end

  authenticate_esp_header_if_needed options, iv, icv
  private_decrypt opt
end

#encrypt!(cipher, iv, options = {}) ⇒ self

Encrypt in-place ESP payload and trailer.

This method removes all data from tfc and padding fields, as their enciphered values are concatenated into body.

It also removes headers under ESP from packet, as they are enciphered in ESP body, and then are no more accessible.

Parameters:

  • cipher (OpenSSL::Cipher)

    keyed cipher. This cipher is confidentiality-only one, or AEAD one. To use a second cipher to add integrity, use :intmode option.

  • iv (String)

    full IV for encryption

    • CTR and GCM modes: iv is 8-bytes long.

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :salt (String)

    salt value for CTR and GCM modes

  • :tfc (Boolean)
  • :tfc_size (Fixnum)

    ESP body size used for TFC (default 1444, max size for a tunneled IPv4/ESP packet). This is the maximum size for ESP packet (without IP header nor Eth one).

  • :esn (Fixnum)

    32 high-orber bits of ESN

  • :pad_length (Fixnum)

    set a padding length

  • :padding (String)

    set a padding. No check with :pad_length is made. If :pad_length is not set, :padding length is shortened to correct padding length

  • :intmode (OpenSSL::HMAC)

    integrity mode to use with a confidentiality-only cipher. Only HMAC are supported.

Returns:

  • (self)


180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/packetgen/plugin/esp.rb', line 180

def encrypt!(cipher, iv, options={})
  opt = { salt: '', tfc_size: 1444 }.merge(options)

  set_crypto cipher, opt[:intmode]

  real_iv = force_binary(opt[:salt]) + force_binary(iv)
  real_iv += [1].pack('N') if confidentiality_mode == 'ctr'
  cipher.iv = real_iv

  authenticate_esp_header_if_needed options, iv

  case confidentiality_mode
  when 'cbc'
    cipher_len = self[:body].sz + 2
    self.pad_length = (16 - (cipher_len % 16)) % 16
  else
    mod4 = to_s.size % 4
    self.pad_length = 4 - mod4 if mod4.positive?
  end

  if opt[:pad_length]
    self.pad_length = opt[:pad_length]
    padding = force_binary(opt[:padding] || (1..self.pad_length).to_a.pack('C*'))
    self[:padding].read padding
  else
    padding = force_binary(opt[:padding] || (1..self.pad_length).to_a.pack('C*'))
    self[:padding].read padding[0...self.pad_length]
  end

  tfc = ''
  if opt[:tfc]
    tfc_size = opt[:tfc_size] - self[:body].sz
    if tfc_size.positive?
      tfc_size = case confidentiality_mode
                 when 'cbc'
                   (tfc_size / 16) * 16
                 else
                   (tfc_size / 4) * 4
                 end
      tfc = force_binary("\0" * tfc_size)
    end
  end

  msg = self[:body].to_s + tfc
  msg += self[:padding].to_s + self[:pad_length].to_s + self[:next].to_s
  enc_msg = encipher(msg)
  # as padding is used to pad for CBC mode, this is unused
  cipher.final

  self[:body] = PacketGen::Types::String.new.read(iv)
  self[:body] << enc_msg[0..-3]
  self[:pad_length].read enc_msg[-2]
  self[:next].read enc_msg[-1]

  # reset padding field as it has no sense in encrypted ESP
  self[:padding].read ''

  set_esp_icv_if_needed

  # Remove enciphered headers from packet
  id = header_id(self)
  if id < packet.headers.size - 1
    (packet.headers.size - 1).downto(id + 1) do |index|
      packet.headers.delete_at index
    end
  end

  self
end

#read(str) ⇒ self

Read a ESP packet from string.

#padding and #tfc are not set as they are enciphered (impossible to guess their respective size). #pad_length and #next are also enciphered.

Parameters:

  • str (String)

Returns:

  • (self)


138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/packetgen/plugin/esp.rb', line 138

def read(str)
  return self if str.nil?

  force_binary str
  self[:spi].read str[0, 4]
  self[:sn].read str[4, 4]
  self[:body].read str[8...-@icv_length - 2]
  self[:tfc].read ''
  self[:padding].read ''
  self[:pad_length].read str[-@icv_length - 2, 1]
  self[:next].read str[-@icv_length - 1, 1]
  self[:icv].read str[-@icv_length, @icv_length] if @icv_length
  self
end