Example #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();
            }
        }