This repository has been archived by the owner on Nov 16, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 227
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #162 from Azure-Samples/drwill/dpsSymmetricKey
Update DPS device symmetric key sample, and fix warnings in other sln samples
- Loading branch information
Showing
9 changed files
with
262 additions
and
142 deletions.
There are no files selected for viewing
18 changes: 18 additions & 0 deletions
18
provisioning/Samples/device/SymmetricKeySample/EnrollmentType.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
namespace Microsoft.Azure.Devices.Provisioning.Client.Samples | ||
{ | ||
/// <summary> | ||
/// The type of enrollment for a device in the provisioning service. | ||
/// </summary> | ||
public enum EnrollmentType | ||
{ | ||
/// <summary> | ||
/// Enrollment for a single device. | ||
/// </summary> | ||
Individual, | ||
|
||
/// <summary> | ||
/// Enrollment for a group of devices. | ||
/// </summary> | ||
Group, | ||
} | ||
} |
53 changes: 53 additions & 0 deletions
53
provisioning/Samples/device/SymmetricKeySample/Parameters.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
using CommandLine; | ||
using Microsoft.Azure.Devices.Client; | ||
|
||
namespace Microsoft.Azure.Devices.Provisioning.Client.Samples | ||
{ | ||
/// <summary> | ||
/// Parameters for the application | ||
/// </summary> | ||
internal class Parameters | ||
{ | ||
[Option( | ||
's', | ||
"IdScope", | ||
Required = true, | ||
HelpText = "The Id Scope of the DPS instance")] | ||
public string IdScope { get; set; } | ||
|
||
[Option( | ||
'i', | ||
"Id", | ||
Required = true, | ||
HelpText = "The registration Id when using individual enrollment, or the desired device Id when using group enrollment.")] | ||
public string Id { get; set; } | ||
|
||
[Option( | ||
'p', | ||
"PrimaryKey", | ||
Required = true, | ||
HelpText = "The primary key of the individual or group enrollment.")] | ||
public string PrimaryKey { get; set; } | ||
|
||
[Option( | ||
'e', | ||
"EnrollmentType", | ||
Default = EnrollmentType.Individual, | ||
HelpText = "The type of enrollment: Individual or Group")] | ||
public EnrollmentType EnrollmentType { get; set; } | ||
|
||
[Option( | ||
'g', | ||
"GlobalDeviceEndpoint", | ||
Default = "global.azure-devices-provisioning.net", | ||
HelpText = "The global endpoint for devices to connect to.")] | ||
public string GlobalDeviceEndpoint { get; set; } | ||
|
||
[Option( | ||
't', | ||
"TransportType", | ||
Default = TransportType.Mqtt, | ||
HelpText = "The transport to use to communicate with the device provisioning instance. Possible values include Mqtt, Mqtt_WebSocket_Only, Mqtt_Tcp_Only, Amqp, Amqp_WebSocket_Only, Amqp_Tcp_only, and Http1.")] | ||
public TransportType TransportType { get; set; } | ||
} | ||
} |
135 changes: 24 additions & 111 deletions
135
provisioning/Samples/device/SymmetricKeySample/Program.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,126 +1,39 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using Microsoft.Azure.Devices.Provisioning.Client; | ||
using CommandLine; | ||
using Microsoft.Azure.Devices.Provisioning.Client.Samples; | ||
using Microsoft.Azure.Devices.Provisioning.Client.Transport; | ||
using Microsoft.Azure.Devices.Shared; | ||
using System; | ||
using System.Security.Cryptography; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace SymmetricKeySample | ||
{ | ||
class Program | ||
/// <summary> | ||
/// A sample to illustrate connecting a device to hub using the device provisioning service. | ||
/// </summary> | ||
internal class Program | ||
{ | ||
// The Provisioning Hub IDScope. | ||
|
||
// For this sample either: | ||
// - pass this value as a command-prompt argument | ||
// - set the DPS_IDSCOPE environment variable | ||
// - create a launchSettings.json (see launchSettings.json.template) containing the variable | ||
private static string s_idScope = Environment.GetEnvironmentVariable("DPS_IDSCOPE"); | ||
|
||
// In your Device Provisioning Service please go to "Manage enrollments" and select "Individual Enrollments". | ||
// Select "Add individual enrollment" then fill in the following: | ||
// Mechanism: Symmetric Key | ||
// Auto-generate keys should be checked | ||
// DeviceID: iothubSymmetricKeydevice1 | ||
|
||
// Symmetric Keys may also be used for enrollment groups. | ||
// In your Device Provisioning Service please go to "Manage enrollments" and select "Enrollment Groups". | ||
// Select "Add enrollment group" then fill in the following: | ||
// Group name: <your group name> | ||
// Attestation Type: Symmetric Key | ||
// Auto-generate keys should be checked | ||
// You may also change other enrollment group parameters according to your needs | ||
|
||
private const string GlobalDeviceEndpoint = "global.azure-devices-provisioning.net"; | ||
|
||
//These are the two keys that belong to your enrollment group. | ||
// Leave them blank if you want to try this sample for an individual enrollment instead | ||
private const string enrollmentGroupPrimaryKey = ""; | ||
private const string enrollmentGroupSecondaryKey = ""; | ||
|
||
//registration id for enrollment groups can be chosen arbitrarily and do not require any portal setup. | ||
//The chosen value will become the provisioned device's device id. | ||
// | ||
//registration id for individual enrollments must be retrieved from the portal and will be unrelated to the provioned | ||
//device's device id | ||
// | ||
//This field is mandatory to provide for this sample | ||
private static string registrationId = ""; | ||
|
||
//These are the two keys that belong to your individual enrollment. | ||
// Leave them blank if you want to try this sample for an individual enrollment instead | ||
private const string individualEnrollmentPrimaryKey = ""; | ||
private const string individualEnrollmentSecondaryKey = ""; | ||
|
||
public static int Main(string[] args) | ||
public static async Task<int> Main(string[] args) | ||
{ | ||
if (string.IsNullOrWhiteSpace(s_idScope) && (args.Length > 0)) | ||
{ | ||
s_idScope = args[0]; | ||
} | ||
// Parse application parameters | ||
Parameters parameters = null; | ||
ParserResult<Parameters> result = Parser.Default.ParseArguments<Parameters>(args) | ||
.WithParsed(parsedParams => | ||
{ | ||
parameters = parsedParams; | ||
}) | ||
.WithNotParsed(errors => | ||
{ | ||
Environment.Exit(1); | ||
}); | ||
|
||
var sample = new ProvisioningDeviceClientSample(parameters); | ||
await sample.RunSampleAsync(); | ||
|
||
Console.WriteLine("Enter any key to exit."); | ||
Console.ReadKey(); | ||
|
||
if (string.IsNullOrWhiteSpace(s_idScope)) | ||
{ | ||
Console.WriteLine("ProvisioningDeviceClientSymmetricKey <IDScope>"); | ||
return 1; | ||
} | ||
|
||
string primaryKey = ""; | ||
string secondaryKey = ""; | ||
if (!String.IsNullOrEmpty(registrationId) && !String.IsNullOrEmpty(enrollmentGroupPrimaryKey) && !String.IsNullOrEmpty(enrollmentGroupSecondaryKey)) | ||
{ | ||
//Group enrollment flow, the primary and secondary keys are derived from the enrollment group keys and from the desired registration id | ||
primaryKey = ComputeDerivedSymmetricKey(Convert.FromBase64String(enrollmentGroupPrimaryKey), registrationId); | ||
secondaryKey = ComputeDerivedSymmetricKey(Convert.FromBase64String(enrollmentGroupSecondaryKey), registrationId); | ||
} | ||
else if (!String.IsNullOrEmpty(registrationId) && !String.IsNullOrEmpty(individualEnrollmentPrimaryKey) && !String.IsNullOrEmpty(individualEnrollmentSecondaryKey)) | ||
{ | ||
//Individual enrollment flow, the primary and secondary keys are the same as the individual enrollment keys | ||
primaryKey = individualEnrollmentPrimaryKey; | ||
secondaryKey = individualEnrollmentSecondaryKey; | ||
} | ||
else | ||
{ | ||
Console.WriteLine("Invalid configuration provided, must provide group enrollment keys or individual enrollment keys"); | ||
return -1; | ||
} | ||
|
||
using (var security = new SecurityProviderSymmetricKey(registrationId, primaryKey, secondaryKey)) | ||
|
||
// Select one of the available transports: | ||
// To optimize for size, reference only the protocols used by your application. | ||
using (var transport = new ProvisioningTransportHandlerAmqp(TransportFallbackType.TcpOnly)) | ||
// using (var transport = new ProvisioningTransportHandlerHttp()) | ||
// using (var transport = new ProvisioningTransportHandlerMqtt(TransportFallbackType.TcpOnly)) | ||
// using (var transport = new ProvisioningTransportHandlerMqtt(TransportFallbackType.WebSocketOnly)) | ||
{ | ||
ProvisioningDeviceClient provClient = | ||
ProvisioningDeviceClient.Create(GlobalDeviceEndpoint, s_idScope, security, transport); | ||
|
||
var sample = new ProvisioningDeviceClientSample(provClient, security); | ||
sample.RunSampleAsync().GetAwaiter().GetResult(); | ||
} | ||
Console.WriteLine("Enter any key to exit"); | ||
Console.ReadLine(); | ||
return 0; | ||
} | ||
|
||
/// <summary> | ||
/// Generate the derived symmetric key for the provisioned device from the enrollment group symmetric key used in attestation | ||
/// </summary> | ||
/// <param name="masterKey">Symmetric key enrollment group primary/secondary key value</param> | ||
/// <param name="registrationId">the registration id to create</param> | ||
/// <returns>the primary/secondary key for the member of the enrollment group</returns> | ||
public static string ComputeDerivedSymmetricKey(byte[] masterKey, string registrationId) | ||
{ | ||
using (var hmac = new HMACSHA256(masterKey)) | ||
{ | ||
return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(registrationId))); | ||
} | ||
} | ||
} | ||
} |
118 changes: 118 additions & 0 deletions
118
provisioning/Samples/device/SymmetricKeySample/ProvisioningDeviceClientSample.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using Microsoft.Azure.Devices.Client; | ||
using Microsoft.Azure.Devices.Provisioning.Client.Transport; | ||
using Microsoft.Azure.Devices.Shared; | ||
using System; | ||
using System.Security.Cryptography; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace Microsoft.Azure.Devices.Provisioning.Client.Samples | ||
{ | ||
/// <summary> | ||
/// Demonstrates how to register a device with the device provisioning service, and then | ||
/// use the registration information to authenticate to IoT Hub. | ||
/// </summary> | ||
internal class ProvisioningDeviceClientSample | ||
{ | ||
private readonly Parameters _parameters; | ||
|
||
public ProvisioningDeviceClientSample(Parameters parameters) | ||
{ | ||
_parameters = parameters; | ||
} | ||
|
||
public async Task RunSampleAsync() | ||
{ | ||
// When registering with a symmetric key using a group enrollment, the provided key will not | ||
// work for a specific device, rather it must be computed based on two values: the group enrollment | ||
// key and the desired device Id. | ||
if (_parameters.EnrollmentType == EnrollmentType.Group) | ||
{ | ||
_parameters.PrimaryKey = ComputeDerivedSymmetricKey(_parameters.PrimaryKey, _parameters.Id); | ||
} | ||
|
||
Console.WriteLine($"Initializing the device provisioning client..."); | ||
|
||
// For individual enrollments, the first parameter must be the registration Id, where in the enrollment | ||
// the device Id is already chosen. However, for group enrollments the device Id can be requested by | ||
// the device, as long as the key has been computed using that value. | ||
// Also, the secondary could could be included, but was left out for the simplicity of this sample. | ||
using var security = new SecurityProviderSymmetricKey( | ||
_parameters.Id, | ||
_parameters.PrimaryKey, | ||
null); | ||
|
||
ProvisioningDeviceClient provClient = ProvisioningDeviceClient.Create( | ||
_parameters.GlobalDeviceEndpoint, | ||
_parameters.IdScope, | ||
security, | ||
GetTransportHandler()); | ||
|
||
Console.WriteLine($"Initialized for registration Id {security.GetRegistrationID()}"); | ||
|
||
Console.WriteLine("Registering with the device provisioning service... "); | ||
DeviceRegistrationResult result = await provClient.RegisterAsync().ConfigureAwait(false); | ||
|
||
Console.WriteLine($"Registration status: {result.Status}."); | ||
if (result.Status != ProvisioningRegistrationStatusType.Assigned) | ||
{ | ||
Console.WriteLine($"Registration status did not assign a hub, so exiting this sample."); | ||
return; | ||
} | ||
|
||
Console.WriteLine($"Device {result.DeviceId} registered to {result.AssignedHub}."); | ||
|
||
Console.WriteLine("Creating symmetric key authentication for IoT Hub..."); | ||
IAuthenticationMethod auth = new DeviceAuthenticationWithRegistrySymmetricKey( | ||
result.DeviceId, | ||
security.GetPrimaryKey()); | ||
|
||
Console.WriteLine($"Testing the provisioned device with IoT Hub..."); | ||
using DeviceClient iotClient = DeviceClient.Create(result.AssignedHub, auth, _parameters.TransportType); | ||
|
||
Console.WriteLine("Sending a telemetry message..."); | ||
using var message = new Message(Encoding.UTF8.GetBytes("TestMessage")); | ||
await iotClient.SendEventAsync(message); | ||
|
||
Console.WriteLine("Finished."); | ||
} | ||
|
||
/// <summary> | ||
/// Compute a symmetric key for the provisioned device from the enrollment group symmetric key used in attestation. | ||
/// </summary> | ||
/// <param name="enrollmentKey">Enrollment group symmetric key.</param> | ||
/// <param name="deviceId">The device Id of the key to create.</param> | ||
/// <returns>The key for the specified device Id registration in the enrollment group.</returns> | ||
/// <seealso> | ||
/// https://docs.microsoft.com/en-us/azure/iot-edge/how-to-auto-provision-symmetric-keys?view=iotedge-2018-06#derive-a-device-key | ||
/// </seealso> | ||
private static string ComputeDerivedSymmetricKey(string enrollmentKey, string deviceId) | ||
{ | ||
if (string.IsNullOrWhiteSpace(enrollmentKey)) | ||
{ | ||
return enrollmentKey; | ||
} | ||
|
||
using var hmac = new HMACSHA256(Convert.FromBase64String(enrollmentKey)); | ||
return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(deviceId))); | ||
} | ||
|
||
private ProvisioningTransportHandler GetTransportHandler() | ||
{ | ||
return _parameters.TransportType switch | ||
{ | ||
TransportType.Mqtt => new ProvisioningTransportHandlerMqtt(), | ||
TransportType.Mqtt_Tcp_Only => new ProvisioningTransportHandlerMqtt(TransportFallbackType.WebSocketOnly), | ||
TransportType.Mqtt_WebSocket_Only => new ProvisioningTransportHandlerMqtt(TransportFallbackType.TcpOnly), | ||
TransportType.Amqp => new ProvisioningTransportHandlerAmqp(), | ||
TransportType.Amqp_Tcp_Only => new ProvisioningTransportHandlerAmqp(TransportFallbackType.WebSocketOnly), | ||
TransportType.Amqp_WebSocket_Only => new ProvisioningTransportHandlerAmqp(TransportFallbackType.TcpOnly), | ||
TransportType.Http1 => new ProvisioningTransportHandlerHttp(), | ||
_ => throw new NotSupportedException($"Unsupported transport type {_parameters.TransportType}"), | ||
}; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.