diff options
Diffstat (limited to 'src/irmd/oap/hdr.c')
| -rw-r--r-- | src/irmd/oap/hdr.c | 462 |
1 files changed, 376 insertions, 86 deletions
diff --git a/src/irmd/oap/hdr.c b/src/irmd/oap/hdr.c index 5465dd2a..f0411f64 100644 --- a/src/irmd/oap/hdr.c +++ b/src/irmd/oap/hdr.c @@ -30,6 +30,7 @@ #include <ouroboros/crypt.h> #include <ouroboros/endian.h> +#include <ouroboros/errno.h> #include <ouroboros/hash.h> #include <ouroboros/logs.h> #include <ouroboros/rib.h> @@ -45,9 +46,17 @@ #include <string.h> #include <time.h> +#define OAP_SEAL_TAGSZ 16 /* AEAD tag on the sealed identity block */ +/* Sealed length prefix: data_len ‖ crt_len. */ +#define OAP_SEAL_LENSZ (sizeof(uint16_t) + sizeof(uint16_t)) + +/* hs_key is single-use per handshake, so a fixed nonce is reuse-safe. */ +static const uint8_t oap_seal_nonce[12]; + int oap_hdr_decode(struct oap_hdr * oap_hdr, buffer_t hdr, - int req_md_nid) + int req_md_nid, + bool rekey) { off_t offset; uint16_t kex_len; @@ -88,11 +97,13 @@ int oap_hdr_decode(struct oap_hdr * oap_hdr, oap_hdr->md_str = md_nid_to_str(oap_hdr->md_nid); offset += sizeof(uint16_t); - /* Validate NIDs: NID_undef is valid at parse time, else must be known. + /* + * Validate NIDs: NID_undef is valid at parse time, else must be known. * Note: md_nid=NID_undef only valid for PQC; enforced at sign/verify. */ if (ciph_nid != NID_undef && crypt_validate_nid(ciph_nid) < 0) goto fail_decode; + if (oap_hdr->kdf_nid != NID_undef && md_validate_nid(oap_hdr->kdf_nid) < 0) goto fail_decode; @@ -115,10 +126,37 @@ int oap_hdr_decode(struct oap_hdr * oap_hdr, data_len = (size_t) ntoh16(*(uint16_t *)(hdr.data + offset)); offset += sizeof(uint16_t); - /* Response includes req_hash when md_nid is set */ + assert((size_t) offset == OAP_HDR_MIN_SIZE); + + /* Response includes rsp_tag when md_nid is set */ hash_len = (req_md_nid != NID_undef) ? (size_t) md_len(req_md_nid) : 0; + /* Encrypted response: sealed block is data_len‖crt_len‖data‖crt‖sig. */ + if (req_md_nid != NID_undef && ciph_nid != NID_undef) { + if (hdr.len < (size_t) offset + oap_hdr->kex.len + hash_len + + OAP_SEAL_TAGSZ + OAP_SEAL_LENSZ) + goto fail_decode; + + oap_hdr->kex.data = hdr.data + offset; + offset += oap_hdr->kex.len; + + oap_hdr->rsp_tag.data = hdr.data + offset; + oap_hdr->rsp_tag.len = hash_len; + offset += hash_len; + + oap_hdr->sealed.data = hdr.data + offset; + oap_hdr->sealed.len = hdr.len - offset; + + /* crt/data/sig lengths are sealed; set by oap_hdr_unseal. */ + oap_hdr->crt.len = crt_len; + oap_hdr->data.len = data_len; + + oap_hdr->hdr = hdr; + + return 0; + } + /* Validate total length */ if (hdr.len < (size_t) offset + crt_len + oap_hdr->kex.len + data_len + hash_len) @@ -128,8 +166,12 @@ int oap_hdr_decode(struct oap_hdr * oap_hdr, sig_len = hdr.len - offset - crt_len - oap_hdr->kex.len - data_len - hash_len; - /* Unsigned packets must not have trailing bytes */ - if (crt_len == 0 && sig_len != 0) + /* + * Unsigned packets must not have trailing bytes. A re-key request + * is signed but cert-less (verified against the cached peer cert), + * so the rekey caller permits crt_len==0 with a signature. + */ + if (crt_len == 0 && sig_len != 0 && !rekey) goto fail_decode; /* Parse variable fields */ @@ -144,8 +186,8 @@ int oap_hdr_decode(struct oap_hdr * oap_hdr, oap_hdr->data.len = data_len; offset += data_len; - oap_hdr->req_hash.data = hdr.data + offset; - oap_hdr->req_hash.len = hash_len; + oap_hdr->rsp_tag.data = hdr.data + offset; + oap_hdr->rsp_tag.len = hash_len; offset += hash_len; oap_hdr->sig.data = hdr.data + offset; @@ -164,6 +206,7 @@ void oap_hdr_fini(struct oap_hdr * oap_hdr) { assert(oap_hdr != NULL); + freebuf(oap_hdr->sealed_pt); freebuf(oap_hdr->hdr); memset(oap_hdr, 0, sizeof(*oap_hdr)); } @@ -207,12 +250,229 @@ void oap_hdr_init(struct oap_hdr * hdr, hdr->nid = nid; } +/* Write the 36-byte fixed header; stamp is already in network order. */ +static void write_oap_fixed(uint8_t * buf, + const struct oap_hdr * hdr, + const struct sec_config * kcfg, + size_t crt_len, + size_t data_len, + uint64_t stamp) +{ + uint16_t v; + uint16_t kex_len; + off_t offset = 0; + + memcpy(buf + offset, hdr->id.data, hdr->id.len); + offset += hdr->id.len; + + memcpy(buf + offset, &stamp, sizeof(stamp)); + offset += sizeof(stamp); + + v = hton16(hdr->nid); + memcpy(buf + offset, &v, sizeof(v)); + offset += sizeof(v); + + v = hton16(kcfg->k.nid); + memcpy(buf + offset, &v, sizeof(v)); + offset += sizeof(v); + + v = hton16(kcfg->d.nid); + memcpy(buf + offset, &v, sizeof(v)); + offset += sizeof(v); + + v = hton16((uint16_t) crt_len); + memcpy(buf + offset, &v, sizeof(v)); + offset += sizeof(v); + + kex_len = (uint16_t) hdr->kex.len; + if (hdr->kex.len > 0 && IS_KEM_ALGORITHM(kcfg->x.str)) { + if (IS_HYBRID_KEM(kcfg->x.str)) + kex_len |= OAP_KEX_FMT_BIT; + if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP) + kex_len |= OAP_KEX_ROLE_BIT; + } + + kex_len = hton16(kex_len); + memcpy(buf + offset, &kex_len, sizeof(kex_len)); + offset += sizeof(kex_len); + + v = hton16((uint16_t) data_len); + memcpy(buf + offset, &v, sizeof(v)); +} + +/* + * Pack lens ‖ data ‖ crt, sign prefix ‖ body, append the signature, then + * AEAD-seal lens ‖ data ‖ crt ‖ sig under prefix as AAD. The cert, app data + * and their sizes stay confidential; *out is the opaque sealed block. The + * signature rides inside the seal so it can't deanonymise the server. + */ +static int oap_seal_body(int nid, + const uint8_t * seal_key, + void * pkp, + int md_nid, + buffer_t prefix, + buffer_t data, + buffer_t crt, + buffer_t * out) +{ + buffer_t sig = BUF_INIT; + buffer_t sign; + buffer_t aad; + buffer_t plain; + uint8_t * buf; + uint8_t * tmp; + uint16_t datalen; + uint16_t crtlen; + size_t body_len; + off_t offset; + + datalen = hton16((uint16_t) data.len); + crtlen = hton16((uint16_t) crt.len); + + body_len = OAP_SEAL_LENSZ + data.len + crt.len; + + buf = malloc(prefix.len + body_len); + if (buf == NULL) + return -1; + + memcpy(buf, prefix.data, prefix.len); + offset = (off_t) prefix.len; + + memcpy(buf + offset, &datalen, sizeof(datalen)); + offset += sizeof(datalen); + + memcpy(buf + offset, &crtlen, sizeof(crtlen)); + offset += sizeof(crtlen); + + if (data.len != 0) + memcpy(buf + offset, data.data, data.len); + + offset += data.len; + + if (crt.len != 0) + memcpy(buf + offset, crt.data, crt.len); + + /* Sign prefix ‖ lens ‖ data ‖ crt (plaintext, before sealing). */ + sign.data = buf; + sign.len = prefix.len + body_len; + + if (pkp != NULL && auth_sign(pkp, md_nid, sign, &sig) < 0) + goto fail_buf; + + /* Append the signature so the seal covers lens ‖ data ‖ crt ‖ sig. */ + if (sig.len != 0) { + tmp = realloc(buf, prefix.len + body_len + sig.len); + if (tmp == NULL) + goto fail_sig; + + buf = tmp; + memcpy(buf + prefix.len + body_len, sig.data, sig.len); + } + + aad.data = buf; + aad.len = prefix.len; + plain.data = buf + prefix.len; + plain.len = body_len + sig.len; + + if (crypt_oneshot_seal(nid, seal_key, oap_seal_nonce, + aad, plain, out) < 0) + goto fail_sig; + + free(buf); + freebuf(sig); + + return 0; + + fail_sig: + freebuf(sig); + fail_buf: + free(buf); + return -1; +} + +/* Encode an identity-hidden response: wire = prefix ‖ oap_seal_body(...). */ +static int oap_hdr_encode_sealed(struct oap_hdr * hdr, + void * pkp, + void * crt, + struct sec_config * kcfg, + buffer_t rsp_tag, + int req_md_nid, + const uint8_t * seal_key) +{ + struct timespec now; + uint64_t stamp; + buffer_t der = BUF_INIT; + buffer_t sealed = BUF_INIT; + buffer_t prefix; + off_t offset; + + clock_gettime(CLOCK_REALTIME, &now); + stamp = hton64(TS_TO_UINT64(now)); + + if (crt != NULL && crypt_crt_der(crt, &der) < 0) + goto fail_der; + + prefix.len = OAP_HDR_MIN_SIZE + hdr->kex.len + rsp_tag.len; + prefix.data = malloc(prefix.len); + if (prefix.data == NULL) + goto fail_der; + + /* Cleartext crt_len/data_len are 0; real lengths prefix the seal. */ + write_oap_fixed(prefix.data, hdr, kcfg, 0, 0, stamp); + offset = OAP_HDR_MIN_SIZE; + + if (hdr->kex.len != 0) + memcpy(prefix.data + offset, hdr->kex.data, hdr->kex.len); + + offset += hdr->kex.len; + + if (rsp_tag.len != 0) + memcpy(prefix.data + offset, rsp_tag.data, rsp_tag.len); + + offset += rsp_tag.len; + + assert((size_t) offset == prefix.len); + + if (oap_seal_body(hdr->nid, seal_key, pkp, kcfg->d.nid, + prefix, hdr->data, der, &sealed) < 0) + goto fail_prefix; + + hdr->hdr.len = prefix.len + sealed.len; + hdr->hdr.data = malloc(hdr->hdr.len); + if (hdr->hdr.data == NULL) + goto fail_sealed; + + memcpy(hdr->hdr.data, prefix.data, prefix.len); + memcpy(hdr->hdr.data + prefix.len, sealed.data, sealed.len); + + freebuf(sealed); + free(prefix.data); + freebuf(der); + + if (oap_hdr_decode(hdr, hdr->hdr, req_md_nid, false) < 0) + goto fail_decode; + + return 0; + + fail_decode: + oap_hdr_fini(hdr); + return -1; + fail_sealed: + freebuf(sealed); + fail_prefix: + free(prefix.data); + fail_der: + freebuf(der); + return -1; +} + int oap_hdr_encode(struct oap_hdr * hdr, void * pkp, void * crt, struct sec_config * kcfg, - buffer_t req_hash, - int req_md_nid) + buffer_t rsp_tag, + int req_md_nid, + const uint8_t * seal_key) { struct timespec now; uint64_t stamp; @@ -220,103 +480,56 @@ int oap_hdr_encode(struct oap_hdr * hdr, buffer_t der = BUF_INIT; buffer_t sig = BUF_INIT; buffer_t sign; - uint16_t len; - uint16_t ciph_nid; - uint16_t kdf_nid; - uint16_t md_nid; - uint16_t kex_len; off_t offset; assert(hdr != NULL); assert(hdr->id.data != NULL && hdr->id.len == OAP_ID_SIZE); assert(kcfg != NULL); + if (seal_key != NULL) + return oap_hdr_encode_sealed(hdr, pkp, crt, kcfg, rsp_tag, + req_md_nid, seal_key); + clock_gettime(CLOCK_REALTIME, &now); stamp = hton64(TS_TO_UINT64(now)); if (crt != NULL && crypt_crt_der(crt, &der) < 0) goto fail_der; - ciph_nid = hton16(hdr->nid); - kdf_nid = hton16(kcfg->k.nid); - md_nid = hton16(kcfg->d.nid); - - /* Build kex_len with flags */ - kex_len = (uint16_t) hdr->kex.len; - if (hdr->kex.len > 0 && IS_KEM_ALGORITHM(kcfg->x.str)) { - if (IS_HYBRID_KEM(kcfg->x.str)) - kex_len |= OAP_KEX_FMT_BIT; - if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP) - kex_len |= OAP_KEX_ROLE_BIT; - } - kex_len = hton16(kex_len); - - /* Fixed header (36 bytes) + variable fields + req_hash (if auth) */ + /* Fixed header (36 bytes) + variable fields + rsp_tag (rsp only) */ out.len = OAP_HDR_MIN_SIZE + der.len + hdr->kex.len + hdr->data.len + - req_hash.len; + rsp_tag.len; out.data = malloc(out.len); if (out.data == NULL) goto fail_out; - offset = 0; - - /* id (16 bytes) */ - memcpy(out.data + offset, hdr->id.data, hdr->id.len); - offset += hdr->id.len; - - /* timestamp (8 bytes) */ - memcpy(out.data + offset, &stamp, sizeof(stamp)); - offset += sizeof(stamp); - - /* cipher_nid (2 bytes) */ - memcpy(out.data + offset, &ciph_nid, sizeof(ciph_nid)); - offset += sizeof(ciph_nid); - - /* kdf_nid (2 bytes) */ - memcpy(out.data + offset, &kdf_nid, sizeof(kdf_nid)); - offset += sizeof(kdf_nid); - - /* md_nid (2 bytes) */ - memcpy(out.data + offset, &md_nid, sizeof(md_nid)); - offset += sizeof(md_nid); - - /* crt_len (2 bytes) */ - len = hton16((uint16_t) der.len); - memcpy(out.data + offset, &len, sizeof(len)); - offset += sizeof(len); - - /* kex_len + flags (2 bytes) */ - memcpy(out.data + offset, &kex_len, sizeof(kex_len)); - offset += sizeof(kex_len); - - /* data_len (2 bytes) */ - len = hton16((uint16_t) hdr->data.len); - memcpy(out.data + offset, &len, sizeof(len)); - offset += sizeof(len); - - /* Fixed header complete (36 bytes) */ - assert((size_t) offset == OAP_HDR_MIN_SIZE); + write_oap_fixed(out.data, hdr, kcfg, der.len, hdr->data.len, stamp); + offset = OAP_HDR_MIN_SIZE; /* certificate (variable) */ if (der.len != 0) memcpy(out.data + offset, der.data, der.len); + offset += der.len; /* kex data (variable) */ if (hdr->kex.len != 0) memcpy(out.data + offset, hdr->kex.data, hdr->kex.len); + offset += hdr->kex.len; /* data (variable) */ if (hdr->data.len != 0) memcpy(out.data + offset, hdr->data.data, hdr->data.len); + offset += hdr->data.len; - /* req_hash (variable, only for authenticated responses) */ - if (req_hash.len != 0) - memcpy(out.data + offset, req_hash.data, req_hash.len); - offset += req_hash.len; + /* rsp_tag (variable, response only) */ + if (rsp_tag.len != 0) + memcpy(out.data + offset, rsp_tag.data, rsp_tag.len); + + offset += rsp_tag.len; assert((size_t) offset == out.len); @@ -340,7 +553,7 @@ int oap_hdr_encode(struct oap_hdr * hdr, clrbuf(out); } - if (oap_hdr_decode(hdr, hdr->hdr, req_md_nid) < 0) + if (oap_hdr_decode(hdr, hdr->hdr, req_md_nid, false) < 0) goto fail_decode; freebuf(der); @@ -360,28 +573,99 @@ int oap_hdr_encode(struct oap_hdr * hdr, return -1; } +int oap_hdr_unseal(struct oap_hdr * hdr, + const uint8_t * key) +{ + buffer_t pt = BUF_INIT; + buffer_t prefix; + uint8_t * recon; + size_t body_len; + size_t pt_len; + size_t data_len; + size_t crt_len; + + assert(hdr != NULL); + assert(key != NULL); + + if (hdr->sealed.data == NULL || hdr->sealed.len == 0) + return -EINVAL; + + /* AAD prefix is fixed‖kex‖rsp_tag; sealed starts right after. */ + prefix.data = hdr->hdr.data; + prefix.len = (size_t) (hdr->sealed.data - hdr->hdr.data); + + if (crypt_oneshot_open(hdr->nid, key, oap_seal_nonce, prefix, + hdr->sealed, &pt) < 0) + return -ECRYPT; + + pt_len = pt.len; + + /* Plaintext = data_len ‖ crt_len ‖ data ‖ crt ‖ sig. */ + if (pt_len < OAP_SEAL_LENSZ) + goto fail_auth; + + data_len = (size_t) ntoh16(*(uint16_t *) pt.data); + crt_len = (size_t) ntoh16(*(uint16_t *)(pt.data + sizeof(uint16_t))); + + body_len = OAP_SEAL_LENSZ + data_len + crt_len; + if (pt_len < body_len) + goto fail_auth; + + /* Rebuild prefix ‖ lens ‖ data ‖ crt ‖ sig (whole signed region). */ + recon = malloc(prefix.len + pt_len); + if (recon == NULL) + goto fail_mem; + + memcpy(recon, prefix.data, prefix.len); + memcpy(recon + prefix.len, pt.data, pt_len); + + freebuf(pt); + + hdr->sealed_pt.data = recon; + hdr->sealed_pt.len = prefix.len + pt_len; + + hdr->data.data = recon + prefix.len + OAP_SEAL_LENSZ; + hdr->data.len = data_len; + hdr->crt.data = recon + prefix.len + OAP_SEAL_LENSZ + data_len; + hdr->crt.len = crt_len; + hdr->sig.data = recon + prefix.len + body_len; + hdr->sig.len = pt_len - body_len; + + return 0; + + fail_mem: + freebuf(pt); + return -ENOMEM; + fail_auth: + freebuf(pt); + return -EAUTH; +} + #ifdef DEBUG_PROTO_OAP #define OAP_KEX_IS_KEM(hdr) ((hdr)->kex_flags.role | (hdr)->kex_flags.fmt) static void debug_oap_hdr(const struct oap_hdr * hdr) { assert(hdr); + if (hdr->sealed.len > 0) + log_proto(" Sealed block: [%zu bytes] on wire", + hdr->sealed.len); + if (hdr->crt.len > 0) log_proto(" crt: [%zu bytes]", hdr->crt.len); + else if (hdr->sealed.len > 0) + log_proto(" crt: <sealed>"); else log_proto(" crt: <none>"); if (hdr->kex.len > 0) { if (OAP_KEX_IS_KEM(hdr)) - log_proto(" Key Exchange Data:" - " [%zu bytes] [%s]", + log_proto(" Key Exchange Data: [%zu bytes] [%s]", hdr->kex.len, hdr->kex_flags.role ? - "Client encaps" : - "Server encaps"); + "Client encaps" : "Server encaps"); else - log_proto(" Key Exchange Data:" - " [%zu bytes]", + log_proto(" Key Exchange Data: [%zu bytes]", hdr->kex.len); } else log_proto(" Key Exchange Data: <none>"); @@ -403,16 +687,20 @@ static void debug_oap_hdr(const struct oap_hdr * hdr) if (hdr->data.len > 0) log_proto(" Data: [%zu bytes]", hdr->data.len); + else if (hdr->sealed.len > 0) + log_proto(" Data: <sealed>"); else log_proto(" Data: <none>"); - if (hdr->req_hash.len > 0) - log_proto(" Req Hash: [%zu bytes]", hdr->req_hash.len); + if (hdr->rsp_tag.len > 0) + log_proto(" Rsp Tag: [%zu bytes]", hdr->rsp_tag.len); else - log_proto(" Req Hash: <none>"); + log_proto(" Rsp Tag: <none>"); if (hdr->sig.len > 0) log_proto(" Signature: [%zu bytes]", hdr->sig.len); + else if (hdr->sealed.len > 0) + log_proto(" Signature: <sealed>"); else log_proto(" Signature: <none>"); } @@ -432,8 +720,9 @@ void debug_oap_hdr_rcv(const struct oap_hdr * hdr) tm = gmtime(&stamp); strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm); - log_proto("OAP_HDR [" HASH_FMT64 " @ %s ] <--", - HASH_VAL64(hdr->id.data), tmstr); + log_proto("OAP_HDR [" HASH_FMT64 " @ %s ]%s <--", + HASH_VAL64(hdr->id.data), tmstr, + hdr->sealed.len > 0 ? " [sealed]" : ""); debug_oap_hdr(hdr); #else @@ -455,8 +744,9 @@ void debug_oap_hdr_snd(const struct oap_hdr * hdr) tm = gmtime(&stamp); strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm); - log_proto("OAP_HDR [" HASH_FMT64 " @ %s ] -->", - HASH_VAL64(hdr->id.data), tmstr); + log_proto("OAP_HDR [" HASH_FMT64 " @ %s ]%s -->", + HASH_VAL64(hdr->id.data), tmstr, + hdr->sealed.len > 0 ? " [sealed]" : ""); debug_oap_hdr(hdr); #else |
