]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
s4:librpc/rpc: implement DCERPC_SCHANNEL_KRB5
authorStefan Metzmacher <metze@samba.org>
Fri, 8 Nov 2024 16:48:31 +0000 (17:48 +0100)
committerStefan Metzmacher <metze@samba.org>
Mon, 13 Jan 2025 23:40:30 +0000 (23:40 +0000)
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Jennifer Sutton <jennifersutton@catalyst.net.nz>
librpc/rpc/rpc_common.h
source4/librpc/rpc/dcerpc_auth.c
source4/librpc/rpc/dcerpc_schannel.c
source4/librpc/rpc/dcerpc_util.c

index 493c31371261d745a051df300e4edc2fc42463f8..2ec411653bc6be81c279b1abe803ca6f1e470b2a 100644 (file)
@@ -114,6 +114,8 @@ struct dcerpc_binding;
 
 #define DCERPC_SMB1                    (1<<27)
 
+#define DCERPC_SCHANNEL_KRB5           (1<<28)
+
 /* The following definitions come from ../librpc/rpc/dcerpc_error.c  */
 
 const char *dcerpc_errstr(TALLOC_CTX *mem_ctx, uint32_t fault_code);
index e8e48cef23cf2ae3d53e38654b381a199b3d14ac..ccc5f607407b50b1265e43df05ad3192de219574 100644 (file)
@@ -380,6 +380,12 @@ struct composite_context *dcerpc_bind_auth_send(TALLOC_CTX *mem_ctx,
                return c;
        }
 
