Пример #1
0
        /// <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();
            }
        }
Пример #2
0
        /// <summary>
        /// Handles handshake and encrypted envelopes.
        /// </summary>
        /// <param name="envelope">The envelope.</param>
        /// <returns></returns>
        public async Task <bool> HandleAsync(Envelope envelope)
        {
            // check for secure header
            SecureHeader secureHeader = null;

            try {
                if (envelope.Headers.ContainsKey(SecureHeader.HEADER_NAME))
                {
                    secureHeader = new SecureHeader(Encoding.UTF8.GetString(envelope.Headers[SecureHeader.HEADER_NAME] as byte[]));
                }
            } catch (Exception) {
                if (envelope.ID != Guid.Empty)
                {
                    await ReplyErrorAsync(envelope, new SecureErrorMsg()
                    {
                        Code    = "ProtocolInvalid",
                        Message = "The secure message header format is invalid"
                    });
                }

                return(false);
            }

            // check if it's a secure message or not
            if (secureHeader != null && envelope.ID != Guid.Empty)
            {
                if (secureHeader.Type == SecureMessageType.RequestCertificate)
                {
#if DEBUG_SECURERPC
                    Console.WriteLine($"[SecureFilter] {nameof(RpcSecureMessageType.RequestCertificate)}");
#endif

                    // build response
                    SecureRespondCertificateMsg respondCertificateMsg = new SecureRespondCertificateMsg();
                    respondCertificateMsg.CertificateData = _certificate.Export(X509ContentType.Cert);

                    // build reply
                    using (MemoryStream ms = new MemoryStream()) {
                        Serializer.Serialize(ms, respondCertificateMsg);

                        // reply
                        await envelope.Node.ReplyAsync(envelope.ReplyTo, envelope.ID, ms.ToArray(), new Dictionary <string, object>() {
                            { SecureHeader.HEADER_NAME, new SecureHeader(SecureHeader.HEADER_VERSION, SecureMessageType.RespondCertificate).ToString() }
                        });
                    }

                    return(false);
                }
                else if (secureHeader.Type == SecureMessageType.RequestKey)
                {
                    // decrypt
                    byte[] decryptedBody = null;

                    using (RSA rsa = _certificate.GetRSAPrivateKey()) {
                        decryptedBody = rsa.Decrypt(envelope.Body, RSAEncryptionPadding.Pkcs1);
                    }

                    // deserialize key request
                    SecureRequestKeyMsg requestKeyMsg = null;

                    using (MemoryStream ms = new MemoryStream(decryptedBody)) {
                        requestKeyMsg = Serializer.Deserialize <SecureRequestKeyMsg>(ms);
                    }

                    // validate that the data is there and is the correct size, if not send an error back
                    if (requestKeyMsg.HandshakeIV == null || requestKeyMsg.HandshakeKey == null || requestKeyMsg.HandshakeIV.Length != 16 || requestKeyMsg.HandshakeKey.Length != 16)
                    {
                        await ReplyErrorAsync(envelope, new SecureErrorMsg()
                        {
                            Code    = "ProtocolInvalid",
                            Message = "The request key data was invalid"
                        });

                        return(false);
                    }

                    // generate random nonce
                    byte[] nonceBytes = new byte[16];

                    using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) {
                        rng.GetBytes(nonceBytes);
                    }

                    // process key request
                    long   timeSlot = SecureUtils.GetNextTimeSlot();
                    byte[] keyBytes = SecureUtils.GenerateKey(nonceBytes, timeSlot, _secret);

                    // log
#if DEBUG_SECURERPC
                    Console.WriteLine($"[SecureFilter] {nameof(RpcSecureMessageType.RequestKey)} TimeSlot: {timeSlot} Nonce: {BitConverter.ToString(nonceBytes).Replace("-", "")}");
#endif

                    // build response
                    SecureRespondKeyMsg respondKeyMsg = new SecureRespondKeyMsg();
                    respondKeyMsg.ServerNonce = nonceBytes;
                    respondKeyMsg.ServerKey   = keyBytes;
                    respondKeyMsg.KeyTimeSlot = timeSlot;

                    // encode and encrypt
                    byte[] respondKeyBody = null;

                    using (Aes aes = Aes.Create()) {
                        // setup aes
                        aes.Key = requestKeyMsg.HandshakeKey;
                        aes.IV  = requestKeyMsg.HandshakeIV;

                        // build body
                        using (MemoryStream ms = new MemoryStream()) {
                            // encrypt using client key
                            using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write)) {
                                Serializer.Serialize(cs, respondKeyMsg);
                            }

                            // get output
                            respondKeyBody = ms.ToArray();
                        }
                    }

                    // reply
                    await envelope.Node.ReplyAsync(envelope.ReplyTo, envelope.ID, respondKeyBody, new Dictionary <string, object>() {
                        { SecureHeader.HEADER_NAME, new SecureHeader(SecureHeader.HEADER_VERSION, SecureMessageType.RespondKey).ToString() }
                    });

                    return(false);
                }
                else if (secureHeader.Type == SecureMessageType.RequestMessage)
                {
                    // deserialize key request
                    SecureMessageMsg msg = null;

                    using (MemoryStream ms = new MemoryStream(envelope.Body)) {
                        msg = Serializer.Deserialize <SecureMessageMsg>(ms);
                    }

                    // validate the data is there and is correct length, if not send invalid data
                    if (msg.Payload == null || msg.ServerNonce == null || msg.ServerNonce.Length != 16)
                    {
                        await ReplyErrorAsync(envelope, new SecureErrorMsg()
                        {
                            Code    = "ProtocolInvalid",
                            Message = "The request message data was invalid"
                        });

                        return(false);
                    }

                    // log
#if DEBUG_SECURERPC
                    Console.WriteLine($"[SecureFilter] {nameof(RpcSecureMessageType.RequestMessage)} TimeSlot: {msg.KeyTimeSlot} Nonce: {BitConverter.ToString(msg.ServerNonce).Replace("-", "")}");
#endif

                    // validate expiry of time slot
                    if (SecureUtils.HasTimeSlotExpired(msg.KeyTimeSlot, true))
                    {
                        await ReplyErrorAsync(envelope, new SecureErrorMsg()
                        {
                            Code    = "KeyExpired",
                            Message = "The secure message is encrypted with an outdated key"
                        });

                        return(false);
                    }

                    // get key
                    byte[] keyBytes = SecureUtils.GenerateKey(msg.ServerNonce, msg.KeyTimeSlot, _secret);

                    using (MemoryStream decryptedPayloadStream = new MemoryStream()) {
                        using (MemoryStream payloadStream = new MemoryStream(msg.Payload)) {
                            using (Aes aes = Aes.Create()) {
                                aes.Key = keyBytes;
                                aes.IV  = msg.ServerNonce;

                                using (CryptoStream decryptStream = new CryptoStream(payloadStream, aes.CreateDecryptor(), CryptoStreamMode.Read)) {
                                    decryptStream.CopyTo(decryptedPayloadStream);
                                }
                            }
                        }

                        // modify body and set reply channel
                        envelope.Body    = decryptedPayloadStream.ToArray();
                        envelope.Channel = new SecureReplyChannel(envelope, keyBytes, msg.ServerNonce);

                        return(true);
                    }
                }
                else
                {
                    // log
#if DEBUG_SECURERPC
                    Console.WriteLine($"[SecureFilter] Unimplemented message type!");
#endif

                    await ReplyErrorAsync(envelope, new SecureErrorMsg()
                    {
                        Code    = "ProtocolViolation",
                        Message = "The message type is not relevant or is invalid"
                    });

                    return(false);
                }
            }
            else
            {
                return(true);
            }
        }
