Ejemplo n.º 1
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);
        }