/// <summary> /// Exchange AS or TGS token with KDC. /// </summary> /// <param name="token">A token</param> /// <param name="sentFromClient">True if the token is a request, false is a response</param> /// <returns>Kerberos PDU</returns> private KilePdu ExchangeLegWithKdc(byte[] token, bool sentFromClient) { kileDecoder.isClientRole = !sentFromClient; int consumedLength; int expectedLength; KilePdu[] pduList = kileDecoder.DecodePacketCallback( FAKE_ENDPOINT, token, out consumedLength, out expectedLength); if (pduList == null || pduList.Length != 1 || consumedLength == 0) { throw new InvalidOperationException("Unable to decode data buffer"); } KilePdu pdu = pduList[0]; if (sentFromClient) { kileDecoder.clientContext.UpdateContext(pdu); } else { kileDecoder.serverContext.UpdateContext(pdu); } return(pdu); }
/// <summary> /// Encrypts Message. User decides what SecBuffers are used. /// </summary> /// <param name="kileRole">Represents client or server</param> /// <param name="securityBuffers">The security buffers to encrypt.</param> /// <exception cref="System.ArgumentException">Thrown when the data or token is not valid.</exception> internal static void Encrypt(KileRole kileRole, params SecurityBuffer[] securityBuffers) { byte[] message = SspiUtility.ConcatenateReadWriteSecurityBuffers(securityBuffers, SecurityBufferType.Data); byte[] token = SspiUtility.ConcatenateSecurityBuffers(securityBuffers, SecurityBufferType.Token); if (token.Length == 0) { throw new ArgumentException("Token buffer is not valid."); } SGN_ALG sgnAlg = GetSgnAlg(kileRole); KilePdu pdu = kileRole.GssWrap(true, sgnAlg, message); byte[] cipherData = null; if (pdu.GetType() == typeof(Token4121)) { cipherData = pdu.ToBytes(); } else { byte[] allData = pdu.ToBytes(); byte[] paddingData = ((Token1964_4757)pdu).paddingData; cipherData = ArrayUtility.SubArray(allData, 0, allData.Length - paddingData.Length); SspiUtility.UpdateSecurityBuffers(securityBuffers, SecurityBufferType.Padding, paddingData); } SspiUtility.UpdateSecurityBuffers(securityBuffers, SecurityBufferType.Data, ArrayUtility.SubArray(cipherData, cipherData.Length - message.Length)); SspiUtility.UpdateSecurityBuffers(securityBuffers, SecurityBufferType.Token, ArrayUtility.SubArray(cipherData, 0, cipherData.Length - message.Length)); }
/// <summary> /// Expect to receive a PDU of any type from the remote host. /// </summary> /// <param name="timeout">Timeout of receiving PDU.</param> /// <param name="kileConnection">The connection with a client.</param> /// <returns>The expected PDU.</returns> public KilePdu ExpectPdu(TimeSpan timeout, out KileConnection kileConnection) { TransportEvent eventPacket = transport.ExpectTransportEvent(timeout); KilePdu packet = (KilePdu)eventPacket.EventObject; kileConnection = new KileConnection((IPEndPoint)eventPacket.EndPoint); return(packet); }
/// <summary> /// Encode a PDU to a binary stream. Then send the stream. /// The pdu could be got by calling method Create***Request or Create***Token. /// </summary> /// <param name="pdu">A specified type of a PDU. This argument cannot be null. /// If it is null, ArgumentNullException will be thrown.</param> /// <exception cref="System.ArgumentNullException">Thrown when the input parameter is null.</exception> public void SendPdu(KilePdu pdu) { if (pdu == null) { throw new ArgumentNullException("pdu"); } context.UpdateContext(pdu); kdcTransport.SendPacket(pdu); }
/// <summary> /// Expect to receive a PDU of any type from the remote host. /// </summary> /// <param name="timeout">Timeout of receiving PDU.</param> /// <returns>The expected PDU.</returns> /// <exception cref="System.TimeoutException">Thrown when the timeout parameter is negative.</exception> public KilePdu ExpectPdu(TimeSpan timeout) { if (timeout.TotalMilliseconds < 0) { throw new TimeoutException(ConstValue.TIMEOUT_EXCEPTION); } TransportEvent eventPacket = kdcTransport.ExpectTransportEvent(timeout); KilePdu packet = (KilePdu)eventPacket.EventObject; return(packet); }
/// <summary> /// Encode a PDU to a binary stream. Then send the stream. /// </summary> /// <param name="pdu">A specified type of a PDU. This argument cannot be null.</param> /// <param name="kileConnection">Maintain a connection with a target client</param> /// <exception cref="System.ArgumentNullException">Thrown when the input parameter is null.</exception> public void SendPdu(KilePdu pdu, KileConnection kileConnection) { if (kileConnection == null) { throw new ArgumentNullException("kileConnection"); } if (pdu == null) { throw new ArgumentNullException("pdu"); } KileServerContext serverContext = GetServerContextByKileConnection(kileConnection); transport.SendPacket(kileConnection.TargetEndPoint, pdu); serverContext.UpdateContext(pdu); }
/// <summary> /// This takes the given SecurityBuffer array, signs data part, and updates signature into token part /// </summary> /// <param name="kileRole">Represents client or server</param> /// <param name="securityBuffers">Data to sign and token to update.</param> /// <exception cref="System.ArgumentException">Thrown when the data or token is not valid.</exception> internal static void Sign(KileRole kileRole, params SecurityBuffer[] securityBuffers) { byte[] token = SspiUtility.ConcatenateReadWriteSecurityBuffers(securityBuffers, SecurityBufferType.Token); if (token.Length == 0) { throw new ArgumentException("No token can be updated for signature."); } byte[] message = GetToBeSignedDataFromSecurityBuffers(securityBuffers); SGN_ALG sgnAlg = GetSgnAlg(kileRole); KilePdu pdu = kileRole.GssGetMic(sgnAlg, message); byte[] signature = pdu.ToBytes(); SspiUtility.UpdateSecurityBuffers(securityBuffers, SecurityBufferType.Token, signature); }
/// <summary> /// This takes the given byte array, decrypts it, and returns /// the original, unencrypted byte array. /// </summary> /// <param name="kileRole">Represents client or server</param> /// <param name="securityBuffers">The security buffers to decrypt.</param> /// <exception cref="System.ArgumentException">Thrown when the data or token is not valid.</exception> internal static bool Decrypt(KileRole kileRole, params SecurityBuffer[] securityBuffers) { KilePdu pdu = kileRole.GssUnWrapEx(securityBuffers); byte[] decryptedMessage = null; if (pdu.GetType() == typeof(Token4121)) { decryptedMessage = ((Token4121)pdu).Data; } else if (pdu.GetType() == typeof(Token1964_4757)) { Token1964_4757 tokenData = (Token1964_4757)pdu; decryptedMessage = tokenData.Data; SspiUtility.UpdateSecurityBuffers(securityBuffers, SecurityBufferType.Padding, tokenData.paddingData); } // else do nothing return(true); }
/// <summary> /// Decrypt a request or response /// </summary> /// <param name="token">A token containing encrypted data.</param> /// <param name="sentFromClient">True if the token is a request, false is a response</param> /// <returns>Plain-text data.</returns> private byte[] Decrypt(byte[] token, bool sentFromClient) { KileContext context = sentFromClient ? (KileContext)kileDecoder.serverContext : (KileContext)kileDecoder.clientContext; KilePdu pdu = KileRole.GssUnWrap(context, token); Token4121 token4121Pdu = pdu as Token4121; if (token4121Pdu != null) { return(token4121Pdu.Data); } Token1964_4757 token1964or4757Pdu = pdu as Token1964_4757; if (token1964or4757Pdu != null) { return(token1964or4757Pdu.Data); } throw new InvalidOperationException("Token type is not supported."); }
/// <summary> /// Decode and verify a Gss_GetMic token. /// </summary> /// <param name="kileConnection">Maintain a connection with a target client</param> /// <param name="token">The token got from an application message. /// If this argument is null, null will be returned.</param> /// <param name="message">The message to be computed signature. /// If this argument is null, null will be returned.</param> /// <param name="pdu">The decoded Gss_GetMic token.</param> /// <returns>If verifying mic token is successful.</returns> /// <exception cref="System.NotSupportedException">Thrown when the encryption is not supported.</exception> public bool GssVerifyMic(KileConnection kileConnection, byte[] token, byte[] message, out KilePdu pdu) { context = GetServerContextByKileConnection(kileConnection); return(GssVerifyMic(token, message, out pdu)); }
/// <summary> /// Update the context. /// </summary> /// <param name="pdu">The PDU to update the context.</param> internal override void UpdateContext(KilePdu pdu) { if (pdu == null) { return; } lock (contextLock) { Type pduType = pdu.GetType(); if (pduType == typeof(KileAsRequest)) { KileAsRequest request = (KileAsRequest)pdu; if (request.Request != null && request.Request.req_body != null) { cName = request.Request.req_body.cname; cRealm = request.Request.req_body.realm; eType = request.Request.req_body.etype; } } else if (pduType == typeof(KileAsResponse)) { KileAsResponse response = (KileAsResponse)pdu; if (response.EncPart != null) { tgsSessionKey = response.EncPart.key; } if (response.Response != null) { tgsTicket = response.Response.ticket; if (tgsTicket != null && tgsTicket.sname != null && tgsTicket.sname.name_string != null && tgsTicket.sname.name_string.Elements != null && tgsTicket.sname.name_string.Elements.Length > 1) { int count = tgsTicket.sname.name_string.Elements.Length; cRealm = new Realm(tgsTicket.sname.name_string.Elements[count - 1].Value); } if (response.Response.padata != null && response.Response.padata.Elements != null) { foreach (PA_DATA paData in response.Response.padata.Elements) { if (paData.padata_type != null && paData.padata_type.Value == (long)PaDataType.PA_ETYPE_INFO2) { Asn1DecodingBuffer buffer = new Asn1DecodingBuffer(paData.padata_value.ByteArrayValue); ETYPE_INFO2 eTypeInfo2 = new ETYPE_INFO2(); eTypeInfo2.BerDecode(buffer); if (eTypeInfo2.Elements != null && eTypeInfo2.Elements.Length > 0) { // the salt is received from KDC salt = eTypeInfo2.Elements[0].salt.Value; return; } } } } } } else if (pduType == typeof(KileTgsResponse)) { KileTgsResponse response = (KileTgsResponse)pdu; if (response.Response != null) { apTicket = response.Response.ticket; if (apTicket != null && apTicket.sname != null && apTicket.sname.name_string != null && apTicket.sname.name_string.Elements != null && apTicket.sname.name_string.Elements.Length > 1) { int count = apTicket.sname.name_string.Elements.Length; cRealm = new Realm(apTicket.sname.name_string.Elements[count - 1].Value); } } if (response.EncPart != null) { apSessionKey = response.EncPart.key; } } else if (pduType == typeof(KileApRequest)) { KileApRequest request = (KileApRequest)pdu; if (request.Authenticator != null) { apSubKey = request.Authenticator.subkey; apRequestCtime = request.Authenticator.ctime; apRequestCusec = request.Authenticator.cusec; if (request.Authenticator.cksum != null && request.Authenticator.cksum.cksumtype.Value == (int)ChecksumType.ap_authenticator_8003 && request.Authenticator.cksum.checksum != null && request.Authenticator.cksum.checksum.Value != null && request.Authenticator.cksum.checksum.Value.Length == ConstValue.AUTH_CHECKSUM_SIZE) { int flag = BitConverter.ToInt32(request.Authenticator.cksum.checksum.ByteArrayValue, ConstValue.AUTHENTICATOR_CHECKSUM_LENGTH + sizeof(int)); checksumFlag = (ChecksumFlags)flag; } if (request.Authenticator.seq_number != null) { currentLocalSequenceNumber = (ulong)request.Authenticator.seq_number.Value; currentRemoteSequenceNumber = currentLocalSequenceNumber; } } } else if (pduType == typeof(KileApResponse)) { KileApResponse response = (KileApResponse)pdu; if (response.ApEncPart != null) { if (response.ApEncPart.seq_number != null) { currentRemoteSequenceNumber = (ulong)response.ApEncPart.seq_number.Value; } if (response.ApEncPart.subkey != null) { acceptorSubKey = response.ApEncPart.subkey; } } } // else do nothing } }
/// <summary> /// Update the context. /// </summary> /// <param name="pdu">The Pdu to update the context.</param> internal override void UpdateContext(KilePdu pdu) { if (pdu != null) { Type pduType = pdu.GetType(); if (pduType == typeof(KileAsRequest)) { KileAsRequest request = (KileAsRequest)pdu; if (request.Request != null && request.Request.req_body != null) { cName = request.Request.req_body.cname; cRealm = request.Request.req_body.realm; sName = request.Request.req_body.sname; encryptType = request.Request.req_body.etype; addresses = request.Request.req_body.addresses; nonce = request.Request.req_body.nonce; endTime = request.Request.req_body.till; rtime = request.Request.req_body.rtime; } } else if (pduType == typeof(KileAsResponse)) { KileAsResponse response = (KileAsResponse)pdu; tgsSessionKey = response.EncPart.key; } else if (pduType == typeof(KileTgsRequest)) { KileTgsRequest request = (KileTgsRequest)pdu; encryptType = request.Request.req_body.etype; nonce = request.Request.req_body.nonce; tgsTicket = request.tgtTicket; sName = request.Request.req_body.sname; if (request.authenticator != null) { tgsSubSessionKey = request.authenticator.subkey; } } else if (pduType == typeof(KileTgsResponse)) { KileTgsResponse response = (KileTgsResponse)pdu; apSessionKey = response.EncPart.key; } else if (pduType == typeof(KileApRequest)) { KileApRequest request = (KileApRequest)pdu; apRequestCtime = request.Authenticator.ctime; apRequestCusec = request.Authenticator.cusec; if (request.Authenticator.cksum != null) { int flag = BitConverter.ToInt32(request.Authenticator.cksum.checksum.mValue, ConstValue.AUTHENTICATOR_CHECKSUM_LENGTH + sizeof(ChecksumFlags)); checksumFlag = (ChecksumFlags)flag; } apSubKey = request.Authenticator.subkey; if (request.Authenticator.seq_number != null) { currentRemoteSequenceNumber = (ulong)request.Authenticator.seq_number.mValue; currentLocalSequenceNumber = currentRemoteSequenceNumber; } } else if (pduType == typeof(KileApResponse)) { KileApResponse response = (KileApResponse)pdu; if (response.ApEncPart.subkey != null) { acceptorSubKey = response.ApEncPart.subkey; } } else { // Do nothing. } } }
/// <summary> /// Initialize the context from a token. /// </summary> /// <param name="inToken">The token used to initialize.</param> /// <exception cref="System.NotSupportedException">Thrown when the ContextAttribute contains a flag that not be /// supported.</exception> /// <exception cref="System.InvalidOperationException">Thrown when an error is returned.</exception> public override void Initialize(byte[] inToken) { if (inToken == null || inToken.Length == 0) { KilePdu response = null; string sname = ConstValue.KERBEROS_SNAME; KileAsRequest asRequest; EncryptionKey subkey = null; // Create and send AS request for pre-authentication KdcOptions options = KdcOptions.FORWARDABLE | KdcOptions.RENEWABLE | KdcOptions.CANONICALIZE | KdcOptions.RENEWABLEOK; KDCOptions flags = new KDCOptions(KerberosUtility.ConvertInt2Flags((int)options)); // Create and send AS request for pre-authentication asRequest = client.CreateAsRequest(sname, flags, null, this.GetClientSupportedEtype()); client.SendPdu(asRequest); response = client.ExpectPdu(ConstValue.TIMEOUT_DEFAULT); KileKrbError preAuthError = response as KileKrbError; if ((preAuthError == null) || (!preAuthError.ErrorCode.Equals(KRB_ERROR_CODE.KDC_ERR_PREAUTH_REQUIRED))) { throw new InvalidOperationException("The Error code should be KDC_ERR_PREAUTH_REQUIRED"); } // Create and send AS request for TGT var defualtEncryptType = (client.ClientContext.TgsSessionKey == null) ? EncryptionType.RC4_HMAC : (EncryptionType)client.ClientContext.TgsSessionKey.keytype.Value; PaEncTimeStamp timestamp = client.ConstructPaEncTimeStamp(defualtEncryptType); PaPacRequest pacRequest = client.ConstructPaPacRequest(true); Asn1SequenceOf <PA_DATA> paData = client.ConstructPaData(timestamp, pacRequest); if ((contextAttribute & ClientSecurityContextAttribute.DceStyle) == ClientSecurityContextAttribute.DceStyle) { asRequest = client.CreateAsRequest(sname, flags, paData, this.GetClientSupportedEtype()); //subkey = new EncryptionKey((int)EncryptionType.AES256_CTS_HMAC_SHA1_96, // KileUtility.GenerateRandomBytes(ConstValue.AES_KEY_LENGTH)); var key = KeyGenerator.MakeKey(EncryptionType.AES256_CTS_HMAC_SHA1_96, client.ClientContext.Password, client.ClientContext.Salt); subkey = new EncryptionKey(new KerbInt32((long)EncryptionType.AES256_CTS_HMAC_SHA1_96), new Asn1OctetString(key)); } else { asRequest = client.CreateAsRequest(sname, flags, paData, this.GetClientSupportedEtype()); } client.SendPdu(asRequest); response = client.ExpectPdu(ConstValue.TIMEOUT_DEFAULT); if (response.GetType() == typeof(KileKrbError)) { throw new InvalidOperationException("Received Kerberos Error response: " + ((KileKrbError)response).ErrorCode); } KileAsResponse asResponse = (KileAsResponse)response; // Create and send TGS request // for example: "KERB.COMldapsut02.kerb.com" client.ClientContext.Salt = domain.ToUpper(); string[] nameList = userLogonName.Split('/'); foreach (string name in nameList) { client.ClientContext.Salt += name; } KileTgsRequest tgsRequest = client.CreateTgsRequest(this.serverName, flags, null, ChecksumType.hmac_md5_string, null, null); client.SendPdu(tgsRequest); response = client.ExpectPdu(ConstValue.TIMEOUT_DEFAULT); if (response.GetType() == typeof(KileKrbError)) { throw new InvalidOperationException("Received Kerberos Error response: " + ((KileKrbError)response).ErrorCode); } KileTgsResponse tgsResponse = (KileTgsResponse)response; ApOptions apOption; ChecksumFlags checksumFlag; GetFlagsByContextAttribute(out apOption, out checksumFlag); KerbAuthDataTokenRestrictions adRestriction = client.ConstructKerbAuthDataTokenRestrictions(0, (uint)LSAP_TOKEN_INFO_INTEGRITY_Flags.FULL_TOKEN, (uint)LSAP_TOKEN_INFO_INTEGRITY_TokenIL.Medium, new Guid().ToString()); AdAuthDataApOptions adApOptions = client.ConstructAdAuthDataApOptions(ConstValue.KERB_AP_OPTIONS_CBT); AuthorizationData authData = client.ConstructAuthorizationData(adRestriction, adApOptions); KileApRequest apRequest = client.CreateApRequest(apOption, ChecksumType.ap_authenticator_8003, ConstValue.SEQUENCE_NUMBER_DEFAULT, checksumFlag, subkey, authData); token = apRequest.ToBytes(); bool isMutualAuth = (contextAttribute & ClientSecurityContextAttribute.MutualAuth) == ClientSecurityContextAttribute.MutualAuth; bool isDceStyle = (contextAttribute & ClientSecurityContextAttribute.DceStyle) == ClientSecurityContextAttribute.DceStyle; if (isMutualAuth || isDceStyle) { continueProcess = true; // SEC_I_CONTINUE_NEEDED; } else { continueProcess = false; // SEC_E_OK; } } else // mutual authentication { KileApResponse apResponse = client.ParseApResponse(inToken); token = null; if ((contextAttribute & ClientSecurityContextAttribute.DceStyle) == ClientSecurityContextAttribute.DceStyle) { KileApResponse apResponseSend = client.CreateApResponse(null); token = apResponseSend.ToBytes(); } this.continueProcess = false; // SEC_E_OK; } }
/// <summary> /// Update the context. /// </summary> /// <param name="pdu">The PDU to update the context.</param> internal abstract void UpdateContext(KilePdu pdu);
/// <summary> /// Decode and verify a Gss_GetMic token. /// </summary> /// <param name="kileConnection">Maintain a connection with a target client</param> /// <param name="token">The token got from an application message. /// If this argument is null, null will be returned.</param> /// <param name="message">The message to be computed signature. /// If this argument is null, null will be returned.</param> /// <param name="pdu">The decoded Gss_GetMic token.</param> /// <returns>If verifying mic token is successful.</returns> /// <exception cref="System.NotSupportedException">Thrown when the encryption is not supported.</exception> public bool GssVerifyMic(KileConnection kileConnection, byte[] token, byte[] message, out KilePdu pdu) { context = GetServerContextByKileConnection(kileConnection); return GssVerifyMic(token, message, out pdu); }
/// <summary> /// Decode KILE PDUs from received message bytes /// </summary> /// <param name="endPoint">An endpoint from which the message bytes are received</param> /// <param name="receivedBytes">The received bytes to be decoded</param> /// <param name="consumedLength">Length of message bytes consumed by decoder</param> /// <param name="expectedLength">Length of message bytes the decoder expects to receive</param> /// <returns>The decoded KILE PDUs</returns> /// <exception cref="System.FormatException">thrown when a kile message type is unsupported</exception> internal KilePdu[] DecodePacketCallback(object endPoint, byte[] receivedBytes, out int consumedLength, out int expectedLength) { // initialize lengths consumedLength = 0; expectedLength = 0; if (null == receivedBytes || 0 == receivedBytes.Length) { return(null); } if (!isClientRole) { serverContext = null; if (serverContextList != null) { KileConnection kileConnection = new KileConnection((IPEndPoint)endPoint); if (!serverContextList.ContainsKey(kileConnection)) { serverContext = new KileServerContext(); serverContext.TransportType = connectionType; serverContextList.Add(kileConnection, serverContext); } else { serverContext = serverContextList[kileConnection]; } } if (serverContext == null) { throw new InvalidOperationException("The kileConnection related context does not exist."); } } // TCP has a 4 bytes length header, while UDP has not byte[] pduBytes = receivedBytes; if ((isClientRole && clientContext.TransportType == KileConnectionType.TCP) || (!isClientRole && serverContext.TransportType == KileConnectionType.TCP)) { // insufficient data, needs to receive more if (receivedBytes.Length < sizeof(int)) { return(null); } // get pdu data length byte[] lengthBytes = ArrayUtility.SubArray(receivedBytes, 0, sizeof(int)); Array.Reverse(lengthBytes); int pduLength = BitConverter.ToInt32(lengthBytes, 0); // insufficient data, needs to receive more expectedLength = sizeof(int) + pduLength; if (receivedBytes.Length < expectedLength) { return(null); } // check if it is a krb zero message if (pduLength == 0 && receivedBytes.Length == sizeof(int)) { consumedLength = sizeof(int); KrbZero krbZeroPdu = new KrbZero(clientContext); return(new KilePdu[] { krbZeroPdu }); } // remove length header from pdu bytes pduBytes = ArrayUtility.SubArray <byte>(receivedBytes, sizeof(int), pduLength); } else { // UDP has no length header expectedLength = pduBytes.Length; } // get message type // (the lower 5 bits indicates its kile message type) MsgType kileMessageType = (MsgType)(pduBytes[0] & 0x1f); // decode according to message type consumedLength = expectedLength; KilePdu pdu = null; switch (kileMessageType) { case MsgType.KRB_AS_REQ: pdu = new KileAsRequest(serverContext); break; case MsgType.KRB_AS_RESP: pdu = new KileAsResponse(clientContext); break; case MsgType.KRB_TGS_REQ: pdu = new KileTgsRequest(serverContext); break; case MsgType.KRB_TGS_RESP: pdu = new KileTgsResponse(clientContext); break; case MsgType.KRB_ERROR: pdu = new KileKrbError(); break; default: throw new FormatException( "Unsupported Message Type: " + kileMessageType.ToString()); } pdu.FromBytes(pduBytes); // update context if (isClientRole) { clientContext.UpdateContext(pdu); } else { serverContext.UpdateContext(pdu); } return(new KilePdu[] { pdu }); }