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

Commit 0bc82f8

Browse files
author
David R. Williamson
authored
Merge pull request #162 from Azure-Samples/drwill/dpsSymmetricKey
Update DPS device symmetric key sample, and fix warnings in other sln samples
2 parents 8250923 + ad089de commit 0bc82f8

File tree

9 files changed

+262
-142
lines changed

9 files changed

+262
-142
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace Microsoft.Azure.Devices.Provisioning.Client.Samples
2+
{
3+
/// <summary>
4+
/// The type of enrollment for a device in the provisioning service.
5+
/// </summary>
6+
public enum EnrollmentType
7+
{
8+
/// <summary>
9+
/// Enrollment for a single device.
10+
/// </summary>
11+
Individual,
12+
13+
/// <summary>
14+
/// Enrollment for a group of devices.
15+
/// </summary>
16+
Group,
17+
}
18+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using CommandLine;
2+
using Microsoft.Azure.Devices.Client;
3+
4+
namespace Microsoft.Azure.Devices.Provisioning.Client.Samples
5+
{
6+
/// <summary>
7+
/// Parameters for the application
8+
/// </summary>
9+
internal class Parameters
10+
{
11+
[Option(
12+
's',
13+
"IdScope",
14+
Required = true,
15+
HelpText = "The Id Scope of the DPS instance")]
16+
public string IdScope { get; set; }
17+
18+
[Option(
19+
'i',
20+
"Id",
21+
Required = true,
22+
HelpText = "The registration Id when using individual enrollment, or the desired device Id when using group enrollment.")]
23+
public string Id { get; set; }
24+
25+
[Option(
26+
'p',
27+
"PrimaryKey",
28+
Required = true,
29+
HelpText = "The primary key of the individual or group enrollment.")]
30+
public string PrimaryKey { get; set; }
31+
32+
[Option(
33+
'e',
34+
"EnrollmentType",
35+
Default = EnrollmentType.Individual,
36+
HelpText = "The type of enrollment: Individual or Group")]
37+
public EnrollmentType EnrollmentType { get; set; }
38+
39+
[Option(
40+
'g',
41+
"GlobalDeviceEndpoint",
42+
Default = "global.azure-devices-provisioning.net",
43+
HelpText = "The global endpoint for devices to connect to.")]
44+
public string GlobalDeviceEndpoint { get; set; }
45+
46+
[Option(
47+
't',
48+
"TransportType",
49+
Default = TransportType.Mqtt,
50+
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.")]
51+
public TransportType TransportType { get; set; }
52+
}
53+
}
Lines changed: 24 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,39 @@
11
// Copyright (c) Microsoft. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4-
using Microsoft.Azure.Devices.Provisioning.Client;
4+
using CommandLine;
55
using Microsoft.Azure.Devices.Provisioning.Client.Samples;
6-
using Microsoft.Azure.Devices.Provisioning.Client.Transport;
7-
using Microsoft.Azure.Devices.Shared;
86
using System;
9-
using System.Security.Cryptography;
10-
using System.Text;
7+
using System.Threading.Tasks;
118

129
namespace SymmetricKeySample
1310
{
14-
class Program
11+
/// <summary>
12+
/// A sample to illustrate connecting a device to hub using the device provisioning service.
13+
/// </summary>
14+
internal class Program
1515
{
16-
// The Provisioning Hub IDScope.
17-
18-
// For this sample either:
19-
// - pass this value as a command-prompt argument
20-
// - set the DPS_IDSCOPE environment variable
21-
// - create a launchSettings.json (see launchSettings.json.template) containing the variable
22-
private static string s_idScope = Environment.GetEnvironmentVariable("DPS_IDSCOPE");
23-
24-
// In your Device Provisioning Service please go to "Manage enrollments" and select "Individual Enrollments".
25-
// Select "Add individual enrollment" then fill in the following:
26-
// Mechanism: Symmetric Key
27-
// Auto-generate keys should be checked
28-
// DeviceID: iothubSymmetricKeydevice1
29-
30-
// Symmetric Keys may also be used for enrollment groups.
31-
// In your Device Provisioning Service please go to "Manage enrollments" and select "Enrollment Groups".
32-
// Select "Add enrollment group" then fill in the following:
33-
// Group name: <your group name>
34-
// Attestation Type: Symmetric Key
35-
// Auto-generate keys should be checked
36-
// You may also change other enrollment group parameters according to your needs
37-
38-
private const string GlobalDeviceEndpoint = "global.azure-devices-provisioning.net";
39-
40-
//These are the two keys that belong to your enrollment group.
41-
// Leave them blank if you want to try this sample for an individual enrollment instead
42-
private const string enrollmentGroupPrimaryKey = "";
43-
private const string enrollmentGroupSecondaryKey = "";
44-
45-
//registration id for enrollment groups can be chosen arbitrarily and do not require any portal setup.
46-
//The chosen value will become the provisioned device's device id.
47-
//
48-
//registration id for individual enrollments must be retrieved from the portal and will be unrelated to the provioned
49-
//device's device id
50-
//
51-
//This field is mandatory to provide for this sample
52-
private static string registrationId = "";
53-
54-
//These are the two keys that belong to your individual enrollment.
55-
// Leave them blank if you want to try this sample for an individual enrollment instead
56-
private const string individualEnrollmentPrimaryKey = "";
57-
private const string individualEnrollmentSecondaryKey = "";
58-
59-
public static int Main(string[] args)
16+
public static async Task<int> Main(string[] args)
6017
{
61-
if (string.IsNullOrWhiteSpace(s_idScope) && (args.Length > 0))
62-
{
63-
s_idScope = args[0];
64-
}
18+
// Parse application parameters
19+
Parameters parameters = null;
20+
ParserResult<Parameters> result = Parser.Default.ParseArguments<Parameters>(args)
21+
.WithParsed(parsedParams =>
22+
{
23+
parameters = parsedParams;
24+
})
25+
.WithNotParsed(errors =>
26+
{
27+
Environment.Exit(1);
28+
});
29+
30+
var sample = new ProvisioningDeviceClientSample(parameters);
31+
await sample.RunSampleAsync();
32+
33+
Console.WriteLine("Enter any key to exit.");
34+
Console.ReadKey();
6535

66-
if (string.IsNullOrWhiteSpace(s_idScope))
67-
{
68-
Console.WriteLine("ProvisioningDeviceClientSymmetricKey <IDScope>");
69-
return 1;
70-
}
71-
72-
string primaryKey = "";
73-
string secondaryKey = "";
74-
if (!String.IsNullOrEmpty(registrationId) && !String.IsNullOrEmpty(enrollmentGroupPrimaryKey) && !String.IsNullOrEmpty(enrollmentGroupSecondaryKey))
75-
{
76-
//Group enrollment flow, the primary and secondary keys are derived from the enrollment group keys and from the desired registration id
77-
primaryKey = ComputeDerivedSymmetricKey(Convert.FromBase64String(enrollmentGroupPrimaryKey), registrationId);
78-
secondaryKey = ComputeDerivedSymmetricKey(Convert.FromBase64String(enrollmentGroupSecondaryKey), registrationId);
79-
}
80-
else if (!String.IsNullOrEmpty(registrationId) && !String.IsNullOrEmpty(individualEnrollmentPrimaryKey) && !String.IsNullOrEmpty(individualEnrollmentSecondaryKey))
81-
{
82-
//Individual enrollment flow, the primary and secondary keys are the same as the individual enrollment keys
83-
primaryKey = individualEnrollmentPrimaryKey;
84-
secondaryKey = individualEnrollmentSecondaryKey;
85-
}
86-
else
87-
{
88-
Console.WriteLine("Invalid configuration provided, must provide group enrollment keys or individual enrollment keys");
89-
return -1;
90-
}
91-
92-
using (var security = new SecurityProviderSymmetricKey(registrationId, primaryKey, secondaryKey))
93-
94-
// Select one of the available transports:
95-
// To optimize for size, reference only the protocols used by your application.
96-
using (var transport = new ProvisioningTransportHandlerAmqp(TransportFallbackType.TcpOnly))
97-
// using (var transport = new ProvisioningTransportHandlerHttp())
98-
// using (var transport = new ProvisioningTransportHandlerMqtt(TransportFallbackType.TcpOnly))
99-
// using (var transport = new ProvisioningTransportHandlerMqtt(TransportFallbackType.WebSocketOnly))
100-
{
101-
ProvisioningDeviceClient provClient =
102-
ProvisioningDeviceClient.Create(GlobalDeviceEndpoint, s_idScope, security, transport);
103-
104-
var sample = new ProvisioningDeviceClientSample(provClient, security);
105-
sample.RunSampleAsync().GetAwaiter().GetResult();
106-
}
107-
Console.WriteLine("Enter any key to exit");
108-
Console.ReadLine();
10936
return 0;
11037
}
111-
112-
/// <summary>
113-
/// Generate the derived symmetric key for the provisioned device from the enrollment group symmetric key used in attestation
114-
/// </summary>
115-
/// <param name="masterKey">Symmetric key enrollment group primary/secondary key value</param>
116-
/// <param name="registrationId">the registration id to create</param>
117-
/// <returns>the primary/secondary key for the member of the enrollment group</returns>
118-
public static string ComputeDerivedSymmetricKey(byte[] masterKey, string registrationId)
119-
{
120-
using (var hmac = new HMACSHA256(masterKey))
121-
{
122-
return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(registrationId)));
123-
}
124-
}
12538
}
12639
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using Microsoft.Azure.Devices.Client;
5+
using Microsoft.Azure.Devices.Provisioning.Client.Transport;
6+
using Microsoft.Azure.Devices.Shared;
7+
using System;
8+
using System.Security.Cryptography;
9+
using System.Text;
10+
using System.Threading.Tasks;
11+
12+
namespace Microsoft.Azure.Devices.Provisioning.Client.Samples
13+
{
14+
/// <summary>
15+
/// Demonstrates how to register a device with the device provisioning service, and then
16+
/// use the registration information to authenticate to IoT Hub.
17+
/// </summary>
18+
internal class ProvisioningDeviceClientSample
19+
{
20+
private readonly Parameters _parameters;
21+
22+
public ProvisioningDeviceClientSample(Parameters parameters)
23+
{
24+
_parameters = parameters;
25+
}
26+
27+
public async Task RunSampleAsync()
28+
{
29+
// When registering with a symmetric key using a group enrollment, the provided key will not
30+
// work for a specific device, rather it must be computed based on two values: the group enrollment
31+
// key and the desired device Id.
32+
if (_parameters.EnrollmentType == EnrollmentType.Group)
33+
{
34+
_parameters.PrimaryKey = ComputeDerivedSymmetricKey(_parameters.PrimaryKey, _parameters.Id);
35+
}
36+
37+
Console.WriteLine($"Initializing the device provisioning client...");
38+
39+
// For individual enrollments, the first parameter must be the registration Id, where in the enrollment
40+
// the device Id is already chosen. However, for group enrollments the device Id can be requested by
41+
// the device, as long as the key has been computed using that value.
42+
// Also, the secondary could could be included, but was left out for the simplicity of this sample.
43+
using var security = new SecurityProviderSymmetricKey(
44+
_parameters.Id,
45+
_parameters.PrimaryKey,
46+
null);
47+
48+
ProvisioningDeviceClient provClient = ProvisioningDeviceClient.Create(
49+
_parameters.GlobalDeviceEndpoint,
50+
_parameters.IdScope,
51+
security,
52+
GetTransportHandler());
53+
54+
Console.WriteLine($"Initialized for registration Id {security.GetRegistrationID()}");
55+
56+
Console.WriteLine("Registering with the device provisioning service... ");
57+
DeviceRegistrationResult result = await provClient.RegisterAsync().ConfigureAwait(false);
58+
59+
Console.WriteLine($"Registration status: {result.Status}.");
60+
if (result.Status != ProvisioningRegistrationStatusType.Assigned)
61+
{
62+
Console.WriteLine($"Registration status did not assign a hub, so exiting this sample.");
63+
return;
64+
}
65+
66+
Console.WriteLine($"Device {result.DeviceId} registered to {result.AssignedHub}.");
67+
68+
Console.WriteLine("Creating symmetric key authentication for IoT Hub...");
69+
IAuthenticationMethod auth = new DeviceAuthenticationWithRegistrySymmetricKey(
70+
result.DeviceId,
71+
security.GetPrimaryKey());
72+
73+
Console.WriteLine($"Testing the provisioned device with IoT Hub...");
74+
using DeviceClient iotClient = DeviceClient.Create(result.AssignedHub, auth, _parameters.TransportType);
75+
76+
Console.WriteLine("Sending a telemetry message...");
77+
using var message = new Message(Encoding.UTF8.GetBytes("TestMessage"));
78+
await iotClient.SendEventAsync(message);
79+
80+
Console.WriteLine("Finished.");
81+
}
82+
83+
/// <summary>
84+
/// Compute a symmetric key for the provisioned device from the enrollment group symmetric key used in attestation.
85+
/// </summary>
86+
/// <param name="enrollmentKey">Enrollment group symmetric key.</param>
87+
/// <param name="deviceId">The device Id of the key to create.</param>
88+
/// <returns>The key for the specified device Id registration in the enrollment group.</returns>
89+
/// <seealso>
90+
/// https://docs.microsoft.com/en-us/azure/iot-edge/how-to-auto-provision-symmetric-keys?view=iotedge-2018-06#derive-a-device-key
91+
/// </seealso>
92+
private static string ComputeDerivedSymmetricKey(string enrollmentKey, string deviceId)
93+
{
94+
if (string.IsNullOrWhiteSpace(enrollmentKey))
95+
{
96+
return enrollmentKey;
97+
}
98+
99+
using var hmac = new HMACSHA256(Convert.FromBase64String(enrollmentKey));
100+
return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(deviceId)));
101+
}
102+
103+
private ProvisioningTransportHandler GetTransportHandler()
104+
{
105+
return _parameters.TransportType switch
106+
{
107+
TransportType.Mqtt => new ProvisioningTransportHandlerMqtt(),
108+
TransportType.Mqtt_Tcp_Only => new ProvisioningTransportHandlerMqtt(TransportFallbackType.WebSocketOnly),
109+
TransportType.Mqtt_WebSocket_Only => new ProvisioningTransportHandlerMqtt(TransportFallbackType.TcpOnly),
110+
TransportType.Amqp => new ProvisioningTransportHandlerAmqp(),
111+
TransportType.Amqp_Tcp_Only => new ProvisioningTransportHandlerAmqp(TransportFallbackType.WebSocketOnly),
112+
TransportType.Amqp_WebSocket_Only => new ProvisioningTransportHandlerAmqp(TransportFallbackType.TcpOnly),
113+
TransportType.Http1 => new ProvisioningTransportHandlerHttp(),
114+
_ => throw new NotSupportedException($"Unsupported transport type {_parameters.TransportType}"),
115+
};
116+
}
117+
}
118+
}

