Skip to content

Commit

Permalink
Convert IPsec ESP apps to library (lib.ipsec.esp).
Browse files Browse the repository at this point in the history
  • Loading branch information
eugeneia committed Jan 20, 2016
1 parent 2c020d5 commit 985c469
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 146 deletions.
36 changes: 0 additions & 36 deletions src/apps/ipsec/README.md

This file was deleted.

47 changes: 0 additions & 47 deletions src/apps/ipsec/README.md.src

This file was deleted.

File renamed without changes
45 changes: 45 additions & 0 deletions src/lib/ipsec/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
### IPsec/ESP (lib.ipsec.esp)

The `lib.ipsec.esp` module contains two classes `esp_v6_encrypt` and
`esp_v6_decrypt` which implement implement packet encryption and
decryption with IPsec ESP using the AES-GCM-128 cipher in IPv6 transport
mode. Packets are encrypted with the key and salt provided to the classes
constructors. These classes do not implement any key exchange protocol.

The encrypt class accepts IPv6 packets and inserts a new [ESP
header](https://en.wikipedia.org/wiki/IPsec#Encapsulating_Security_Payload)
between the outer IPv6 header and the inner protocol header (e.g. TCP,
UDP, L2TPv3) and also encrypts the contents of the inner protocol
header. The decrypt class does the reverse: it decrypts the inner
protocol header and removes the ESP protocol header.

References:

- [IPsec Wikipedia page](https://en.wikipedia.org/wiki/IPsec).
- [RFC 4106](https://tools.ietf.org/html/rfc4106) on using AES-GCM with IPsec ESP.
- [LISP Data-Plane Confidentiality](https://tools.ietf.org/html/draft-ietf-lisp-crypto-02) example of a software layer above these apps that includes key exchange.

— Method **esp_v6_encrypt:new** *config*

— Method **esp_v6_decrypt:new** *config*

Returns a new encryption/decryption context respectively. *Config* must a
be a table with the following keys:

* `mode` - Encryption mode (string). The only accepted value is the
string `"aes-128-gcm"`.
* `keymat` - Hex string containing 16 bytes of key material as specified
in RFC 4106.
* `salt` - Hex string containing four bytes of salt as specified in
RFC 4106.

— Method **esp_v6_encrypt:encapsulate** *packet*

Returns a freshly allocated packet that is the encrypted and encapsulated
version of *packet*.

— Method **esp_v6_decrypt:decapsulate** *packet*

Returns a freshly allocated packet that is the decrypted and decapsulated
version of *packet* or `nil` if authentication failed. The contents of
*packet* are destroyed in the process.
12 changes: 6 additions & 6 deletions src/apps/ipsec/aes_128_gcm.lua → src/lib/ipsec/aes_128_gcm.lua
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module(..., package.seeall)
local ffi = require("ffi")
local C = ffi.C
local ASM = require("apps.ipsec.aes_128_gcm_avx")
local ASM = require("lib.ipsec.aes_128_gcm_avx")
local header = require("lib.protocol.header")
local lib = require("core.lib")
local ntohl, htonl, htonll = lib.ntohl, lib.htonl, lib.htonll
Expand Down Expand Up @@ -61,13 +61,13 @@ local function u8_ptr (ptr) return ffi.cast("uint8_t *", ptr) end

local aes_128_gcm = {}

function aes_128_gcm:new (conf)
assert(conf.keymat and #conf.keymat == 32, "Need 16 bytes of key material.")
assert(conf.salt and #conf.salt == 8, "Need 4 bytes of salt.")
function aes_128_gcm:new (keymat, salt)
assert(keymat and #keymat == 32, "Need 16 bytes of key material.")
assert(salt and #salt == 8, "Need 4 bytes of salt.")
local o = {}
o.keymat = ffi.new("uint8_t[16]")
ffi.copy(o.keymat, lib.hexundump(conf.keymat, 16), 16)
o.iv = iv:new(lib.hexundump(conf.salt, 4))
ffi.copy(o.keymat, lib.hexundump(keymat, 16), 16)
o.iv = iv:new(lib.hexundump(salt, 4))
-- Compute subkey (H)
o.hash_subkey = ffi.new("uint8_t[?] __attribute__((aligned(16)))", 128)
o.gcm_data = ffi.new("gcm_data[1] __attribute__((aligned(16)))")
Expand Down
File renamed without changes.
114 changes: 57 additions & 57 deletions src/apps/ipsec/ipsec.lua → src/lib/ipsec/esp.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ local datagram = require("lib.protocol.datagram")
local ethernet = require("lib.protocol.ethernet")
local esp = require("lib.protocol.esp")
local esp_tail = require("lib.protocol.esp_tail")
local aes_128_gcm = require("apps.ipsec.aes_128_gcm")
local aes_128_gcm = require("lib.ipsec.aes_128_gcm")
local lib = require("core.lib")
local ffi = require("ffi")

Expand All @@ -12,17 +12,17 @@ local esp_nh = 50 -- https://tools.ietf.org/html/rfc4303#section-2
local esp_length = esp:sizeof()
local esp_tail_length = esp_tail:sizeof()

function esp_v6_new (arg)
local conf = arg and config.parse_app_arg(arg) or {}
function esp_v6_new (conf)
assert(conf.mode == "aes-128-gcm", "Only supports aes-128-gcm.")
return { aes_128_gcm = aes_128_gcm:new(conf), seq_no = 0 }
return { aes_128_gcm = aes_128_gcm:new(conf.keymat, conf.salt),
seq_no = 0 }
end


local esp_v6_encrypt = {}

function esp_v6_encrypt:new (arg)
local o = esp_v6_new(arg)
function esp_v6_encrypt:new (conf)
local o = esp_v6_new(conf)
o.pad_buf = ffi.new("uint8_t[?]", o.aes_128_gcm.blocksize-1)
o.esp_buf = ffi.new("uint8_t[?]", o.aes_128_gcm.aad_size)
-- Fix me https://tools.ietf.org/html/rfc4303#section-3.3.3
Expand Down Expand Up @@ -57,29 +57,25 @@ function esp_v6_encrypt:encrypt (nh, payload, length)
return p
end

function esp_v6_encrypt:push ()
for n = 1,math.min(link.nreadable(self.input.input),
link.nwritable(self.output.output)) do
local plain = datagram:new(link.receive(self.input.input), ethernet)
local eth = plain:parse_match()
local ip = plain:parse_match()
local nh = ip:next_header()
local encrypted = datagram:new(self:encrypt(nh, plain:payload()))
local _, length = encrypted:payload()
ip:next_header(esp_nh)
ip:payload_length(length)
encrypted:push(ip)
encrypted:push(eth)
link.transmit(self.output.output, encrypted:packet())
packet.free(plain:packet())
end
function esp_v6_encrypt:encapsulate (p)
local plain = datagram:new(p, ethernet)
local eth = plain:parse_match()
local ip = plain:parse_match()
local nh = ip:next_header()
local encrypted = datagram:new(self:encrypt(nh, plain:payload()))
local _, length = encrypted:payload()
ip:next_header(esp_nh)
ip:payload_length(length)
encrypted:push(ip)
encrypted:push(eth)
return encrypted:packet()
end


local esp_v6_decrypt = {}

function esp_v6_decrypt:new (arg)
local o = esp_v6_new(arg)
function esp_v6_decrypt:new (conf)
local o = esp_v6_new(conf)
o.esp_overhead_size = esp_length + o.aes_128_gcm.auth_size
o.min_payload_length = o.aes_128_gcm.blocksize + o.esp_overhead_size
return setmetatable(o, {__index=esp_v6_decrypt})
Expand Down Expand Up @@ -107,49 +103,53 @@ function esp_v6_decrypt:decrypt (payload, length)
end
end

function esp_v6_decrypt:push ()
for n = 1,math.min(link.nreadable(self.input.input),
link.nwritable(self.output.output)) do
local encrypted = datagram:new(link.receive(self.input.input), ethernet)
local eth = encrypted:parse_match()
local ip = encrypted:parse_match()
if ip:next_header() == esp_nh then
local seq_no, payload, nh = self:decrypt(encrypted:payload())
if payload and self:check_seq_no(seq_no) then
local plain = datagram:new(payload)
ip:next_header(nh)
ip:payload_length(packet.length(payload))
plain:push(ip)
plain:push(eth)
link.transmit(self.output.output, plain:packet())
end
function esp_v6_decrypt:decapsulate (p)
local encrypted = datagram:new(p, ethernet)
local eth = encrypted:parse_match()
local ip = encrypted:parse_match()
local decrypted = nil
if ip:next_header() == esp_nh then
local seq_no, payload, nh = self:decrypt(encrypted:payload())
if payload and self:check_seq_no(seq_no) then
local plain = datagram:new(payload)
ip:next_header(nh)
ip:payload_length(packet.length(payload))
plain:push(ip)
plain:push(eth)
return plain:packet()
end
packet.free(encrypted:packet())
end
end


function selftest ()
local pcap = require("apps.pcap.pcap")
local input_file = "apps/keyed_ipv6_tunnel/selftest.cap.input"
local output_file = "apps/ipsec/selftest.cap.output"
local C = require("ffi").C
local ipv6 = require("lib.protocol.ipv6")
local conf = { mode = "aes-128-gcm",
keymat = "00112233445566778899AABBCCDDEEFF",
salt = "00112233"}
local c = config.new()
config.app(c, "PcapReader", pcap.PcapReader, input_file)
config.app(c, "Encrypt", esp_v6_encrypt, conf)
config.app(c, "Decrypt", esp_v6_decrypt, conf)
config.app(c, "PcapWriter", pcap.PcapWriter, output_file)
config.link(c, "PcapReader.output -> Encrypt.input")
config.link(c, "Encrypt.output -> Decrypt.input")
config.link(c, "Decrypt.output -> PcapWriter.input")
engine.configure(c)
engine.main({duration=0.1})
local enc, dec = esp_v6_encrypt:new(conf), esp_v6_decrypt:new(conf)
local payload = packet.from_string(
[[abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
0123456789]]
)
local d = datagram:new(payload)
local ip = ipv6:new({})
ip:payload_length(packet.length(payload))
d:push(ip)
d:push(ethernet:new({type=0x86dd}))
-- Check integrity
if io.open(input_file):read('*a') ~= io.open(output_file):read('*a') then
print("selftest failed")
local p = d:packet()
print("original", lib.hexdump(ffi.string(packet.data(p), packet.length(p))))
local p_enc = enc:encapsulate(packet.clone(p))
print("encrypted", lib.hexdump(ffi.string(packet.data(p_enc), packet.length(p_enc))))
local p2 = dec:decapsulate(p_enc)
print("decrypted", lib.hexdump(ffi.string(packet.data(p2), packet.length(p2))))
if p2 and p2.length == p.length and C.memcmp(p, p2, p.length) == 0 then
print("selftest passed")
else
print("integrity check failed")
os.exit(1)
end
print("selftest passed")
end

0 comments on commit 985c469

Please sign in to comment.