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.
This commit is contained in:
Jason Williams 2016-08-29 15:34:27 -07:00
Родитель da9630bcad
Коммит 9bf97dc860
21 изменённых файлов: 344 добавлений и 503 удалений

Просмотреть файл

@ -106,7 +106,9 @@ namespace SampleWdpClient.UniversalWindows
sb.AppendLine(connectArgs.Message);
}
};
// TODO: Support proper certificate validation instead of blindly trusting this cert (Issue #154/#145).
await portal.GetDeviceCertificate();
await portal.Connect();
this.MarshalUpdateCommandOutput(sb.ToString());

Просмотреть файл

@ -1,6 +1,8 @@
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
@ -330,7 +336,7 @@ namespace SampleWdpClient
}
/// <summary>
/// Executes the update of the text displayed in the command output UI element ont he UI thread.
/// Executes the update of the text displayed in the command output UI element on the UI thread.
/// </summary>
/// <param name="output">The text to display in the command output UI element.</param>
private void MarshalUpdateCommandOutput(string output)
@ -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;
}
}
}

Просмотреть файл

@ -0,0 +1,70 @@
//----------------------------------------------------------------------------------------------
// <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));
}
}
}

Просмотреть файл

@ -1,216 +0,0 @@
//----------------------------------------------------------------------------------------------
// <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();
}
}
}

Просмотреть файл

@ -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 (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
{
if (!parameters.HasParameter(ParameterHelper.WdpUser) || !parameters.HasParameter(ParameterHelper.WdpPassword))
try
{
connection = new DevicePortalConnection(targetConsole);
// No creds were provided on the command line.
CredManager.RetrieveStoredCreds(targetConsole, ref userName, ref password);
}
else
{
connection = new DevicePortalConnection(targetConsole, parameters.GetParameterValue(ParameterHelper.WdpUser), parameters.GetParameterValue(ParameterHelper.WdpPassword));
}
}
catch (TypeLoadException)
{
// Windows 7 doesn't support credential storage so we'll get a TypeLoadException
if (!parameters.HasParameter(ParameterHelper.WdpUser) || !parameters.HasParameter(ParameterHelper.WdpPassword))
catch (TypeLoadException)
{
// Windows 7 doesn't support credential storage so we'll get a TypeLoadException
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
}
else
{
try
{
string connectionUri = string.Format("https://{0}:11443", targetConsole);
connection = new DefaultDevicePortalConnection(connectionUri, parameters.GetParameterValue(ParameterHelper.WdpUser), parameters.GetParameterValue(ParameterHelper.WdpPassword));
// 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;
}
}
}

Просмотреть файл

@ -49,7 +49,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="DevicePortalConnection.cs" />
<Compile Include="CredManager.cs" />
<Compile Include="NetworkShare.cs" />
<Compile Include="Operations\AppOperation.cs" />
<Compile Include="Operations\SystemPerfOperation.cs" />

Просмотреть файл

@ -100,9 +100,9 @@ namespace MockDataGenerator
if (portal.ConnectionHttpStatusCode != HttpStatusCode.OK)
{
if (portal.ConnectionHttpStatusCode != 0)
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
{

Просмотреть файл

@ -6,6 +6,7 @@
using System;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using Microsoft.Tools.WindowsDevicePortal;
@ -18,11 +19,6 @@ namespace Microsoft.Tools.WindowsDevicePortal.Tests
/// </summary>
public class MockDevicePortalConnection : IDevicePortalConnection
{
/// <summary>
/// Device Certificate
/// </summary>
private X509Certificate2 deviceCertificate = null;
/// <summary>
/// Initializes a new instance of the <see cref="MockDevicePortalConnection"/> class.
/// </summary>
@ -97,22 +93,13 @@ namespace Microsoft.Tools.WindowsDevicePortal.Tests
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()
public X509Certificate2 GetDeviceCertificate()
{
return this.deviceCertificate.GetRawCertData();
throw new NotImplementedException();
}
/// <summary>
@ -121,35 +108,26 @@ namespace Microsoft.Tools.WindowsDevicePortal.Tests
/// <param name="certificate">The device's root certificate.</param>
public void SetDeviceCertificate(X509Certificate2 certificate)
{
if (!certificate.IssuerName.Name.Contains(DevicePortalCertificateIssuer))
{
throw new DevicePortalException(
(HttpStatusCode)0,
"Invalid certificate issuer",
null,
"Failed to download device certificate");
}
this.deviceCertificate = certificate;
throw new NotImplementedException();
}
/// <summary>
/// Xbox will never update the connection.
/// The Mock will never update the connection.
/// </summary>
/// <param name="requiresHttps">https required</param>
public void UpdateConnection(bool requiresHttps)
{
return;
throw new NotImplementedException();
}
/// <summary>
/// Xbox will never update the connection.
/// The Mock 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)
{
return;
throw new NotImplementedException();
}
}
}

Просмотреть файл

@ -280,7 +280,7 @@ namespace Microsoft.Tools.WindowsDevicePortal
/// <summary>
/// Presents a user readable representation of a list of AppPackages
/// </summary>
/// <returns></returns>
/// <returns>User readable list of AppPackages.</returns>
public override string ToString()
{
string output = "Packages:\n";
@ -288,6 +288,7 @@ namespace Microsoft.Tools.WindowsDevicePortal
{
output += package;
}
return output;
}
}

Просмотреть файл

@ -244,7 +244,8 @@ namespace Microsoft.Tools.WindowsDevicePortal
}
}
catch
{ }
{
}
return platform;
}

