-
Notifications
You must be signed in to change notification settings - Fork 298
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
apps/ipsec: Implement ESP (RFC 4303) apps.
- Loading branch information
Showing
8 changed files
with
363 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,4 +60,3 @@ uint16_t htons(uint16_t); | |
uint16_t ntohs(uint16_t); | ||
uint32_t htonl(uint32_t); | ||
uint32_t ntohl(uint32_t); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |