Übung: Ein simuliertes Geräts mit X.509-Zertifikat konfigurieren
In dieser Übung generieren Sie ein Gerätezertifikat mithilfe des Stammzertifikats und konfigurieren ein simuliertes Gerät, das mithilfe des Gerätezertifikats als Nachweis eine Verbindung herstellt.
Aufgabe 1: Generieren eines Gerätezertifikats
Stellen Sie in der Azure-Sandbox sicher, dass Sie sich im Verzeichnis ~/certificates befinden, in das das Hilfsskript certGen.sh heruntergeladen wurde:
cd ~/certificates
Generieren Sie mithilfe des folgenden Befehls ein X.509-Gerätezertifikat innerhalb der ZS-Zertifikatkette für das erste Gerät:
./certGen.sh create_device_certificate sensor-thl-001
Mit diesem Befehl wird ein neues X.509-Zertifikatpaar aus .pem und .pfx erstellt, das vom zuvor generierten ZS-Zertifikat signiert wurde. Beachten Sie, dass die Geräte-ID (sensor-thl-001) an den Befehl create_device_certificate des Skripts certGen.sh übergeben wird. Diese Geräte-ID wird als Common Name oder CN= als Wert des Gerätezertifikats festgelegt. Mit diesem Befehl wird ein X.509-Blattzertifikat für Ihr simuliertes Gerät generiert, das zum Authentifizieren des Geräts mit dem Device Provisioning Service (DPS) verwendet wird. In diesem Modul wird die .pfx-Zertifikatdatei verwendet, um das Programm zu überprüfen, das eine Verbindung mit dem DPS von Ihrem Computer aus herstellt.
Sobald der Befehl create_device_certificate abgeschlossen ist, wird das generierte X.509-Gerätezertifikatpaar in new-device.cert.pfx bzw. new-device.cert.pem benannt und befindet sich im Unterverzeichnis /certs.
Wichtig
Mit diesem Befehl werden alle vorhandenen Gerätezertifikate im Unterverzeichnis /certs überschrieben. Wenn Sie ein Zertifikat für mehrere Geräte erstellen möchten, stellen Sie sicher, dass Sie bei jeder Ausführung des Befehls eine Kopie von new-device.cert.pfx und new-device.cert.pem speichern.
Benennen Sie die Gerätezertifikatdateien in den Gerätenamen sensor-thl-001 um, den Sie im letzten Schritt erstellt haben, indem Sie die folgenden Befehle verwenden:
mv ~/certificates/certs/new-device.cert.pfx ~/certificates/certs/sensor-thl-001-device.cert.pfx mv ~/certificates/certs/new-device.cert.pem ~/certificates/certs/sensor-thl-001-device.cert.pem
Erstellen Sie die Zertifikatdateien für ein zweites Gerät und benennen Sie diese mithilfe der folgenden Befehle um:
./certGen.sh create_device_certificate sensor-thl-002 mv ~/certificates/certs/new-device.cert.pfx ~/certificates/certs/sensor-thl-002-device.cert.pfx mv ~/certificates/certs/new-device.cert.pem ~/certificates/certs/sensor-thl-002-device.cert.pem
Laden Sie das erste generierte X.509-Gerätezertifikat aus der Cloud Shell auf Ihren lokalen Computer herunter und geben Sie den folgenden Befehl ein:
download ~/certificates/certs/sensor-thl-001-device.cert.pfx
Hinweis
Eine Eingabeaufforderung im Browser erscheint, in der Sie aufgefordert werden, die Datei zu speichern. Wählen Sie Klicken Sie hier, um Ihre Datei herunterzuladen. aus oder Datei herunterladen herunter, wenn Sie dazu aufgefordert werden. Die Datei wird auf Ihren Computer in den Ordner Downloads heruntergeladen.
Laden Sie das zweite generierte X.509-Gerätezertifikat aus der Cloud Shell auf Ihren lokalen Computer herunter, indem Sie den folgenden Befehl eingeben:
download ~/certificates/certs/sensor-thl-002-device.cert.pfx
Hinweis
Eine Eingabeaufforderung im Browser erscheint, in der Sie aufgefordert werden, die Datei zu speichern. Wählen Sie Klicken Sie hier, um Ihre Datei herunterzuladen. aus oder Datei herunterladen herunter, wenn Sie dazu aufgefordert werden. Die Datei wird auf Ihren Computer in den Ordner Downloads heruntergeladen.
In der nächsten Aufgabe beginnen Sie mit dem Erstellen der simulierten Geräte, die die X.509-Gerätezertifikate verwenden, um sich beim Device Provisioning Service (DPS) zu authentifizieren.
Aufgabe 2: Konfigurieren eines simulierten Geräts
In dieser Übung führen Sie die folgenden Schritte aus:
- Erstellen Sie zwei Projektordner
- Kopieren Sie das heruntergeladene Gerätezertifikat in den Stammordner der Anwendung
- Konfigurieren Sie die Anwendung in Visual Studio Code zur Verwendung des DPS-ID-Bereichs
Erstellen Sie auf Ihrem Computer zwei Ordner in Ihrem bevorzugten Arbeitsverzeichnis:
- sensor-thl-001-device
- sensor-thl-002-device
Verschieben Sie die beiden Zertifikatdateien, die Sie im vorherigen Schritt heruntergeladen haben, in die Ordner, und stellen Sie sicher, dass die Zertifikatdatei mit dem Ordnernamen übereinstimmt.
Wichtig
Auf einem produktiven Gerät sollte die Zertifikatdatei mithilfe eines Hardwaresicherheitsmoduls (Hardware Security Module, HSM) sicher gespeichert werden.
Öffnen Sie den ersten Ordner sensor-thl-001-device in Visual Studio Code.
Erstellen Sie eine Datei namens ContainerDevice.csproj.
Fügen Sie den folgenden Code ein und speichern Sie die Datei:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp3.1</TargetFramework> </PropertyGroup> <ItemGroup> <None Update="sensor-thl-001-device.cert.pfx" CopyToOutputDirectory="PreserveNewest" /> <PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.*" /> <PackageReference Include="Microsoft.Azure.Devices.Provisioning.Transport.Mqtt" Version="1.*" /> <PackageReference Include="Microsoft.Azure.Devices.Provisioning.Transport.Amqp" Version="1.*" /> <PackageReference Include="Microsoft.Azure.Devices.Provisioning.Transport.Http" Version="1.*" /> </ItemGroup> </Project>
Diese Konfiguration stellt sicher, dass die Zertifikatsdatei sensor-thl-001-device.cert.pfx beim Kompilieren des C#-Codes in den Buildordner kopiert und für das Programm verfügbar gemacht wird, wenn es ausgeführt wird.
Wenn Sie die Zertifikatdatei anderes benannt haben, aktualisieren Sie den Variablenwert entsprechend.
Erstellen Sie eine Datei mit dem Namen Program.cs.
Fügen Sie den folgenden Code in Program.cs ein:
// 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; using Microsoft.Azure.Devices.Provisioning.Client.Transport; using Microsoft.Azure.Devices.Shared; using System; using System.IO; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using System.Security.Cryptography.X509Certificates; namespace ContainerDevice { class Program { // Azure Device Provisioning Service (DPS) ID Scope private static string dpsIdScope = "PASTE_YOUR_DPS_ID_SCOPE_HERE"; // Certificate (PFX) File Name private static string certificateFileName = "sensor-thl-001-device.cert.pfx"; // Certificate (PFX) Password private static string certificatePassword = "1234"; // NOTE: For the purposes of this example, the certificatePassword is // hard coded. In a production device, the password will need to be stored // in a more secure manner. Additionally, the certificate file (PFX) should // be stored securely on a production device using a Hardware Security Module. private const string GlobalDeviceEndpoint = "global.azure-devices-provisioning.net"; private static int telemetryDelay = 1; private static DeviceClient deviceClient; public static async Task Main(string[] args) { X509Certificate2 certificate = LoadProvisioningCertificate(); using (var security = new SecurityProviderX509Certificate(certificate)) using (var transport = new ProvisioningTransportHandlerAmqp(TransportFallbackType.TcpOnly)) { ProvisioningDeviceClient provClient = ProvisioningDeviceClient.Create(GlobalDeviceEndpoint, dpsIdScope, security, transport); using (deviceClient = await ProvisionDevice(provClient, security)) { await deviceClient.OpenAsync().ConfigureAwait(false); // INSERT Setup OnDesiredPropertyChanged Event Handling below here await deviceClient.SetDesiredPropertyUpdateCallbackAsync(OnDesiredPropertyChanged, null).ConfigureAwait(false); // INSERT Load Device Twin Properties below here var twin = await deviceClient.GetTwinAsync().ConfigureAwait(false); await OnDesiredPropertyChanged(twin.Properties.Desired, null); // Start reading and sending device telemetry Console.WriteLine("Start reading and sending device telemetry..."); await SendDeviceToCloudMessagesAsync(); await deviceClient.CloseAsync().ConfigureAwait(false); } } } private static X509Certificate2 LoadProvisioningCertificate() { var certificateCollection = new X509Certificate2Collection(); certificateCollection.Import(certificateFileName, certificatePassword, X509KeyStorageFlags.UserKeySet); X509Certificate2 certificate = null; foreach (X509Certificate2 element in certificateCollection) { Console.WriteLine($"Found certificate: {element?.Thumbprint} {element?.Subject}; PrivateKey: {element?.HasPrivateKey}"); if (certificate == null && element.HasPrivateKey) { certificate = element; } else { element.Dispose(); } } if (certificate == null) { throw new FileNotFoundException($"{certificateFileName} did not contain any certificate with a private key."); } Console.WriteLine($"Using certificate {certificate.Thumbprint} {certificate.Subject}"); return certificate; } private static async Task<DeviceClient> ProvisionDevice(ProvisioningDeviceClient provisioningDeviceClient, SecurityProviderX509Certificate security) { var result = await provisioningDeviceClient.RegisterAsync().ConfigureAwait(false); Console.WriteLine($"ProvisioningClient AssignedHub: {result.AssignedHub}; DeviceID: {result.DeviceId}"); if (result.Status != ProvisioningRegistrationStatusType.Assigned) { throw new Exception($"DeviceRegistrationResult.Status is NOT 'Assigned'"); } var auth = new DeviceAuthenticationWithX509Certificate( result.DeviceId, security.GetAuthenticationCertificate()); return DeviceClient.Create(result.AssignedHub, auth, TransportType.Amqp); } private static async Task SendDeviceToCloudMessagesAsync() { var sensor = new EnvironmentSensor(); while (true) { var currentTemperature = sensor.ReadTemperature(); var currentHumidity = sensor.ReadHumidity(); var currentPressure = sensor.ReadPressure(); var currentLocation = sensor.ReadLocation(); var messageString = CreateMessageString(currentTemperature, currentHumidity, currentPressure, currentLocation); var message = new Message(Encoding.ASCII.GetBytes(messageString)); // Add a custom application property to the message. // An IoT hub can filter on these properties without access to the message body. message.Properties.Add("temperatureAlert", (currentTemperature > 30) ? "true" : "false"); // Send the telemetry message await deviceClient.SendEventAsync(message); Console.WriteLine("{0} > Sending message: {1}", DateTime.Now, messageString); // Delay before next Telemetry reading await Task.Delay(telemetryDelay * 1000); } } private static string CreateMessageString(double temperature, double humidity, double pressure, EnvironmentSensor.Location location) { // Create an anonymous object that matches the data structure we wish to send var telemetryDataPoint = new { temperature = temperature, humidity = humidity, pressure = pressure, latitude = location.Latitude, longitude = location.Longitude }; var messageString = JsonConvert.SerializeObject(telemetryDataPoint); // Create a JSON string from the anonymous object return JsonConvert.SerializeObject(telemetryDataPoint); } private static async Task OnDesiredPropertyChanged(TwinCollection desiredProperties, object userContext) { Console.WriteLine("Desired Twin Property Changed:"); Console.WriteLine($"{desiredProperties.ToJson()}"); // Read the desired Twin Properties if (desiredProperties.Contains("telemetryDelay")) { string desiredTelemetryDelay = desiredProperties["telemetryDelay"]; if (desiredTelemetryDelay != null) { telemetryDelay = int.Parse(desiredTelemetryDelay); } // if desired telemetryDelay is null or unspecified, don't change it } // Report Twin Properties var reportedProperties = new TwinCollection(); reportedProperties["telemetryDelay"] = telemetryDelay.ToString(); await deviceClient.UpdateReportedPropertiesAsync(reportedProperties).ConfigureAwait(false); Console.WriteLine("Reported Twin Properties:"); Console.WriteLine($"{reportedProperties.ToJson()}"); } } internal class EnvironmentSensor { // Initial telemetry values double minTemperature = 20; double minHumidity = 60; double minPressure = 1013.25; double minLatitude = 39.810492; double minLongitude = -98.556061; Random rand = new Random(); internal class Location { internal double Latitude; internal double Longitude; } internal double ReadTemperature() { return minTemperature + rand.NextDouble() * 15; } internal double ReadHumidity() { return minHumidity + rand.NextDouble() * 20; } internal double ReadPressure() { return minPressure + rand.NextDouble() * 12; } internal Location ReadLocation() { return new Location { Latitude = minLatitude + rand.NextDouble() * 0.5, Longitude = minLongitude + rand.NextDouble() * 0.5 }; } } }
Suchen Sie die Variable GlobalDeviceEndpoint und beachten Sie, dass der Wert auf den globalen Geräteendpunkt für den Device Provisioning Service festgelegt ist. Sie sollten einen Code sehen, der ähnlich wie der folgende aussieht:
private const string GlobalDeviceEndpoint = "global.azure-devices-provisioning.net";
Alle Geräte, die eine Verbindung zum DPS herstellen, werden mit diesem globalen Geräteendpunkt-DNS-Namen konfiguriert.
Die Anwendung ContainerDevice verwendet X.509-Zertifikate als Nachweis. Aus Sicht der Anwendung spielt es keine Rolle, dass dieses Gerät eine Verbindung mit einer Gruppenregistrierung statt einer einzelnen Registrierung herstellt. Das gesamte Gerät muss eine Verbindung mit seiner zugewiesenen DPS-Instanz herstellen und seine zugewiesenen IoT-Hub-Informationen empfangen.
Suchen Sie die Variable dpsIdScope
Aktualisieren Sie den zugewiesenen Wert mithilfe des DPS-ID-Bereichs, den Sie beim Erstellen der DPS-Instanz abgerufen haben.
Wenn Sie Ihren Code aktualisieren, sollte er jetzt in etwa wie folgt aussehen:
private static string dpsIdScope = "0ne00000000";
Hinweis
Wenn Sie nicht über den DPS-ID-Bereich (idScope) verfügen, können Sie eine Kopie abrufen, indem Sie den CLI-Befehl
az iot dps show --name dps-$suffix
ausführen.Suchen Sie die Variable certificateFileName und beachten Sie, dass der Wert auf den Namen der von Ihnen generierten Gerätezertifikatdatei festgelegt ist (sensor-thl-001-device.cert.pfx). Wenn Sie die Zertifikatdatei anderes benannt haben, aktualisieren Sie den Variablenwert entsprechend.
Die Geräteanwendung verwendet ein X.509-Zertifikat für die Authentifizierung. Diese Variable zeigt dem Gerätecode, welche Datei das X.509-Gerätezertifikat enthält, das beim Authentifizieren mit dem Device Provisioning Service verwendet wird.
Suchen Sie die Variable CertificatePassword und beachten Sie, dass der Wert auf das Standardkennwort festgelegt ist, das vom Skript certGen.sh definiert ist.
Die Variable CertificatePassword enthält das Kennwort für das X.509-Gerätezertifikat. Es ist auf das Standardkennwort 1234 festgelegt, das vom Hilfsskript certGen.shbeim Generieren der X.509-Zertifikate verwendet wird.
Wichtig
Für diese Übung ist das Kennwort hartcodiert. In einem realen Szenario muss das Kennwort sicherer gespeichert werden, z. B. in einem Azure Key Vault. Auf einem produktiven Gerät sollte die Zertifikatdatei (PFX) zusätzlich mithilfe eines Hardwaresicherheitsmoduls (Hardware Security Module, HSM) sicher gespeichert werden.
Das HSM dient der sicheren, hardwarebasierten Speicherung von Gerätegeheimnissen und gilt als die sicherste Form der Geheimnisspeicherung. Sowohl X.509-Zertifikate als auch SAS-Token können im HSM gespeichert werden. HSMs können mit allen Nachweismechanismen verwendet werden, die der Bereitstellungsdienst unterstützt.
Öffnen Sie das Visual Studio Code-Menü Datei und wählen Sie die Option Speichern aus.
Kopieren Sie sensor-thl-001-device.cert.pfx aus Ihrem Ordner Downloads in den Ordner sensor-thl-001-device.
Kopieren Sie sensor-thl-002-device.cert.pfx aus Ihrem Ordner Downloads in den Ordner sensor-thl-002-device.
Überprüfen Ihrer Arbeit
Öffnen Sie in Visual Studio Code das Menü Terminal und wählen Sie dann Neues Terminal aus.
Stellen Sie an der Eingabeaufforderung „Terminal“ sicher, dass der Ordner \sensor-thl-001-device das aktuelle Arbeitsverzeichnis ist.
Erstellen Sie in der Visual Studio Terminal-Eingabeaufforderung den Code, um nach Fehlern zu suchen.
dotnet build ContainerDevice.csproj
Wenn Buildfehler aufgelistet werden, beheben Sie diese, bevor Sie mit der nächsten Übung fortfahren.