+       if (p->conn->flags & DCERPC_SCHANNEL) {
+               service = "netlogon";
+               gensec_want_feature(sec->generic_state,
+                                   GENSEC_FEATURE_NO_DELEGATION);
+       }
+
        if (service != NULL) {
                c->status = gensec_set_target_service(sec->generic_state,
                                                      service);
index 42d072b90c3c301f2cfbed804768947bb3223e05..19f731a85b5507b7f412336a02cf67d142703ba5 100644 (file)
@@ -36,6 +36,7 @@
 #include "lib/param/loadparm.h"
 
 struct schannel_key_state {
+       struct loadparm_context *lp_ctx;
        struct dcerpc_pipe *pipe;
        struct dcerpc_pipe *pipe2;
        struct dcerpc_binding *binding;
@@ -51,12 +52,17 @@ struct schannel_key_state {
        struct netr_Credential credentials3;
        struct netr_ServerReqChallenge r;
        struct netr_ServerAuthenticate2 a;
+       uint32_t rid;
+       struct netr_ServerAuthenticateKerberos k;
        const struct samr_Password *mach_pwd;
 };
 
 
 static void continue_secondary_connection(struct composite_context *ctx);
+static void continue_bind_auth_krb5(struct composite_context *ctx);
+static void continue_srv_auth_krb5(struct tevent_req *subreq);
 static void continue_bind_auth_none(struct composite_context *ctx);
+static void start_srv_challenge(struct composite_context *c);
 static void continue_srv_challenge(struct tevent_req *subreq);
 static void continue_srv_auth2(struct tevent_req *subreq);
 static void continue_get_negotiated_capabilities(struct tevent_req *subreq);
@@ -113,6 +119,27 @@ static void continue_secondary_connection(struct composite_context *ctx)
 
        talloc_steal(s, s->pipe2);
 
+       s->r.in.server_name   = talloc_asprintf(c, "\\\\%s", dcerpc_server_name(s->pipe));
+       if (composite_nomem(s->r.in.server_name, c)) return;
+       s->r.in.computer_name = cli_credentials_get_workstation(s->credentials);
+
+       if (s->requested_negotiate_flags & NETLOGON_NEG_SUPPORTS_KERBEROS_AUTH) {
+               struct composite_context *auth_krb5_req = NULL;
+               const char *target_service =
+                       ndr_table_netlogon.authservices->names[0];
+
+               auth_krb5_req = dcerpc_bind_auth_send(c,
+                                                     s->pipe2,
+                                                     &ndr_table_netlogon,
+                                                     s->credentials,
+                                                     lpcfg_gensec_settings(c, s->lp_ctx),
+                                                     DCERPC_AUTH_TYPE_KRB5,
+                                                     DCERPC_AUTH_LEVEL_PRIVACY,
+                                                     target_service);
+               composite_continue(c, auth_krb5_req, continue_bind_auth_krb5, c);
+               return;
+       }
+
        /* initiate a non-authenticated bind */
        auth_none_req = dcerpc_bind_auth_none_send(c, s->pipe2, &ndr_table_netlogon);
        if (composite_nomem(auth_none_req, c)) return;
@@ -120,6 +147,139 @@ static void continue_secondary_connection(struct composite_context *ctx)
        composite_continue(c, auth_none_req, continue_bind_auth_none, c);
 }
 
+static void continue_bind_auth_krb5(struct composite_context *ctx)
+{
+       struct composite_context *c;
+       struct schannel_key_state *s;
+       struct tevent_req *subreq;
+       NTSTATUS status;
+
+       c = talloc_get_type(ctx->async.private_data, struct composite_context);
+       s = talloc_get_type(c->private_data, struct schannel_key_state);
+
+       /* receive result of krb5 bind request */
+       status = dcerpc_bind_auth_recv(ctx);
+       if (!NT_STATUS_IS_OK(status)) {
+               struct composite_context *auth_none_req = NULL;
+
+               if (s->required_negotiate_flags & NETLOGON_NEG_SUPPORTS_KERBEROS_AUTH) {
+                       composite_error(c, status);
+                       return;
+               }
+
+               /* initiate a non-authenticated bind */
+               auth_none_req = dcerpc_bind_auth_none_send(c, s->pipe2, &ndr_table_netlogon);
+               if (composite_nomem(auth_none_req, c)) return;
+
+               composite_continue(c, auth_none_req, continue_bind_auth_none, c);
+               return;
+       }
+
+       /* AuthenticateKerberos request arguments */
+       s->k.in.server_name      = talloc_asprintf(c, "\\\\%s", dcerpc_server_name(s->pipe2));
+       if (composite_nomem(s->k.in.server_name, c)) return;
+       s->k.in.account_name     = cli_credentials_get_username(s->credentials);
+       s->k.in.account_type     =
+               cli_credentials_get_secure_channel_type(s->credentials);
+       s->k.in.computer_name    = cli_credentials_get_workstation(s->credentials);
+       s->k.in.negotiate_flags  = &s->requested_negotiate_flags;
+       s->k.out.rid             = &s->rid;
+       s->k.out.negotiate_flags = &s->remote_negotiate_flags;
+
+       s->creds = netlogon_creds_kerberos_init(s,
+                                               s->k.in.account_name,
+                                               s->k.in.computer_name,
+                                               s->k.in.account_type,
+                                               s->requested_negotiate_flags,
+                                               NULL, /* client_sid */
+                                               s->local_negotiate_flags);
+       if (composite_nomem(s->creds, c)) {
+               return;
+       }
+
+       /*
+         authenticate on the netlogon pipe - a rpc request over secondary pipe
+       */
+       subreq = dcerpc_netr_ServerAuthenticateKerberos_r_send(s, c->event_ctx,
+                                                              s->pipe2->binding_handle,
+                                                              &s->k);
+       if (composite_nomem(subreq, c)) return;
+
+       tevent_req_set_callback(subreq, continue_srv_auth_krb5, c);
+}
+
+static void continue_srv_auth_krb5(struct tevent_req *subreq)
+{
+       struct composite_context *c;
+       struct schannel_key_state *s;
+
+       c = tevent_req_callback_data(subreq, struct composite_context);
+       s = talloc_get_type(c->private_data, struct schannel_key_state);
+
+       c->status = dcerpc_netr_ServerAuthenticateKerberos_r_recv(subreq, s);
+       TALLOC_FREE(subreq);
+       if (NT_STATUS_EQUAL(c->status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
+               if (s->required_negotiate_flags & NETLOGON_NEG_SUPPORTS_KERBEROS_AUTH) {
+                       DBG_ERR("%s: NT_STATUS_DOWNGRADE_DETECTED\n", __location__);
+                       composite_error(c, NT_STATUS_DOWNGRADE_DETECTED);
+                       return;
+               }
+
+               if (!s->dcerpc_schannel_auto) {
+                       composite_error(c, c->status);
+                       return;
+               }
+
+               /*
+                * Fallback to ServerReqChallenge
+                * and ServerAuthenticate2
+                */
+               start_srv_challenge(c);
+               return;
+       }
+
+       if (!composite_is_ok(c)) return;
+
+       if (!NT_STATUS_IS_OK(s->k.out.result)) {
+               composite_error(c, s->k.out.result);
+               return;
+       }
+
+       SMB_ASSERT(s->requested_negotiate_flags == s->local_negotiate_flags);
+
+       {
+               uint32_t rqf = s->required_negotiate_flags;
+               uint32_t rf = s->remote_negotiate_flags;
+               uint32_t lf = s->local_negotiate_flags;
+
+               rqf |= NETLOGON_NEG_SUPPORTS_KERBEROS_AUTH;
+
+               if (rf & NETLOGON_NEG_SUPPORTS_KERBEROS_AUTH) {
+                       rqf &= ~NETLOGON_NEG_ARCFOUR;
+                       rqf &= ~NETLOGON_NEG_STRONG_KEYS;
+                       rqf &= ~NETLOGON_NEG_SUPPORTS_AES;
+               }
+
+               if ((rqf & rf) != rqf) {
+                       rqf = s->required_negotiate_flags;
+                       DBG_ERR("The client capabilities don't match "
+                               "the server capabilities: local[0x%08X] "
+                               "required[0x%08X] remote[0x%08X]\n",
+                               lf, rqf, rf);
+                       composite_error(c, NT_STATUS_DOWNGRADE_DETECTED);
+                       return;
+               }
+       }
+
+       /*
+        * Without a downgrade in the crypto we proposed
+        * we can adjust the otherwise downgraded flags
+        * before storing.
+        */
+       s->creds->negotiate_flags &= s->remote_negotiate_flags;
+
+       composite_done(c);
+}
 
 /*
   Stage 4 of schannel_key: Receive non-authenticated bind and get
@@ -128,16 +288,23 @@ static void continue_secondary_connection(struct composite_context *ctx)
 static void continue_bind_auth_none(struct composite_context *ctx)
 {
        struct composite_context *c;
-       struct schannel_key_state *s;
-       struct tevent_req *subreq;
 
        c = talloc_get_type(ctx->async.private_data, struct composite_context);
-       s = talloc_get_type(c->private_data, struct schannel_key_state);
 
        /* receive result of non-authenticated bind request */
        c->status = dcerpc_bind_auth_none_recv(ctx);
        if (!composite_is_ok(c)) return;
-       
+
+       start_srv_challenge(c);
+}
+
+static void start_srv_challenge(struct composite_context *c)
+{
+       struct schannel_key_state *s = NULL;
+       struct tevent_req *subreq = NULL;
+
+       s = talloc_get_type(c->private_data, struct schannel_key_state);
+
        /* prepare a challenge request */
        s->r.in.server_name   = talloc_asprintf(c, "\\\\%s", dcerpc_server_name(s->pipe));
        if (composite_nomem(s->r.in.server_name, c)) return;
@@ -379,9 +546,17 @@ static struct composite_context *dcerpc_schannel_key_send(TALLOC_CTX *mem_ctx,
        struct schannel_key_state *s;
        struct composite_context *epm_map_req;
        enum netr_SchannelType schannel_type = cli_credentials_get_secure_channel_type(credentials);
+       enum credentials_use_kerberos krb5_state =
+               cli_credentials_get_kerberos_state(credentials);
        struct cli_credentials *epm_creds = NULL;
+       bool reject_aes_servers = false;
        bool reject_md5_servers = false;
        bool require_strong_key = false;
+#if defined(HAVE_ADS) && defined(HAVE_KRB5_INIT_CREDS_STEP)
+       const bool support_krb5_netlogon = true;
+#else
+       const bool support_krb5_netlogon = false;
+#endif
 
        /* composite context allocation and setup */
        c = composite_create(mem_ctx, p->conn->event_ctx);
@@ -392,6 +567,7 @@ static struct composite_context *dcerpc_schannel_key_send(TALLOC_CTX *mem_ctx,
        c->private_data = s;
 
        /* store parameters in the state structure */
+       s->lp_ctx      = lp_ctx;
        s->pipe        = p;
        s->credentials = credentials;
        s->local_negotiate_flags = NETLOGON_NEG_AUTH2_FLAGS;
@@ -406,10 +582,54 @@ static struct composite_context *dcerpc_schannel_key_send(TALLOC_CTX *mem_ctx,
                s->local_negotiate_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS;
                reject_md5_servers = true;
        }
+       if (s->pipe->conn->flags & DCERPC_SCHANNEL_KRB5) {
+               s->local_negotiate_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS;
+               reject_aes_servers = true;
+       }
        if (s->pipe->conn->flags & DCERPC_SCHANNEL_AUTO) {
+               bool trust_support_kerberos = false;
+               int use_krb5_netlogon = true;
+               enum dcerpc_transport_t transport =
+                       dcerpc_binding_get_transport(s->pipe->binding);
+
+               if (schannel_type != SEC_CHAN_DOMAIN) {
+                       if (krb5_state != CRED_USE_KERBEROS_DISABLED) {
+                               trust_support_kerberos = true;
+                       }
+               }
+
+               switch (transport) {
+               case NCACN_NP:
+               case NCACN_IP_TCP:
+                       break;
+               default:
+                       /*
+                        * Things like NCALRPC don't support
+                        * kerberos.
+                        */
+                       trust_support_kerberos = false;
+                       break;
+               }
+
                s->local_negotiate_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS;
                s->local_negotiate_flags |= NETLOGON_NEG_SUPPORTS_AES;
                s->dcerpc_schannel_auto = true;
+
+               use_krb5_netlogon = lpcfg_client_use_krb5_netlogon(lp_ctx);
+               if (use_krb5_netlogon == Auto) {
+                       if (support_krb5_netlogon) {
+                               use_krb5_netlogon = trust_support_kerberos;
+                       } else {
+                               use_krb5_netlogon = false;
+                       }
+               }
+
+               if (use_krb5_netlogon) {
+                       s->local_negotiate_flags |=
+                               NETLOGON_NEG_SUPPORTS_KERBEROS_AUTH;
+               }
+
+               reject_aes_servers = lpcfg_reject_aes_netlogon_servers(lp_ctx);
                reject_md5_servers = lpcfg_reject_md5_servers(lp_ctx);
                require_strong_key = lpcfg_require_strong_key(lp_ctx);
        }
@@ -418,6 +638,10 @@ static struct composite_context *dcerpc_schannel_key_send(TALLOC_CTX *mem_ctx,
                reject_md5_servers = true;
        }
 
+       if (reject_aes_servers) {
+               reject_md5_servers = true;
+       }
+
        if (reject_md5_servers) {
                require_strong_key = true;
        }
@@ -432,13 +656,30 @@ static struct composite_context *dcerpc_schannel_key_send(TALLOC_CTX *mem_ctx,
                s->required_negotiate_flags |= NETLOGON_NEG_SUPPORTS_AES;
        }
 
+       if (reject_aes_servers) {
+               s->required_negotiate_flags |= NETLOGON_NEG_SUPPORTS_KERBEROS_AUTH;
+       }
+
        s->local_negotiate_flags |= s->required_negotiate_flags;
 
+       if (s->local_negotiate_flags & NETLOGON_NEG_SUPPORTS_KERBEROS_AUTH) {
+               if (!support_krb5_netlogon) {
+                       DBG_ERR("No support for ServerAuthenticateKerberos!\n");
+                       composite_error(c, NT_STATUS_DEVICE_FEATURE_NOT_SUPPORTED);
+                       return c;
+               }
+               s->pipe->conn->flags |= DCERPC_SEAL;
+       }
+
        if (s->required_negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) {
                s->required_negotiate_flags &= ~NETLOGON_NEG_ARCFOUR;
                s->required_negotiate_flags &= ~NETLOGON_NEG_STRONG_KEYS;
        }
 
+       if (s->required_negotiate_flags & NETLOGON_NEG_SUPPORTS_KERBEROS_AUTH) {
+               s->required_negotiate_flags &= ~NETLOGON_NEG_SUPPORTS_AES;
+       }
+
        /* type of authentication depends on schannel type */
        if (schannel_type == SEC_CHAN_RODC) {
                s->local_negotiate_flags |= NETLOGON_NEG_RODC_PASSTHROUGH;
@@ -519,6 +760,7 @@ static void continue_schannel_key(struct composite_context *ctx)
                                                      struct composite_context);
        struct auth_schannel_state *s = talloc_get_type(c->private_data,
                                                        struct auth_schannel_state);
+       enum dcerpc_AuthType auth_type;
        NTSTATUS status;
 
        /* receive schannel key */
@@ -534,12 +776,19 @@ static void continue_schannel_key(struct composite_context *ctx)
        s->requested_negotiate_flags =
                s->creds_state->client_requested_flags;
 
+       if (s->creds_state->authenticate_kerberos) {
+               auth_type = DCERPC_AUTH_TYPE_KRB5;
+               s->auth_level = DCERPC_AUTH_LEVEL_PRIVACY;
+       } else {
+               auth_type = DCERPC_AUTH_TYPE_SCHANNEL;
+       }
+
        /* send bind auth request with received creds */
        cli_credentials_set_netlogon_creds(s->credentials, s->creds_state);
 
        auth_req = dcerpc_bind_auth_send(c, s->pipe, s->table, s->credentials, 
                                         lpcfg_gensec_settings(c, s->lp_ctx),
-                                        DCERPC_AUTH_TYPE_SCHANNEL, s->auth_level,
+                                        auth_type, s->auth_level,
                                         NULL);
        if (composite_nomem(auth_req, c)) return;
        
@@ -630,6 +879,12 @@ static void continue_get_negotiated_capabilities(struct tevent_req *subreq)
        c->status = dcerpc_netr_LogonGetCapabilities_r_recv(subreq, s);
        TALLOC_FREE(subreq);
        if (NT_STATUS_EQUAL(c->status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
+               if (s->creds_state->authenticate_kerberos) {
+                       DBG_ERR("%s: NT_STATUS_DOWNGRADE_DETECTED\n", __location__);
+                       composite_error(c, NT_STATUS_DOWNGRADE_DETECTED);
+                       return;
+               }
+
                if (s->creds_state->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) {
                        DBG_ERR("%s: NT_STATUS_DOWNGRADE_DETECTED\n", __location__);
                        composite_error(c, NT_STATUS_DOWNGRADE_DETECTED);
@@ -648,6 +903,12 @@ static void continue_get_negotiated_capabilities(struct tevent_req *subreq)
        }
 
        if (NT_STATUS_EQUAL(s->c.out.result, NT_STATUS_NOT_IMPLEMENTED)) {
+               if (s->creds_state->authenticate_kerberos) {
+                       DBG_ERR("%s: NT_STATUS_DOWNGRADE_DETECTED\n", __location__);
+                       composite_error(c, NT_STATUS_DOWNGRADE_DETECTED);
+                       return;
+               }
+
                if (s->creds_state->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) {
                        /* This means AES isn't supported. */
                        DBG_ERR("%s: NT_STATUS_DOWNGRADE_DETECTED\n", __location__);
@@ -688,7 +949,8 @@ static void continue_get_negotiated_capabilities(struct tevent_req *subreq)
                return;
        }
 
-       if ((s->requested_negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) &&
+       if (!s->creds_state->authenticate_kerberos &&
+           (s->requested_negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) &&
            (!(s->creds_state->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES)))
        {
                DBG_ERR("%s: NT_STATUS_DOWNGRADE_DETECTED\n", __location__);
@@ -749,6 +1011,12 @@ static void continue_get_client_capabilities(struct tevent_req *subreq)
                c->status = NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE;
        }
        if (NT_STATUS_EQUAL(c->status, NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE)) {
+               if (s->creds_state->authenticate_kerberos) {
+                       DBG_ERR("%s: NT_STATUS_DOWNGRADE_DETECTED\n", __location__);
+                       composite_error(c, NT_STATUS_DOWNGRADE_DETECTED);
+                       return;
+               }
+
                /*
                 * Here we know the negotiated flags were already
                 * verified with query_level=1, which means
index d0a163500fe48b54f6ee83c5362792b7efbc9e56..f093a6e9b1b55bb1e56fdc203e1d02e63d31609c 100644 (file)
@@ -30,6 +30,7 @@
 #include "librpc/gen_ndr/ndr_epmapper_c.h"
 #include "librpc/gen_ndr/ndr_dcerpc.h"
 #include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_schannel.h"
 #include "librpc/rpc/dcerpc_proto.h"
 #include "auth/credentials/credentials.h"
 #include "auth/gensec/gensec.h"
@@ -635,7 +636,20 @@ struct composite_context *dcerpc_pipe_auth_send(struct dcerpc_pipe *p,
                auth_type = DCERPC_AUTH_TYPE_KRB5;
 
        } else if (conn->flags & DCERPC_SCHANNEL) {
-               auth_type = DCERPC_AUTH_TYPE_SCHANNEL;
+               struct netlogon_creds_CredentialState *ncreds = NULL;
+
+               ncreds = cli_credentials_get_netlogon_creds(s->credentials);
+               if (ncreds->authenticate_kerberos) {
+                       conn->flags |= DCERPC_SCHANNEL_KRB5;
+               }
+
+               if (conn->flags & DCERPC_SCHANNEL_KRB5) {
+                       conn->flags &= ~DCERPC_SCHANNEL_AUTO;
+                       conn->flags |= DCERPC_SEAL;
+                       auth_type = DCERPC_AUTH_TYPE_KRB5;
+               } else {
+                       auth_type = DCERPC_AUTH_TYPE_SCHANNEL;
+               }
 
        } else if (conn->flags & DCERPC_AUTH_NTLM) {
                auth_type = DCERPC_AUTH_TYPE_NTLMSSP;