This commit is contained in:
Ken McMillan 2018-11-08 18:44:23 -08:00
Родитель 49c9b061e7
Коммит 489a50db05
6 изменённых файлов: 492 добавлений и 88 удалений

Просмотреть файл

@ -426,7 +426,14 @@ around tls_recv_event {
# crypto_pos(pcid,0) := crypto_pos(pcid,0) + data.end
}
#
# Client initial request event
# ============================
#
# This event occurs when a client transmits in initial request to open
# a connection.
action client_initial_request(src:ip.endpoint,dst:ip.endpoint,pkt:quic_packet)
# Packet events
# -------------
@ -640,8 +647,9 @@ around packet_event(src:ip.endpoint,dst:ip.endpoint,pkt:quic_packet) {
| (pkt.payload.value(I) isa frame.connection_close));
# require ~conn_closed(src,scid); # [6]
# An initial packet with an unseen source cid is a connection request.
if ~conn_seen(scid) {
# An initial packet with an unseen destination cid is a connection request.
if ~conn_seen(dcid) {
call client_initial_request(src,dst,pkt);
conn_requested(src,dst,scid) := true;
};

Просмотреть файл

@ -0,0 +1,191 @@
#lang ivy1.7
# Here, we define functions for QUIC packet packet protection.
include tls_msg
include byte_stream
include quic_types
module quic_protection(tls) = {
# Protect a packet, given the TLS session and the actual (not compressed)
# sequence number of the packet;
action encrypt(c:tls.cid,seq:pkt_num,pkt:stream_data) returns (pkt:stream_data)
# Protect a packet, given the TLS session and the actual (not
# compressed) highest sequence number of any packet received so
# far. The latter is used to guess the real sequence number of the
# packet, which is needed to decrypt the payload. This means that
# a significantly delayed packet might not be successfully
# decrypted, since we might not guess its full sequence number
# correctly. The return parameter `ok` is true if the packet is
# successfully decrypted. Note, unsuccessful decryption might
# indicate a stateless reset.
action decrypt(c:tls.cid,seq:pkt_num,pkt:stream_data) returns (ok:bool, pkt:stream_data)
implement encrypt {
var level := get_level(pkt);
var sample_size := tls.iv_size(level);
var pnum_pos := get_pnum_pos(pkt);
var pnum_len := get_pnum_len(pkt,pnum_pos);
var hdr_len := pnum_pos + pnum_len;
var hdr := pkt.segment(0,pnum_pos);
var pnum := pkt.segment(pnum_pos,hdr_len);
var pyld := pkt.segment(hdr_len,pkt.end);
var auth := pkt.segment(0,hdr_len);
var seq := get_pnum(pnum,0,pnum_len);
pyld := tls.encrypt_aead(c,level,pyld,seq,auth);
# Computing the sample position is a bit tricky. First we
# assume the length of the pnum is 4, which means the offset
# of the sample withing the payload is `4 -
# pnum_len`. However, if there are not enough bytes in the
# payload, we change it to `payld.end - sample`. Note,
# however, that we are using natural numbers, so if the
# position would be negative, it saturates to zero. This is
# consistent with the statement that you should never sample
# any bytes from the packet number itself. This leaves the
# possibility that there are not enough bytes to sample, that
# is, in the sample, there will be some trailing
# zeros. Nonetheless, the spec says that the sample size will
# never be larger than the smallest encrypted payload, so we
# should be OK.
var sample_pos := 4 - pnum_len;
if sample_pos + sample_size > pyld.end {
sample_pos := pyld.end - sample_size
};
var sample := pyld.segment(sample_pos,sample_pos+sample_size);
var pnum := tls.encrypt_cipher(c,level,pnum,sample);
pkt := hdr.extend(pnum).extend(pyld);
}
implement decrypt {
var level := get_level(pkt);
var sample_size := tls.iv_size(level);
var pnum_pos := get_pnum_pos(pkt);
var sample_pos := pnum_pos + 4;
if sample_pos + sample_size > pkt.end {
sample_pos := pkt.end - sample_size
};
var sample := pkt.segment(sample_pos,sample_pos+sample_size);
var pnum := pkt.segment(pnum_pos,pnum_pos+4);
(ok,pnum) := tls.decrypt_cipher(c,level,pnum,sample);
if ok {
var pnum_len := get_pnum_len(pnum,0);
var hdr := pkt.segment(0,pnum_pos);
hdr := hdr.extend(pnum.segment(0,pnum_len);
var new_seq := get_pnum(pnum,0,pnum_len);
new_seq := correct_pnum(seq,new_seq);
var pyld := pkt.segment(pnum_pos + pnum_len);
(ok,pyld) := tls.decrypt_aead(c,level,pyld,new_seq,hdr);
if ok {
pkt := hdr.extend(pyld);
}
}
}
# This returns the encryption level of a protected or unprotected
# packet.
action get_level(pkt:stream_data) returns (level:tls.level) = {
var hdr_type := pkt.value(0);
if hdr_type = 0x7f {
level := 0;
} else if hdr_type = 0x7d {
level := 2;
} else {
level := 3;
}
}
# This returns the position of the packet number in a protected or
# unprotected packet. For long packets, we have to skip over 6
# bytes of header, plus the two cid's, plus the retry token length
# and bytes, plus the payload length. For short packets, we just
# skip ver the type byte and the cid. Since we don't know the
# length of the cid here, we assume it is 8.
action get_pnum_pos(pkt:stream_data) returns (pnum_pos:stream_pos) = {
var hdr_type := pkt.value(0);
if hdr_type = 0x7f | hdr_type = 0x7d {
var lens := pkt.value[5];
var dcil := bfe[4][7](lens);
var scil := bfe[0][3](lens);
dcil := dcil + 3 if dcil ~= 0 else 0;
scil := scil + 3 if scil ~= 0 else 0;
pnum_pos := 6 + dcil + scil;
var len := get_var_int_len(pkt,pnum_pos);
var retry_token_len := get_var_int(pkt,pnum_pos,len);
pnum_pos := pnum_pos + len + retry_token_len;
pnum_pos := pnum_pos + len := get_var_int_len(pkt,pnum_pos);
}
else {
pnum_pos := 9;
}
}
# Get the length of a packet number at position `pos`
action get_pnum_len(pkt:stream_data,pnum_pos:stream_pos) returns (pnum_len:stream_pos) = {
var byte = pkt.value(pnum_pos);
if bvand(byte,0x80) = 0 {
pnum_len := 1;
} else if bvand(byte,0x40) = 0 {
pnum_len := 2;
} else {
pnum_len := 4;
}
}
# Given the length of the packet number, get the number it encodes.
action get_pnum_len(pkt:stream_data,pnum_pos:stream_pos,pnum_len:stream_pos)
returns (seq:pkt_num) =
{
var data := pkt.segment(pnum_pos,pnum_pos + pnum_len);
var mask:byte := 0x7f if pnum_len = 1 else 0x3f;
var data := data.set(0,bvand(mask,data.value(0)));
seq := 0;
var idx:stream_pos := 0;
while idx < data.end {
seq := 256 * seq + data.value(idx);
idx := idx.next;
}
}
# Get the length of a variable-length ingteger in a packet.
action get_var_int_len(pkt:stream_data,pos:stream_pos) returns (len:stream_pos) = {
var code := bfe[6][7](pkt.value(pos));
if code = 0 {
len := 1;
} else if code = 1 {
len := 2;
} else if code = 2 {
len := 4;
} else {
len := 8;
}
}
# Get the value of a variable-length integer at position `pos`,
# whose length is `len`.
action get_var_int(pkt:stream_data,pos:stream_pos,len:stream_pos) returns (val:stream_pos) {
var idx:stream_pos := pos;
val := 0;
while idx < pos + len {
var byte := pkt.value(idx);
if idx = pos {
byte := bvand(byte,0x3f);
}
val := 256 * val + byte;
idx := idx.next;
}
}
}

Просмотреть файл

@ -6,7 +6,7 @@ include quic_connection
include quic_deser
include quic_ser
include quic_infer
include tls
include tls_msg
include udp_impl
include serdes
@ -163,6 +163,47 @@ before packet_event(src:ip.endpoint,dst:ip.endpoint,pkt:quic_packet) {
# Hook up to the remote server
# If this is an initial client request, we set up the initial keys
# based on the destination cid (which in principle is a random sequence
# of bytes. Note, the "salt" is specified in QUIC-TLS version 15, and is
# unchanged in version 16.
after client_initial_request(src:ip.endpoint,dst:ip.endpoint,pkt:quic_packet) {
var ikm := cid_to_bytes(pkt.dst_cid,pkt.dcid + 3 if pkt.dcid > 0 else 0);
var salt : stream_data := stream_data.empty;
salt := salt.append(0x9c);
salt := salt.append(0x10);
salt := salt.append(0x8f);
salt := salt.append(0x98);
salt := salt.append(0x52);
salt := salt.append(0x0a);
salt := salt.append(0x5c);
salt := salt.append(0x5c);
salt := salt.append(0x32);
salt := salt.append(0x96);
salt := salt.append(0x8e);
salt := salt.append(0x95);
salt := salt.append(0x0e);
salt := salt.append(0x8a);
salt := salt.append(0x2c);
salt := salt.append(0x5f);
salt := salt.append(0xe0);
salt := salt.append(0x6d);
salt := salt.append(0x6c);
salt := salt.append(0x38);
call botan.upper.set_initial_keys(0,salt,ikm);
}
action cid_to_bytes(c:cid,len:cid_length) returns(res:stream_data) = {
<<<
res.resize(len);
for (unsigned i = 0; i < len; i++) {
res[len-i-1] = 0xff & (c.val >> (i * 8));
}
>>>
}
# When the client sends a packet, route it to the server on the socket
# corresponding to the ip endpoint. TODO: coalesce packets. As of now,
# we send only one QUIC packet per UDP datagram.

147
ivy/include/1.7/tls_msg.ivy Normal file
Просмотреть файл

@ -0,0 +1,147 @@
#lang ivy
include tls_picotls
# This module defines the interface to TLS at the message level. This
# interface eliminates the TLS record layer, instead delegating
# encryption to the client, as needed by the QUIC protocol. The
# interface provides suiatable encryption and decryption methods that
# can be used for this purpose.
module tls_lower_intf(cid,bytes) = {
# Transfers message data from connection to underlying transport
action send(c:cid,data:bytes)
# Transfers message data from the underlying transport to connection
action recv(c:cid,data:bytes)
}
# This module defines the interface of a TLS endpoint (either a client
# or a server. The parameters are:
#
# `cid` : the type of connection identifiers
# `bytes` : the type of byte arrays
# `lower` : the interface to the underlying transport
# `extens` : the type of lists of extensions
# `exten_ser` : serializer for extensions
module tls_intf(cid,index,bytes,lower,extens,exten_ser,seq_num) = {
# The upper interface is between TLS and the application
# Transfers connection data to application
action recv(c:cid,data:bytes)
# Transfers application data to conection
action send(c:cid,data:bytes)
# Alert from connection to application
action alert(c:cid,data:bytes)
# Indicates to application that a session is established,
# meaning the connection is ready to send on.
action session_established(c:cid)
# Generates key material based on master secret, per RFC 5705.
action key_material_export(c:cid,label:bytes,context:bytes,length:index)
returns (key:bytes)
# Create a new connection with a fresh cid, and list of extensions
action create(c:cid, is_server : bool, e : extens)
# Destroy an existing connection
action destroy(c:cid)
# Set the initial keys for a session. There are determined by the
# salt and the initial key material `ikm`. In QUIC, the salt is
# fixed by the protocol version, whereas `ikm` is the client's
# initial destination connection id.
action set_initial_keys(c:cid,salt:bytes,ikm:bytes)
# Get the size of the cipher initial value for a given level.
action iv_size(c:cid,l:level) returns (sz:index)
# Encrypt data with cipher for given level and initial value.
action encrypt_cipher(c:dic,l:level,clear:bytes,iv:bytes) returns (cipher:bytes)
# Decrypt data with cipher for given level and initial value. Return parameter
# `ok` indicates decryption was successful.
action decrypt_cipher(c:dic,l:level,cipher:bytes,iv:bytes) returns (ok:bool,clear:bytes)
# Encrypt data with AEAD for given level, additional data `ad` and
# sequence number `seq`.
action encrypt_aead(c:dic,l:level,clear:bytes,seq:seq_num,ad:bytes) returns (cipher:bytes)
# Decrypt data with cipher for given level, additional data `ad`
# and sequence number `seq`. Return parameter `ok` indicates
# decryption was successful.
action decrypt_cipher(c:dic,l:level,cipher:bytes,seq:seq_num,ad:bytes)
returns (ok:bool,clear:bytes)
specification {
relation open(C:cid)
relation established(C:cid)
after init {
open(C) := false;
established(C) := false;
}
around create {
require ~open(c);
...
open(c) := true;
}
around destroy {
require open(c);
...
open(c) := false;
established(c) := false;
}
before recv {
require open(c);
}
before send {
require open(c) & established(c);
}
before session_established {
require open(c) & ~established(c);
established(c) := true
}
before key_material_export {
require open(c) & established(c);
}
invariant established(C) -> open(C)
}
implementation {
instance impl : tls_gnutls(cid,index,bytes,extens,exten_ser,lower,this)
}
}

Просмотреть файл

@ -94,9 +94,11 @@ module tls_gnutls(cid,index,bytes,extens,exten_ser,lower,upper) = {
std::vector<char> input;
int handshake_status;
std::vector<quic_crypto_context_t> crypto_context;
bool is_server;
picotls_connection(`cid` id, ptls_t *gs, tls_callbacks cb,
ptls_handshake_properties_t *hsp)
: id(id),gs(gs),cb(cb),hsp(hsp) {
ptls_handshake_properties_t *hsp, bool is_server)
: id(id),gs(gs),cb(cb),hsp(hsp),is_server(is_server) {
handshake_status = 0;
crypto_context.resize(4);
}
@ -114,8 +116,14 @@ module tls_gnutls(cid,index,bytes,extens,exten_ser,lower,upper) = {
return 0;
}
// These are some parameters defined byt the QUIC-TLS standard. They
// don't really belong here, but then again, QUIC is currently the only
// existing client of this TLS interface.
#define QUIC_LABEL_QUIC_BASE "quic "
#define QUIC_LABEL_PN "pn"
#define QUIC_LABEL_INITIAL_CLIENT "client in"
#define QUIC_LABEL_INITIAL_SERVER "server in"
// The following mess is to get the traffic keys from picotls
@ -157,6 +165,54 @@ module tls_gnutls(cid,index,bytes,extens,exten_ser,lower,upper) = {
return 0;
}
// This additional mess sets the initial traffic keys
void bail(int ret, const char *msg) {
if (ret) {
std::cerr << msg;
exit(1);
}
}
void vec_to_ptls_iovec(ptls_iovec_t &res, const `bytes` &vec) {
res.base = new uint8_t[vec.size()];
std::copy(vec.begin(),vec.end(),res.base);
res.len = vec.size();
}
int setup_initial_traffic_keys(picotls_connection *session,
const `bytes` &salt_vec,
const `bytes` &ikm_vec)
{
int ret = 0;
uint8_t master_secret[256]; /* secret_max */
ptls_cipher_suite_t cipher = { 0, &ptls_openssl_aes128gcm, &ptls_openssl_sha256 };
ptls_iovec_t salt;
ptls_iovec_t ikm;
ptls_iovec_t prk;
uint8_t client_secret[256];
uint8_t server_secret[256];
uint8_t *secret1, *secret2;
vec_to_ptls_iovec(salt,salt_vec);
vec_to_ptls_iovec(ikm,ikm_vec);
ret = ptls_hkdf_extract(cipher.hash, master_secret, salt, ikm);
bail(ret,"tls: failed to set up initial master secret\n");
prk.base = master_secret;
prk.len = cipher.hash->digest_size;
ret = ptls_hkdf_expand_label(cipher.hash, client_secret, cipher.hash->digest_size,
prk, QUIC_LABEL_INITIAL_CLIENT, ptls_iovec_init(NULL, 0),
QUIC_LABEL_QUIC_BASE);
bail(ret,"tls: failed to set up initial client secret\n");
ret = ptls_hkdf_expand_label(cipher.hash, server_secret, cipher.hash->digest_size,
prk, QUIC_LABEL_INITIAL_SERVER, ptls_iovec_init(NULL, 0),
QUIC_LABEL_QUIC_BASE);
bail(ret,"tls: failed to set up initial server secret\n");
quic_set_key_from_secret(&cipher, session->is_server, &session->crypto_context[0], server_secret);
quic_set_key_from_secret(&cipher, !session->is_server, &session->crypto_context[0], client_secret);
}
// Some parameters for picotls as used by picoquic.
@ -192,87 +248,6 @@ module tls_gnutls(cid,index,bytes,extens,exten_ser,lower,upper) = {
}
}
#if false
// Handle callback from gnutls to send data
ssize_t gnutls_session_push_func(gnutls_transport_ptr_t ptr, const void* data, size_t len) {
gnutls_connection *s = (gnutls_connection *) ptr;
`cid` c = s->id;
unsigned char *d = (unsigned char *)data;
`bytes` bytes;
bytes.resize(len);
std::copy(d,d+len,bytes.begin());
s->cb.ls(c,bytes);
return len;
}
// Handle callback from gnutls to recv data
ssize_t gnutls_session_pull_func(gnutls_transport_ptr_t ptr, void* data, size_t len){
gnutls_connection *s = (gnutls_connection *) ptr;
`cid` c = s->id;
std::vector<char> &input = s->input;
size_t res = input.size();
if (len < res)
res = len;
if (res == 0) {
errno = EAGAIN;
return -1;
}
char *d = (char *)data;
for (size_t i = 0; i < res; i++) {
d[i] = input[i];
}
input.erase(input.begin(),input.begin()+res);
return res;
}
// Start or resume handshake
void gnutls_do_handshake(gnutls_connection *s) {
// HACK HACK HACK HACK
// set the read and write epochs to zero to hopefully disable encryption of records
// s->gs->security_parameters.epoch_read = 0;
// s->gs->security_parameters.epoch_write = 0;
s->handshake_status = gnutls_handshake(s->gs);
if (s->handshake_status != GNUTLS_E_SUCCESS &&
s->handshake_status != GNUTLS_E_AGAIN) {
std::cerr << "gnutls_handshake returned error\n";
exit(1);
}
}
// This is horrible, but it can't be helped, because the gnutls
// callbacks for external extensions don't have parameters.
hash_space::hash_map<gnutls_session_t,std::vector<std::vector<unsigned char> > > gnutls_ext_data_map;
// Send extension data
int gnutls_ext_supp_recv_params(gnutls_session_t session, const unsigned char *data, size_t _data_size) {
const unsigned char *d = (const unsigned char *) data;
std::vector<unsigned char> v;
v.resize(_data_size);
std::copy(d,d+_data_size,v.begin());
gnutls_ext_data_map[session].push_back(v);
return _data_size;
}
// Receive extension data
int gnutls_ext_supp_send_params(gnutls_session_t session, gnutls_buffer_t buf) {
std::vector<std::vector<unsigned char> > &datas = gnutls_ext_data_map[session];
std::vector<unsigned char> v = datas.front();
int len = v.size();
if (gnutls_buffer_append_data(buf, &v[0], len) < 0) {
std::cerr << "gnutls_buffer_append_data returned error\n";
exit(1);
}
datas.erase(datas.begin());
return len;
}
#endif
>>>
# Here we put any new members of the ivy C++ class. If we have allocated a per-instance
@ -366,7 +341,7 @@ module tls_gnutls(cid,index,bytes,extens,exten_ser,lower,upper) = {
ptls_t *session;
session = ptls_new(ctx,is_server ? 1 : 0);
picotls_connection *s = new picotls_connection(c,session,*`cb`,handshake_properties);
picotls_connection *s = new picotls_connection(c,session,*`cb`,handshake_properties,is_server);
*ptls_get_data_ptr(session) = s;
`cid_map`[c] = s;
@ -390,6 +365,47 @@ module tls_gnutls(cid,index,bytes,extens,exten_ser,lower,upper) = {
}
# Set the initial key material.
implement set_initial_keys(c:cid,salt:bytes,ikm:bytes) {
<<< impure
picotls_connection *s = `cid_map`[c];
setup_initial_traffic_keys(s,salt,ikm);
>>>
}
# Get the cipher initial value size
implement iv_size(c:cid,l:level) returns (sz:index) {
<<< impure
picotls_connection *s = `cid_map`[c];
ptls_cipher_context_t *pn_enc = (ptls_cipher_context_t *)(s->crypto_context[l].pn_enc);
sz = pn_enc->algo->iv_size
>>>
}
implement encrypt_cipher(c:dic,l:level,clear:bytes,iv:bytes) returns (cipher:bytes) {
<<< impure
picotls_connection *s = `cid_map`[c];
ptls_cipher_context_t *pn_enc = (ptls_cipher_context_t *)(s->crypto_context[l].pn_enc);
std::vector<uint8_t> bytes;
bytes.resize(iv.size());
std::copy(iv.begin(),iv.end(),bytes.end());
ptls_cipher_init(pn_enc, &bytes[0]);
std::vector<uint8_t> input, output;
input.resize(clear.size());
std::
output.resize(clear.size() + aead_checksum_size);
ptls_cipher_encrypt(pn_enc, &output[0], &input, len);
>>>
}
# upper.send is called with application data to be transmitted to the peer.
implement upper.send(c:cid,data:bytes) {

Просмотреть файл

@ -1592,7 +1592,8 @@ def create_constructor_schemata(mod):
for sortname,destrs in mod.sort_destructors.iteritems():
if any(len(f.sort.dom) > 1 for f in destrs):
continue # TODO: higher-order constructors!
sort = destrs[0].sort.dom[0]
sort = ivy_logic.find_sort(sortname)
#sort = destrs[0].sort.dom[0]
Y = ivy_logic.Variable('Y',sort)
eqs = [ivy_logic.Equals(f(Y),ivy_logic.Variable('X'+str(n),f.sort.rng)) for n,f in enumerate(destrs)]
fmla = ivy_logic.Exists([Y],ivy_logic.And(*eqs))