CVE-2022-37967 Add new PAC checksum
authorJoseph Sutton <josephsutton@catalyst.net.nz>
Wed, 9 Nov 2022 00:45:13 +0000 (13:45 +1300)
committerJoseph Sutton <josephsutton@catalyst.net.nz>
Wed, 3 May 2023 04:13:16 +0000 (16:13 +1200)
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15231

Pair-Programmed-With: Andrew Bartlett <abartlet@samba.org>

Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Signed-off-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
kdc/kerberos5.c
kdc/krb5tgs.c
lib/krb5/pac.c

index 2ae215e556688bb31b0d7018ab42b473fec16fc8..21586a43c88da5e0f806583ccecad10682666640 100644 (file)
@@ -1913,6 +1913,7 @@ generate_pac(astgs_request_t r, const Key *skey, const Key *tkey,
                         rodc_id,
                         NULL, /* UPN */
                         canon_princ,
+                        false, /* add_full_sig */
                         is_tgs ? &r->pac_attributes : NULL,
                         &data);
     krb5_free_principal(r->context, client);
index 95c4e0101fa01c956a05f3365d341615fb2c3e07..4f9d3c8ec2928900873b904f35c9ffdd051d565f 100644 (file)
@@ -778,7 +778,7 @@ tgs_make_reply(astgs_request_t r,
 
            ret = _krb5_kdc_pac_sign_ticket(r->context, r->pac, r->client_princ, serverkey,
                                            krbtgtkey, rodc_id, NULL, r->canon_client_princ,
-                                           add_ticket_sig, et,
+                                           add_ticket_sig, add_ticket_sig, et,
                                            is_tgs ? &r->pac_attributes : NULL);
            if (ret)
                goto out;
index e6dfe2aef3d5af770614f90fa7d012113c866874..26403e665a0f29edecc7ea5e59de7fce0365e979 100644 (file)
@@ -74,6 +74,7 @@ struct krb5_pac_data {
     struct PAC_INFO_BUFFER *upn_dns_info;
     struct PAC_INFO_BUFFER *ticket_checksum;
     struct PAC_INFO_BUFFER *attributes_info;
+    struct PAC_INFO_BUFFER *full_checksum;
     krb5_data ticket_sign_data;
 
     /* PAC_UPN_DNS_INFO */
@@ -101,6 +102,7 @@ struct krb5_pac_data {
 #define PAC_TICKET_CHECKSUM            16
 #define PAC_ATTRIBUTES_INFO            17
 #define PAC_REQUESTOR_SID              18
+#define PAC_FULL_CHECKSUM              19
 
 /* Flag in PAC_UPN_DNS_INFO */
 #define PAC_EXTRA_LOGON_INFO_FLAGS_UPN_DEFAULTED       0x1
@@ -398,6 +400,13 @@ krb5_pac_parse(krb5_context context, const void *ptr, size_t len,
             else
                 p->attributes_info = &p->pac->buffers[i];
             break;
+        case PAC_FULL_CHECKSUM:
+           if (p->full_checksum)
+               krb5_set_error_message(context, ret = EINVAL,
+                                      N_("PAC has multiple full checksums", ""));
+            else
+                p->full_checksum = &p->pac->buffers[i];
+            break;
         default: break;
         }
     }
@@ -668,7 +677,8 @@ verify_checksum(krb5_context context,
                const struct PAC_INFO_BUFFER *sig,
                const krb5_data *data,
                void *ptr, size_t len,
-               const krb5_keyblock *key)
+               const krb5_keyblock *key,
+               krb5_boolean strict_cksumtype_match)
 {
     krb5_storage *sp = NULL;
     uint32_t type;
@@ -726,7 +736,7 @@ verify_checksum(krb5_context context,
      * http://blogs.msdn.com/b/openspecification/archive/2010/01/01/verifying-the-server-signature-in-kerberos-privilege-account-certificate.aspx
      * for Microsoft's explaination */
 
-    if (cksum.cksumtype == CKSUMTYPE_HMAC_MD5) {
+    if (cksum.cksumtype == CKSUMTYPE_HMAC_MD5 && !strict_cksumtype_match) {
        Checksum local_checksum;
 
        memset(&local_checksum, 0, sizeof(local_checksum));
@@ -1192,7 +1202,7 @@ parse_attributes_info(krb5_context context,
  * @param pac the pac structure returned by krb5_pac_parse().
  * @param authtime The time of the ticket the PAC belongs to.
  * @param principal the principal to verify.
- * @param server The service key, most always be given.
+ * @param server The service key, may be given.
  * @param privsvr The KDC key, may be given.
 
  * @return Returns 0 to indicate success. Otherwise an kerberos et
@@ -1210,6 +1220,21 @@ krb5_pac_verify(krb5_context context,
                const krb5_keyblock *privsvr)
 {
     krb5_error_code ret;
+    /*
+     * If we are in the KDC, we expect back a full signature in the PAC
+     *
+     * This is set up as a seperate variable to make it easier if a
+     * subsequent patch is added to make this configurable in the
+     * krb5.conf (or forced into the krb5_context via Samba)
+     */
+    krb5_boolean expect_full_sig = privsvr != NULL;
+
+    /*
+     * If we are on the KDC, then we trust we are not in a realm with
+     * buggy Windows 2008 or similar era DCs that give our HMAC-MD5
+     * sigatures over AES keys.  DES is also already gone.
+     */
+    krb5_boolean strict_cksumtype_match = expect_full_sig;
 
     if (pac->server_checksum == NULL) {
        krb5_set_error_message(context, EINVAL, "PAC missing server checksum");
@@ -1223,6 +1248,10 @@ krb5_pac_verify(krb5_context context,
        krb5_set_error_message(context, EINVAL, "PAC missing logon name");
        return EINVAL;
     }
+    if (expect_full_sig && pac->full_checksum == NULL) {
+       krb5_set_error_message(context, EINVAL, "PAC missing full checksum");
+       return EINVAL;
+    }
 
     if (principal != NULL) {
        ret = verify_logonname(context, pac->logon_name, &pac->data, authtime,
@@ -1235,14 +1264,15 @@ krb5_pac_verify(krb5_context context,
         pac->privsvr_checksum->buffersize < 4)
        return EINVAL;
 
-    /*
-     * in the service case, clean out data option of the privsvr and
-     * server checksum before checking the checksum.
-     */
-    if (server != NULL)
+    if (server != NULL || privsvr != NULL)
     {
        krb5_data *copy;
 
+       /*
+        * in the service case, clean out data option of the privsvr and
+        * server checksum before checking the checksum.
+        */
+
        ret = krb5_copy_data(context, &pac->data, &copy);
        if (ret)
            return ret;
@@ -1255,15 +1285,43 @@ krb5_pac_verify(krb5_context context,
               0,
               pac->privsvr_checksum->buffersize - 4);
 
-       ret = verify_checksum(context,
-                             pac->server_checksum,
-                             &pac->data,
-                             copy->data,
-                             copy->length,
-                             server);
+       if (server != NULL) {
+           ret = verify_checksum(context,
+                                 pac->server_checksum,
+                                 &pac->data,
+                                 copy->data,
+                                 copy->length,
+                                 server,
+                                 strict_cksumtype_match);
+           if (ret) {
+               krb5_free_data(context, copy);
+               return ret;
+           }
+       }
+
+       if (privsvr != NULL && pac->full_checksum != NULL) {
+           /*
+            * in the full checksum case, also clean out the full
+            * checksum before verifying it.
+            */
+           memset((char *)copy->data + pac->full_checksum->offset + 4,
+                  0,
+                  pac->full_checksum->buffersize - 4);
+
+           ret = verify_checksum(context,
+                                 pac->full_checksum,
+                                 &pac->data,
+                                 copy->data,
+                                 copy->length,
+                                 privsvr,
+                                 strict_cksumtype_match);
+           if (ret) {
+               krb5_free_data(context, copy);
+               return ret;
+           }
+       }
+
        krb5_free_data(context, copy);
-       if (ret)
-           return ret;
     }
     if (privsvr) {
        /* The priv checksum covers the server checksum */
@@ -1273,7 +1331,8 @@ krb5_pac_verify(krb5_context context,
                              (char *)pac->data.data
                              + pac->server_checksum->offset + 4,
                              pac->server_checksum->buffersize - 4,
-                             privsvr);
+                             privsvr,
+                             strict_cksumtype_match);
        if (ret)
            return ret;
 
@@ -1286,7 +1345,8 @@ krb5_pac_verify(krb5_context context,
 
            ret = verify_checksum(context, pac->ticket_checksum, &pac->data,
                                 pac->ticket_sign_data.data,
-                                pac->ticket_sign_data.length, privsvr);
+                                pac->ticket_sign_data.length, privsvr,
+                                strict_cksumtype_match);
            if (ret)
                return ret;
        }
@@ -1377,6 +1437,7 @@ _krb5_pac_sign(krb5_context context,
               uint16_t rodc_id,
               krb5_const_principal upn_princ,
               krb5_const_principal canon_princ,
+              krb5_boolean add_full_sig,
               uint64_t *pac_attributes, /* optional */
               krb5_data *data)
 {
@@ -1384,7 +1445,7 @@ _krb5_pac_sign(krb5_context context,
     krb5_storage *sp = NULL, *spdata = NULL;
     uint32_t end;
     size_t server_size, priv_size;
-    uint32_t server_offset = 0, priv_offset = 0, ticket_offset = 0;
+    uint32_t server_offset = 0, priv_offset = 0, ticket_offset = 0, full_offset = 0;
     uint32_t server_cksumtype = 0, priv_cksumtype = 0;
     uint32_t num = 0;
     uint32_t i, sz;
@@ -1461,6 +1522,16 @@ _krb5_pac_sign(krb5_context context,
                                       N_("PAC has multiple attributes info buffers", ""));
                goto out;
            }
+       } else if (p->pac->buffers[i].type == PAC_FULL_CHECKSUM) {
+           if (p->full_checksum == NULL) {
+               p->full_checksum = &p->pac->buffers[i];
+           }
+           if (p->full_checksum != &p->pac->buffers[i]) {
+               ret = KRB5KDC_ERR_BADOPTION;
+               krb5_set_error_message(context, ret,
+                                      N_("PAC has multiple full checksums", ""));
+               goto out;
+           }
        }
     }
 
@@ -1473,6 +1544,8 @@ _krb5_pac_sign(krb5_context context,
        num++;
     if (p->ticket_sign_data.length != 0 && p->ticket_checksum == NULL)
        num++;
+    if (add_full_sig && p->full_checksum == NULL)
+       num++;
 
     /* Allocate any missing-but-necessary buffers */
     if (num) {
@@ -1515,6 +1588,11 @@ _krb5_pac_sign(krb5_context context,
            p->ticket_checksum = &p->pac->buffers[p->pac->numbuffers++];
            p->ticket_checksum->type = PAC_TICKET_CHECKSUM;
        }
+       if (add_full_sig && p->full_checksum == NULL) {
+           p->full_checksum = &p->pac->buffers[p->pac->numbuffers++];
+           memset(p->full_checksum, 0, sizeof(*p->full_checksum));
+           p->full_checksum->type = PAC_FULL_CHECKSUM;
+       }
     }
 
     /* Calculate LOGON NAME */
@@ -1647,6 +1725,31 @@ _krb5_pac_sign(krb5_context context,
                len += sizeof(rodc_id);
                CHECK(ret, krb5_store_uint16(spdata, rodc_id), out);
            }
+       } else if (add_full_sig &&
+                  p->pac->buffers[i].type == PAC_FULL_CHECKSUM) {
+           if (priv_size > UINT32_MAX - 4) {
+               ret = EINVAL;
+               krb5_set_error_message(context, ret, "integer overrun");
+               goto out;
+           }
+           len = priv_size + 4;
+           if (end > UINT32_MAX - 4) {
+               ret = EINVAL;
+               krb5_set_error_message(context, ret, "integer overrun");
+               goto out;
+           }
+           full_offset = end + 4;
+           CHECK(ret, krb5_store_uint32(spdata, priv_cksumtype), out);
+           CHECK(ret, fill_zeros(context, spdata, priv_size), out);
+           if (rodc_id != 0) {
+               if (len > UINT32_MAX - sizeof(rodc_id)) {
+                   ret = EINVAL;
+                   krb5_set_error_message(context, ret, "integer overrun");
+                   goto out;
+               }
+               len += sizeof(rodc_id);
+               CHECK(ret, fill_zeros(context, spdata, sizeof(rodc_id)), out);
+           }
        } else if (p->pac->buffers[i].type == PAC_LOGON_NAME) {
            len = krb5_storage_write(spdata, logon.data, logon.length);
            if (logon.length != len) {
@@ -1708,6 +1811,21 @@ _krb5_pac_sign(krb5_context context,
                              p->ticket_sign_data.data,
                              p->ticket_sign_data.length,
                              (char *)d.data + ticket_offset, priv_size);
+    if (ret == 0 && add_full_sig)
+        ret = create_checksum(context, priv_key, priv_cksumtype,
+                              d.data, d.length,
+                              (char *)d.data + full_offset, priv_size);
+    if (ret == 0 && add_full_sig && rodc_id != 0) {
+       void *buf = (char *)d.data + full_offset + priv_size;
+       krb5_storage *rs = krb5_storage_from_mem(buf, sizeof(rodc_id));
+       if (rs == NULL)
+           ret = krb5_enomem(context);
+        else
+            krb5_storage_set_flags(rs, KRB5_STORAGE_BYTEORDER_LE);
+        if (ret == 0)
+            ret = krb5_store_uint16(rs, rodc_id);
+       krb5_storage_free(rs);
+    }
     if (ret == 0)
         ret = create_checksum(context, server_key, server_cksumtype,
                               d.data, d.length,
@@ -1717,22 +1835,15 @@ _krb5_pac_sign(krb5_context context,
                               (char *)d.data + server_offset, server_size,
                               (char *)d.data + priv_offset, priv_size);
     if (ret == 0 && rodc_id != 0) {
-       krb5_data rd;
-       krb5_storage *rs = krb5_storage_emem();
+       void *buf = (char *)d.data + priv_offset + priv_size;
+       krb5_storage *rs = krb5_storage_from_mem(buf, sizeof(rodc_id));
        if (rs == NULL)
            ret = krb5_enomem(context);
         else
             krb5_storage_set_flags(rs, KRB5_STORAGE_BYTEORDER_LE);
         if (ret == 0)
             ret = krb5_store_uint16(rs, rodc_id);
-        if (ret == 0)
-            ret = krb5_storage_to_data(rs, &rd);
        krb5_storage_free(rs);
-       if (ret)
-           goto out;
-       heim_assert(rd.length == sizeof(rodc_id), "invalid length");
-       memcpy((char *)d.data + priv_offset + priv_size, rd.data, rd.length);
-       krb5_data_free(&rd);
     }
 
     if (ret)
@@ -1975,6 +2086,7 @@ _krb5_kdc_pac_sign_ticket(krb5_context context,
                          krb5_const_principal upn,
                          krb5_const_principal canon_name,
                          krb5_boolean add_ticket_sig,
+                         krb5_boolean add_full_sig,
                          EncTicketPart *tkt,
                          uint64_t *pac_attributes) /* optional */
 {
@@ -2012,6 +2124,7 @@ _krb5_kdc_pac_sign_ticket(krb5_context context,
 
     ret = _krb5_pac_sign(context, pac, tkt->authtime, client, server_key,
                         kdc_key, rodc_id, upn, canon_name,
+                        add_full_sig,
                         pac_attributes, &rspac);
     if (ret == 0)
         ret = _kdc_tkt_insert_pac(context, tkt, &rspac);