//static internal int TargetFirmwareVersionNumber; internal static void RunServer(string _serverCA, string serverCert, string serverKey, string deviceCA, string deviceIDPublic) { if (deviceCA != null) { DeviceCA = new X509Certificate2(deviceCA); } DeviceIDPEMFile = deviceIDPublic; // Windows likes PFX files so make one out of the cert and key PEM files string serverPFXFile = "TO_ServerKey.pfx"; string password = "******"; Helpers.MakePFXFile(serverCert, serverKey, serverPFXFile, password); ServerCert = new X509Certificate2(serverPFXFile, password); TcpListener listener = new TcpListener(IPAddress.Any, 5556); Helpers.Notify("SSL Server starting on localhost:5556"); listener.Start(); while (true) { Helpers.Notify("Waiting for a client to connect..."); TcpClient client = listener.AcceptTcpClient(); if (deviceIDPublic != null) { ProcessClient(client, false); return; } else { ProcessClient(client, true); // and wait for the next one } } }
internal static string ReadMessage(SslStream sslStream) { byte[] buf = new byte[1024]; try { int numRead = sslStream.Read(buf, 0, 1); if (numRead != 1) { Helpers.Notify("Got a bad message from the server"); } int pos = 0; int lenX = (int)buf[0]; while (true) { numRead = sslStream.Read(buf, pos, lenX - pos); pos += numRead; if (pos == lenX) { break; } } string serverMessage = Encoding.UTF8.GetString(buf, 0, lenX); return(serverMessage); } catch (Exception e) { Debug.WriteLine("error reading: " + e.ToString()); throw; } }
/// <summary> /// Enroll a new device /// </summary> /// <param name="deviceId"></param> /// <param name="fwVersionNumber"></param> /// <param name="fwId"></param> /// <param name="aliasCertThumbprint"></param> /// <returns></returns> internal Device EnrollDevice(string deviceId, int fwVersionNumber, string fwId, string aliasCertThumbprint) { if (GetDeviceIDs().Contains(deviceId)) { Helpers.Notify($"Device {deviceId} already exists"); return(null); } Device newDevice = null; Device dTemplate = new Device(deviceId); dTemplate.Authentication = new AuthenticationMechanism(); dTemplate.Authentication.X509Thumbprint = new X509Thumbprint(); dTemplate.Authentication.X509Thumbprint.PrimaryThumbprint = aliasCertThumbprint; try { // create the device newDevice = RegMgr.AddDeviceAsync(dTemplate).Result; // and set the FWID property in the twin var props = new TwinCollection(); props["FWID"] = fwId; props["VersionNumber"] = fwVersionNumber.ToString(); var twin = RegMgr.GetTwinAsync(deviceId).Result; twin.Properties.Desired = props; RegMgr.UpdateTwinAsync(deviceId, twin, twin.ETag).Wait(); return(newDevice); } catch (Exception e) { Helpers.Notify($"Failed to add device. Error is {e.ToString()}"); return(null); } }
internal void CertifyExistingFromCsr(int chainLen) { var req = (Pkcs10CertificationRequest)Helpers.ReadPemObject(ToPath(Program.DeviceIDCSR)); //var req = (Pkcs10CertificationRequest)Helpers.ReadPemObject(fileName); if (!req.Verify()) { Helpers.Notify("PKCS10 csr is not properly self-signed"); return; } // todo: should propagate the subject in the CSR into the DeviceID certificate. var info = req.GetCertificationRequestInfo(); AsymmetricKeyParameter deviceIdKey = PublicKeyFactory.CreateKey(info.SubjectPublicKeyInfo); DeviceBundle bundle = new DeviceBundle(); bundle.AliasCert = (X509Certificate)Helpers.ReadPemObject(ToPath(Program.AliasCert)); bundle.DeviceIDPublic = (AsymmetricKeyParameter)deviceIdKey; bundle.AliasKeyPair = (AsymmetricCipherKeyPair)Helpers.ReadPemObject(ToPath(Program.AliasKey)); if (bundle.AliasCert.IssuerDN.ToString() != info.Subject.ToString()) { Helpers.Notify("CSR Subject Name does not match Alias Certificate Issuer Name: Chain will not build.", true); return; } // todo (maybe). Check that the Alias extension claimed DeviceID matches the DeviceID CSR key. MakeCertChain(bundle, chainLen, 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); }
static void Help() { var progName = Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().MainModule.FileName); Helpers.Notify($"Usage:\n{progName} options"); foreach (var p in Parms) { var px = p.NumParms == 0 ? "" : "[string parm]"; Helpers.Notify($" -{p.Flag} {px} {p.Help}"); } }
public static void RunClientX() { Thread.Sleep(3000); // The PFX file is created by this utility, but it's just a re-packaging // of the Alias Key pair and the the certificate generated by RIoT string tempCertFile = "AliasCert.PFX"; string password = ""; Helpers.MakePFXFile(aliasCert, aliasKey, tempCertFile, password); var clientCert = new X509Certificate2(tempCertFile); var certs = new X509Certificate2Collection(new X509Certificate2[] { clientCert }); // connect to server TcpClient client = new TcpClient("127.0.0.1", 5556); //Helpers.NotifyClient("Client connected."); // Create an SSL stream and connect. SslStream sslStream = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null); try { sslStream.AuthenticateAsClient("RIoT Server CA", certs, SslProtocols.Tls, false); } catch (AuthenticationException e) { Console.WriteLine("Exception: {0}", e.Message); if (e.InnerException != null) { Helpers.Notify($"Inner exception: {e.InnerException.Message}", true); } Helpers.Notify("Authentication failed - closing the connection."); client.Close(); return; } byte[] messsage = Encoding.UTF8.GetBytes("GET /ABC/123"); sslStream.Write(messsage); sslStream.Flush(); // Read message from the server. string serverMessage = ReadMessage(sslStream); Helpers.Notify($"Client received: {serverMessage}"); client.Close(); Helpers.Notify("Client closed."); }
static bool ParseParms(string[] parms) { if (parms.Length == 0) { parms = new string[] { "help" }; } int j = 0; while (true) { if (j == parms.Length) { break; } var parm = parms[j++]; var parmL = parm.ToLower(); bool processed = false; foreach (var p in Parms) { if ("-" + p.Flag == parmL) { p.Active = true; if (p.NumParms != 0) { if (j == parms.Length - 1) { Helpers.Notify($"Missing paramter for flag: {p.Flag}", true); return(false); } p.Parameter = parms[j++]; } ActiveParms.Add(p); processed = true; continue; } } if (!processed) { Helpers.Notify($"Unknown command line parameter: {parm}", true); Help(); return(false); } } 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); }
internal int GetTargetVersionNumber() { try { /* * ///////////////////////////////////////////////// * Look at Notes.txt for what's going on' * ///////////////////////////////////////////////// */ var twin = RegMgr.GetTwinAsync(ControlDevice).Result; int targetVersionNumber = (int)twin.Properties.Desired["VersionNumber"]; return(targetVersionNumber); } catch (Exception ex) { Helpers.Notify($"Failed to get control device twin. Error is {ex.ToString()}"); return(0); } }
public static void ValidateEmulatorChain(string alias, string deviceID, string root) { try { X509Certificate2 aliasCert = new X509Certificate2(); X509Certificate2 devIDCert = null; X509Certificate2 rootCert = new X509Certificate2(); rootCert.Import(Helpers.GetBytesFromPEM(root, "CERTIFICATE")); aliasCert = new X509Certificate2(Helpers.GetBytesFromPEM(alias, "CERTIFICATE")); devIDCert = new X509Certificate2(Helpers.GetBytesFromPEM(deviceID, "CERTIFICATE")); rootCert = new X509Certificate2(Helpers.GetBytesFromPEM(root, "CERTIFICATE")); var chain = new X509Chain { ChainPolicy = { RevocationMode = X509RevocationMode.NoCheck, RevocationFlag = X509RevocationFlag.ExcludeRoot, VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority } }; //chain.ChainPolicy.ExtraStore.Add(devIDCert); chain.ChainPolicy.ExtraStore.Add(rootCert); bool chainBuildSucceeded = chain.Build(aliasCert as X509Certificate2 ?? new X509Certificate2(aliasCert.Export(X509ContentType.Cert))); if (!chainBuildSucceeded) { foreach (var err in chain.ChainStatus) { Helpers.Notify($"Error:{err.StatusInformation.ToString()}", true); } } } catch (Exception e) { Helpers.Notify($"ValidateEmulatorChain error {e.ToString()}"); } }
internal bool RemoveDevice(string devId) { // quick try to see if it exists if (!GetDeviceIDs().Contains(devId)) { // nothing to do. return(false); } // else try to delete try { RegMgr.RemoveDeviceAsync(devId); } catch (Exception e) { Helpers.Notify($"Failed to remove device. Error is {e.ToString()}"); return(false); } return(true); }
/// <summary> /// Make an administrative connection to the hub /// </summary> /// <returns></returns> internal bool Connect() { if (!File.Exists(ConnectionStringFile)) { Helpers.Notify($"Missing connection string file {ConnectionStringFile}. This file is omitted from the distribution because it contains passwords.", true); Helpers.Notify($"The file should contain somehting like this:", true); Helpers.Notify($"HostName=pengland.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=0frrKXXXSomeStuffXXXXvWQ=", true); return(false); } var connectionString = File.ReadAllText(ConnectionStringFile); try { RegMgr = RegistryManager.CreateFromConnectionString(connectionString); } catch (Exception e) { Helpers.Notify($"Failed to CreateFromConnectionString: {e.ToString()}"); return(false); } return(true); }
static void ProcessFakeDRSMessage(SslStream s) { lock (HubLock) { try { // clients send us their Id string messageFromClient = ReadMessage(s); X509Certificate2 cert = new X509Certificate2(s.RemoteCertificate); UpdateDemo.HubController.FakeDRSServerEnrollOrRefreshDevice(messageFromClient, cert.Thumbprint); Debug.Write($"Device {messageFromClient} connected"); SendMessage(s, "OK"); Thread.Sleep(30); } catch (Exception e) { Helpers.Notify($"ProcessFakeDRSMessage error {e.ToString()}"); } } }
static RIoTDeviceInfo ParseError(string error) { Helpers.Notify($"Extension parsing error: {error}", true); return(null); }
static void Main(string[] args) { // This invokes testing using WeClient, etc. Not yet working. //HttpsListener.StartListener(IODir + ServerCert, IODir + ServerKey, IODir + ServerCA, IODir+AliasCert, IODir+AliasKey); InitParms(); bool ok = ParseParms(args); if (!ok) { return; } foreach (var action in ActiveParms) { if (action.Flag == "dir") { IODir = action.Parameter; if (!IODir.EndsWith("\\")) { IODir += "\\"; } continue; } if (action.Flag == "gentest") { CertMaker m = new CertMaker(IODir); m.MakeNew(5, false, 0); continue; } if (action.Flag == "bare") { ChainOrBareCert = "B"; continue; } if (action.Flag == "certify") { CertMaker m = new CertMaker(IODir); m.CertifyExisting(5); continue; } if (action.Flag == "certifyj") { CertMaker m = new CertMaker(IODir); m.CertifyExistingForJava(5); continue; } if (action.Flag == "csr") { CertMaker m = new CertMaker(IODir); m.CertifyExistingFromCsr(5); continue; } if (action.Flag == "server") { SslTcpServer.RunServer( ToPath(Program.ServerCA), ToPath(Program.ServerCert), ToPath(Program.ServerKey), ToPath(Program.DeviceCA), ToPath(Program.DeviceIDPublic) ); continue; } if (action.Flag == "testemu") { SslTcpServer.ValidateEmulatorChain(@"AliasCert.pem", @"DeviceIDCrt.pem", @"r00tcrt.pem"); continue; } if (action.Flag == "sc") { Helpers.Notify("Starting TLSClient..."); var psi = new ProcessStartInfo("TlsClient.exe"); psi.Arguments = ChainOrBareCert + " " + IODir; psi.UseShellExecute = true; var proc = Process.Start(psi);; SslTcpServer.RunServer( ToPath(Program.ServerCA), ToPath(Program.ServerCert), ToPath(Program.ServerKey), ToPath(Program.DeviceCA), ToPath(Program.DeviceIDPublic) ); proc.WaitForExit(); continue; } if (action.Flag == "nogen") { MakeCerts = false; continue; } if (action.Flag == "e2e") { if (MakeCerts) { Helpers.Notify("Making a new certificate set"); CertMaker m = new CertMaker(IODir); m.MakeNew(5, false, 0); //m.MakeNew(5, true, 1); } Helpers.Notify("Starting TLSClient..."); var psi = new ProcessStartInfo("TlsClient.exe"); psi.Arguments = ChainOrBareCert + " " + IODir; psi.UseShellExecute = true; var proc = Process.Start(psi);; SslTcpServer.RunServer( ToPath(Program.ServerCA), ToPath(Program.ServerCert), ToPath(Program.ServerKey), ToPath(Program.DeviceCA), ToPath(Program.DeviceIDPublic) ); proc.WaitForExit(); continue; } if (action.Flag == "ossl_server") { Helpers.Notify("OpenSSL s_server parameters for TLS test server (start in directory with certificates and files)"); Helpers.Notify($"openssl s_server -cert {ToPath(ServerCert)} -key {ToPath(ServerKey)} -CAfile {ToPath(DeviceCertChainAndServerCA)} -status_verbose -verify 10 -rev -accept 5556"); continue; } if (action.Flag == "ossl_client") { Helpers.Notify("OpenSSL s_client parameters for TLS test client (start in directory with certificates and files)"); Helpers.Notify($"openssl s_client -connect localhost:5556 -cert {ToPath(AliasCert)} -key {ToPath(AliasKey)} -CAfile {ToPath(DeviceCertChainAndServerCA)}"); continue; } if (action.Flag == "tls_client") { Helpers.Notify("Starting TLSClient..."); var psi = new ProcessStartInfo("TlsClient.exe"); psi.Arguments = ChainOrBareCert + " " + IODir; psi.CreateNoWindow = true; psi.UseShellExecute = false; psi.RedirectStandardError = true; var proc = Process.Start(psi);; string op = proc.StandardError.ReadToEnd(); proc.WaitForExit(); Helpers.Notify(op); continue; } if (action.Flag == "demo") { var demo = new UpdateDemo(); demo.FakeDRSTest(); } } if (System.Diagnostics.Debugger.IsAttached) { Thread.Sleep(3000); } return; }
internal bool FakeDRSServerHandshake(string devId) { string tempCertFile = "AliasCert.PFX"; string password = ""; Helpers.MakePFXFile(Program.ToPath(Program.AliasCert), Program.ToPath(Program.AliasKey), tempCertFile, password); var clientCert = new X509Certificate2(tempCertFile); var certs = new X509Certificate2Collection(new X509Certificate2[] { clientCert }); // connect to server TcpClient client = new TcpClient("127.0.0.1", 5556); // Create an SSL stream and connect. SslStream sslStream = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null); try { sslStream.AuthenticateAsClient("RIoT Server CA", certs, SslProtocols.Tls, false); } catch (AuthenticationException e) { Console.WriteLine("Exception: {0}", e.Message); if (e.InnerException != null) { Helpers.Notify($"Inner exception: {e.InnerException.Message}", true); } Helpers.Notify("Authentication failed - closing the connection."); client.Close(); return(false); } sslStream.ReadTimeout = 10000; sslStream.WriteTimeout = 10000; SslTcpServer.SendMessage(sslStream, devId); string messageFromServer = SslTcpServer.ReadMessage(sslStream); /* * byte[] message = Encoding.UTF8.GetBytes(devId); * byte[] len = new byte[] { (byte) message.Length }; * sslStream.Write(len, 0, 1); * sslStream.Write(message,0, message.Length); * sslStream.Flush(); * byte[] buf = new byte[1024]; * int numRead = sslStream.Read(buf, 0, 1); * if(numRead!=1) * { * Helpers.Notify("TLSClient got a bad message from the server"); * } * int pos = 0; * int lenX = (int) buf[0]; * while (true) * { * numRead = sslStream.Read(buf, pos, lenX - pos); * pos += numRead; * if (pos == lenX) break; * } * string serverMessage = Encoding.UTF8.GetString(buf, 0, lenX); */ Helpers.Notify($"Client received: {messageFromServer}"); Thread.Sleep(30); client.Close(); Helpers.Notify("Client closed."); return(true); }
/// <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); }
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(); } }
static internal RIoTDeviceInfo Decode(X509Certificate2 aliasCert) { AsnEncodedData altNames = null; foreach (var ext in aliasCert.Extensions) { if (ext.Oid.Value != RIoTOid) { continue; } altNames = new AsnEncodedData(ext.Oid, ext.RawData); } // an AltName is mandatory if (altNames == null) { Helpers.Notify("Certificate does not have an altName field", true); return(null); } // parse the extension: this is a collection of nested thus - /* * DER Sequence * ObjectIdentifier(1.2.3.4.5.6) <- RIoT Composite ID OID * DER Sequence * Integer(1) <- Version number * DER Sequence <- DeviceID public key * DER Sequence (same encoding as in DeviceID cert) * ObjectIdentifier(1.2.840.10045.2.1) EC pubkey * ObjectIdentifier(1.2.840.10045.3.1.7) prime256 * DER Bit String[65, 0] key value * DER Sequence <- Encoded FWID * ObjectIdentifier(2.16.840.1.101.3.4.2.1) sha256 * DER Octet String[32] FWID hash value * * * */ try { DerSequence seq = (DerSequence)DerSequence.FromByteArray(altNames.RawData); //DerTaggedObject obj = (DerTaggedObject)seq[0]; //DerSequence obj2 = (DerSequence)obj.GetObject(); //var oid = (DerObjectIdentifier)obj2[0]; //if (oid.Id != RIoTOid) return ParseError("Incorrect RIoT OID"); var versionNumber = (DerInteger)seq[0]; if (versionNumber.PositiveValue.IntValue != 1) { return(ParseError("Wrong version number")); } DerSequence obj4 = (DerSequence)seq[1]; DerSequence obj5 = (DerSequence)obj4[0]; var keyAlg1 = (DerObjectIdentifier)obj5[0]; var keyAlg2 = (DerObjectIdentifier)obj5[1]; if (keyAlg1.Id != ecPubKeyOID) { return(ParseError("Bad ECPubKey OID")); } if (keyAlg2.Id != prime256v1Oid) { return(ParseError("Bad curve OID")); } var key = (DerBitString)obj4[1]; var obj4b = (DerSequence)seq[2]; var hashAlg = (DerObjectIdentifier)obj4b[0]; if (hashAlg.Id != sha256Oid) { return(ParseError("Bad fwid hash OID")); } var hash = (DerOctetString)obj4b[1]; RIoTDeviceInfo deviceInfo = new RIoTDeviceInfo() { FirmwareID = hash.GetOctets(), EncodedDeviceIDKey = key.GetBytes(), Cert = aliasCert }; return(deviceInfo); } catch (Exception e) { Debug.WriteLine(e.ToString()); return(null); } }