Пример #3
0
        /// <summary>
        /// Broadcasts 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 to receive all replies.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns></returns>
        public async Task <Envelope[]> BroadcastAsync(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[] responses = await _node.BroadcastAsync(message, timeout, cancellationToken);

            return(responses.Select(response => {
                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[]));

                try {
                    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
                    {
                        return null;
                    }
                } catch (Exception) {
                    return null;
                }
            }).Where(e => e != null).ToArray());
        }
Пример #4
0
        /// <summary>
        /// Sends the envelope message to the provided service address and waits for a response.
        /// </summary>
        /// <param name="body">The body.</param>
        /// <param name="headers">The headers.</param>
        /// <param name="timeout">The timeout.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns></returns>
        public async Task <Envelope> AskAsync(byte[] body, TimeSpan timeout, IDictionary <string, object> headers = null, 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 (headers == null)
            {
                headers = new Dictionary <string, object>(StringComparer.CurrentCultureIgnoreCase);
            }

            headers[SecureHeader.HEADER_NAME] = new SecureHeader(SecureHeader.HEADER_VERSION, SecureMessageType.RequestMessage).ToString();

            // send the envelope and wait for the response
            Envelope response = await _node.AskAsync(_address, EncryptBody(body), timeout, headers, cancellationToken);

            if (!response.Headers.ContainsKey(SecureHeader.HEADER_NAME))
            {
                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.HEADER_NAME] 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})");
            }
        }