* encrypt the input message with the cipher, and format the type, sequence
* number, IV, and ciphertext as follows.
*
- * +------+ +------+---...---+------...------+
- * | type | | seq | iv | ciphertext |
- * +------+ +------+---...---+------...------+
- * 32 32 blksz sz
+ * +--------+ +--------+---...---+------...------+
+ * | type | | seq | iv | ciphertext |
+ * +--------+ +--------+---...---+------...------+
+ * 32 32 blksz sz
*
* All of this is fed into the MAC to compute a tag. The type is not
* transmitted: the other end knows what type of message it expects, and the
* kind of ciphertext has been substituted. The tag is prepended to the
* remainder, to yield the finished cryptogram, as follows.
*
- * +---...---+------+---...---+------...------+
- * | tag | seq | iv | ciphertext |
- * +---...---+------+---...---+------...------+
- * tagsz 32 blksz sz
+ * +---...---+--------+---...---+------...------+
+ * | tag | seq | iv | ciphertext |
+ * +---...---+--------+---...---+------...------+
+ * tagsz 32 blksz sz
*
* Decryption: checks the overall size, verifies the tag, then decrypts the
* ciphertext and extracts the sequence number.
+ *
+ * Challenge tags are calculated by applying the MAC to the sequence number
+ * and message, concatenated as follows.
+ *
+ * +--------+---...---+
+ * | seq | m |
+ * +--------+---...---+
+ * 32 msz
*/
typedef struct v0_algs {
*
* So, a MAC is computed over
*
- * +------+ +------+------...------+
- * | type | | seq | ciphertext |
- * +------+ +------+------...------+
- * 32 32 sz
+ * +--------+ +--------+------...------+
+ * | type | | seq | ciphertext |
+ * +--------+ +--------+------...------+
+ * 32 32 sz
*
* and we actually transmit the following as the cryptogram.
*
* | tag | seq | ciphertext |
* +---...---+------+------...------+
* tagsz 32 sz
+ *
+ * Challenge tags are calculated by applying the MAC to the sequence number
+ * and message, concatenated as follows.
+ *
+ * +--------+---...---+
+ * | seq | m |
+ * +--------+---...---+
+ * 32 msz
*/
typedef struct iiv_algs {
/*----- The AEAD transform ------------------------------------------------*
*
- * This transform uses a general authenticated encryption scheme (the
- * additional data isn't necessary). Good options include
- * `chacha20-poly1305' or `rijndael-ocb3'.
+ * This transform uses a general authenticated encryption scheme. Processing
+ * additional authenticated data isn't needed for encrypting messages, but it
+ * is required for challenge generation. Good options include `chacha20-
+ * poly1305' or `rijndael-ocb3'; alas, `salsa20-naclbox' isn't acceptable.
*
- * To be acceptable, the scheme must accept at least a 64-bit nonce. (All of
- * Catacomb's current AEAD schemes are suitable.) The low 32 bits are the
- * sequence number, and the high 32 bits are the type, both big-endian.
+ * To be acceptable, the scheme must accept at least a 40-bit nonce. (All of
+ * Catacomb's current AEAD schemes are suitable in this respect.) The low 32
+ * bits are the sequence number. The type is written to the next 8--32
+ * bytes: if the nonce size is 64 bits or more (preferred, for compatibility
+ * reasons) then the type is written as 32 bits, and the remaining space is
+ * padded with zero bytes; otherwise, the type is right-aligned in the
+ * remaining space. Both fields are big-endian.
+ *
+ * +--------+--+
+ * | seq |ty|
+ * +--------+--+
+ * 32 8
+ *
+ * +--------+----+
+ * | seq | ty |
+ * +--------+----+
+ * 32 16
+ *
+ * +--------+------+
+ * | seq | type |
+ * +--------+------+
+ * 32 24
*
- * +------+------+
- * | seq | type |
- * +------+------+
- * 32 32
+ * +--------+--------+---...---+
+ * | seq | type | 0 |
+ * +--------+--------+---...---+
+ * 32 32 nsz - 64
*
* The ciphertext is formatted as
*
- * +---...---+------+------...------+
- * | tag | seq | ciphertext |
- * +---...---+------+------...------+
- * tagsz 32 sz
+ * +---...---+--------+------...------+
+ * | tag | seq | ciphertext |
+ * +---...---+--------+------...------+
+ * tagsz 32 sz
*
+ * Challenge tags are calculated by encrypting the message, using the
+ * sequence number as a nonce (as a big-endian integer, padding with leading
+ * zeroes as needed to fill the space), and discarding the ciphertext.
+ *
+ * +---...---+--------+ +-----...------+
+ * | 0 | seq | | message |
+ * +---...---+--------+ +-----...------+
+ * nsz - 32 32 msz
*/
#define AEAD_NONCEMAX 64
goto fail;
}
a->nsz = keysz_pad(8, a->c->noncesz);
+ if (!a->nsz) a->nsz = keysz_pad(5, a->c->noncesz);
if (!a->nsz) {
a_format(e, "unsuitable-aead-cipher", "%s", p, "nonce-too-small", A_END);
goto fail;
p = key_getattr(kf, k, "tagsz");
if (!p) {
p = key_getattr(kf, k, "mac");
- if (strncmp(p, "aead", 4) != 0 || (p[4] && p[4] != '/')) {
- a_format(e, "unknown-mac", "%s", p, A_END);
- goto fail;
- }
- if (p[4] == '/') p += 5;
+ if (!p) ;
+ else if (strncmp(p, "aead", 4) != 0 || (p[4] && p[4] != '/'))
+ { a_format(e, "unknown-mac", "%s", p, A_END); goto fail; }
+ else if (p[4] == '/') p += 5;
else p = 0;
}
if (!p)
DESTROY(bc);
}
+static void aead_fmtnonce(aead_ctx *bc, octet *n, uint32 seq, unsigned ty)
+{
+ assert(bc->nsz <= AEAD_NONCEMAX); assert(ty <= 255);
+ STORE32(n, seq);
+ switch (bc->nsz) {
+ case 5: STORE8(n + SEQSZ, ty); break;
+ case 6: STORE16(n + SEQSZ, ty); break;
+ case 7: STORE24(n + SEQSZ, ty); break;
+ default: memset(n + 8, 0, bc->nsz - 8); /* and continue */
+ case 8: STORE32(n + SEQSZ, ty); break;
+ }
+ TRACE_IV(n, bc->nsz);
+}
+
static int aead_encrypt(bulkctx *bbc, unsigned ty,
buf *b, buf *bb, uint32 seq)
{
qmac = BCUR(bb); qseq = qmac + bc->tsz; qpk = qseq + SEQSZ;
STORE32(qseq, seq);
- assert(bc->nsz <= sizeof(n));
- memcpy(n, qseq, SEQSZ); STORE32(n + SEQSZ, ty);
- if (bc->nsz > 8) memset(n + 8, 0, bc->nsz - 8);
- TRACE_IV(n, bc->nsz);
-
+ aead_fmtnonce(bc, n, seq, ty);
rc = gaead_encrypt(k, n, bc->nsz, 0, 0, p, sz, qpk, &csz, qmac, bc->tsz);
assert(!rc);
BSTEP(bb, bc->tsz + SEQSZ + csz);
}
static int aead_decrypt(bulkctx *bbc, unsigned ty,
- buf *b, buf *bb, uint32 *seq)
+ buf *b, buf *bb, uint32 *seq_out)
{
aead_ctx *bc = (aead_ctx *)bbc;
gaead_key *k = bc->d[DIR_IN].k;
const octet *pmac, *pseq, *ppk;
+ uint32 seq;
size_t psz = BLEFT(b);
size_t sz;
octet *q = BCUR(bb);
}
sz = psz - bc->tsz - SEQSZ;
pmac = BCUR(b); pseq = pmac + bc->tsz; ppk = pseq + SEQSZ;
+ seq = LOAD32(pseq);
- assert(bc->nsz <= sizeof(n));
- memcpy(n, pseq, SEQSZ); STORE32(n + SEQSZ, ty);
- if (bc->nsz > 8) memset(n + 8, 0, bc->nsz - 8);
- TRACE_IV(n, bc->nsz);
-
+ aead_fmtnonce(bc, n, seq, ty);
rc = gaead_decrypt(k, n, bc->nsz, 0, 0, ppk, sz, q, &sz, pmac, bc->tsz);
assert(rc >= 0);
if (!rc) { TRACE_MACERR(pmac, bc->tsz); return (KSERR_DECRYPT); }
- *seq = LOAD32(pseq);
+ *seq_out = seq;
BSTEP(bb, sz);
return (0);
}
* Salsa20 and ChaCha accept a 64-bit nonce. The low 32 bits are the
* sequence number, and the high 32 bits are the type, both big-endian.
*
- * +------+------+
- * | seq | type |
- * +------+------+
- * 32 32
+ * +--------+--------+
+ * | seq | type |
+ * +--------+--------+
+ * 32 32
*
* A stream is generated by concatenating the raw output blocks generated
* with this nonce and successive counter values starting from zero. The
* Note that there is no need to authenticate the type separately, since it
* was used to select the cipher nonce, and hence the Poly1305 key. The
* Poly1305 tag length is fixed.
+ *
+ * Challenge formation is rather tricky. We can't use Poly1305 directly
+ * because we need a random mask. So we proceed as follows. The challenge
+ * generator has a Salsa20 or ChaCha key. The sequence number is used as the
+ * Salsa20 message number/nonce, padded at the start with zeroes to form,
+ * effectively, a 64-bit big-endian integer.
+ *
+ * +--------+--------+
+ * | 0 | seq |
+ * +--------+--------+
+ * 32 32
+ *
+ * 256 bits (32 bytes) of keystream are generated and used as a Poly1305 hash
+ * key r and mask s. These are then used to hash the message, and the
+ * resulting tag is the challenge.
*/
typedef struct naclbox_algs {
poly1305_ctx pm;
octet b[POLY1305_KEYSZ + POLY1305_MASKSZ];
- assert(SALSA20_NONCESZ <= sizeof(b));
+ STATIC_ASSERT(SALSA20_NONCESZ <= sizeof(b), "Need more space for nonce");
+
memset(b, 0, SALSA20_NONCESZ - 4); STORE32(b + SALSA20_NONCESZ - 4, seq);
GC_SETIV(c->c, b); GC_ENCRYPT(c->c, 0, b, sizeof(b));
poly1305_keyinit(&pk, b, POLY1305_KEYSZ);
poly1305_ctx pm;
octet b[POLY1305_KEYSZ + POLY1305_MASKSZ];
- assert(SALSA20_NONCESZ <= sizeof(b));
+ STATIC_ASSERT(SALSA20_NONCESZ <= sizeof(b), "Need more space for nonce");
+ STATIC_ASSERT(POLY1305_TAGSZ <= sizeof(b), "Need more space for tag");
+
memset(b, 0, SALSA20_NONCESZ - 4); STORE32(b + SALSA20_NONCESZ - 4, seq);
GC_SETIV(c->c, b); GC_ENCRYPT(c->c, 0, b, sizeof(b));
poly1305_keyinit(&pk, b, POLY1305_KEYSZ);
poly1305_macinit(&pm, &pk, b + POLY1305_KEYSZ);
if (msz) poly1305_hash(&pm, m, msz);
- assert(POLY1305_TAGSZ <= sizeof(b)); poly1305_done(&pm, b);
+ poly1305_done(&pm, b);
return (ct_memeq(t, b, POLY1305_TAGSZ) ? 0 : -1);
}