Commit 9bf97dc8 authored by Jason Williams's avatar Jason Williams
Browse files

Updates the library for .NET to not accept untrusted roots.

There are two ways to override this--the connection can set a cert which could be provided by the app via the command line or the app can subscribe to an Untrusted cert event and have its own logic for determining if it trusts the certificate. The XboxWdpDriver app shows an example of both of these methods.

Currently the other .NET sample app just always accepts the cert. There will be a follow up change to fix this app to prompt the user to make a trust decision, as well as UWP needing a follow up change still to do the same thing.
parent da9630bc
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -107,6 +107,8 @@ namespace SampleWdpClient.UniversalWindows
                            }
                        };

                        // TODO: Support proper certificate validation instead of blindly trusting this cert (Issue #154/#145).
                        await portal.GetDeviceCertificate();
                        await portal.Connect();

                        this.MarshalUpdateCommandOutput(sb.ToString());
+22 −2
Original line number Diff line number Diff line
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
@@ -76,6 +78,9 @@ namespace SampleWdpClient
                    this.username.Text,
                    this.password.Password));

            // Add additional handling for untrusted certs.
            portal.UnvalidatedCert += DoCertValidation;

            StringBuilder sb = new StringBuilder();
            Task connectTask = new Task(
                async () =>
@@ -117,7 +122,8 @@ namespace SampleWdpClient
                    this.MarshalEnableConnectionControls(true);
                });

            connectTask.Start();        }
            connectTask.Start();
        }

        /// <summary>
        /// Enables or disables the Connect button based on the current state of the
@@ -452,5 +458,19 @@ namespace SampleWdpClient
        {
            this.EnableConnectButton();
        }

        /// <summary>
        /// Validate the server certificate
        /// </summary>
        /// <param name="sender">The sender object</param>
        /// <param name="certificate">The server's certificate</param>
        /// <param name="chain">The cert chain</param>
        /// <param name="sslPolicyErrors">Policy Errors</param>
        /// <returns>whether the cert passes validation</returns>
        private bool DoCertValidation(DevicePortal sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            // TODO: Allow the user to accept this certificate (trust once, trust always). (Issue #154)
            return true;
        }
    }
}
+70 −0
Original line number Diff line number Diff line
//----------------------------------------------------------------------------------------------
// <copyright file="CredManager.cs" company="Microsoft Corporation">
//     Licensed under the MIT License. See LICENSE.TXT in the project root license information.
// </copyright>
//----------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using Windows.Security.Credentials;

namespace XboxWdpDriver
{
    /// <summary>
    /// Manages storing and retrieving WDP creds on Windows 8 and above.
    /// </summary>
    public static class CredManager
    {
        /// <summary>
        /// Retrieves any stored credentials for this target.
        /// </summary>
        /// <param name="target">The target the credentials are stored for.</param>
        /// <param name="userName">The stored username.</param>
        /// <param name="password">The stored password.</param>
        public static void RetrieveStoredCreds(string target, ref string userName, ref string password)
        {
            try
            {
                PasswordVault vault = new PasswordVault();
                // Set the first stored cred as our network creds.
                IReadOnlyList<PasswordCredential> creds = vault.FindAllByResource(target);
                if (creds != null && creds.Count > 0)
                {
                    creds[0].RetrievePassword();
                    userName = creds[0].UserName;
                    password = creds[0].Password;
                }
            }
            catch (Exception)
            {
                // Do nothing. No credentials were stored. If they are needed, REST calls will fail with Unauthorized.
            }
        }

        /// <summary>
        /// Updates the stored credentials for the target.
        /// </summary>
        /// <param name="target">The target for which to update credentials.</param>
        /// <param name="userName">The new username.</param>
        /// <param name="password">The new password.</param>
        public static void UpdateStoredCreds(string target, string userName, string password)
        {
            PasswordVault vault = new PasswordVault();

            try
            {
                // Remove any existing stored creds for this address and add these ones.
                foreach (var cred in vault.FindAllByResource(target))
                {
                    vault.Remove(cred);
                }
            }
            catch (Exception)
            {
                // Do nothing. This is expected if no credentials have been previously stored
            }

            vault.Add(new PasswordCredential(target, userName, password));
        }
    }
}
+0 −216
Original line number Diff line number Diff line
//----------------------------------------------------------------------------------------------
// <copyright file="DevicePortalConnection.cs" company="Microsoft Corporation">
//     Licensed under the MIT License. See LICENSE.TXT in the project root license information.
// </copyright>
//----------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using Microsoft.Tools.WindowsDevicePortal;
using Windows.Security.Credentials;
using static Microsoft.Tools.WindowsDevicePortal.DevicePortal;

