Example #1
0
        static internal void DoDemo(int numDevices)
        {
            Program.IODir += "MultiDevice/";
            CertMaker m = new CertMaker(Program.IODir);

            IoTDevice[] deviceList = new IoTDevice[numDevices];
            HubController = new HubControl();

            // make the devices and enroll them in the hub
            for (int j = 0; j < numDevices; j++)
            {
                string devId = GetDeviceID(j);
                Program.SetDeviceNumber(j);

                int fwidSeed = 0;
                m.MakeNew(5, false, fwidSeed);

                HubController.Connect();
                HubController.RemoveDevice(devId);

                var devInfo = ExtensionDecoder.Decode(Program.ToPath(Program.AliasCert));
                HubController.EnrollDevice(devId, fwidSeed, Helpers.Hexify(devInfo.FirmwareID), devInfo.Cert.Thumbprint);

                IoTDevice device = new IoTDevice(devId, 0, j);
                deviceList[j] = device;
            }

            // run through messaging and update
            bool[] primaryOrSEcondary = new bool[numDevices];
            int    epoch = 0;

            while (true)
            {
                for (int j = 0; j < numDevices; j++)
                {
                    Program.SetDeviceNumber(j);
                    var    device = deviceList[j];
                    string devId  = GetDeviceID(j);

                    // send messages using current firmware
                    device.RefreshCert();
                    device.SendMessages(1, 30);

                    if (device.FirmwareUpdateNeeded)
                    {
                        // update the firmware on the device
                        int fwidSeed = device.DesiredFwVersionNumber;
                        m.MakeAliasCert(true, fwidSeed);
                        var devInfo = ExtensionDecoder.Decode(Program.ToPath(Program.AliasCert));

                        // and tell the hub
                        HubController.RefreshDevice(devId, fwidSeed, Helpers.Hexify(devInfo.FirmwareID), devInfo.Cert.Thumbprint, primaryOrSEcondary[j]);
                        primaryOrSEcondary[j]         = !primaryOrSEcondary[j];
                        device.CurrentFwVersionNumber = fwidSeed;
                    }
                }
                Debug.WriteLine($"Epoch == {epoch++}");
            }
        }
Example #2
0
        /// <summary>
        /// If the device is not "vendor certified" it will only present the Alias Certificate, which is
        /// validated in this routine.  The essential security checks are:
        ///     1) Does it have a RIoT extension containing the DeviceID?
        ///     2) Is the certificate signed by the corresponding private DeviceID?
        ///     3) Is the DeviceID "authorized."  In the simple case, is it exactly the device DeviceID
        ///             indicated by the code that instantiated the TLSServer object.
        /// </summary>
        /// <param name="certificate"></param>
        /// <returns></returns>
        internal static bool ValidateBareCertificate(X509Certificate2 certificate)
        {
            Helpers.Notify($"Device presented a bare certificate");

            var deviceInfo = ExtensionDecoder.Decode(certificate);

            // check we have a good extension
            if (deviceInfo == null)
            {
                Helpers.Notify("Certificate does not have well-formed RIoT extension", true);
                return(false);
            }
            var devIdPubKeyDEREncoded = deviceInfo.EncodedDeviceIDKey;

            if (devIdPubKeyDEREncoded.Length != 65)
            {
                Helpers.Notify("Public key in extension has incorrect length", true);
                return(false);
            }

            // validating the certificate is signed with the public key encoded in the extension.
            // This is a critical security check.
            // Note: this uses the Bouncy Castle libraries
            var                   bcCert     = new X509CertificateParser().ReadCertificate(certificate.GetRawCertData());
            X9ECParameters        p          = NistNamedCurves.GetByName("P-256");
            ECDomainParameters    parameters = new ECDomainParameters(p.Curve, p.G, p.N, p.H);
            var                   pt         = parameters.Curve.DecodePoint(deviceInfo.EncodedDeviceIDKey);
            ECPublicKeyParameters bcPubKey   = new ECPublicKeyParameters(pt, parameters);

            try
            {
                bcCert.Verify(bcPubKey);
            }
            catch (Exception e)
            {
                Helpers.Notify($"Certificate is not signed using key in extension {e.ToString()}", true);
                return(false);
            }
            if (DeviceIDPEMFile != null)
            {
                // Is this key one of the keys registered with DRS (this test code only has one.)
                ECPublicKeyParameters authorizedDevice = (ECPublicKeyParameters)Helpers.ReadPemObject(DeviceIDPEMFile);
                // todo: there are probably better equality tests than this!
                bool keyIsRecognized =
                    (authorizedDevice.Q.XCoord.ToString() == bcPubKey.Q.XCoord.ToString()) &&
                    (authorizedDevice.Q.YCoord.ToString() == bcPubKey.Q.YCoord.ToString());

                if (!keyIsRecognized)
                {
                    Helpers.Notify($"DeviceID is not known", true);
                    return(false);
                }
                return(true);
            }
            // this code supports the "FakeDRSServer.  Here, any device that connects to us is presumed good

            return(true);
        }