Просмотреть файл

@ -122,6 +122,15 @@ namespace Microsoft.Tools.WindowsDevicePortal
private set;
}
/// <summary>
/// Gets a description of why the connection failed.
/// </summary>
public string ConnectionFailedDescription
{
get;
private set;
}
/// <summary>
/// Gets the device operating system family.
/// </summary>
@ -172,15 +181,13 @@ namespace Microsoft.Tools.WindowsDevicePortal
/// <param name="ssid">Optional network SSID.</param>
/// <param name="ssidKey">Optional network key.</param>
/// <param name="updateConnection">Indicates whether we should update this connection's IP address after connecting.</param>
/// <param name="rawManualCertificate">Allows specifying the raw certificate manually. This allows loading it from a local file or loading an override for a web proxy.</param>
/// <remarks>Connect sends ConnectionStatus events to indicate the current progress in the connection process.
/// Some applications may opt to not register for the ConnectionStatus event and await on Connect.</remarks>
/// <returns>Task for tracking the connect.</returns>
public async Task Connect(
string ssid = null,
string ssidKey = null,
bool updateConnection = true,
byte[] rawManualCertificate = null)
bool updateConnection = true)
{
#if WINDOWS_UWP
this.ConnectionHttpStatusCode = HttpStatusCode.Ok;
@ -191,43 +198,6 @@ namespace Microsoft.Tools.WindowsDevicePortal
try
{
// Get the device certificate
bool certificateAcquired = false;
if (rawManualCertificate == null)
{
try
{
connectionPhaseDescription = "Acquiring device certificate";
this.SendConnectionStatus(
DeviceConnectionStatus.Connecting,
DeviceConnectionPhase.AcquiringCertificate,
connectionPhaseDescription);
this.deviceConnection.SetDeviceCertificate(await this.GetDeviceCertificate());
certificateAcquired = true;
}
catch
{
// This device does not support the root certificate endpoint.
this.SendConnectionStatus(
DeviceConnectionStatus.Connecting,
DeviceConnectionPhase.AcquiringCertificate,
"No device certificate available");
}
}
else
{
#if WINDOWS_UWP
this.deviceConnection.SetDeviceCertificate(new Certificate(rawManualCertificate.AsBuffer()));
#else
this.deviceConnection.SetDeviceCertificate(new X509Certificate2(rawManualCertificate));
#endif // WINDOWS_UWP
certificateAcquired = true;
}
// Get the device family and operating system information.
connectionPhaseDescription = "Requesting operating system information";
this.SendConnectionStatus(
@ -237,8 +207,8 @@ namespace Microsoft.Tools.WindowsDevicePortal
this.deviceConnection.Family = await this.GetDeviceFamily();
this.deviceConnection.OsInfo = await this.GetOperatingSystemInformation();
// Default to using HTTPS if we were successful in acquiring the device's root certificate.
bool requiresHttps = certificateAcquired;
// Default to using HTTPS.
bool requiresHttps = true;
// HoloLens is the only device that supports the GetIsHttpsRequired method.
if (this.deviceConnection.OsInfo.Platform == DevicePortalPlatforms.HoloLens)
@ -289,16 +259,26 @@ namespace Microsoft.Tools.WindowsDevicePortal
if (dpe != null)
{
this.ConnectionHttpStatusCode = dpe.StatusCode;
this.ConnectionFailedDescription = dpe.Message;
}
else
{
this.ConnectionHttpStatusCode = HttpStatusCode.Conflict;
// Get to the innermost exception for our return message.
Exception innermostException = e;
while (innermostException.InnerException != null)
{
innermostException = innermostException.InnerException;
}
this.ConnectionFailedDescription = innermostException.Message;
}
this.SendConnectionStatus(
DeviceConnectionStatus.Failed,
DeviceConnectionPhase.Idle,
string.Format("Device connection failed: {0}", connectionPhaseDescription));
string.Format("Device connection failed: {0}, {1}", connectionPhaseDescription, this.ConnectionFailedDescription));
}
}