namespace XboxWdpDriver
{
    /// <summary>
    /// IDevicePortalConnection implementation for Xbox test project
    /// </summary>
    public class DevicePortalConnection : IDevicePortalConnection
    {
        /// <summary>
        /// Device Certificate
        /// </summary>
        private X509Certificate2 deviceCertificate = null;

        /// <summary>
        /// Initializes a new instance of the <see cref="DevicePortalConnection"/> class.
        /// </summary>
        /// <param name="address">The ip address or hostname of the device we are connecting to.</param>
        /// <param name="userName">The WDP username.</param>
        /// <param name="password">The WDP password.</param>
        public DevicePortalConnection(
            string address,
            string userName,
            string password)
        {
            this.Connection = new Uri(string.Format("https://{0}:11443", address));
            this.Credentials = new NetworkCredential(userName, password);

            PasswordVault vault = new PasswordVault();

            try
            {
                // Remove any existing stored creds for this address and add these ones.
                foreach (var cred in vault.FindAllByResource(address))
                {
                    vault.Remove(cred);
                }
            }
            catch (Exception)
            {
                // Do nothing. This is expected if no credentials have been previously stored
            }

            vault.Add(new PasswordCredential(address, userName, password));
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DevicePortalConnection"/> class.
        /// This version of the contructor can be used if WDP credentials are not provided,
        /// and should be used if they were previously persisted or are not needed.
        /// </summary>
        /// <param name="address">The ip address or hostname of the device we are connecting to.</param>
        public DevicePortalConnection(
            string address)
        {
            this.Connection = new Uri(string.Format("https://{0}:11443", address));

            try
            {
                PasswordVault vault = new PasswordVault();
                // Set the first stored cred as our network creds.
                IReadOnlyList<PasswordCredential> creds = vault.FindAllByResource(address);
                if (creds != null && creds.Count > 0)
                {
                    creds[0].RetrievePassword();
                    this.Credentials = new NetworkCredential(creds[0].UserName, creds[0].Password);
                }
            }
            catch (Exception)
            {
                // Do nothing. No credentials were stored. If they are needed, REST calls will fail with Unauthorized.
            }
        }

        /// <summary>
        /// Gets Connection property
        /// </summary>
        public Uri Connection
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets Web Socket Connection property
        /// </summary>
        public Uri WebSocketConnection
        {
            get
            {
                if (this.Connection == null)
                {
                    return null;
                }

                string absoluteUri = this.Connection.AbsoluteUri;

                if (absoluteUri.StartsWith("https", StringComparison.OrdinalIgnoreCase))
                {
                    return new Uri(Regex.Replace(absoluteUri, "https", "wss", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant));
                }
                else
                {
                    return new Uri(Regex.Replace(absoluteUri, "http", "ws", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant));
                }
            }
        }

        /// <summary>
        /// Gets Credentials property
        /// </summary>
        public NetworkCredential Credentials
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets device family
        /// </summary>
        public string Family
        { 
            get; 
            set;
        }

        /// <summary>
        /// Gets or sets the device name
        /// </summary>
        public string Name
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets device OS Info
        /// </summary>
        public OperatingSystemInformation OsInfo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets a value indicating whether or not we are allowing cert override which may specify a proxy instead of the web management service.
        /// </summary>
        public bool AllowCertOverride
        {
            get;
            set;
        }

        /// <summary>
        /// Returns certificate data
        /// </summary>
        /// <returns>certificate data</returns>
        public byte[] GetDeviceCertificateData()
        {
            return this.deviceCertificate.GetRawCertData();
        }

        /// <summary>
        /// Validates and sets the device certificate.
        /// </summary>
        /// <param name="certificate">The device's root certificate.</param>
        public void SetDeviceCertificate(X509Certificate2 certificate)
        {
            if (!this.AllowCertOverride)
            {
                if (!certificate.IssuerName.Name.Contains(DevicePortalCertificateIssuer))
                {
                    throw new DevicePortalException(
                        (HttpStatusCode)0,
                        "Invalid certificate issuer",
                        null,
                        "Failed to download device certificate");
                }
            }

            this.deviceCertificate = certificate;
        }

        /// <summary>
        /// Xbox will never update the connection.
        /// </summary>
        /// <param name="requiresHttps">https required</param>
        public void UpdateConnection(bool requiresHttps)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        ///  Xbox will never update the connection.
        /// </summary>
        /// <param name="ipConfig">IP info</param>
        /// <param name="requiresHttps">https required</param>
        public void UpdateConnection(IpConfiguration ipConfig, bool requiresHttps)
        {
            throw new NotImplementedException();
        }
    }
}
+89 −23
Original line number Diff line number Diff line
@@ -6,12 +6,12 @@

using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Tools.WindowsDevicePortal;
using static Microsoft.Tools.WindowsDevicePortal.DevicePortal;

namespace XboxWdpDriver
{
@@ -132,6 +132,12 @@ namespace XboxWdpDriver
            XblUserOperation,
        }