provisioning/Samples/device/SymmetricKeySample/SymmetricKeySample.csproj

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>netcoreapp2.0</TargetFramework>
5+
<TargetFramework>netcoreapp2.1</TargetFramework>
6+
<LangVersion>8.0</LangVersion>
7+
<RootNamespace>Microsoft.Azure.Devices.Provisioning.Client.Samples</RootNamespace>
68
</PropertyGroup>
79
<ItemGroup>
8-
<Compile Include="..\Common\ProvisioningDeviceClientSample.cs" Link="ProvisioningDeviceClientSample.cs" />
9-
</ItemGroup>
10-
<ItemGroup>
11-
<PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.20.0" />
12-
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Transport.Amqp" Version="1.1.6" />
13-
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Transport.Http" Version="1.1.5" />
10+
<PackageReference Include="CommandLineParser" Version="2.8.0" />
11+
<PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.33.1" />
12+
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Transport.Amqp" Version="1.13.3" />
13+
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Transport.Http" Version="1.12.2" />
14+
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Transport.Mqtt" Version="1.13.2" />
1415
</ItemGroup>
1516

1617
</Project>

provisioning/Samples/device/TpmSample/TpmSample.csproj

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
55
<TargetFramework>netcoreapp2.1</TargetFramework>
66
<RootDir>$(MSBuildProjectDirectory)\..\..\..\..</RootDir>
7+
<LangVersion>8.0</LangVersion>
78
</PropertyGroup>
89

910
<ItemGroup>
1011
<Compile Include="..\Common\ProvisioningDeviceClientSample.cs" Link="ProvisioningDeviceClientSample.cs" />
1112
</ItemGroup>
1213

1314
<ItemGroup>
14-
<PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.*" />
15+
<PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.33.1" />
1516
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Client" Version="1.*" />
1617
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Security.Tpm" Version="1.*" />
1718

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

2324
<ItemGroup>

0 commit comments

Comments
 (0)