Просмотреть файл

@ -6,6 +6,10 @@
using System;
using System.Net;
#if !WINDOWS_UWP
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
#endif
using static Microsoft.Tools.WindowsDevicePortal.DevicePortal;
namespace Microsoft.Tools.WindowsDevicePortal
@ -45,29 +49,20 @@ namespace Microsoft.Tools.WindowsDevicePortal
/// </summary>
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>
bool AllowCertOverride { get; set; }
#if !WINDOWS_UWP
/// <summary>
/// Get the raw data of the device's root certificate.
/// Gets the provided device certificate.
/// </summary>
/// <returns>Byte array containing the certificate data.</returns>
byte[] GetDeviceCertificateData();
#endif
/// <returns>Stored device certificate.</returns>
X509Certificate2 GetDeviceCertificate();
/// <summary>
/// Validates and sets the device certificate.
/// Stores a manually provided device certificate.
/// </summary>
/// <param name="certificate">The device's root certificate.</param>
/// <remarks>How this data is used and/or stored is implementation specific.</remarks>
#if WINDOWS_UWP
void SetDeviceCertificate(Windows.Security.Cryptography.Certificates.Certificate certificate);
#else
void SetDeviceCertificate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate);
void SetDeviceCertificate(X509Certificate2 certificate);
#endif
/// <summary>
/// Updates the http security requirements for device communication.
/// </summary>

Просмотреть файл

@ -58,19 +58,25 @@ namespace Microsoft.Tools.WindowsDevicePortal
/// </summary>
/// <param name="str">The string to encode.</param>
/// <returns>Base64 encoded version of the string data.</returns>
internal static string Hex64Encode(string str)
public static string Hex64Encode(string str)
{
return Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(str));
}
public static bool IsHoloLens(DevicePortalPlatforms platform,
string deviceFamily)
/// <summary>
/// Checks if this device is a hololens.
/// </summary>
/// <param name="platform">The platform.</param>
/// <param name="deviceFamily">The device family.</param>
/// <returns>Whether this is a hololens.</returns>
public static bool IsHoloLens(
DevicePortalPlatforms platform,
string deviceFamily)
{
bool isHoloLens = false;
if ((platform == DevicePortalPlatforms.HoloLens) ||
((platform == DevicePortalPlatforms.VirtualMachine) &&
(deviceFamily == "Windows.Holographic")) )
((platform == DevicePortalPlatforms.VirtualMachine) && (deviceFamily == "Windows.Holographic")))
{
isHoloLens = true;
}
@ -97,6 +103,5 @@ namespace Microsoft.Tools.WindowsDevicePortal
endpoint = endpoint.Replace('=', '_');
endpoint = endpoint.Replace('&', '_');
}
}
}

