Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create best practice solution sample using X.509 certificate authentication #3036

Merged
merged 19 commits into from
Dec 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions azureiot.sln
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MethodSample", "iothub\devi
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TwinSample", "iothub\device\samples\getting started\TwinSample\TwinSample.csproj", "{6B0BF31A-2FAE-49AB-A838-E32146E869AE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "X509DeviceCertWithChainSample", "iothub\device\samples\how to guides\X509DeviceCertWithChainSample\X509DeviceCertWithChainSample.csproj", "{ABE001CF-C9E2-4F3F-80F8-59328B2E2BBA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PnpDeviceSamples", "PnpDeviceSamples", "{FF919D6B-5D2B-4DC0-80F6-5F5E9AD7068E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TemperatureController", "iothub\device\samples\solutions\PnpDeviceSamples\TemperatureController\TemperatureController.csproj", "{83FAEAF2-BF8B-4D71-A77E-7650B8A163EC}"
Expand Down Expand Up @@ -199,6 +197,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DpsCustomAllocator", "DpsCu
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HubRoutingSample", "iothub\device\samples\how to guides\HubRoutingSample\HubRoutingSample.csproj", "{8F651FE8-BD4B-45C8-A57C-69A85DBAE366}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "X509DeviceCertWithChainSample", "iothub\device\samples\how to guides\X509DeviceCertWithChainSample\X509DeviceCertWithChainSample.csproj", "{CBAB50ED-91E9-42D7-878F-B547E98B953F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -261,10 +261,6 @@ Global
{6B0BF31A-2FAE-49AB-A838-E32146E869AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B0BF31A-2FAE-49AB-A838-E32146E869AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6B0BF31A-2FAE-49AB-A838-E32146E869AE}.Release|Any CPU.Build.0 = Release|Any CPU
{ABE001CF-C9E2-4F3F-80F8-59328B2E2BBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ABE001CF-C9E2-4F3F-80F8-59328B2E2BBA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ABE001CF-C9E2-4F3F-80F8-59328B2E2BBA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ABE001CF-C9E2-4F3F-80F8-59328B2E2BBA}.Release|Any CPU.Build.0 = Release|Any CPU
{83FAEAF2-BF8B-4D71-A77E-7650B8A163EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{83FAEAF2-BF8B-4D71-A77E-7650B8A163EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{83FAEAF2-BF8B-4D71-A77E-7650B8A163EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -389,6 +385,10 @@ Global
{8F651FE8-BD4B-45C8-A57C-69A85DBAE366}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8F651FE8-BD4B-45C8-A57C-69A85DBAE366}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8F651FE8-BD4B-45C8-A57C-69A85DBAE366}.Release|Any CPU.Build.0 = Release|Any CPU
{CBAB50ED-91E9-42D7-878F-B547E98B953F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CBAB50ED-91E9-42D7-878F-B547E98B953F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBAB50ED-91E9-42D7-878F-B547E98B953F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CBAB50ED-91E9-42D7-878F-B547E98B953F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -417,7 +417,6 @@ Global
{66CBFE14-5F59-4799-ABD6-176EC2D8A998} = {9BEF9B8B-7800-4C14-819F-F736DB4F1426}
{310937CF-4E40-4A28-B5CF-8670028A81D9} = {9BEF9B8B-7800-4C14-819F-F736DB4F1426}
{6B0BF31A-2FAE-49AB-A838-E32146E869AE} = {9BEF9B8B-7800-4C14-819F-F736DB4F1426}
{ABE001CF-C9E2-4F3F-80F8-59328B2E2BBA} = {FA7C7E33-CC9C-4ACD-A761-C2B4C55E82F6}
{FF919D6B-5D2B-4DC0-80F6-5F5E9AD7068E} = {89BC5146-6077-4B23-9538-4C546E212C44}
{83FAEAF2-BF8B-4D71-A77E-7650B8A163EC} = {FF919D6B-5D2B-4DC0-80F6-5F5E9AD7068E}
{9C778E31-10DD-47DC-9FB2-D1EB3C89B5BA} = {FF919D6B-5D2B-4DC0-80F6-5F5E9AD7068E}
Expand Down Expand Up @@ -465,6 +464,7 @@ Global
{1C8A8E21-1EBC-4771-BB17-1E81434168B8} = {662CE01D-7A7F-48D8-80CE-5DC9C60677E8}
{FE579E0D-D55A-495E-A758-B611BA8C5C24} = {1C8A8E21-1EBC-4771-BB17-1E81434168B8}
{8F651FE8-BD4B-45C8-A57C-69A85DBAE366} = {FA7C7E33-CC9C-4ACD-A761-C2B4C55E82F6}
{CBAB50ED-91E9-42D7-878F-B547E98B953F} = {FA7C7E33-CC9C-4ACD-A761-C2B4C55E82F6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AF61665D-340A-494B-9705-571456BDC752}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -21,6 +22,12 @@ internal class DeviceReconnectionSample
private readonly IotHubClientOptions _clientOptions;
private readonly IIotHubClientRetryPolicy _customRetryPolicy;

// These fields are specific for X.509 certificate authentication
private readonly string _certificatePath;
private readonly string _certificatePassword;
private readonly string _deviceId;
private readonly string _hostname;

// An UnauthorizedException is handled in the connection status change handler through its corresponding status change event.
// We will ignore this exception when thrown by client API operations.
private readonly HashSet<Type> _exceptionsToBeRetried = new()
Expand Down Expand Up @@ -53,8 +60,9 @@ public DeviceReconnectionSample(List<string> deviceConnectionStrings, Parameters
// if any more are remaining, will try the next one.
// To test this, either pass an invalid connection string as the first one, or rotate it while the sample is running, and wait about
// 5 minutes.
if (deviceConnectionStrings == null
if ((deviceConnectionStrings == null
|| !deviceConnectionStrings.Any())
&& parameters.CertificateName == null)
{
throw new ArgumentException("At least one connection string must be provided.", nameof(deviceConnectionStrings));
}
Expand All @@ -66,6 +74,22 @@ public DeviceReconnectionSample(List<string> deviceConnectionStrings, Parameters
SdkAssignsMessageId = SdkAssignsMessageId.WhenUnset,
RetryPolicy = _customRetryPolicy,
};

if (!String.IsNullOrWhiteSpace(parameters.DeviceId)){
_deviceId = parameters.DeviceId;
}

if (!String.IsNullOrWhiteSpace(parameters.CertificateName)){
_certificatePath = parameters.CertificateName;
}

if (!String.IsNullOrWhiteSpace(parameters.CertificatePassword)){
_certificatePassword = parameters.CertificatePassword;
}

if (!String.IsNullOrWhiteSpace(parameters.HostName)){
_hostname = parameters.HostName;
}
}

public async Task RunSampleAsync(TimeSpan sampleRunningTime)
Expand Down Expand Up @@ -120,8 +144,18 @@ private async Task InitializeAndSetupClientAsync(CancellationToken cancellationT
}
else
{
// Otherwise instantiate it for the first time.
s_deviceClient = new IotHubDeviceClient(_deviceConnectionStrings.First(), _clientOptions);
// if the certificate is provided, initialize the client using certificate
if (!String.IsNullOrWhiteSpace(_certificatePath) && !String.IsNullOrWhiteSpace(_certificatePassword))
{
using var deviceCert = new X509Certificate2(_certificatePath, _certificatePassword);
var auth = new ClientAuthenticationWithX509Certificate(deviceCert, _deviceId);
s_deviceClient = new IotHubDeviceClient(_hostname, auth, _clientOptions);
}
else
{
// Otherwise instantiate it for the first time.
s_deviceClient = new IotHubDeviceClient(_deviceConnectionStrings.First(), _clientOptions);
}
}

s_deviceClient.ConnectionStatusChangeCallback = ConnectionStatusChangeHandlerAsync;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ internal class Parameters
[Option(
'c',
"PrimaryConnectionString",
Required = true,
Required = false,
HelpText = "The primary connection string for the device to simulate.")]
public string PrimaryConnectionString { get; set; }

Expand All @@ -44,6 +44,35 @@ internal class Parameters
HelpText = "The transport to use to communicate with the device provisioning instance.")]
public IotHubClientTransportProtocol TransportProtocol { get; set; }

[Option(
'n',
"CertificateName",
Default = "certificate.pfx",
Required = false,
HelpText = "The PFX certificate to load for authentication.")]
public string CertificateName { get; set; }

[Option(
'p',
"CertificatePassword",
Required = false,
HelpText = "The password of the PFX certificate file.")]
public string CertificatePassword { get; set; }

[Option(
'd',
"DeviceId",
Required = false,
HelpText = "The Id of device.")]
public string DeviceId { get; set; }

[Option(
'h',
"HostName",
Required = false,
HelpText = "The hostname of IoT hub.")]
public string HostName { get; set; }

[Option(
'r',
"Application running time (in seconds)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,31 @@ var options = new IotHubClientOptions(new IotHubClientMqttSettings())
deviceClient = new IotHubDeviceClient(connectionString, options);
```

```csharp
// X.509 certificate:
// Run provisioning/device/samples/getting started/X509Sample/GenerateTestCertificate.ps1 script to generate a test X.509 certificate.
// Azure portal -
// Navigate to your IoT Hub and copy the hostname.
//
// Transport:
// The transport to use to communicate with IoT ub. Possible values include Mqtt and Amqp.
//
// Transport protocol:
// The protocol to use to communicate with IoT hub. Possible values include Tcp and WebSocket.
var deviceCert = new X509Certificate2(devicePfxPath, devicePfxPassword);
var auth = new ClientAuthenticationWithX509Certificate(deviceCert, deviceName);
// This option is helpful in delegating the assignment of Message.MessageId to the sdk.
// If the user doesn't set a value for Message.MessageId, the sdk will assign it a random GUID before sending the message.
var options = new IotHubClientOptions(new IotHubClientMqttSettings())
{
SdkAssignsMessageId = SdkAssignsMessageId.WhenUnset,
};
deviceClient = new IotHubDeviceClient(
hostName,
auth,
options);
```

### Send device to cloud telemetry:

```csharp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,30 +31,35 @@ internal class Parameters
'n',
"CertificateName",
Default = "certificate.pfx",
HelpText = "The PFX certificate to load for device provisioning authentication.")]
Required = true,
HelpText = "The name to the PFX certificate to load for device provisioning authentication. You can also pass in the absolute path to the device PFX certificate file.")]
public string CertificateName { get; set; }

