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++}"); } }
/// <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); }
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); }
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(); } }
/// <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); }