Просмотреть файл

@ -25,7 +25,7 @@ namespace Microsoft.Tools.WindowsDevicePortal
/// </summary>
/// <returns>The device certificate.</returns>
#pragma warning disable 1998
private async Task<Certificate> GetDeviceCertificate()
public async Task<Certificate> GetDeviceCertificate()
{
Certificate certificate = null;
bool useHttps = true;

Просмотреть файл

@ -105,36 +105,6 @@ namespace Microsoft.Tools.WindowsDevicePortal
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>
/// Sets the device's root certificate in the certificate store.
/// </summary>
/// <param name="certificate">The device's root certificate.</param>
public void SetDeviceCertificate(Certificate certificate)
{
if (!this.AllowCertOverride)
{
// Verify that the certificate is one we recognize.
if (!certificate.Issuer.Contains(DevicePortalCertificateIssuer))
{
certificate = null;
throw new DevicePortalException(
(Windows.Web.Http.HttpStatusCode)0,
"Invalid certificate issuer",
null,
"Failed to set the device certificate");
}
}
}
/// <summary>
/// Updates the device's connection Uri.
/// </summary>

Просмотреть файл

@ -18,126 +18,96 @@ namespace Microsoft.Tools.WindowsDevicePortal
/// </content>
public partial class DevicePortal
{
/// <summary>
/// Gets or sets handler for untrusted certificate handling
/// </summary>
public event UnvalidatedCertEventHandler UnvalidatedCert;
/// <summary>
/// Gets the root certificate from the device.
/// </summary>
/// <returns>The device certificate.</returns>
private async Task<X509Certificate2> GetDeviceCertificate()
public async Task<X509Certificate2> GetDeviceCertificate()
{
X509Certificate2 certificate = null;
bool useHttps = true;
// try https then http
while (true)
Uri uri = Utilities.BuildEndpoint(this.deviceConnection.Connection, RootCertificateEndpoint);
using (Stream stream = await this.Get(uri))
{
Uri uri = null;
if (useHttps)
using (BinaryReader reader = new BinaryReader(stream))
{
uri = Utilities.BuildEndpoint(this.deviceConnection.Connection, RootCertificateEndpoint);
}
else
{
Uri baseUri = new Uri(string.Format("http://{0}", this.deviceConnection.Connection.Authority));
uri = Utilities.BuildEndpoint(baseUri, RootCertificateEndpoint);
}
try
{
using (Stream stream = await this.Get(uri, false))
{
using (BinaryReader reader = new BinaryReader(stream))
{
byte[] certData = reader.ReadBytes((int)stream.Length);
// Validate the issuer.
certificate = new X509Certificate2(certData);
if (!certificate.IssuerName.Name.Contains(DevicePortalCertificateIssuer))
{
certificate = null;
throw new DevicePortalException(
(HttpStatusCode)0,
"Invalid certificate issuer",
uri,
"Failed to get the device certificate");
}
}
}
return certificate;
}
catch (Exception e)
{
if (useHttps)
{
useHttps = false;
}
else
{
throw e;
}
byte[] certData = reader.ReadBytes((int)stream.Length);
certificate = new X509Certificate2(certData);
}
}
return certificate;
}
/// <summary>
/// Validate the server certificate
/// </summary>
/// <param name="sender">The sender object</param>
/// <param name="cert">The server's certificate</param>
/// <param name="certificate">The server's certificate</param>
/// <param name="chain">The cert chain</param>
/// <param name="policyErrors">Policy Errors</param>
/// <param name="sslPolicyErrors">Policy Errors</param>
/// <returns>whether the cert passes validation</returns>
private bool ServerCertificateValidation(
object sender,
X509Certificate cert,
X509Chain chain,
SslPolicyErrors policyErrors)
private bool ServerCertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
byte[] deviceCertData = this.deviceConnection.GetDeviceCertificateData();
X509Certificate2 manualCert = this.deviceConnection.GetDeviceCertificate();
if (deviceCertData == null)
if (manualCert != null)
{
// No certificate, fail validation.
return false;
chain.ChainPolicy.ExtraStore.Add(manualCert);
}
X509Certificate deviceCert = new X509Certificate(deviceCertData);
X509Certificate2 certv2 = new X509Certificate2(certificate);
bool isValid = chain.Build(certv2);
// Check the certificate
// * First, make sure we are in the date range
DateTime now = DateTime.Now;
if ((now < DateTime.Parse(cert.GetEffectiveDateString())) ||
(now > DateTime.Parse(cert.GetExpirationDateString())))
// If chain validation failed but we have a manual cert, we can still
// check the chain to see if the server cert chains up to our manual cert
// (or matches it) in which case this is valid.
if (!isValid && manualCert != null)
{
// The current date is out of bounds, fail validation.
return false;
foreach (X509ChainElement element in chain.ChainElements)
{
foreach (X509ChainStatus status in element.ChainElementStatus)
{
// Check if this is a failure that should cause the chain to be rejected
if (status.Status != X509ChainStatusFlags.NoError &&
status.Status != X509ChainStatusFlags.UntrustedRoot &&
status.Status != X509ChainStatusFlags.RevocationStatusUnknown)
{
return false;
}
}
// This cert chained to our provided cert. Continue walking
// the chain to ensure we don't hit a failure that would
// cause our chain to be rejected.
if (element.Certificate.Issuer == manualCert.Issuer &&
element.Certificate.Thumbprint == manualCert.Thumbprint)
{
isValid = true;
break;
}
}
}
// * Next, compare the issuer
if (deviceCert.Issuer != cert.Issuer)
// If this still appears invalid, we give the app a chance via a handler
// to override the trust decision.
if (!isValid)
{
return false;
bool? overridenIsValid = this.UnvalidatedCert?.Invoke(this, certificate, chain, sslPolicyErrors);
if (overridenIsValid != null && overridenIsValid == true)
{
isValid = true;
}
}
return true;
}
/// <summary>
/// No-op version of cert validation for skipping the validation
/// </summary>
/// <param name="sender">the sender</param>
/// <param name="cert">the certificate</param>
/// <param name="chain">cert chain</param>
/// <param name="policyErrors">policy Errors</param>
/// <returns>Always returns true since validation is skipped.</returns>
private bool ServerCertificateNonValidation(
object sender,
X509Certificate cert,
X509Chain chain,
SslPolicyErrors policyErrors)
{
return true;
return isValid;
}
}
}