Example #3
0
        internal static bool ValidateBareCertificateWithBcrypt(X509Certificate2 certificate)
        {
            var deviceInfo = ExtensionDecoder.Decode(certificate);

            // check we have a good extension
            if (deviceInfo == null)
            {
                Helpers.Notify("Certificate does not have well-formed RIoT extension", true);
                return(false);
            }
            var devIdPubKeyDEREncoded = deviceInfo.EncodedDeviceIDKey;

            if (devIdPubKeyDEREncoded.Length != 65)
            {
                Helpers.Notify("Public key in extension has incorrect length", true);
                return(false);
            }

            // We need to convert to the Windows key format before we can import
            // #define BCRYPT_ECDSA_PUBLIC_P256_MAGIC  0x31534345  // ECS1
            // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375520(v=vs.85).aspx
            byte[] windowsEncodedKey = new byte[32 * 2 + 4 + 4];
            // todo - endianess
            byte[] magic = BitConverter.GetBytes((uint)0x31534345);
            byte[] len   = BitConverter.GetBytes((uint)32);

            Array.Copy(magic, 0, windowsEncodedKey, 0, 4);
            Array.Copy(len, 0, windowsEncodedKey, 4, 4);
            Array.Copy(devIdPubKeyDEREncoded, 1, windowsEncodedKey, 8, 32);
            Array.Copy(devIdPubKeyDEREncoded, 32 + 1, windowsEncodedKey, 8 + 32, 32);

            var devIdPubKey = CngKey.Import(windowsEncodedKey, CngKeyBlobFormat.EccPublicBlob);

            ECDsaCng verifier = new ECDsaCng(devIdPubKey);

            ECDsaCng testSigner = new ECDsaCng(256);
            var      sig        = testSigner.SignData(new byte[] { 1, 2, 3, 4 });
            bool     okx        = testSigner.VerifyData(new byte[] { 1, 2, 3, 4 }, sig);

            var  bits    = ExtensionDecoder.Decompose(certificate);
            var  tbsHash = Helpers.HashData(bits.Tbs, 0, bits.Tbs.Length);
            bool ok      = verifier.VerifyHash(tbsHash, bits.Signature);


            return(true);
        }
Example #4
0
        private static void ProcessClient(TcpClient client, bool chat)
        {
            SslStream sslStream = new SslStream(client.GetStream(), false, ValidateDeviceCertificate);

            try
            {
                // authenticate as server and request client cert.  This invokes the ValidateDeviceCertificate
                // callback.  If the callback returns false (or there are other errors), AuthenticateAsServer
                // throws an exception
                sslStream.AuthenticateAsServer(ServerCert, true, SslProtocols.Tls, false);
                // if we get to here, all TLS+RIoT checks have succeeded.
                // Print info about the connected device
                var deviceCert = sslStream.RemoteCertificate;
                var devCert2   = new X509Certificate2(deviceCert);
                var info       = ExtensionDecoder.Decode(devCert2);
                if (info == null)
                {
                    // should not happen since the cert has already been
                    // validated in the callback..
                    Helpers.Notify("Unexpected missing or malformed RIoT device certificate", true);
                    return;
                }
                // we have a good device: tell the world
                Helpers.Notify($"RIoT Device Connected:");
                Helpers.Notify($"            DeviceID:{Helpers.Hexify(info.EncodedDeviceIDKey).Substring(0, 60) + "..."}");
                Helpers.Notify($"                FWID:{Helpers.Hexify(info.FirmwareID)}");

                if (chat)
                {
                    // Read a message from the client.
                    sslStream.ReadTimeout  = 10000;
                    sslStream.WriteTimeout = 10000;
                    Helpers.Notify("Waiting for client message...");
                    if (DeviceIDPEMFile != null)
                    {
                        string messageData = ReadMessageX(sslStream);
                        Helpers.Notify($"Server received: {messageData}");
                        // Write a message to the client.
                        byte[] message = Encoding.UTF8.GetBytes("Hello from the server.<EOF>");
                        Helpers.Notify("Sending hello message.");
                        sslStream.Write(message);
                    }
                    else
                    {
                        ProcessFakeDRSMessage(sslStream);
                    }
                }
                // give the client some time to process before closing the stream
                Thread.Sleep(30);
            }
            catch (AuthenticationException e)
            {
                Helpers.Notify($"Exception in AuthenticateAs server block: {e.Message}", true);
                if (e.InnerException != null)
                {
                    Helpers.Notify($"Inner exception: {e.InnerException.Message}", true);
                }
                Helpers.Notify("Authentication failed - closing the connection.", true);
                sslStream.Close();
                client.Close();
                return;
            }
            finally
            {
                Helpers.Notify("Client has disconnected. Stream is closing");
                sslStream.Close();
                client.Close();
            }
        }
