Skip to content
This repository has been archived by the owner on Nov 16, 2023. It is now read-only.

Commit

Permalink
Merge pull request #162 from Azure-Samples/drwill/dpsSymmetricKey
Browse files Browse the repository at this point in the history
Update DPS device symmetric key sample, and fix warnings in other sln samples
  • Loading branch information
David R. Williamson committed Dec 2, 2020
2 parents 8250923 + ad089de commit 0bc82f8
Show file tree
Hide file tree
Showing 9 changed files with 262 additions and 142 deletions.
18 changes: 18 additions & 0 deletions provisioning/Samples/device/SymmetricKeySample/EnrollmentType.cs
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 provisioning/Samples/device/SymmetricKeySample/Parameters.cs
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 provisioning/Samples/device/SymmetricKeySample/Program.cs
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)));
}
}
}
}
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}"),
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
<LangVersion>8.0</LangVersion>
<RootNamespace>Microsoft.Azure.Devices.Provisioning.Client.Samples</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Common\ProvisioningDeviceClientSample.cs" Link="ProvisioningDeviceClientSample.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.20.0" />
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Transport.Amqp" Version="1.1.6" />
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Transport.Http" Version="1.1.5" />
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.33.1" />
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Transport.Amqp" Version="1.13.3" />
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Transport.Http" Version="1.12.2" />
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Transport.Mqtt" Version="1.13.2" />
</ItemGroup>

</Project>
9 changes: 5 additions & 4 deletions provisioning/Samples/device/TpmSample/TpmSample.csproj
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RootDir>$(MSBuildProjectDirectory)\..\..\..\..</RootDir>
<LangVersion>8.0</LangVersion>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\Common\ProvisioningDeviceClientSample.cs" Link="ProvisioningDeviceClientSample.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.*" />
<PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.33.1" />
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Client" Version="1.*" />
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Security.Tpm" Version="1.*" />

<!-- Note: Applications should not need to import both protocols. This was done to simplify protocol selection within the sample.-->
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Transport.Amqp" Version="1.*" />
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Transport.Http" Version="1.*" />
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Transport.Amqp" Version="1.13.3" />
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Transport.Http" Version="1.12.2" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading

0 comments on commit 0bc82f8

Please sign in to comment.