[Option(
'p',
"CertificatePassword",
Required = false,
HelpText = "The password of the PFX certificate file. If not specified, the program will prompt at run time.")]
public string CertificatePassword { get; set; }

[Option(
'g',
"GlobalDeviceEndpoint",
Required = false,
Default = "global.azure-devices-provisioning.net",
HelpText = "The global endpoint for devices to connect to.")]
public string GlobalDeviceEndpoint { get; set; }

[Option(
"Transport",
Default = Transport.Mqtt,
Required = false,
HelpText = "The transport to use for the connection.")]
public Transport Transport { get; set; }

[Option(
"TransportProtocol",
Required = false,
Default = ProvisioningClientTransportProtocol.Tcp,
HelpText = "The transport to use to communicate with the device provisioning instance.")]
public ProvisioningClientTransportProtocol TransportProtocol { get; set; }
Expand All @@ -63,7 +68,7 @@ internal string GetCertificatePath()
{
if (string.IsNullOrWhiteSpace(CertificateName))
{
throw new InvalidOperationException("The certificate name has not been set.");
throw new ArgumentNullException("The certificate name has not been set.");
}

string codeBase = Assembly.GetExecutingAssembly().Location;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using CommandLine;
using Microsoft.Azure.Devices.Logging;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;

Expand All @@ -12,6 +14,8 @@ namespace Microsoft.Azure.Devices.Provisioning.Client.Samples
/// </summary>
internal class Program
{
private const string SdkEventProviderPrefix = "Microsoft-Azure-";

public static async Task<int> Main(string[] args)
{
// Parse application parameters
Expand All @@ -26,8 +30,28 @@ public static async Task<int> Main(string[] args)
Environment.Exit(1);
});

var sample = new ProvisioningDeviceClientSample(parameters);
await sample.RunSampleAsync();
// Set up logging
using ILoggerFactory loggerFactory = new LoggerFactory();
loggerFactory.AddColorConsoleLogger(
new ColorConsoleLoggerConfiguration
{
// The SDK logs are written at Trace level. Set this to LogLevel.Trace to get ALL logs.
MinLogLevel = LogLevel.Debug,
});
ILogger<Program> logger = loggerFactory.CreateLogger<Program>();

// Instantiating this seems to do all we need for outputting SDK events to our console log.
using var sdkLogs = new ConsoleEventListener(SdkEventProviderPrefix, logger);

try
{
var sample = new ProvisioningDeviceClientSample(parameters, logger);
await sample.RunSampleAsync();
}
catch (Exception ex)
{
logger.LogWarning($"Exception caught: {ex}");
}

return 0;
}
Expand Down
Loading