Class: PacketGen::Header::ESP

Inherits:
Struct
  • Object
show all
Extended by:
HeaderClassMethods
Includes:
HeaderMethods, StructFu
Defined in:
lib/packetgen/header/esp.rb

Overview

A ESP header consists of:

  • a Security Parameters Index (##spi, StructFu::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 (StructFu::Int8),

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

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

Create an ESP header

# standalone
esp = PacketGen::Header::ESP.new
# in a packet
pkt = PacketGen.gen('IP').add('ESP')
# access to ESP header
pkt.esp   # => PacketGen::Header::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 HeaderClassMethods

bind_header, define_bit_fields_on, known_headers

Methods included from HeaderMethods

#header_id, #inspect, #ip_header, #packet, #packet=, #protocol_name

Methods included from StructFu

#clone, #set_endianness, #sz, #to_s, #typecast

Methods inherited from Struct

#force_binary

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



93
94
95
96
97
98
99
100
101
102
103
# File 'lib/packetgen/header/esp.rb', line 93

def initialize(options={})
  @icv_length = options[:icv_length] || 0
  super Int32.new(options[:spi]),
        Int32.new(options[:sn]),
        StructFu::String.new.read(options[:body]),
        StructFu::String.new.read(options[:tfc]),
        StructFu::String.new.read(options[:padding]),
        Int8.new(options[:pad_length]),
        Int8.new(options[:next]),
        StructFu::String.new.read(options[:icv])
end

Instance Attribute Details

#bodyObject

Returns the value of attribute body

Returns:

  • (Object)

    the current value of body



65
66
67
# File 'lib/packetgen/header/esp.rb', line 65

def body
  @body
end

#icvObject

Returns the value of attribute icv

Returns:

  • (Object)

    the current value of icv



65
66
67
# File 'lib/packetgen/header/esp.rb', line 65

def icv
  @icv
end

#icv_lengthInteger

ICV (Integrity Check Value) length

Returns:

  • (Integer)


79
80
81
# File 'lib/packetgen/header/esp.rb', line 79

def icv_length
  @icv_length
end

#nextInteger

Getter for next attribute

Returns:

  • (Integer)


65
66
67
# File 'lib/packetgen/header/esp.rb', line 65

def next
  @next
end

#pad_lengthInteger

Getter for pad_length attribute

Returns:

  • (Integer)


65
66
67
# File 'lib/packetgen/header/esp.rb', line 65

def pad_length
  @pad_length
end

#paddingObject

Returns the value of attribute padding

Returns:

  • (Object)

    the current value of padding



65
66
67
# File 'lib/packetgen/header/esp.rb', line 65

def padding
  @padding
end

#snInteger

Getter for SN attribute

Returns:

  • (Integer)


65
66
67
# File 'lib/packetgen/header/esp.rb', line 65

def sn
  @sn
end

#spiInteger

Getter for SPI attribute

Returns:

  • (Integer)


65
66
67
# File 'lib/packetgen/header/esp.rb', line 65

def spi
  @spi
end

#tfcObject

Returns the value of attribute tfc

Returns:

  • (Object)

    the current value of tfc



65
66
67
# File 'lib/packetgen/header/esp.rb', line 65

def tfc
  @tfc
end

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



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/packetgen/header/esp.rb', line 289

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? and (@icv_length == 0 or opt[:icv_length])
    raise 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, self[:icv]
  private_decrypt cipher, 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)


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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/packetgen/header/esp.rb', line 206

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 > 0
  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] - body.sz
    if tfc_size > 0
      case confidentiality_mode
      when 'cbc'
        tfc_size = (tfc_size / 16) * 16
      else
        tfc_size = (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] = StructFu::String.new(iv) << 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:

Returns:

  • (self)

Raises:



112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/packetgen/header/esp.rb', line 112

def read(str)
  return self if str.nil?
  raise ParseError, 'string too short for ESP' if str.size < self.sz
  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