Просмотреть файл

@ -6,6 +6,7 @@
using System;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using static Microsoft.Tools.WindowsDevicePortal.DevicePortal;
@ -114,41 +115,20 @@ namespace Microsoft.Tools.WindowsDevicePortal
}
/// <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.
/// Gets the provided device certificate.
/// </summary>
public bool AllowCertOverride
/// <returns>Stored device certificate.</returns>
public X509Certificate2 GetDeviceCertificate()
{
get;
set;
return this.deviceCertificate;
}
/// <summary>
/// Gets the raw device certificate.
/// </summary>
/// <returns>Byte array containing the raw certificate data.</returns>
public byte[] GetDeviceCertificateData()
{
return this.deviceCertificate.GetRawCertData();
}
/// <summary>
/// Validates and sets the device certificate.
/// Stores a manually provided 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;
}

Просмотреть файл

@ -0,0 +1,21 @@
//----------------------------------------------------------------------------------------------
// <copyright file="UnvalidatedCert.cs" company="Microsoft Corporation">
// Licensed under the MIT License. See LICENSE.TXT in the project root license information.
// </copyright>
//----------------------------------------------------------------------------------------------
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
namespace Microsoft.Tools.WindowsDevicePortal
{
/// <summary>
/// Handler for when an unvalidated cert is received.
/// </summary>
/// <param name="sender">The sender of the event.</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 should still pass validation.</returns>
public delegate bool UnvalidatedCertEventHandler(DevicePortal sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors);
}