Example #5
0
        /// <summary>
        /// This callback validates a RIoT certificate chain or a bare Alias certificate.
        /// We generally don't want to validate a chain against the user or machine cert store,
        /// so we do it (semi) manually with X509Chain.  If the device only presents the Alias
        /// Certificate (no chain) we call it a "bare certificate.)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="_certificate"></param>
        /// <param name="inChain"></param>
        /// <param name="sslPolicyErrors"></param>
        /// <returns></returns>
        public static bool ValidateDeviceCertificate(object sender, System.Security.Cryptography.X509Certificates.X509Certificate _certificate, X509Chain inChain,
                                                     SslPolicyErrors sslPolicyErrors)
        {
            //ValidateBareCertificateWithBcrypt(new X509Certificate2(_certificate));


            if (_certificate == null)
            {
                // not sure that this can happen
                Helpers.Notify($"No certificate presented by client", true);
                return(false);
            }

            // This is the leaf (alias) certificate
            X509Certificate2 certificate = new X509Certificate2(_certificate);

            // Did the device just send one certificate?  If so, we don't check the chain: we just
            // check that the certificate was signed by a known DeviceID in the routine below.
            // Note that if the built-in chain builder found certificates in the system store to build
            // chain, then inChain will contain those and the processing will follow the "chain" rules
            // rather than the bare-certificate validation rules.
            if (inChain.ChainElements.Count == 1)
            {
                Helpers.Notify($"count==1", true);
                return(ValidateBareCertificate(certificate));
            }

            // Else we have a chain...
            // We need at least 3 certificates:
            //      Alias
            //      DeviceID (issued by vendor)
            //      [zero or any number of intermediate CA]
            //      Device Vendor CA

            int chainLength = inChain.ChainElements.Count;

            if (chainLength < 3)
            {
                Helpers.Notify($"Chain length too short: {chainLength}", true);
                return(false);
            }
            Helpers.Notify($"Device presented a certificate chain of length {inChain.ChainElements.Count}");

            // Put the device-provided chain in a new X509Chain so that we can validate it
            X509Chain chain = new X509Chain(false);

            // todo: this seems like a reasonable starting point for the flags
            chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
            chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
            // Note: this flag seems to be ignored.  In any case, we don't use the
            // machine or user-context cert store: we check the root against DeviceCA
            // provided as a parameter during class instantiation (or a database of authorized
            // CAs in the final service.)
            chain.ChainPolicy.VerificationFlags |= X509VerificationFlags.AllowUnknownCertificateAuthority;

            // Add the intermediate and root-CA certs that came out of the TLS session.
            foreach (var c in inChain.ChainElements)
            {
                chain.ChainPolicy.ExtraStore.Add(c.Certificate);
            }

            // Can we build a chain using the leaf-cert and the rest of the provided certs?
            bool valid = chain.Build(new X509Certificate2(certificate));

            if (!valid)
            {
                Helpers.Notify($"Chain building failed: {chainLength}", true);
                foreach (var err in chain.ChainStatus)
                {
                    Helpers.Notify($"        Error:{err.StatusInformation.ToString()}", true);
                }
                return(false);
            }
            // Get the chain we built.  Regardless of what the client sent, we
            // want a chain of 3 or more certificates (alias, DevID, [...], CA) in our
            // chain.
            var deviceCertChain = chain.ChainElements;

            if (deviceCertChain.Count < 3)
            {
                Helpers.Notify($"Chain length too short: {deviceCertChain.Count}", true);
                return(false);
            }

            // Is the root one of the registered CAs for this DRS instance?
            // Here we just recognize a single root
            var thisDeviceRootCert = deviceCertChain[deviceCertChain.Count - 1];

            if (thisDeviceRootCert.Certificate.Thumbprint != DeviceCA.Thumbprint)
            {
                Helpers.Notify($"Device not certified by a known/authorized CA", true);
                return(false);
            }

            // Next, extract the RIoT extension, and see if it is present/well-formed
            var aliasCert    = deviceCertChain[0].Certificate;
            var deviceIDCert = deviceCertChain[1].Certificate;

            var deviceInfo = ExtensionDecoder.Decode(aliasCert);

            // check we have a good extension
            if (deviceInfo == null)
            {
                Helpers.Notify("Certificate does not have well-formed RIoT extension", true);
                return(false);
            }
            // Check that the DevID claimed in the RIoT extension matches that defined by the
            // cert chain.  This is a *critical* security check if the server just wants to authenticate
            // based on the Alias (leaf) certificate rather than the DeviceID cert and the Alias Cert.
            var encodedDeviceID = deviceIDCert.PublicKey.EncodedKeyValue;

            if (!Helpers.ArraysAreEqual(encodedDeviceID.RawData, deviceInfo.EncodedDeviceIDKey))
            {
                Helpers.Notify("Alias Certificate DeviceID does not match DeviceID certificate", true);
                return(false);
            }
            Helpers.Notify("All RIoT Certificate checks passed");
            return(true);
        }