diff options
author | Stefan Metzmacher <metze@samba.org> | 2013-12-31 09:42:36 +0100 |
---|---|---|
committer | Stefan Metzmacher <metze@samba.org> | 2014-01-07 00:27:11 +0100 |
commit | 616cd009955b1722e6749019e2c1cac8bbb94e52 (patch) | |
tree | 29d3834654dda13ecdafdc6554cb9c094914f5aa /auth/gensec/schannel.c | |
parent | 54b5b3067f5b7a0eb6dd9f1326c903f9fe4a5592 (diff) | |
download | samba-616cd009955b1722e6749019e2c1cac8bbb94e52.tar.gz |
auth/gensec: move libcli/auth/schannel_sign.c into schannel.c
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Diffstat (limited to 'auth/gensec/schannel.c')
-rw-r--r-- | auth/gensec/schannel.c | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/auth/gensec/schannel.c b/auth/gensec/schannel.c index eb2e100e2e5..c60ab4f57bc 100644 --- a/auth/gensec/schannel.c +++ b/auth/gensec/schannel.c @@ -31,6 +31,386 @@ #include "librpc/gen_ndr/dcerpc.h" #include "param/param.h" #include "auth/gensec/gensec_toplevel_proto.h" +#include "lib/crypto/crypto.h" + +struct schannel_state { + uint64_t seq_num; + bool initiator; + struct netlogon_creds_CredentialState *creds; +}; + +#define SETUP_SEQNUM(state, buf, initiator) do { \ + uint8_t *_buf = buf; \ + uint32_t _seq_num_low = (state)->seq_num & UINT32_MAX; \ + uint32_t _seq_num_high = (state)->seq_num >> 32; \ + if (initiator) { \ + _seq_num_high |= 0x80000000; \ + } \ + RSIVAL(_buf, 0, _seq_num_low); \ + RSIVAL(_buf, 4, _seq_num_high); \ +} while(0) + +static struct schannel_state *netsec_create_state(TALLOC_CTX *mem_ctx, + struct netlogon_creds_CredentialState *creds, + bool initiator) +{ + struct schannel_state *state; + + state = talloc(mem_ctx, struct schannel_state); + if (state == NULL) { + return NULL; + } + + state->initiator = initiator; + state->seq_num = 0; + state->creds = netlogon_creds_copy(state, creds); + if (state->creds == NULL) { + talloc_free(state); + return NULL; + } + + return state; +} + +static void netsec_offset_and_sizes(struct schannel_state *state, + bool do_seal, + uint32_t *_min_sig_size, + uint32_t *_used_sig_size, + uint32_t *_checksum_length, + uint32_t *_confounder_ofs) +{ + uint32_t min_sig_size; + uint32_t used_sig_size; + uint32_t checksum_length; + uint32_t confounder_ofs; + + if (state->creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + min_sig_size = 48; + used_sig_size = 56; + /* + * Note: windows has a bug here and uses the old values... + * + * checksum_length = 32; + * confounder_ofs = 48; + */ + checksum_length = 8; + confounder_ofs = 24; + } else { + min_sig_size = 24; + used_sig_size = 32; + checksum_length = 8; + confounder_ofs = 24; + } + + if (do_seal) { + min_sig_size += 8; + } + + if (_min_sig_size) { + *_min_sig_size = min_sig_size; + } + + if (_used_sig_size) { + *_used_sig_size = used_sig_size; + } + + if (_checksum_length) { + *_checksum_length = checksum_length; + } + + if (_confounder_ofs) { + *_confounder_ofs = confounder_ofs; + } +} + +/******************************************************************* + Encode or Decode the sequence number (which is symmetric) + ********************************************************************/ +static void netsec_do_seq_num(struct schannel_state *state, + const uint8_t *checksum, + uint32_t checksum_length, + uint8_t seq_num[8]) +{ + if (state->creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + AES_KEY key; + uint8_t iv[AES_BLOCK_SIZE]; + + AES_set_encrypt_key(state->creds->session_key, 128, &key); + ZERO_STRUCT(iv); + memcpy(iv+0, checksum, 8); + memcpy(iv+8, checksum, 8); + + aes_cfb8_encrypt(seq_num, seq_num, 8, &key, iv, AES_ENCRYPT); + } else { + static const uint8_t zeros[4]; + uint8_t sequence_key[16]; + uint8_t digest1[16]; + + hmac_md5(state->creds->session_key, zeros, sizeof(zeros), digest1); + hmac_md5(digest1, checksum, checksum_length, sequence_key); + arcfour_crypt(seq_num, sequence_key, 8); + } + + state->seq_num++; +} + +static void netsec_do_seal(struct schannel_state *state, + const uint8_t seq_num[8], + uint8_t confounder[8], + uint8_t *data, uint32_t length, + bool forward) +{ + if (state->creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + AES_KEY key; + uint8_t iv[AES_BLOCK_SIZE]; + uint8_t sess_kf0[16]; + int i; + + for (i = 0; i < 16; i++) { + sess_kf0[i] = state->creds->session_key[i] ^ 0xf0; + } + + AES_set_encrypt_key(sess_kf0, 128, &key); + ZERO_STRUCT(iv); + memcpy(iv+0, seq_num, 8); + memcpy(iv+8, seq_num, 8); + + if (forward) { + aes_cfb8_encrypt(confounder, confounder, 8, &key, iv, AES_ENCRYPT); + aes_cfb8_encrypt(data, data, length, &key, iv, AES_ENCRYPT); + } else { + aes_cfb8_encrypt(confounder, confounder, 8, &key, iv, AES_DECRYPT); + aes_cfb8_encrypt(data, data, length, &key, iv, AES_DECRYPT); + } + } else { + uint8_t sealing_key[16]; + static const uint8_t zeros[4]; + uint8_t digest2[16]; + uint8_t sess_kf0[16]; + int i; + + for (i = 0; i < 16; i++) { + sess_kf0[i] = state->creds->session_key[i] ^ 0xf0; + } + + hmac_md5(sess_kf0, zeros, 4, digest2); + hmac_md5(digest2, seq_num, 8, sealing_key); + + arcfour_crypt(confounder, sealing_key, 8); + arcfour_crypt(data, sealing_key, length); + } +} + +/******************************************************************* + Create a digest over the entire packet (including the data), and + MD5 it with the session key. + ********************************************************************/ +static void netsec_do_sign(struct schannel_state *state, + const uint8_t *confounder, + const uint8_t *data, size_t length, + uint8_t header[8], + uint8_t *checksum) +{ + if (state->creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + struct HMACSHA256Context ctx; + + hmac_sha256_init(state->creds->session_key, + sizeof(state->creds->session_key), + &ctx); + + if (confounder) { + SSVAL(header, 0, NL_SIGN_HMAC_SHA256); + SSVAL(header, 2, NL_SEAL_AES128); + SSVAL(header, 4, 0xFFFF); + SSVAL(header, 6, 0x0000); + + hmac_sha256_update(header, 8, &ctx); + hmac_sha256_update(confounder, 8, &ctx); + } else { + SSVAL(header, 0, NL_SIGN_HMAC_SHA256); + SSVAL(header, 2, NL_SEAL_NONE); + SSVAL(header, 4, 0xFFFF); + SSVAL(header, 6, 0x0000); + + hmac_sha256_update(header, 8, &ctx); + } + + hmac_sha256_update(data, length, &ctx); + + hmac_sha256_final(checksum, &ctx); + } else { + uint8_t packet_digest[16]; + static const uint8_t zeros[4]; + MD5_CTX ctx; + + MD5Init(&ctx); + MD5Update(&ctx, zeros, 4); + if (confounder) { + SSVAL(header, 0, NL_SIGN_HMAC_MD5); + SSVAL(header, 2, NL_SEAL_RC4); + SSVAL(header, 4, 0xFFFF); + SSVAL(header, 6, 0x0000); + + MD5Update(&ctx, header, 8); + MD5Update(&ctx, confounder, 8); + } else { + SSVAL(header, 0, NL_SIGN_HMAC_MD5); + SSVAL(header, 2, NL_SEAL_NONE); + SSVAL(header, 4, 0xFFFF); + SSVAL(header, 6, 0x0000); + + MD5Update(&ctx, header, 8); + } + MD5Update(&ctx, data, length); + MD5Final(packet_digest, &ctx); + + hmac_md5(state->creds->session_key, + packet_digest, sizeof(packet_digest), + checksum); + } +} + +static NTSTATUS netsec_incoming_packet(struct schannel_state *state, + bool do_unseal, + uint8_t *data, size_t length, + const DATA_BLOB *sig) +{ + uint32_t min_sig_size = 0; + uint8_t header[8]; + uint8_t checksum[32]; + uint32_t checksum_length = sizeof(checksum_length); + uint8_t _confounder[8]; + uint8_t *confounder = NULL; + uint32_t confounder_ofs = 0; + uint8_t seq_num[8]; + int ret; + + netsec_offset_and_sizes(state, + do_unseal, + &min_sig_size, + NULL, + &checksum_length, + &confounder_ofs); + + if (sig->length < min_sig_size) { + return NT_STATUS_ACCESS_DENIED; + } + + if (do_unseal) { + confounder = _confounder; + memcpy(confounder, sig->data+confounder_ofs, 8); + } else { + confounder = NULL; + } + + SETUP_SEQNUM(state, seq_num, !state->initiator); + + if (do_unseal) { + netsec_do_seal(state, seq_num, + confounder, + data, length, + false); + } + + netsec_do_sign(state, confounder, + data, length, + header, checksum); + + ret = memcmp(checksum, sig->data+16, checksum_length); + if (ret != 0) { + dump_data_pw("calc digest:", checksum, checksum_length); + dump_data_pw("wire digest:", sig->data+16, checksum_length); + return NT_STATUS_ACCESS_DENIED; + } + + netsec_do_seq_num(state, checksum, checksum_length, seq_num); + + ret = memcmp(seq_num, sig->data+8, 8); + if (ret != 0) { + dump_data_pw("calc seq num:", seq_num, 8); + dump_data_pw("wire seq num:", sig->data+8, 8); + return NT_STATUS_ACCESS_DENIED; + } + + return NT_STATUS_OK; +} + +static uint32_t netsec_outgoing_sig_size(struct schannel_state *state) +{ + uint32_t sig_size = 0; + + netsec_offset_and_sizes(state, + true, + NULL, + &sig_size, + NULL, + NULL); + + return sig_size; +} + +static NTSTATUS netsec_outgoing_packet(struct schannel_state *state, + TALLOC_CTX *mem_ctx, + bool do_seal, + uint8_t *data, size_t length, + DATA_BLOB *sig) +{ + uint32_t min_sig_size = 0; + uint32_t used_sig_size = 0; + uint8_t header[8]; + uint8_t checksum[32]; + uint32_t checksum_length = sizeof(checksum_length); + uint8_t _confounder[8]; + uint8_t *confounder = NULL; + uint32_t confounder_ofs = 0; + uint8_t seq_num[8]; + + netsec_offset_and_sizes(state, + do_seal, + &min_sig_size, + &used_sig_size, + &checksum_length, + &confounder_ofs); + + SETUP_SEQNUM(state, seq_num, state->initiator); + + if (do_seal) { + confounder = _confounder; + generate_random_buffer(confounder, 8); + } else { + confounder = NULL; + } + + netsec_do_sign(state, confounder, + data, length, + header, checksum); + + if (do_seal) { + netsec_do_seal(state, seq_num, + confounder, + data, length, + true); + } + + netsec_do_seq_num(state, checksum, checksum_length, seq_num); + + (*sig) = data_blob_talloc_zero(mem_ctx, used_sig_size); + + memcpy(sig->data, header, 8); + memcpy(sig->data+8, seq_num, 8); + memcpy(sig->data+16, checksum, checksum_length); + + if (confounder) { + memcpy(sig->data+confounder_ofs, confounder, 8); + } + + dump_data_pw("signature:", sig->data+ 0, 8); + dump_data_pw("seq_num :", sig->data+ 8, 8); + dump_data_pw("digest :", sig->data+16, checksum_length); + dump_data_pw("confound :", sig->data+confounder_ofs, 8); + + return NT_STATUS_OK; +} _PUBLIC_ NTSTATUS gensec_schannel_init(void); |