summaryrefslogtreecommitdiff
path: root/source4/auth/gensec/spnego.c
diff options
context:
space:
mode:
Diffstat (limited to 'source4/auth/gensec/spnego.c')
-rw-r--r--source4/auth/gensec/spnego.c208
1 files changed, 205 insertions, 3 deletions
diff --git a/source4/auth/gensec/spnego.c b/source4/auth/gensec/spnego.c
index fae32d8ade..fa20c45a46 100644
--- a/source4/auth/gensec/spnego.c
+++ b/source4/auth/gensec/spnego.c
@@ -30,6 +30,7 @@
#include "auth/gensec/gensec_proto.h"
#include "auth/gensec/gensec_toplevel_proto.h"
#include "param/param.h"
+#include "lib/util/asn1.h"
_PUBLIC_ NTSTATUS gensec_spnego_init(void);
@@ -51,6 +52,16 @@ struct spnego_state {
const char *neg_oid;
DATA_BLOB mech_types;
+
+ /*
+ * The following is used to implement
+ * the update token fragmentation
+ */
+ size_t in_needed;
+ DATA_BLOB in_frag;
+ size_t out_max_length;
+ DATA_BLOB out_frag;
+ NTSTATUS out_status;
};
@@ -58,7 +69,7 @@ static NTSTATUS gensec_spnego_client_start(struct gensec_security *gensec_securi
{
struct spnego_state *spnego_state;
- spnego_state = talloc(gensec_security, struct spnego_state);
+ spnego_state = talloc_zero(gensec_security, struct spnego_state);
if (!spnego_state) {
return NT_STATUS_NO_MEMORY;
}
@@ -68,6 +79,8 @@ static NTSTATUS gensec_spnego_client_start(struct gensec_security *gensec_securi
spnego_state->sub_sec_security = NULL;
spnego_state->no_response_expected = false;
spnego_state->mech_types = data_blob(NULL, 0);
+ spnego_state->out_max_length = gensec_max_update_size(gensec_security);
+ spnego_state->out_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
gensec_security->private_data = spnego_state;
return NT_STATUS_OK;
@@ -77,7 +90,7 @@ static NTSTATUS gensec_spnego_server_start(struct gensec_security *gensec_securi
{
struct spnego_state *spnego_state;
- spnego_state = talloc(gensec_security, struct spnego_state);
+ spnego_state = talloc_zero(gensec_security, struct spnego_state);
if (!spnego_state) {
return NT_STATUS_NO_MEMORY;
}
@@ -87,6 +100,8 @@ static NTSTATUS gensec_spnego_server_start(struct gensec_security *gensec_securi
spnego_state->sub_sec_security = NULL;
spnego_state->no_response_expected = false;
spnego_state->mech_types = data_blob(NULL, 0);
+ spnego_state->out_max_length = gensec_max_update_size(gensec_security);
+ spnego_state->out_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
gensec_security->private_data = spnego_state;
return NT_STATUS_OK;
@@ -1130,6 +1145,193 @@ static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TA
return NT_STATUS_INVALID_PARAMETER;
}
+static NTSTATUS gensec_spnego_update_in(struct gensec_security *gensec_security,
+ const DATA_BLOB in, DATA_BLOB *full_in)
+{
+ struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
+ size_t expected;
+ uint8_t *buf;
+ NTSTATUS status;
+ bool ok;
+
+ *full_in = data_blob_null;
+
+ if (spnego_state->in_needed == 0) {
+ size_t size = 0;
+
+ /*
+ * try to work out the size of the full
+ * input token, it might be fragmented
+ */
+ status = asn1_peek_full_tag(in, ASN1_APPLICATION(0), &size);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
+ status = asn1_peek_full_tag(in, ASN1_CONTEXT(1), &size);
+ }
+
+ if (NT_STATUS_IS_OK(status) ||
+ NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
+ spnego_state->in_needed = size;
+ } else {
+ /*
+ * If it is not an asn1 message
+ * just call the next layer.
+ */
+ spnego_state->in_needed = in.length;
+ }
+ }
+
+ if (spnego_state->in_needed > UINT16_MAX) {
+ /*
+ * limit the incoming message to 0xFFFF
+ * to avoid DoS attacks.
+ */
+ return NT_STATUS_INVALID_BUFFER_SIZE;
+ }
+
+ if ((spnego_state->in_needed > 0) && (in.length == 0)) {
+ /*
+ * If we reach this, we know we got at least
+ * part of an asn1 message, getting 0 means
+ * the remote peer wants us to spin.
+ */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ expected = spnego_state->in_needed - spnego_state->in_frag.length;
+ if (in.length > expected) {
+ /*
+ * we got more than expected
+ */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (in.length == spnego_state->in_needed) {
+ /*
+ * if the in.length contains the full blob
+ * we are done.
+ *
+ * Note: this implies spnego_state->in_frag.length == 0,
+ * but we do not need to check this explicitly
+ * because we already know that we did not get
+ * more than expected.
+ */
+ *full_in = in;
+ return NT_STATUS_OK;
+ }
+
+ ok = data_blob_append(spnego_state, &spnego_state->in_frag,
+ in.data, in.length);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (spnego_state->in_needed > spnego_state->in_frag.length) {
+ return NT_STATUS_MORE_PROCESSING_REQUIRED;
+ }
+
+ *full_in = spnego_state->in_frag;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS gensec_spnego_update_out(struct gensec_security *gensec_security,
+ TALLOC_CTX *out_mem_ctx,
+ DATA_BLOB *_out)
+{
+ struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
+ size_t new_length;
+ uint8_t *buf;
+ DATA_BLOB out = data_blob_null;
+
+ *_out = data_blob_null;
+
+ if (spnego_state->out_frag.length == 0) {
+ return spnego_state->out_status;
+ }
+
+ /*
+ * There is still more data to be delivered
+ * to the remote peer.
+ */
+
+ if (spnego_state->out_frag.length <= spnego_state->out_max_length) {
+ /*
+ * Fast path, we can deliver everything
+ */
+
+ *_out = spnego_state->out_frag;
+ talloc_steal(out_mem_ctx, _out->data);
+ spnego_state->out_frag = data_blob_null;
+ return spnego_state->out_status;
+ }
+
+ out = spnego_state->out_frag;
+
+ /*
+ * copy the remaining bytes
+ */
+ spnego_state->out_frag = data_blob_talloc(spnego_state,
+ out.data + spnego_state->out_max_length,
+ out.length - spnego_state->out_max_length);
+ if (spnego_state->out_frag.data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * truncate the buffer
+ */
+ data_blob_realloc(spnego_state, &out, spnego_state->out_max_length);
+
+ talloc_steal(out_mem_ctx, out.data);
+ *_out = out;
+ return NT_STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+static NTSTATUS gensec_spnego_update_wrapper(struct gensec_security *gensec_security,
+ TALLOC_CTX *out_mem_ctx,
+ struct tevent_context *ev,
+ const DATA_BLOB in, DATA_BLOB *out)
+{
+ struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
+ DATA_BLOB full_in = data_blob_null;
+ NTSTATUS status;
+
+ *out = data_blob_null;
+
+ if (spnego_state->out_frag.length > 0) {
+ if (in.length > 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return gensec_spnego_update_out(gensec_security,
+ out_mem_ctx,
+ out);
+ }
+
+ status = gensec_spnego_update_in(gensec_security,
+ in, &full_in);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = gensec_spnego_update(gensec_security,
+ spnego_state, ev,
+ full_in,
+ &spnego_state->out_frag);
+ data_blob_free(&spnego_state->in_frag);
+ spnego_state->in_needed = 0;
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ return status;
+ }
+
+ spnego_state->out_status = status;
+
+ return gensec_spnego_update_out(gensec_security,
+ out_mem_ctx,
+ out);
+}
+
static void gensec_spnego_want_feature(struct gensec_security *gensec_security,
uint32_t feature)
{
@@ -1168,7 +1370,7 @@ static const struct gensec_security_ops gensec_spnego_security_ops = {
.oid = gensec_spnego_oids,
.client_start = gensec_spnego_client_start,
.server_start = gensec_spnego_server_start,
- .update = gensec_spnego_update,
+ .update = gensec_spnego_update_wrapper,
.seal_packet = gensec_spnego_seal_packet,
.sign_packet = gensec_spnego_sign_packet,
.sig_size = gensec_spnego_sig_size,