        /// <summary>
        /// Gets the thumbprint that we use to manually accept server certificates even
        /// if they failed initial validation.
        /// </summary>
        public string AcceptedThumbprint { get; private set; }

        /// <summary>
        /// Main entry point
        /// </summary>
@@ -184,36 +190,39 @@ namespace XboxWdpDriver
                    }
                }

                IDevicePortalConnection connection = null;
                string finalConnectionAddress = string.Format("https://{0}:11443", targetConsole);
                string userName = parameters.GetParameterValue(ParameterHelper.WdpUser);
                string password = parameters.GetParameterValue(ParameterHelper.WdpPassword);

                try
                {
                    if (!parameters.HasParameter(ParameterHelper.WdpUser) || !parameters.HasParameter(ParameterHelper.WdpPassword))
                if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
                {
                        connection = new DevicePortalConnection(targetConsole);
                    }
                    else
                    try
                    {
                        connection = new DevicePortalConnection(targetConsole, parameters.GetParameterValue(ParameterHelper.WdpUser), parameters.GetParameterValue(ParameterHelper.WdpPassword));
                    }
                        // No creds were provided on the command line.
                        CredManager.RetrieveStoredCreds(targetConsole, ref userName, ref password);
                    }
                    catch (TypeLoadException)
                    {
                        // Windows 7 doesn't support credential storage so we'll get a TypeLoadException
                    if (!parameters.HasParameter(ParameterHelper.WdpUser) || !parameters.HasParameter(ParameterHelper.WdpPassword))
                    {
                        throw new Exception("Credential storage is not supported on your PC. It requires Windows 8+ to run. Please provide the user and password parameters.");
                    }
                }
                else
                {
                        string connectionUri = string.Format("https://{0}:11443", targetConsole);
                        connection = new DefaultDevicePortalConnection(connectionUri, parameters.GetParameterValue(ParameterHelper.WdpUser), parameters.GetParameterValue(ParameterHelper.WdpPassword));
                    try
                    {
                        // Creds were provided on the command line.
                        CredManager.UpdateStoredCreds(targetConsole, userName, password);
                    }
                    catch (TypeLoadException)
                    {
                        // Do nothing. We can't store these on Win7
                    }
                }

                DevicePortal portal = new DevicePortal(connection);
                IDevicePortalConnection connection = new DefaultDevicePortalConnection(finalConnectionAddress, userName, password);

                byte[] rawCert = null;
                DevicePortal portal = new DevicePortal(connection);

                if (parameters.HasParameter(ParameterHelper.Cert))
                {
@@ -221,8 +230,7 @@ namespace XboxWdpDriver

                    try
                    {
                        rawCert = File.ReadAllBytes(certFile);
                        connection.AllowCertOverride = true;
                        connection.SetDeviceCertificate(new System.Security.Cryptography.X509Certificates.X509Certificate2(certFile));
                    }
                    catch (Exception e)
                    {
@@ -230,7 +238,26 @@ namespace XboxWdpDriver
                    }
                }

                Task connectTask = portal.Connect(updateConnection: false, rawManualCertificate: rawCert);
                // Add additional handling for untrusted certs.
                portal.UnvalidatedCert += app.DoCertValidation;

                // If a thumbprint is provided, use it for this connection. Otherwise check the registry.
                if (parameters.HasParameter("thumbprint"))
                {
                    app.AcceptedThumbprint = parameters.GetParameterValue("thumbprint");
                }
                else
                {
                    object regValue;
                    regValue = Microsoft.Win32.Registry.GetValue(DefaultConsoleRegkey, targetConsole, null);

                    if (regValue is string)
                    {
                        app.AcceptedThumbprint = regValue as string;
                    }
                }

                Task connectTask = portal.Connect(updateConnection: false);
                connectTask.Wait();

                if (portal.ConnectionHttpStatusCode != HttpStatusCode.OK)
