Ćwiczenie — konfigurowanie symulowanego urządzenia przy użyciu certyfikatu X.509
W tym ćwiczeniu wygenerujesz certyfikat urządzenia przy użyciu certyfikatu głównego i skonfigurujesz symulowane urządzenie, które łączy się przy użyciu certyfikatu urządzenia na potrzeby zaświadczania.
Zadanie 1. Generowanie certyfikatu urządzenia
W piaskownicy platformy Azure upewnij się, że pracujesz w katalogu ~/certificates, w którym został pobrany skrypt pomocnika certGen.sh:
cd ~/certificates
Wygeneruj certyfikat urządzenia X.509 w łańcuchu certyfikatów urzędu certyfikacji dla pierwszego urządzenia przy użyciu następującego polecenia:
./certGen.sh create_device_certificate sensor-thl-001
To polecenie tworzy nowe urządzenie X.509 certyfikatu pem i pfx pary, które są podpisane przez certyfikat urzędu certyfikacji, który został wygenerowany wcześniej. Zwróć uwagę, że identyfikator urządzenia (sensor-thl-001) jest przekazywany do polecenia create_device_certificate skryptu certGen.sh . Ten identyfikator urządzenia jest ustawiany jako nazwa pospolita lub CN=, wartość certyfikatu urządzenia. To polecenie generuje certyfikat X.509 urządzenia liścia dla symulowanego urządzenia, który służy do uwierzytelniania urządzenia za pomocą usługi Device Provisioning Service (DPS). W tym module użyto pliku certyfikatu pfx do zweryfikowania programu, który nawiązuje połączenie z usługą DPS z komputera.
Po zakończeniu create_device_certificate polecenia wygenerowana para certyfikatów urządzenia X.509 nosi nazwę new-device.cert.pfx i new-device.cert.pem odpowiednio i znajduje się w podkatalogu /certs.
Ważne
To polecenie zastępuje dowolny istniejący certyfikat urządzenia w podkatalogu /certs . Jeśli chcesz utworzyć certyfikat dla wielu urządzeń, za każdym razem, gdy uruchomisz polecenie, zapisz kopię pliku new-device.cert.pfx i new-device.cert.pem .
Zmień nazwę plików certyfikatów urządzenia na nazwę urządzenia sensor-thl-001 utworzoną w ostatnim kroku, używając następujących poleceń:
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
Utwórz i zmień nazwę plików certyfikatów dla drugiego urządzenia przy użyciu następujących poleceń:
./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
Pobierz pierwszy wygenerowany certyfikat urządzenia X.509 z usługi Cloud Shell do komputera lokalnego, wprowadź następujące polecenie:
download ~/certificates/certs/sensor-thl-001-device.cert.pfx
Uwaga
Poszukaj monitu przeglądarki z prośbą o zapisanie pliku. Wybierz przycisk Kliknij tutaj, aby pobrać plik. Lub Pobierz komunikat o pliku po wyświetleniu monitu. Plik zostanie pobrany do folderu Pobierania komputera.
Pobierz drugi wygenerowany certyfikat urządzenia X.509 z usługi Cloud Shell do komputera lokalnego przy użyciu następującego polecenia:
download ~/certificates/certs/sensor-thl-002-device.cert.pfx
Uwaga
Poszukaj monitu przeglądarki z prośbą o zapisanie pliku. Wybierz przycisk Kliknij tutaj, aby pobrać plik. Lub Pobierz komunikat o pliku po wyświetleniu monitu. Plik zostanie pobrany do folderu Pobierania komputera.
W następnym zadaniu rozpoczniesz kompilowanie symulowanych urządzeń, które używają certyfikatów urządzeń X.509 do uwierzytelniania za pomocą usługi Device Provisioning Service (DPS).
Zadanie 2. Konfigurowanie symulowanego urządzenia
W tym zadaniu wykonasz następujące czynności:
- Tworzenie dwóch folderów projektu
- Skopiuj pobrany certyfikat urządzenia do folderu głównego aplikacji
- Konfigurowanie aplikacji w programie Visual Studio Code do korzystania z zakresu identyfikatorów usługi DPS
Na komputerze deweloperskim utwórz dwa foldery w preferowanym katalogu roboczym:
- sensor-thl-001-device
- czujnik-thl-002-device
Przenieś dwa pliki certyfikatów pobrane w poprzednim kroku do folderów, upewniając się, że plik certyfikatu jest zgodny z nazwą folderu.
Ważne
Na urządzeniu produkcyjnym plik certyfikatu powinien być przechowywany bezpiecznie przy użyciu sprzętowego modułu zabezpieczeń (HSM).
Otwórz pierwszy folder sensor-thl-001-device w programie Visual Studio Code.
Utwórz plik o nazwie ContainerDevice.csproj.
Wklej następujący kod i zapisz plik:
<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>
Ta konfiguracja gwarantuje, że plik certyfikatu sensor-thl-001-device.cert.pfx zostanie skopiowany do folderu kompilacji po skompilowaniu kodu języka C# i udostępniony programowi w celu uzyskania dostępu do jego wykonania.
Jeśli nazwano plik certyfikatu innym elementem, zaktualizuj wartość zmiennej tak, aby odpowiadała.
Utwórz plik o nazwie Program.cs.
Wklej następujący kod do Program.cs:
// 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 }; } } }
Znajdź zmienną GlobalDeviceEndpoint i zwróć uwagę, że jej wartość jest ustawiona na globalny punkt końcowy urządzenia dla usługi Device Provisioning Service. Powinien zostać wyświetlony kod podobny do następującego:
private const string GlobalDeviceEndpoint = "global.azure-devices-provisioning.net";
Wszystkie urządzenia, które łączą się z usługą DPS, są skonfigurowane z tą globalną nazwą DNS punktu końcowego urządzenia.
Aplikacja ContainerDevice używa certyfikatów X.509 jako mechanizmu zaświadczania. Z perspektywy aplikacji nie ma znaczenia, że to urządzenie łączy się przy użyciu rejestracji grupowej, a nie rejestracji indywidualnej. Wszystkie urządzenia muszą nawiązać połączenie z przypisanym wystąpieniem usługi DPS i otrzymywać przypisane informacje o centrum IoT.
Lokalizowanie zmiennej dpsIdScope
Zaktualizuj przypisaną wartość przy użyciu zakresu identyfikatora usługi DPS pobranego podczas tworzenia wystąpienia usługi DPS.
Po zaktualizowaniu kodu powinien on wyglądać podobnie do następującego:
private static string dpsIdScope = "0ne00000000";
Uwaga
Jeśli nie masz wartości zakresu identyfikatora usługi DPS (idScope), możesz uzyskać kopię, uruchamiając polecenie interfejsu wiersza polecenia
az iot dps show --name dps-$suffix
.Znajdź zmienną certificateFileName i zwróć uwagę, że jej wartość jest ustawiona na nazwę wygenerowanego pliku certyfikatu urządzenia (sensor-thl-001-device.cert.pfx). Jeśli nazwano plik certyfikatu innym elementem, zaktualizuj wartość zmiennej tak, aby odpowiadała.
Aplikacja urządzenia używa certyfikatu X.509 do uwierzytelniania. Ta zmienna informuje kod urządzenia, który plik zawiera certyfikat urządzenia X.509 używany podczas uwierzytelniania w usłudze Device Provisioning.
Znajdź zmienną certificatePassword i zwróć uwagę, że jej wartość jest ustawiona na domyślne hasło zdefiniowane przez skrypt certGen.sh .
Zmienna certificatePassword zawiera hasło certyfikatu urządzenia X.509. Jest ona ustawiona na 1234, czyli domyślne hasło używane przez skrypt pomocnika certGen.sh podczas generowania certyfikatów X.509.
Ważne
Na potrzeby tego laboratorium hasło jest zakodowane w kodzie. W scenariuszu produkcyjnym hasło musi być przechowywane w sposób bardziej bezpieczny, na przykład w usłudze Azure Key Vault. Ponadto plik certyfikatu (PFX) powinien być bezpiecznie przechowywany na urządzeniu produkcyjnym przy użyciu sprzętowego modułu zabezpieczeń (HSM).
Moduł HSM jest używany do bezpiecznego, sprzętowego przechowywania wpisów tajnych urządzeń i jest najbezpieczniejszą formą magazynu wpisów tajnych. Zarówno certyfikaty X.509, jak i tokeny SAS mogą być przechowywane w module HSM. Moduły HSM mogą być używane ze wszystkimi mechanizmami zaświadczania, które obsługuje usługa aprowizacji.
Otwórz menu Plik programu Visual Studio Code, a następnie wybierz pozycję Zapisz.
Skopiuj plik sensor-thl-001-device.cert.pfx z folderu Downloads do folderu sensor-thl-001-device .
Skopiuj plik sensor-thl-002-device.cert.pfx z folderu Downloads do folderu sensor-thl-002-device .
Weryfikowanie pracy
W programie Visual Studio otwórz menu Terminal , a następnie wybierz pozycję Nowy terminal.
W wierszu polecenia terminalu upewnij się, że bieżącym katalogiem roboczym jest folder \sensor-thl-001-device .
W wierszu polecenia terminalu programu Visual Studio skompiluj kod, aby sprawdzić błędy.
dotnet build ContainerDevice.csproj
Jeśli na liście wystąpią jakiekolwiek błędy kompilacji, napraw je przed kontynuowaniem następnego ćwiczenia.