s4:kerberos Add support for user principal names in certificates
authorAndrew Bartlett <abartlet@samba.org>
Tue, 28 Jul 2009 04:05:19 +0000 (14:05 +1000)
committerAndrew Bartlett <abartlet@samba.org>
Tue, 28 Jul 2009 04:10:47 +0000 (14:10 +1000)
This extends the PKINIT code in Heimdal to ask the HDB layer if the
User Principal Name name in the certificate is an alias (perhaps just
by case change) of the name given in the AS-REQ.  (This was a TODO in
the Heimdal KDC)

The testsuite is extended to test this behaviour, and the other PKINIT
certficate (using the standard method to specify a principal name in a
certificate) is updated to use a Administrator (not administrator).
(This fixes the kinit test).

Andrew Bartlett

selftest/target/Samba4.pm
source4/auth/ntlm/auth_sam.c
source4/auth/sam.c
source4/heimdal/kdc/kerberos5.c
source4/heimdal/kdc/pkinit.c
source4/heimdal/lib/hdb/hdb.h
source4/kdc/hdb-samba4.c
testprogs/blackbox/test_kinit.sh

index 7833bf447956960689c89ef704e07314c4ee0db2..d2c11e4f321993d6de6f72c2865a7ecd8a9f57ff 100644 (file)
@@ -295,6 +295,7 @@ sub mk_keyblobs($$)
        my $adminkeyfile = "$tlsdir/adminkey.pem";
        my $reqadmin = "$tlsdir/req-admin.der";
        my $admincertfile = "$tlsdir/admincert.pem";
+       my $admincertupnfile = "$tlsdir/admincertupn.pem";
 
        mkdir($tlsdir, 0777);
 
@@ -442,24 +443,51 @@ EOF
        open(ADMINCERTFILE, ">$admincertfile");
        print ADMINCERTFILE <<EOF;
 -----BEGIN CERTIFICATE-----
-MIIDHTCCAoagAwIBAgIUC0W5dW/N9kE+NgD0mKK34YgyqQ0wCwYJKoZIhvcNAQEFMFIxEzAR
+MIIDHTCCAoagAwIBAgIUUggzW4lLRkMKe1DAR2NKatkMDYwwCwYJKoZIhvcNAQELMFIxEzAR
 BgoJkiaJk/IsZAEZDANjb20xFzAVBgoJkiaJk/IsZAEZDAdleGFtcGxlMRUwEwYKCZImiZPy
-LGQBGQwFc2FtYmExCzAJBgNVBAMMAkNBMCIYDzIwMDgwMzAxMTMyMzAwWhgPMjAzMzAyMjQx
-MzIzMDBaMG0xEzARBgoJkiaJk/IsZAEZDANjb20xFzAVBgoJkiaJk/IsZAEZDAdleGFtcGxl
+LGQBGQwFc2FtYmExCzAJBgNVBAMMAkNBMCIYDzIwMDkwNzI3MDMzMjE1WhgPMjAzNDA3MjIw
+MzMyMTVaMG0xEzARBgoJkiaJk/IsZAEZDANjb20xFzAVBgoJkiaJk/IsZAEZDAdleGFtcGxl
 MRUwEwYKCZImiZPyLGQBGQwFc2FtYmExDjAMBgNVBAMMBXVzZXJzMRYwFAYDVQQDDA1BZG1p
 bmlzdHJhdG9yMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD0+OL7TQBj0RejbIH1+g5G
 eRaWaM9xF43uE5y7jUHEsi5owhZF5iIoHZeeL6cpDF5y1BZRs0JlA1VqMry1jjKlzFYVEMMF
 xB6esnXhl0Jpip1JkUMMXLOP1m/0dqayuHBWozj9f/cdyCJr0wJIX1Z8Pr+EjYRGPn/MF0xd
 l3JRlwIDAQABo4HSMIHPMA4GA1UdDwEB/wQEAwIFoDAoBgNVHSUEITAfBgcrBgEFAgMEBggr
 BgEFBQcDAgYKKwYBBAGCNxQCAjBIBgNVHREEQTA/oD0GBisGAQUCAqAzMDGgExsRU0FNQkEu
