/// <summary> /// Sends a secure error reply to the envelope. /// </summary> /// <param name="envelope">The envelope.</param> /// <param name="errorMsg">The error message.</param> /// <returns></returns> private async Task ReplyErrorAsync(Envelope envelope, SecureErrorMsg errorMsg) { using (MemoryStream ms = new MemoryStream()) { Serializer.Serialize(ms, errorMsg); // reply await envelope.ReplyAsync(ms.ToArray(), new Dictionary <string, object>() { { SecureHeader.HEADER_NAME, new SecureHeader(SecureHeader.HEADER_VERSION, SecureMessageType.Error).ToString() } }); } }
/// <summary> /// Performs the secure handshake so we can start sending messages. /// </summary> /// <returns></returns> private async Task HandshakeAsync() { // wait on semaphore await _handshakeSemaphore.WaitAsync(); // check if we got raced if (_serverEncryptionKey != null && !SecureUtils.HasTimeSlotExpired(_serverEncryptionKeyTimeSlot, false)) { return; } try { // create client key GenerateHandshakeKey(); // get certificate if we don't have it yet if (_serverCertificate == null) { // log #if DEBUG_SECURE Console.WriteLine($"[Secure] Handshake Requesting certificate..."); #endif // request certificate SecureHeader requestCertificate = new SecureHeader(SecureHeader.HeaderVersion, SecureMessageType.RequestCertificate); // send request Envelope respondCert = await _node.AskAsync(_address, new byte[0], _configuration.HandshakeTimeout, new Dictionary <string, object>() { { SecureHeader.HeaderName, requestCertificate.ToString() } }); // parse response header SecureHeader respondCertHeader = null; try { respondCertHeader = new SecureHeader(Encoding.UTF8.GetString(respondCert.Headers[SecureHeader.HeaderName] as byte[])); } catch (Exception ex) { throw new InvalidDataException("The certificate request response header was invalid", ex); } // check if the certificate response is an error or if it's an incorrect type if (respondCertHeader.Type == SecureMessageType.Error) { SecureErrorMsg errorMsg = respondCert.AsProtoBuf <SecureErrorMsg>(); throw new SecurityException($"{errorMsg.Message} ({errorMsg.Code})"); } else if (respondCertHeader.Type != SecureMessageType.RespondCertificate) { throw new InvalidDataException("The certificate request response header was invalid"); } // decode the certificate response and then check it has actual data SecureRespondCertificateMsg respondCertMsg = respondCert.AsProtoBuf <SecureRespondCertificateMsg>(); if (respondCertMsg.CertificateData == null) { throw new InvalidDataException("The certificate request response was invalid"); } // load certificate from response X509Certificate2 cert = new X509Certificate2(respondCertMsg.CertificateData); // validate it's allowed to act as this service if (_configuration.ValidateAddress) { // check extension is actually there if (cert.Extensions["HolonSecureServices"] == null) { throw new InvalidDataException("The service certificate has no claim for the invoked operation"); } // parse extension X509Extension servicesExt = cert.Extensions["HolonSecureServices"]; } else { #if DEBUG_SECURE Console.WriteLine($"[Secure] Handshake WARNING: Not validating services due to configuration!"); #endif } // validate that it's signed by ca authority if (_configuration.ValidateAuthority) { X509Chain chain = new X509Chain(); chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; chain.ChainPolicy.ExtraStore.Add(_configuration.RootAuthority); chain.Build(cert); // get status X509ChainStatus status = chain.ChainStatus.First(); if (status.Status != X509ChainStatusFlags.UntrustedRoot && status.Status != X509ChainStatusFlags.NoError) { throw new InvalidDataException("The service certificate is not signed by the root authority"); } } else { #if DEBUG_SECURE Console.WriteLine($"[Secure] Handshake WARNING: Not validating authority due to configuration!"); #endif } // log #if DEBUG_SECURE Console.WriteLine($"[Secure] Handshake Got certifcate {cert.Subject} ({cert.Thumbprint})"); #endif _serverCertificate = cert; } // log #if DEBUG_SECURE Console.WriteLine($"[Secure] Handshake Requesting key... FirstTime: {_serverEncryptionKey == null}"); #endif // key request SecureRequestKeyMsg requestKeyMsg = new SecureRequestKeyMsg() { HandshakeIV = _handshakeEncryptionIV, HandshakeKey = _handshakeEncryptionKey }; SecureHeader requestKey = new SecureHeader(SecureHeader.HeaderVersion, SecureMessageType.RequestKey); // send request Envelope respondKey = null; using (RSA rsa = _serverCertificate.GetRSAPublicKey()) { using (MemoryStream ms = new MemoryStream()) { // serialize to stream Serializer.Serialize(ms, requestKeyMsg); // encrypt with server certificate byte[] keyRequestBody = rsa.Encrypt(ms.ToArray(), RSAEncryptionPadding.Pkcs1); respondKey = await _node.AskAsync(_address, keyRequestBody, _configuration.HandshakeTimeout, new Dictionary <string, object>() { { SecureHeader.HeaderName, requestKey.ToString() } }); } } // parse response SecureHeader respondKeyHeader = null; try { respondKeyHeader = new SecureHeader(Encoding.UTF8.GetString(respondKey.Headers[SecureHeader.HeaderName] as byte[])); } catch (Exception ex) { throw new InvalidDataException("The key request response header was invalid", ex); } // check if the key response is an error or if it's an incorrect type if (respondKeyHeader.Type == SecureMessageType.Error) { SecureErrorMsg errorMsg = respondKey.AsProtoBuf <SecureErrorMsg>(); throw new SecurityException($"{errorMsg.Message} ({errorMsg.Code})"); } else if (respondKeyHeader.Type != SecureMessageType.RespondKey) { throw new InvalidDataException("The key request response header was invalid"); } // try and decrypt using (MemoryStream decryptedStream = new MemoryStream()) { using (Aes aes = Aes.Create()) { aes.Key = _handshakeEncryptionKey; aes.IV = _handshakeEncryptionIV; using (CryptoStream decryptStream = new CryptoStream(respondKey.AsStream(), aes.CreateDecryptor(), CryptoStreamMode.Read)) { decryptStream.CopyTo(decryptedStream); } } // seek to beginning decryptedStream.Seek(0, SeekOrigin.Begin); // deserialize SecureRespondKeyMsg respondKeyMsg = Serializer.Deserialize <SecureRespondKeyMsg>(decryptedStream); // validate key if (respondKeyMsg.ServerKey == null || respondKeyMsg.ServerNonce == null || respondKeyMsg.ServerKey.Length != 16) { throw new InvalidDataException("The secure key is invalid"); } // log #if DEBUG_SECURE Console.WriteLine($"[Secure] Handshake Got key Timeslot: {respondKeyMsg.KeyTimeSlot} Nonce: {BitConverter.ToString(respondKeyMsg.ServerNonce).Replace("-", "")}"); #endif // set server encryption key _serverEncryptionKey = respondKeyMsg.ServerKey; _serverNonce = respondKeyMsg.ServerNonce; _serverEncryptionKeyTimeSlot = respondKeyMsg.KeyTimeSlot; } } finally { // make sure to release semaphore no matter what _handshakeSemaphore.Release(); } }
/// <summary> /// Sends the envelope message to the provided service address and waits for a response. /// </summary> /// <param name="message">The message.</param> /// <param name="timeout">The timeout.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns></returns> public async Task <Envelope> AskAsync(Message message, TimeSpan timeout, CancellationToken cancellationToken = default(CancellationToken)) { // perform handshake if we don't have our key yet or it has expired if (_serverEncryptionKey == null || SecureUtils.HasTimeSlotExpired(_serverEncryptionKeyTimeSlot, false)) { #if DEBUG_SECURE Console.WriteLine($"[Secure] InvokeOperation KeyNull: {_serverEncryptionKey == null} Expired: {SecureUtils.HasTimeSlotExpired(_serverEncryptionKeyTimeSlot, false)}"); #endif // perform handshake (partial if required) await HandshakeAsync(); } // add secure header if (message.Headers == null) { message.Headers = new Dictionary <string, object>(StringComparer.CurrentCultureIgnoreCase); } message.Headers[SecureHeader.HeaderName] = new SecureHeader(SecureHeader.HeaderVersion, SecureMessageType.RequestMessage).ToString(); message.Address = _address; message.Body = EncryptBody(message.Body); // send the envelope and wait for the response Envelope response = await _node.AskAsync(message, timeout, cancellationToken); if (!response.Headers.ContainsKey(SecureHeader.HeaderName)) { throw new InvalidDataException("The secure service sent an invalid response"); } // decode header and take action depending on the response SecureHeader header = new SecureHeader(Encoding.UTF8.GetString(response.Headers[SecureHeader.HeaderName] as byte[])); if (header.Type == SecureMessageType.RespondMessage) { using (MemoryStream outputStream = new MemoryStream()) { // decrypt using (MemoryStream inputStream = new MemoryStream(response.Body)) { using (Aes aes = Aes.Create()) { aes.Key = _serverEncryptionKey; aes.IV = _serverNonce; using (CryptoStream decryptStream = new CryptoStream(inputStream, aes.CreateDecryptor(), CryptoStreamMode.Read)) { decryptStream.CopyTo(outputStream); } } } response.Body = outputStream.ToArray(); return(response); } } else if (header.Type == SecureMessageType.Error) { // deserialize SecureErrorMsg errorMsg = null; try { errorMsg = response.AsProtoBuf <SecureErrorMsg>(); } catch (Exception ex) { throw new InvalidDataException("The secure service sent an invalid error respsonse", ex); } throw new SecurityException($"{errorMsg.Message} ({errorMsg.Code})"); } else { throw new InvalidDataException($"The secure service sent an invalid response ({header.Type})"); } }