@@ -246,13 +273,13 @@ namespace XboxWdpDriver
                            Console.WriteLine("The WDP connection was rejected due to bad credentials.\n\nPlease check the /user:<username> and /pwd:<pwd> parameters.");
                        }
                    }
                    else if (portal.ConnectionHttpStatusCode != 0)
                    else if (!string.IsNullOrEmpty(portal.ConnectionFailedDescription))
                    {
                        Console.WriteLine(string.Format("Failed to connect to WDP with HTTP Status code: {0}", portal.ConnectionHttpStatusCode));
                        Console.WriteLine(string.Format("Failed to connect to WDP (HTTP {0}) : {1}", (int)portal.ConnectionHttpStatusCode, portal.ConnectionFailedDescription));
                    }
                    else
                    {
                        Console.WriteLine("Failed to connect to WDP for unknown reason.\n\nEnsure your address is the system IP or hostname ({0}) and the machine has WDP configured.", targetConsole);
                        Console.WriteLine("Failed to connect to WDP for unknown reason.");
                    }
                }
                else
@@ -282,6 +309,13 @@ namespace XboxWdpDriver
                                Console.WriteLine("Connected to Default console: {0}", targetConsole);
                            }

                            if (parameters.HasParameter("thumbprint"))
                            {
                                string thumbprint = parameters.GetParameterValue("thumbprint");
                                Microsoft.Win32.Registry.SetValue(DefaultConsoleRegkey, targetConsole, thumbprint);
                                Console.WriteLine("Thumbprint {0} saved for console with address {1}.", thumbprint, targetConsole);
                            }

                            break;

                        case OperationType.FiddlerOperation:
@@ -420,5 +454,37 @@ namespace XboxWdpDriver

            throw new Exception("Unknown Operation Type. " + AvailableOperationsText);
        }

        /// <summary>
        /// Validate the server certificate
        /// </summary>
        /// <param name="sender">The sender object</param>
        /// <param name="certificate">The server's certificate</param>
        /// <param name="chain">The cert chain</param>
        /// <param name="sslPolicyErrors">Policy Errors</param>
        /// <returns>whether the cert passes validation</returns>
        private bool DoCertValidation(DevicePortal sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            X509Certificate2 cert = new X509Certificate2(certificate);

            if (!string.IsNullOrEmpty(this.AcceptedThumbprint))
            {
                if (cert.Thumbprint.Equals(this.AcceptedThumbprint, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }

            Console.WriteLine("The certificate provided by the device failed validation.");
            Console.WriteLine("Issuer:\t\t{0}", cert.Issuer);
            Console.WriteLine("Thumbprint:\t{0}", cert.Thumbprint);
            Console.WriteLine();

            Console.WriteLine("If you trust this endpoint, you can manually specify this certificate should be accepted by adding the following to any call:\n\t/thumbprint:{0}", cert.Thumbprint);
            Console.WriteLine("Or you can permanently add this as a trusted certificate for this device by calling the following:\n\tXboxWdpDriver.exe /op:connect /thumbprint:{0}", cert.Thumbprint);
            Console.WriteLine();

            return false;
        }
    }
}
Loading