Skip to content

Commit

Permalink
apps/ipsec: Implement ESP (RFC 4303) apps.
Browse files Browse the repository at this point in the history
  • Loading branch information
eugeneia committed Dec 11, 2015
1 parent e285507 commit e855537
Show file tree
Hide file tree
Showing 8 changed files with 363 additions and 1 deletion.
92 changes: 92 additions & 0 deletions src/apps/ipsec/aes_128_gcm.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
module(..., package.seeall)
local ffi = require("ffi")
local header = require("lib.protocol.header")
local lib = require("core.lib")
local ntohl, htonl, htonll = lib.ntohl, lib.htonl, lib.htonll


-- IV pseudo header

local iv = subClass(header)

-- Class variables
iv._name = "iv"
iv:init(
{
[1] = ffi.typeof[[
struct {
uint8_t salt[4];
uint64_t iv;
uint32_t padding;
} __attribute__((packed))
]]
})

-- Class methods

function iv:new (salt)
local o = iv:superClass().new(self)
local h = o:header()
o:salt(salt)
h.padding = htonl(0x1)
return o
end

-- Instance methods

function iv:salt (salt)
local h = self:header()
if salt ~= nil then
ffi.copy(h.salt, salt, 4)
else
return h.salt
end
end

function iv:iv (iv)
local h = self:header()
if iv ~= nil then
h.iv = htonll(iv)
else
return self:header_ptr()+4, 8
end
end


-- AES-128-GCM wrapper

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.")
local o = {}
o.keymat = lib.hexundump(conf.keymat, 16)
o.iv = iv:new(lib.hexundump(conf.salt, 4))
o.blocksize = 128
o.auth_size = 16
o.aad_size = 16
return setmetatable(o, {__index=aes_128_gcm})
end

function aes_128_gcm:encrypt (out_ptr, payload, length, esp)
self.iv:iv(esp:seq_no())
-- encrypt_in_place(self.keymat,
-- out_ptr,
-- payload, length,
-- self.iv:header_ptr(),
-- esp:header_ptr(), esp:sizeof(),
-- payload + length, self.auth_size)
end

function aes_128_gcm:decrypt (out_ptr, ciphertext, length, esp)
self.iv:iv(esp:seq_no())
-- encrypt_in_place(self.keymat,
-- out_ptr,
-- ciphertext, length,
-- self.iv:header_ptr(),
-- esp:header_ptr(), esp:sizeof(),
-- ciphertext + length, self.auth_size)
end

return aes_128_gcm
155 changes: 155 additions & 0 deletions src/apps/ipsec/ipsec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
module(..., package.seeall)
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 lib = require("core.lib")
local ffi = require("ffi")


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 {}
assert(conf.mode == "aes-128-gcm", "Only supports aes-128-gcm.")
return { aes_128_gcm = aes_128_gcm:new(conf), seq_no = 0 }
end


local esp_v6_encrypt = {}

function esp_v6_encrypt:new (arg)
local o = esp_v6_new(arg)
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
o.esp = esp:new_from_mem(o.esp_buf, esp_length)
o.esp:spi(0x0) -- Fix me, set esp:spi value.
o.esp_tail = esp_tail:new({})
return setmetatable(o, {__index=esp_v6_encrypt})
end

-- Return next sequence number.
function esp_v6_encrypt:next_seq_no ()
self.seq_no = self.seq_no + 1
return self.seq_no
end

function esp_v6_encrypt:encrypt (nh, payload, length)
local p = packet.allocate()
self.esp:seq_no(self:next_seq_no())
packet.append(p, self.esp:header_ptr(), esp_length)
packet.append(p, payload, length)
local pad_length = self.aes_128_gcm.blocksize
- ((length + esp_tail_length) % self.aes_128_gcm.blocksize)
packet.append(p, self.pad_buf, pad_length)
self.esp_tail:next_header(nh)
self.esp_tail:pad_length(pad_length)
packet.append(p, self.esp_tail:header_ptr(), esp_tail_length)
packet.append(p, self.pad_buf, self.aes_128_gcm.auth_size)
self.aes_128_gcm:encrypt(packet.data(p) + esp_length,
packet.data(p) + esp_length,
length + pad_length + esp_tail_length,
self.esp)
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
end


local esp_v6_decrypt = {}

function esp_v6_decrypt:new (arg)
local o = esp_v6_new(arg)
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})
end

-- Verify sequence number.
function esp_v6_decrypt:check_seq_no (seq_no)
self.seq_no = self.seq_no + 1
return self.seq_no <= seq_no
end

