devtls: AEAD record layer (AES-GCM and ChaCha20-Poly1305)
Extends the kernel TLS record layer (#a/tls, devtls.c) to
support AEAD ciphers. Prerequisite for any TLS 1.2 cipher
suite using AES-GCM or ChaCha20-Poly1305.
Applied identically to both kernel trees:
sys/src/9/port/devtls.c (i386 family)
sys/src/9k/port/devtls.c (amd64 family)
Secret gains aead_enc / aead_dec function pointers and a
recivlen field for the per-record explicit IV (8 bytes for
GCM, 0 for ChaCha20-Poly1305 under RFC 7905). tlsrecread and
tlsrecwrite branch on sec->aead_{enc,dec}: the AEAD path builds
the 13-byte additional_data (RFC 5246 §6.2.3.3) and combines
encrypt+MAC; the legacy CBC+HMAC path is unchanged. The
explicit GCM IV is derived salt||counter XOR sequence — matches
9front and avoids per-record random reads. encrypttab[] gains
ccpoly96_aead, aes_128_gcm_aead, and aes_256_gcm_aead entries
recognized by the "secret" ctl command.
AEAD plumbing is modelled on 9front's devtls.c, adapted to
9legacy's framing (per-version packMac function pointer,
smalloc, randfill for the GCM IV seed).
RFC 5246 §6 (record protocol); 5288 §3 (AES-GCM);
7905 §2 (ChaCha20-Poly1305).
--- sys/src/9/port/devtls.c
+++ sys/src/9/port/devtls.c
@@ -87,7 +87,10 @@
int (*dec)(Secret*, uchar*, int);
int (*unpad)(uchar*, int, int);
DigestState *(*mac)(uchar*, ulong, uchar*, ulong, uchar*, DigestState*);
+ int (*aead_enc)(Secret*, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len);
+ int (*aead_dec)(Secret*, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len);
int block; /* encryption block len, 0 if none */
+ int recivlen; /* explicit record IV length (AEAD only, else 0) */
int maclen;
void *enckey;
uchar mackey[MaxMacLen];
@@ -238,6 +241,10 @@
static int des3dec(Secret *sec, uchar *buf, int n);
static int aesenc(Secret *sec, uchar *buf, int n);
static int aesdec(Secret *sec, uchar *buf, int n);
+static int ccpoly_aead_enc(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len);
+static int ccpoly_aead_dec(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len);
+static int aesgcm_aead_enc(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len);
+static int aesgcm_aead_dec(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len);
static int noenc(Secret *sec, uchar *buf, int n);
static int sslunpad(uchar *buf, int n, int block);
static int tlsunpad(uchar *buf, int n, int block);
@@ -734,9 +741,9 @@
{
OneWay *volatile in;
Block *volatile b;
- uchar *p, seq[8], header[RecHdrLen], hmac[MaxMacLen];
+ uchar *p, seq[8], header[RecHdrLen], hmac[MaxMacLen], aad[13];
int volatile nconsumed;
- int len, type, ver, unpad_len;
+ int len, type, ver, unpad_len, ivlen;
nconsumed = 0;
if(waserror()){
@@ -801,32 +808,45 @@
if(in->sec != nil) {
/* to avoid Canvel-Hiltgen-Vaudenay-Vuagnoux attack, all errors here
should look alike, including timing of the response. */
- unpad_len = (*in->sec->dec)(in->sec, p, len);
+ if(in->sec->aead_dec != nil)
+ unpad_len = len;
+ else
+ unpad_len = (*in->sec->dec)(in->sec, p, len);
+if(tr->debug) pprint("decrypted %d\n", unpad_len);
+if(tr->debug) pdump(unpad_len, p, "decrypted:");
- /* excplicit iv */
- if(tr->version >= TLS11Version){
- len -= in->sec->block;
- if(len < 0)
- rcvError(tr, EDecodeError, "runt record message");
+ /* explicit iv: AEAD uses recivlen, CBC uses block len starting from TLS 1.1 */
+ ivlen = in->sec->recivlen;
+ if(ivlen == 0 && tr->version >= TLS11Version)
+ ivlen = in->sec->block;
+ len -= ivlen;
+ if(len < 0)
+ rcvError(tr, EDecodeError, "runt record message");
+ unpad_len -= ivlen;
+ p += ivlen;
- unpad_len -= in->sec->block;
- p += in->sec->block;
- }
-
if(unpad_len >= in->sec->maclen)
len = unpad_len - in->sec->maclen;
-if(tr->debug) pprint("decrypted %d\n", unpad_len);
-if(tr->debug) pdump(unpad_len, p, "decrypted:");
/* update length */
put16(header+3, len);
put64(seq, in->seq);
in->seq++;
- (*tr->packMac)(in->sec, in->sec->mackey, seq, header, p, len, hmac);
- if(unpad_len < in->sec->maclen)
- rcvError(tr, EBadRecordMac, "short record mac");
- if(memcmp(hmac, p+len, in->sec->maclen) != 0)
- rcvError(tr, EBadRecordMac, "record mac mismatch");
+
+ if(in->sec->aead_dec != nil) {
+ /* aad per RFC 5246 §6.2.3.3: seq (8) | type (1) | version (2) | length (2) */
+ memmove(aad, seq, 8);
+ memmove(aad+8, header, RecHdrLen);
+ len = (*in->sec->aead_dec)(in->sec, aad, sizeof(aad), p - ivlen, p, unpad_len);
+ if(len < 0)
+ rcvError(tr, EBadRecordMac, "record mac mismatch");
+ } else {
+ (*tr->packMac)(in->sec, in->sec->mackey, seq, header, p, len, hmac);
+ if(unpad_len < in->sec->maclen)
+ rcvError(tr, EBadRecordMac, "short record mac");
+ if(memcmp(hmac, p+len, in->sec->maclen) != 0)
+ rcvError(tr, EBadRecordMac, "record mac mismatch");
+ }
b->rp = p;
b->wp = p+len;
}
@@ -1279,7 +1299,8 @@
if(out->sec != nil){
maclen = out->sec->maclen;
pad = maclen + out->sec->block;
- if(tr->version >= TLS11Version)
+ ivlen = out->sec->recivlen;
+ if(ivlen == 0 && tr->version >= TLS11Version)
ivlen = out->sec->block;
}
n = BLEN(bb);
@@ -1308,17 +1329,26 @@
if(out->sec != nil){
put64(seq, out->seq);
out->seq++;
- (*tr->packMac)(out->sec, out->sec->mackey, seq, p, p + RecHdrLen + ivlen, n, p + RecHdrLen + ivlen + n);
- n += maclen;
- /* explicit iv */
- if(ivlen > 0){
- randfill(p + RecHdrLen, ivlen);
- n += ivlen;
- }
+ if(out->sec->aead_enc != nil){
+ /* aad per RFC 5246 §6.2.3.3: seq (8) | type (1) | version (2) | length (2) */
+ uchar aad[13];
+ memmove(aad, seq, 8);
+ memmove(aad+8, p, RecHdrLen);
+ n = (*out->sec->aead_enc)(out->sec, aad, sizeof(aad), p + RecHdrLen, p + RecHdrLen + ivlen, n) + ivlen;
+ } else {
+ (*tr->packMac)(out->sec, out->sec->mackey, seq, p, p + RecHdrLen + ivlen, n, p + RecHdrLen + ivlen + n);
+ n += maclen;
- /* encrypt */
- n = (*out->sec->enc)(out->sec, p + RecHdrLen, n);
+ /* explicit iv */
+ if(ivlen > 0){
+ randfill(p + RecHdrLen, ivlen);
+ n += ivlen;
+ }
+
+ /* encrypt */
+ n = (*out->sec->enc)(out->sec, p + RecHdrLen, n);
+ }
nb->wp = p + RecHdrLen + n;
/* update length */
@@ -1489,6 +1519,32 @@
}
static void
+initccpolykey(Encalg *ea, Secret *s, uchar *p, uchar *iv)
+{
+ s->enckey = smalloc(sizeof(Chachastate));
+ s->aead_enc = ccpoly_aead_enc;
+ s->aead_dec = ccpoly_aead_dec;
+ s->maclen = Poly1305dlen;
+ /* RFC 7905: 96-bit iv xored with sequence number (stored in mackey as salt) */
+ memmove(s->mackey, iv, ea->ivlen);
+ setupChachastate(s->enckey, p, ea->keylen, iv, ea->ivlen, 20);
+}
+
+static void
+initaesgcmkey(Encalg *ea, Secret *s, uchar *p, uchar *iv)
+{
+ s->enckey = smalloc(sizeof(AESGCMstate));
+ s->aead_enc = aesgcm_aead_enc;
+ s->aead_dec = aesgcm_aead_dec;
+ s->maclen = 16;
+ s->recivlen = 8;
+ /* mackey slot holds 4-byte salt || 8-byte running explicit-IV counter */
+ memmove(s->mackey, iv, ea->ivlen);
+ randfill(s->mackey + ea->ivlen, s->recivlen);
+ setupAESGCMstate(s->enckey, p, ea->keylen, nil, 0);
+}
+
+static void
initclearenc(Encalg *, Secret *s, uchar *, uchar *)
{
s->enc = noenc;
@@ -1503,6 +1559,9 @@
{ "3des_ede_cbc", 3 * 8, 8, initDES3key },
{ "aes_128_cbc", 128/8, 16, initAESkey },
{ "aes_256_cbc", 256/8, 16, initAESkey },
+ { "ccpoly96_aead", 256/8, 96/8, initccpolykey },
+ { "aes_128_gcm_aead", 128/8, 4, initaesgcmkey },
+ { "aes_256_gcm_aead", 256/8, 4, initaesgcmkey },
{ 0 }
};
@@ -2092,6 +2151,77 @@
{
aesCBCdecrypt(buf, n, sec->enckey);
return (*sec->unpad)(buf, n, 16);
+}
+
+static void
+ccpoly_aead_setiv(Secret *sec, uchar seq[8])
+{
+ uchar iv[ChachaIVlen];
+ Chachastate *cs;
+ int i;
+
+ cs = (Chachastate*)sec->enckey;
+ memmove(iv, sec->mackey, ChachaIVlen);
+ for(i=0; i<8; i++)
+ iv[i+(ChachaIVlen-8)] ^= seq[i];
+ chacha_setiv(cs, iv);
+ memset(iv, 0, sizeof(iv));
+}
+
+static int
+ccpoly_aead_enc(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len)
+{
+ USED(reciv);
+ ccpoly_aead_setiv(sec, aad);
+ ccpoly_encrypt(data, len, aad, aadlen, data+len, sec->enckey);
+ return len + sec->maclen;
+}
+
+static int
+ccpoly_aead_dec(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len)
+{
+ USED(reciv);
+ len -= sec->maclen;
+ if(len < 0)
+ return -1;
+ ccpoly_aead_setiv(sec, aad);
+ if(ccpoly_decrypt(data, len, aad, aadlen, data+len, sec->enckey) != 0)
+ return -1;
+ return len;
+}
+
+static int
+aesgcm_aead_enc(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len)
+{
+ uchar iv[12];
+ int i;
+
+ /* explicit IV = salt(4) || counter(8) XOR seq(8). sec->mackey = salt||counter. */
+ memmove(iv, sec->mackey, 4+8);
+ for(i=0; i<8; i++)
+ iv[4+i] ^= aad[i];
+ memmove(reciv, iv+4, 8);
+ aesgcm_setiv(sec->enckey, iv, 12);
+ memset(iv, 0, sizeof(iv));
+ aesgcm_encrypt(data, len, aad, aadlen, data+len, sec->enckey);
+ return len + sec->maclen;
+}
+
+static int
+aesgcm_aead_dec(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len)
+{
+ uchar iv[12];
+
+ len -= sec->maclen;
+ if(len < 0)
+ return -1;
+ memmove(iv, sec->mackey, 4);
+ memmove(iv+4, reciv, 8);
+ aesgcm_setiv(sec->enckey, iv, 12);
+ memset(iv, 0, sizeof(iv));
+ if(aesgcm_decrypt(data, len, aad, aadlen, data+len, sec->enckey) != 0)
+ return -1;
+ return len;
}
static DigestState*
--- sys/src/9k/port/devtls.c
+++ sys/src/9k/port/devtls.c
@@ -87,7 +87,10 @@
int (*dec)(Secret*, uchar*, int);
int (*unpad)(uchar*, int, int);
DigestState *(*mac)(uchar*, ulong, uchar*, ulong, uchar*, DigestState*);
+ int (*aead_enc)(Secret*, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len);
+ int (*aead_dec)(Secret*, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len);
int block; /* encryption block len, 0 if none */
+ int recivlen; /* explicit record IV length (AEAD only, else 0) */
int maclen;
void *enckey;
uchar mackey[MaxMacLen];
@@ -238,6 +241,10 @@
static int des3dec(Secret *sec, uchar *buf, int n);
static int aesenc(Secret *sec, uchar *buf, int n);
static int aesdec(Secret *sec, uchar *buf, int n);
+static int ccpoly_aead_enc(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len);
+static int ccpoly_aead_dec(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len);
+static int aesgcm_aead_enc(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len);
+static int aesgcm_aead_dec(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len);
static int noenc(Secret *sec, uchar *buf, int n);
static int sslunpad(uchar *buf, int n, int block);
static int tlsunpad(uchar *buf, int n, int block);
@@ -734,9 +741,9 @@
{
OneWay *volatile in;
Block *volatile b;
- uchar *p, seq[8], header[RecHdrLen], hmac[MaxMacLen];
+ uchar *p, seq[8], header[RecHdrLen], hmac[MaxMacLen], aad[13];
int volatile nconsumed;
- int len, type, ver, unpad_len;
+ int len, type, ver, unpad_len, ivlen;
nconsumed = 0;
if(waserror()){
@@ -801,32 +808,45 @@
if(in->sec != nil) {
/* to avoid Canvel-Hiltgen-Vaudenay-Vuagnoux attack, all errors here
should look alike, including timing of the response. */
- unpad_len = (*in->sec->dec)(in->sec, p, len);
+ if(in->sec->aead_dec != nil)
+ unpad_len = len;
+ else
+ unpad_len = (*in->sec->dec)(in->sec, p, len);
+if(tr->debug) pprint("decrypted %d\n", unpad_len);
+if(tr->debug) pdump(unpad_len, p, "decrypted:");
- /* excplicit iv */
- if(tr->version >= TLS11Version){
- len -= in->sec->block;
- if(len < 0)
- rcvError(tr, EDecodeError, "runt record message");
+ /* explicit iv: AEAD uses recivlen, CBC uses block len starting from TLS 1.1 */
+ ivlen = in->sec->recivlen;
+ if(ivlen == 0 && tr->version >= TLS11Version)
+ ivlen = in->sec->block;
+ len -= ivlen;
+ if(len < 0)
+ rcvError(tr, EDecodeError, "runt record message");
+ unpad_len -= ivlen;
+ p += ivlen;
- unpad_len -= in->sec->block;
- p += in->sec->block;
- }
-
if(unpad_len >= in->sec->maclen)
len = unpad_len - in->sec->maclen;
-if(tr->debug) pprint("decrypted %d\n", unpad_len);
-if(tr->debug) pdump(unpad_len, p, "decrypted:");
/* update length */
put16(header+3, len);
put64(seq, in->seq);
in->seq++;
- (*tr->packMac)(in->sec, in->sec->mackey, seq, header, p, len, hmac);
- if(unpad_len < in->sec->maclen)
- rcvError(tr, EBadRecordMac, "short record mac");
- if(memcmp(hmac, p+len, in->sec->maclen) != 0)
- rcvError(tr, EBadRecordMac, "record mac mismatch");
+
+ if(in->sec->aead_dec != nil) {
+ /* aad per RFC 5246 §6.2.3.3: seq (8) | type (1) | version (2) | length (2) */
+ memmove(aad, seq, 8);
+ memmove(aad+8, header, RecHdrLen);
+ len = (*in->sec->aead_dec)(in->sec, aad, sizeof(aad), p - ivlen, p, unpad_len);
+ if(len < 0)
+ rcvError(tr, EBadRecordMac, "record mac mismatch");
+ } else {
+ (*tr->packMac)(in->sec, in->sec->mackey, seq, header, p, len, hmac);
+ if(unpad_len < in->sec->maclen)
+ rcvError(tr, EBadRecordMac, "short record mac");
+ if(memcmp(hmac, p+len, in->sec->maclen) != 0)
+ rcvError(tr, EBadRecordMac, "record mac mismatch");
+ }
b->rp = p;
b->wp = p+len;
}
@@ -1279,7 +1299,8 @@
if(out->sec != nil){
maclen = out->sec->maclen;
pad = maclen + out->sec->block;
- if(tr->version >= TLS11Version)
+ ivlen = out->sec->recivlen;
+ if(ivlen == 0 && tr->version >= TLS11Version)
ivlen = out->sec->block;
}
n = BLEN(bb);
@@ -1308,17 +1329,26 @@
if(out->sec != nil){
put64(seq, out->seq);
out->seq++;
- (*tr->packMac)(out->sec, out->sec->mackey, seq, p, p + RecHdrLen + ivlen, n, p + RecHdrLen + ivlen + n);
- n += maclen;
- /* explicit iv */
- if(ivlen > 0){
- randfill(p + RecHdrLen, ivlen);
- n += ivlen;
- }
+ if(out->sec->aead_enc != nil){
+ /* aad per RFC 5246 §6.2.3.3: seq (8) | type (1) | version (2) | length (2) */
+ uchar aad[13];
+ memmove(aad, seq, 8);
+ memmove(aad+8, p, RecHdrLen);
+ n = (*out->sec->aead_enc)(out->sec, aad, sizeof(aad), p + RecHdrLen, p + RecHdrLen + ivlen, n) + ivlen;
+ } else {
+ (*tr->packMac)(out->sec, out->sec->mackey, seq, p, p + RecHdrLen + ivlen, n, p + RecHdrLen + ivlen + n);
+ n += maclen;
- /* encrypt */
- n = (*out->sec->enc)(out->sec, p + RecHdrLen, n);
+ /* explicit iv */
+ if(ivlen > 0){
+ randfill(p + RecHdrLen, ivlen);
+ n += ivlen;
+ }
+
+ /* encrypt */
+ n = (*out->sec->enc)(out->sec, p + RecHdrLen, n);
+ }
nb->wp = p + RecHdrLen + n;
/* update length */
@@ -1489,6 +1519,32 @@
}
static void
+initccpolykey(Encalg *ea, Secret *s, uchar *p, uchar *iv)
+{
+ s->enckey = smalloc(sizeof(Chachastate));
+ s->aead_enc = ccpoly_aead_enc;
+ s->aead_dec = ccpoly_aead_dec;
+ s->maclen = Poly1305dlen;
+ /* RFC 7905: 96-bit iv xored with sequence number (stored in mackey as salt) */
+ memmove(s->mackey, iv, ea->ivlen);
+ setupChachastate(s->enckey, p, ea->keylen, iv, ea->ivlen, 20);
+}
+
+static void
+initaesgcmkey(Encalg *ea, Secret *s, uchar *p, uchar *iv)
+{
+ s->enckey = smalloc(sizeof(AESGCMstate));
+ s->aead_enc = aesgcm_aead_enc;
+ s->aead_dec = aesgcm_aead_dec;
+ s->maclen = 16;
+ s->recivlen = 8;
+ /* mackey slot holds 4-byte salt || 8-byte running explicit-IV counter */
+ memmove(s->mackey, iv, ea->ivlen);
+ randfill(s->mackey + ea->ivlen, s->recivlen);
+ setupAESGCMstate(s->enckey, p, ea->keylen, nil, 0);
+}
+
+static void
initclearenc(Encalg *, Secret *s, uchar *, uchar *)
{
s->enc = noenc;
@@ -1503,6 +1559,9 @@
{ "3des_ede_cbc", 3 * 8, 8, initDES3key },
{ "aes_128_cbc", 128/8, 16, initAESkey },
{ "aes_256_cbc", 256/8, 16, initAESkey },
+ { "ccpoly96_aead", 256/8, 96/8, initccpolykey },
+ { "aes_128_gcm_aead", 128/8, 4, initaesgcmkey },
+ { "aes_256_gcm_aead", 256/8, 4, initaesgcmkey },
{ 0 }
};
@@ -2092,6 +2151,77 @@
{
aesCBCdecrypt(buf, n, sec->enckey);
return (*sec->unpad)(buf, n, 16);
+}
+
+static void
+ccpoly_aead_setiv(Secret *sec, uchar seq[8])
+{
+ uchar iv[ChachaIVlen];
+ Chachastate *cs;
+ int i;
+
+ cs = (Chachastate*)sec->enckey;
+ memmove(iv, sec->mackey, ChachaIVlen);
+ for(i=0; i<8; i++)
+ iv[i+(ChachaIVlen-8)] ^= seq[i];
+ chacha_setiv(cs, iv);
+ memset(iv, 0, sizeof(iv));
+}
+
+static int
+ccpoly_aead_enc(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len)
+{
+ USED(reciv);
+ ccpoly_aead_setiv(sec, aad);
+ ccpoly_encrypt(data, len, aad, aadlen, data+len, sec->enckey);
+ return len + sec->maclen;
+}
+
+static int
+ccpoly_aead_dec(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len)
+{
+ USED(reciv);
+ len -= sec->maclen;
+ if(len < 0)
+ return -1;
+ ccpoly_aead_setiv(sec, aad);
+ if(ccpoly_decrypt(data, len, aad, aadlen, data+len, sec->enckey) != 0)
+ return -1;
+ return len;
+}
+
+static int
+aesgcm_aead_enc(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len)
+{
+ uchar iv[12];
+ int i;
+
+ /* explicit IV = salt(4) || counter(8) XOR seq(8). sec->mackey = salt||counter. */
+ memmove(iv, sec->mackey, 4+8);
+ for(i=0; i<8; i++)
+ iv[4+i] ^= aad[i];
+ memmove(reciv, iv+4, 8);
+ aesgcm_setiv(sec->enckey, iv, 12);
+ memset(iv, 0, sizeof(iv));
+ aesgcm_encrypt(data, len, aad, aadlen, data+len, sec->enckey);
+ return len + sec->maclen;
+}
+
+static int
+aesgcm_aead_dec(Secret *sec, uchar *aad, int aadlen, uchar *reciv, uchar *data, int len)
+{
+ uchar iv[12];
+
+ len -= sec->maclen;
+ if(len < 0)
+ return -1;
+ memmove(iv, sec->mackey, 4);
+ memmove(iv+4, reciv, 8);
+ aesgcm_setiv(sec->enckey, iv, 12);
+ memset(iv, 0, sizeof(iv));
+ if(aesgcm_decrypt(data, len, aad, aadlen, data+len, sec->enckey) != 0)
+ return -1;
+ return len;
}
static DigestState*
|