Просмотреть файл

@ -20,25 +20,16 @@ namespace Microsoft.Tools.WindowsDevicePortal
/// Submits the http get request to the specified uri.
/// </summary>
/// <param name="uri">The uri to which the get request will be issued.</param>
/// /// <param name="validateCertificate">Whether the certificate should be validated.</param>
/// <returns>Response data as a stream.</returns>
private async Task<Stream> Get(
Uri uri,
bool validateCertificate = true)
Uri uri)
{
MemoryStream dataStream = null;
WebRequestHandler handler = new WebRequestHandler();
handler.UseDefaultCredentials = false;
handler.Credentials = this.deviceConnection.Credentials;
if (validateCertificate)
{
handler.ServerCertificateValidationCallback = this.ServerCertificateValidation;
}
else
{
handler.ServerCertificateValidationCallback = this.ServerCertificateNonValidation;
}
handler.ServerCertificateValidationCallback = this.ServerCertificateValidation;
using (HttpClient client = new HttpClient(handler))
{

Просмотреть файл

@ -46,6 +46,7 @@
<Compile Include="CertificateHandling.cs" />
<Compile Include="Core\AppDeployment.cs" />
<Compile Include="DefaultDevicePortalConnection.cs" />
<Compile Include="Events\UnvalidatedCert.cs" />
<Compile Include="HttpRest\RestPost.cs" />
<Compile Include="HttpRest\RestPut.cs" />
<Compile Include="HttpRest\RestDelete.cs" />

Просмотреть файл

@ -13,7 +13,8 @@ Scripts or other executables could be written to interface with XboxWdpDriver.ex
| /Ip | The system IP address for the Xbox One console (required if no default console is set). |
| /User | WDP username (if required, will be stored after the first connection starting with Windows 8). |
| /Pwd | WDP password (if required, will be stored after the first connection starting with Windows 8). |
| /CertFile | (optional) Path to a certificate file. This allows extra security on an untrusted network or allows specifying a proxy cert for a web proxy such as Fiddler |
| /CertFile | (optional) Path to a certificate file. This allows accepting an untrusted root certificate and allows specifying a proxy cert for a web proxy such as Fiddler |
| /Thumbprint | (optional) Thumbprint for an SSL certificate that we are willing to accept from the console. This is another way to accept an untrusted certificate without providing the entire certificate file |
| /Op | The operation to run. Run XboxWdpDriver without this parameter to get a list of all available operations. |
Supported operations (in alphabetical order) are the following:
@ -98,7 +99,7 @@ XboxWdpDriver.exe /op:config /setting:TVResolution /value:1080p
<a name="connect"/>
### The connect operation
The ip parameter is required if no default console is configured. You can set a default console or list the current default console by using the 'connect' operation.
The ip parameter is required if no default console is configured. You can set a default console or list the current default console by using the 'connect' operation. Specifying the /thumbprint parameter to connect will cause the thumbprint to be persisted allowing future connections to trust the device without specifying the SLL thumbprint.
Examples:
```shell
@ -109,6 +110,11 @@ or
XboxWdpDriver.exe /op:connect
```
Persisting the SSL thumbprint:
```shell
XboxWdpDriver.exe /op:connect /ip:10.0.0.1 /thumbprint:0000111122223333444455556666777788889999
```
<a name="fiddler"/>
### The Fiddler operation