-RVhBTVBMRS5DT02hGjAYoAMCAQGhETAPGw1hZG1pbmlzdHJhdG9yMB8GA1UdIwQYMBaAFMLZ
+RVhBTVBMRS5DT02hGjAYoAMCAQGhETAPGw1BZG1pbmlzdHJhdG9yMB8GA1UdIwQYMBaAFMLZ
 ufegDKLZs0VOyFXYK1L6M8oyMB0GA1UdDgQWBBQg81bLyfCA88C2B/BDjXlGuaFaxjAJBgNV
-HRMEAjAAMA0GCSqGSIb3DQEBBQUAA4GBAHsqSqul0hZCXn4t8Kfp3v/JLMiUMJihR1XOgzoa
-ufLOQ1HNzFUHKuo1JEQ1+i5gHT/arLu/ZBF4BfQol7vW27gKIEt0fkRV8EvoPxXvSokHq0Ku
-HCuPOhYNEP3wYiwB3g93NMCinWVlz0mh5aijEU7y/XrjlZxBKFFrTE+BJi1o
+HRMEAjAAMA0GCSqGSIb3DQEBCwUAA4GBAEf/OSHUDJaGdtWGNuJeqcVYVMwrfBAc0OSwVhz1
+7/xqKHWo8wIMPkYRtaRHKLNDsF8GkhQPCpVsa6mX/Nt7YQnNvwd+1SBP5E8GvwWw9ZzLJvma
+nk2n89emuayLpVtp00PymrDLRBcNaRjFReQU8f0o509kiVPHduAp3jOiy13l
 -----END CERTIFICATE-----
 EOF
        close(ADMINCERTFILE);
+
+       # hxtool issue-certificate --ca-certificate=FILE:$CAFILE,$KEYFILE \
+       # --type="pkinit-client" \
+       # --ms-upn="administrator@samba.example.com" \
+       # --req="PKCS10:$ADMINREQFILE" --certificate="FILE:$ADMINCERTUPNFILE" \
+       # --lifetime="25 years"
+       
+       open(ADMINCERTUPNFILE, ">$admincertupnfile");
+       print ADMINCERTUPNFILE <<EOF;
+-----BEGIN CERTIFICATE-----
+MIIDDzCCAnigAwIBAgIUUp3CJMuNaEaAdPKp3QdNIwG7a4wwCwYJKoZIhvcNAQELMFIxEzAR
+BgoJkiaJk/IsZAEZDANjb20xFzAVBgoJkiaJk/IsZAEZDAdleGFtcGxlMRUwEwYKCZImiZPy
+LGQBGQwFc2FtYmExCzAJBgNVBAMMAkNBMCIYDzIwMDkwNzI3MDMzMzA1WhgPMjAzNDA3MjIw
+MzMzMDVaMG0xEzARBgoJkiaJk/IsZAEZDANjb20xFzAVBgoJkiaJk/IsZAEZDAdleGFtcGxl
+MRUwEwYKCZImiZPyLGQBGQwFc2FtYmExDjAMBgNVBAMMBXVzZXJzMRYwFAYDVQQDDA1BZG1p
+bmlzdHJhdG9yMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD0+OL7TQBj0RejbIH1+g5G
+eRaWaM9xF43uE5y7jUHEsi5owhZF5iIoHZeeL6cpDF5y1BZRs0JlA1VqMry1jjKlzFYVEMMF
+xB6esnXhl0Jpip1JkUMMXLOP1m/0dqayuHBWozj9f/cdyCJr0wJIX1Z8Pr+EjYRGPn/MF0xd
+l3JRlwIDAQABo4HEMIHBMA4GA1UdDwEB/wQEAwIFoDAoBgNVHSUEITAfBgcrBgEFAgMEBggr
+BgEFBQcDAgYKKwYBBAGCNxQCAjA6BgNVHREEMzAxoC8GCisGAQQBgjcUAgOgIQwfYWRtaW5p
+c3RyYXRvckBzYW1iYS5leGFtcGxlLmNvbTAfBgNVHSMEGDAWgBTC2bn3oAyi2bNFTshV2CtS
++jPKMjAdBgNVHQ4EFgQUIPNWy8nwgPPAtgfwQ415RrmhWsYwCQYDVR0TBAIwADANBgkqhkiG
+9w0BAQsFAAOBgQBk42+egeUB3Ji2PC55fbt3FNKxvmm2xUUFkV9POK/YR9rajKOwk5jtYSeS
+Zd7J9s//rNFNa7waklFkDaY56+QWTFtdvxfE+KoHaqt6X8u6pqi7p3M4wDKQox+9Dx8yWFyq
+Wfz/8alZ5aMezCQzXJyIaJsCLeKABosSwHcpAFmxlQ==
+-----END CERTIFICATE-----
+EOF
 }
 
 #