function esp_v6_decrypt:decrypt (payload, length)
if length < self.min_payload_length
or (length - self.esp_overhead_size) % self.aes_128_gcm.blocksize ~= 0
then return end
local data_start = payload + esp_length
local data_length = length - esp_length - self.aes_128_gcm.auth_size
local esp = esp:new_from_mem(payload, esp_length)
-- How do we know if authentication failed?
self.aes_128_gcm:decrypt(data_start, data_start, data_length, esp)
local esp_tail_start = data_start + data_length - esp_tail_length
local esp_tail = esp_tail:new_from_mem(esp_tail_start, esp_tail_length)
local cleartext_length = data_length - esp_tail:pad_length() - esp_tail_length
local p = packet.from_pointer(data_start, cleartext_length)
return esp:seq_no(), p, esp_tail:next_header()
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
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 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})
-- Check integrity
if io.open(input_file):read('*a') ~= io.open(output_file):read('*a') then
print("selftest failed")
os.exit(1)
end
print("selftest passed")
end
1 change: 0 additions & 1 deletion src/core/clib.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,3 @@ uint16_t htons(uint16_t);
uint16_t ntohs(uint16_t);
uint32_t htonl(uint32_t);
uint32_t ntohl(uint32_t);

12 changes: 12 additions & 0 deletions src/core/lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,15 @@ void prefetch_for_write(const void *address)
__builtin_prefetch(address, 1);
}

/* Bitswap uint64_t. */
uint64_t bswap64 (uint64_t b)
{
return ((((uint64_t) b & (uint64_t) 0x00000000000000ff) << 56) |
(((uint64_t) b & (uint64_t) 0x000000000000ff00) << 40) |
(((uint64_t) b & (uint64_t) 0x0000000000ff0000) << 24) |
(((uint64_t) b & (uint64_t) 0x00000000ff000000) << 8) |
(((uint64_t) b & (uint64_t) 0x000000ff00000000) >> 8) |
(((uint64_t) b & (uint64_t) 0x0000ff0000000000) >> 24) |
(((uint64_t) b & (uint64_t) 0x00ff000000000000) >> 40) |
(((uint64_t) b & (uint64_t) 0xff00000000000000) >> 56));
}
1 change: 1 addition & 0 deletions src/core/lib.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ void full_memory_barrier();
void prefetch_for_read(const void *address);
void prefetch_for_write(const void *address);
unsigned int stat_mtime(const char *path);
uint64_t bswap64 (uint64_t b);
4 changes: 4 additions & 0 deletions src/core/lib.lua
Original file line number Diff line number Diff line change
Expand Up @@ -351,15 +351,19 @@ end
-- avoid C function call overhead while using C.xxxx counterparts
if ffi.abi("be") then
-- nothing to do
function htonll(b) return b end
function htonl(b) return b end
function htons(b) return b end
else
function htonll(b) return C.bswap64(b) end
function htonl(b) return bswap(b) end
function htons(b) return rshift(bswap(b), 16) end
end
ntohll = htonll
ntohl = htonl
ntohs = htons


-- Manipulation of bit fields in uint{8,16,32)_t stored in network
-- byte order. Using bit fields in C structs is compiler-dependent
-- and a little awkward for handling endianness and fields that cross
Expand Down
51 changes: 51 additions & 0 deletions src/lib/protocol/esp.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
module(..., package.seeall)
local ffi = require("ffi")
local header = require("lib.protocol.header")
local lib = require("core.lib")
local ntohl, htonl = lib.ntohl, lib.htonl
local ntohll, htonll = lib.ntohll, lib.htonll

local esp = subClass(header)

-- Class variables
esp._name = "esp"
esp:init(
{
[1] = ffi.typeof[[
struct {
uint32_t spi;
uint64_t seq_no;
} __attribute__((packed))
]]
})

-- Class methods

function esp:new (config)
local o = esp:superClass().new(self)
o:spi(config.spi)
o:seq_no(config.seq_no)
return o
end

-- Instance methods

function esp:spi (spi)
local h = self:header()
if spi ~= nil then
h.spi = htonl(spi)
else
return(ntohl(h.spi))
end
end

function esp:seq_no (seq_no)
local h = self:header()
if seq_no ~= nil then
h.seq_no = htonll(seq_no)
else
return(ntohll(h.seq_no))
end
end

return esp
48 changes: 48 additions & 0 deletions src/lib/protocol/esp_tail.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module(..., package.seeall)
local ffi = require("ffi")
local header = require("lib.protocol.header")

local esp_tail = subClass(header)

-- Class variables
esp_tail._name = "esp_tail"
esp_tail:init(
{
[1] = ffi.typeof[[
struct {
uint8_t pad_length;
uint8_t next_header;
} __attribute__((packed))
]]
})

-- Class methods

function esp_tail:new (config)
local o = esp_tail:superClass().new(self)
o:pad_length(config.pad_length)
o:next_header(config.next_header)
return o
end

-- Instance methods

function esp_tail:pad_length (length)
local h = self:header()
if length ~= nil then
h.pad_length = length
else
return h.pad_length
end
end

function esp_tail:next_header (next_header)
local h = self:header()
if next_header ~= nil then
h.next_header = next_header
else
return h.next_header
end
end

return esp_tail

0 comments on commit e855537

Please sign in to comment.