index 253ddf2286d039fa12a3de327bafbbb1f91bed7b..a64c56d920f276064d01d39d92e6c68fb480cbf8 100644 (file)
@@ -330,7 +330,7 @@ NTSTATUS authsam_get_server_info_principal(TALLOC_CTX *mem_ctx,
        }
 
        nt_status = sam_get_results_principal(sam_ctx, tmp_ctx, principal, 
-                                             &domain_dn, &msg);
+                                             user_attrs, &domain_dn, &msg);
        if (!NT_STATUS_IS_OK(nt_status)) {
                return nt_status;
        }
index 635d94242f6012643b8a21b153fddd82298a0402..8865170b147d6f1e6dcacd991ce9beb2513a51b9 100644 (file)
@@ -399,6 +399,7 @@ _PUBLIC_ NTSTATUS authsam_make_server_info(TALLOC_CTX *mem_ctx, struct ldb_conte
 
 NTSTATUS sam_get_results_principal(struct ldb_context *sam_ctx,
                                   TALLOC_CTX *mem_ctx, const char *principal,
+                                  const char **attrs,
                                   struct ldb_dn **domain_dn,
                                   struct ldb_message **msg)
 {                         
@@ -411,7 +412,8 @@ NTSTATUS sam_get_results_principal(struct ldb_context *sam_ctx,
                return NT_STATUS_NO_MEMORY;
        }
 
-       nt_status = crack_user_principal_name(sam_ctx, tmp_ctx, principal, &user_dn, domain_dn);
+       nt_status = crack_user_principal_name(sam_ctx, tmp_ctx, principal, 
+                                             &user_dn, domain_dn);
        if (!NT_STATUS_IS_OK(nt_status)) {
                talloc_free(tmp_ctx);
                return nt_status;
@@ -419,7 +421,7 @@ NTSTATUS sam_get_results_principal(struct ldb_context *sam_ctx,
        
        /* pull the user attributes */
        ret = gendb_search_single_extended_dn(sam_ctx, tmp_ctx, user_dn, LDB_SCOPE_BASE,
-                                             msg, user_attrs, "(objectClass=*)");
+                                             msg, attrs, "(objectClass=*)");
        if (ret != LDB_SUCCESS) {
                talloc_free(tmp_ctx);
                return NT_STATUS_INTERNAL_DB_CORRUPTION;
index 43d54bf702540bf24178fb53c4bc169d160648bf..53e9f54537f52db5fe684039116be376a7f6f10c 100644 (file)
@@ -1053,6 +1053,7 @@ _kdc_as_rep(krb5_context context,
 
            ret = _kdc_pk_check_client(context,
                                       config,
+                                      clientdb, 
                                       client,
                                       pkp,
                                       &client_cert);
index 22734be811e379f486bef3a01df6d80becfaa071..644eae0fe483982f858d2a5e2347a4384224b41c 100644 (file)
@@ -1613,11 +1613,12 @@ match_ms_upn_san(krb5_context context,
                 krb5_kdc_configuration *config,
                 hx509_context hx509ctx,
                 hx509_cert client_cert,
-                krb5_const_principal match)
+                HDB *clientdb,
+                hdb_entry_ex *client)
 {
     hx509_octet_string_list list;
     krb5_principal principal = NULL;
-    int ret, found = 0;
+    int ret;
     MS_UPN_SAN upn;
     size_t size;
 
@@ -1651,32 +1652,32 @@ match_ms_upn_san(krb5_context context,
        goto out;
     }
 
-    /*
-     * This is very wrong, but will do for now, should really and a
-     * plugin to the windc layer to very this ACL.
-    */
-    strupr(principal->realm);
-
-    if (krb5_principal_compare(context, principal, match) == TRUE)
-       found = 1;
+    if (clientdb->hdb_check_pkinit_ms_upn_match) {
+       ret = clientdb->hdb_check_pkinit_ms_upn_match(context, clientdb, client, principal);
+    } else {
+           
+       /*
+        * This is very wrong, but will do for a fallback
+        */
+       strupr(principal->realm);
+           
+       if (krb5_principal_compare(context, principal, client->entry.principal) == FALSE)
+           ret = KRB5_KDC_ERR_CLIENT_NAME_MISMATCH;
+    }
 
 out:
     if (principal)
        krb5_free_principal(context, principal);
     hx509_free_octet_string_list(&list);
-    if (ret)
-       return ret;
-
-    if (!found)
-       return KRB5_KDC_ERR_CLIENT_NAME_MISMATCH;
 
-    return 0;
+    return ret;
 }
 
 krb5_error_code
 _kdc_pk_check_client(krb5_context context,
                     krb5_kdc_configuration *config,
-                    const hdb_entry_ex *client,
+                    HDB *clientdb,
+                    hdb_entry_ex *client,
                     pk_client_params *cp,
                     char **subject_name)
 {
@@ -1745,7 +1746,8 @@ _kdc_pk_check_client(krb5_context context,
        ret = match_ms_upn_san(context, config,
                               kdc_identity->hx509ctx,
                               cp->cert,
-                              client->entry.principal);
+                              clientdb, 
+                              client);
        if (ret == 0) {
            kdc_log(context, config, 5,
                    "Found matching MS UPN SAN in certificate");
index f490dbf2f0f1a5807013ef452741f41d47f11508..8eba864fd39f438802cf326206a06fa07760cb46 100644 (file)
@@ -220,9 +220,14 @@ typedef struct HDB{
      * Check is delegation is allowed.
      */
     krb5_error_code (*hdb_check_constrained_delegation)(krb5_context, struct HDB *, hdb_entry_ex *, krb5_const_principal);
+
+    /**
+     * Check if this name is an alias for the supplied client for PKINIT userPrinicpalName logins
+     */
+    krb5_error_code (*hdb_check_pkinit_ms_upn_match)(krb5_context, struct HDB *, hdb_entry_ex *, krb5_const_principal);
 }HDB;
 
-#define HDB_INTERFACE_VERSION  5
+#define HDB_INTERFACE_VERSION  6
 
 struct hdb_so_method {
     int version;
index 1a0e93f7cef333fae593153a7b5b92ba78643ae2..e39366c4078e0b805f54416c9de526f03a611c21 100644 (file)
@@ -978,17 +978,16 @@ static krb5_error_code hdb_samba4_rename(krb5_context context, HDB *db, const ch
        return HDB_ERR_DB_INUSE;
 }
 
-static krb5_error_code hdb_samba4_fetch_client(krb5_context context, HDB *db, 
-                                       struct loadparm_context *lp_ctx, 
-                                       TALLOC_CTX *mem_ctx, 
-                                       krb5_const_principal principal,
-                                       unsigned flags,
-                                       hdb_entry_ex *entry_ex) {
+static krb5_error_code hdb_samba4_lookup_client(krb5_context context, HDB *db, 
+                                               struct loadparm_context *lp_ctx, 
+                                               TALLOC_CTX *mem_ctx, 
+                                               krb5_const_principal principal,
+                                               const char **attrs,
+                                               struct ldb_dn **realm_dn, 
+                                               struct ldb_message **msg) {
        NTSTATUS nt_status;
        char *principal_string;
-       struct ldb_dn *realm_dn;
        krb5_error_code ret;
-       struct ldb_message *msg = NULL;
 
        ret = krb5_unparse_name(context, principal, &principal_string);
        
@@ -997,8 +996,8 @@ static krb5_error_code hdb_samba4_fetch_client(krb5_context context, HDB *db,
        }
        
        nt_status = sam_get_results_principal((struct ldb_context *)db->hdb_db,
-                                             mem_ctx, principal_string, 
-                                             &realm_dn, &msg);
+                                             mem_ctx, principal_string, attrs, 
+                                             realm_dn, msg);
        free(principal_string);
        if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_SUCH_USER)) {
                return HDB_ERR_NOENTRY;
@@ -1008,9 +1007,29 @@ static krb5_error_code hdb_samba4_fetch_client(krb5_context context, HDB *db,
                return EINVAL;
        }
        
+       return ret;
+}
+
+static krb5_error_code hdb_samba4_fetch_client(krb5_context context, HDB *db, 
+                                              struct loadparm_context *lp_ctx, 
+                                              TALLOC_CTX *mem_ctx, 
+                                              krb5_const_principal principal,
+                                              unsigned flags,
+                                              hdb_entry_ex *entry_ex) {
+       struct ldb_dn *realm_dn;
+       krb5_error_code ret;
+       struct ldb_message *msg = NULL;
+
+       ret = hdb_samba4_lookup_client(context, db, lp_ctx, 
+                                      mem_ctx, principal, user_attrs, 
+                                      &realm_dn, &msg);
+       if (ret != 0) {
+               return ret;
+       }
+       
        ret = hdb_samba4_message2entry(context, db, lp_ctx, mem_ctx, 
-                               principal, HDB_SAMBA4_ENT_TYPE_CLIENT,
-                               realm_dn, msg, entry_ex);
+                                      principal, HDB_SAMBA4_ENT_TYPE_CLIENT,
+                                      realm_dn, msg, entry_ex);
        return ret;
 }
 
@@ -1422,6 +1441,11 @@ static krb5_error_code hdb_samba4_destroy(krb5_context context, HDB *db)
        return 0;
 }
 
+
+/* Check if a given entry may delegate to this target principal
+ *
+ * This is currently a very nasty hack - allowing only delegation to itself. 
+ */
 krb5_error_code hdb_samba4_check_constrained_delegation(krb5_context context, HDB *db, 
                                                        hdb_entry_ex *entry,
                                                        krb5_const_principal target_principal)
@@ -1491,6 +1515,60 @@ krb5_error_code hdb_samba4_check_constrained_delegation(krb5_context context, HD
        return ret;
 }
 
+/* Certificates printed by a the Certificate Authority might have a
+ * slightly different form of the user principal name to that in the
+ * database.  Allow a mismatch where they both refer to the same
+ * SID */
+
+krb5_error_code hdb_samba4_check_pkinit_ms_upn_match(krb5_context context, HDB *db, 
+                                                    hdb_entry_ex *entry,
+                                                    krb5_const_principal certificate_principal)
+{
+       struct ldb_context *ldb_ctx = (struct ldb_context *)db->hdb_db;
+       struct loadparm_context *lp_ctx = talloc_get_type(ldb_get_opaque(ldb_ctx, "loadparm"), 
+                                                         struct loadparm_context);
+       krb5_error_code ret;
+       struct ldb_dn *realm_dn;
+       struct ldb_message *msg;
+       struct dom_sid *orig_sid;
+       struct dom_sid *target_sid;
+       struct hdb_samba4_private *p = talloc_get_type(entry->ctx, struct hdb_samba4_private);
+       const char *ms_upn_check_attrs[] = {
+               "objectSid", NULL
+       };
+       
+       TALLOC_CTX *mem_ctx = talloc_named(db, 0, "hdb_samba4_check_constrained_delegation");
+
+       if (!mem_ctx) {
+               ret = ENOMEM;
+               krb5_set_error_message(context, ret, "hdb_samba4_fetch: talloc_named() failed!");
+               return ret;
+       }
+
+       ret = hdb_samba4_lookup_client(context, db, lp_ctx, 
+                                      mem_ctx, certificate_principal,
+                                      ms_upn_check_attrs, &realm_dn, &msg);
+       
+       if (ret != 0) {
+               talloc_free(mem_ctx);
+               return ret;
+       }
+
+       orig_sid = samdb_result_dom_sid(mem_ctx, p->msg, "objectSid");
+       target_sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid");
+
+       /* Consider these to be the same principal, even if by a different
+        * name.  The easy and safe way to prove this is by SID
+        * comparison */
+       if (!(orig_sid && target_sid && dom_sid_equal(orig_sid, target_sid))) {
+               talloc_free(mem_ctx);
+               return KRB5_KDC_ERR_CLIENT_NAME_MISMATCH;
+       }
+
+       talloc_free(mem_ctx);
+       return ret;
+}
+
 /* This interface is to be called by the KDC and libnet_keytab_dump, which is expecting Samba
  * calling conventions.  It is also called by a wrapper
  * (hdb_samba4_create) from the kpasswdd -> krb5 -> keytab_hdb -> hdb
@@ -1556,6 +1634,7 @@ NTSTATUS hdb_samba4_create_kdc(TALLOC_CTX *mem_ctx,
 
        (*db)->hdb_auth_status = NULL;
        (*db)->hdb_check_constrained_delegation = hdb_samba4_check_constrained_delegation;
+       (*db)->hdb_check_pkinit_ms_upn_match = hdb_samba4_check_pkinit_ms_upn_match;
 
        return NT_STATUS_OK;
 }
index 2349afae7e0550a97fe4c962c6dc931a6bfffa1d..91f21f473b4e65248b6821240268590321f12cd8 100755 (executable)
@@ -53,7 +53,9 @@ echo $PASSWORD > ./tmppassfile
 testit "kinit with password" $samba4kinit --password-file=./tmppassfile --request-pac $USERNAME@$REALM   || failed=`expr $failed + 1`
 testit "kinit with password (enterprise style)" $samba4kinit --enterprise --password-file=./tmppassfile --request-pac $USERNAME@$REALM   || failed=`expr $failed + 1`
 testit "kinit with password (windows style)" $samba4kinit --windows --password-file=./tmppassfile --request-pac $USERNAME@$REALM   || failed=`expr $failed + 1`
-testit "kinit with pkinit" $samba4kinit --request-pac --renewable --pk-user=FILE:$PREFIX/dc/private/tls/admincert.pem,$PREFIX/dc/private/tls/adminkey.pem $USERNAME@$REALM || failed=`expr $failed + 1`
+testit "kinit with pkinit (name specified)" $samba4kinit --request-pac --renewable --pk-user=FILE:$PREFIX/dc/private/tls/admincert.pem,$PREFIX/dc/private/tls/adminkey.pem $USERNAME@$REALM || failed=`expr $failed + 1`
+testit "kinit with pkinit (enterprise name specified)" $samba4kinit --request-pac --renewable --pk-user=FILE:$PREFIX/dc/private/tls/admincert.pem,$PREFIX/dc/private/tls/adminkey.pem --enterprise $USERNAME@$REALM || failed=`expr $failed + 1`
+testit "kinit with pkinit (enterprise name in cert)" $samba4kinit --request-pac --renewable --pk-user=FILE:$PREFIX/dc/private/tls/admincertupn.pem,$PREFIX/dc/private/tls/adminkey.pem --pk-enterprise || failed=`expr $failed + 1`
 testit "kinit renew ticket" $samba4kinit --request-pac -R
 
 test_smbclient "Test login with kerberos ccache" 'ls' -k yes || failed=`expr $failed + 1`