I'm putting together a test harness for IoT as an internal reference for our company.
I can switch between SymmetricKey and X509 Certs, provision either using DPS and run tests against the endpoints we're interested in.
As I come to test File Upload functionality, when using SymmetricKey, I can request and post a file to the storage account. Using exactly the same method to do the same with X509 cert as the authentication method, I encounter the issue below.
When using X509 using the same 'DeviceClient' instance, I can send data, update and recall twins, Send C2D messages and invoke methods. Only GetFileUploadSasUriAsync encounters this issue and I'm stumped.
Line which generates the error:
"FileUploadSasUriResponse sasUri = await _deviceClient.GetFileUploadSasUriAsync(fileUploadSasUriRequest, CancellationToken.None);"
What am I doing wrong?
CODE
InitialiseIoTClient
var certPath = AppDomain.CurrentDomain.BaseDirectory + storedConfig.CertName;
Console.WriteLine($"Loading the certificate...");
using X509Certificate2 certificate = new X509Certificate2(certPath, storedConfig.CertPassword);
using var security = new SecurityProviderX509Certificate(certificate);
using var auth = new DeviceAuthenticationWithX509Certificate(storedConfig.DeviceName, certificate);
IoTDeviceClient = DeviceClient.Create(storedConfig.AssignedIoTHub, auth, TransportType.Mqtt);
IoTDeviceClient.SetRetryPolicy(new ExponentialBackoff(5, new TimeSpan(0, 1, 30), new TimeSpan(0, 1, 30), new TimeSpan(0, 1, 15)));
IoTDeviceClient.OpenAsync().Wait();
FileOperations Class
public class FileOperations
{
readonly DeviceClient _deviceClient;
public static LocalConfigStore ConfigStore = new LocalConfigStore();
/// <summary>
/// Load the client and start listening for file upload notifications
/// </summary>
internal FileOperations()
{
_deviceClient = Program.IoTDeviceClient;
ReceiveFileUploadNotificationAsync();
}
/// <summary>
/// This method is used to upload a file to the IoT Hub
/// </summary>
public async void UploadFile()
{
// Generate a large file to upload
var commonOps = new Utils.Common();
var testFilePath = commonOps.GenerateLargeFile();
// Create stream
using var fileStreamSource = new FileStream(testFilePath, FileMode.Open);
var fileName = Path.GetFileName(testFilePath);
var fileUploadTime = Stopwatch.StartNew();
// Get the SAS URI for the file upload
var fileUploadSasUriRequest = new FileUploadSasUriRequest
{
BlobName = fileName
};
FileUploadSasUriResponse sasUri = await _deviceClient.GetFileUploadSasUriAsync(fileUploadSasUriRequest, CancellationToken.None);
Uri uploadUri = sasUri.GetBlobUri();
Console.WriteLine($"Uploading file {fileName} to {uploadUri}.");
// Upload the file to blob storage
var blockBlobClient = new BlockBlobClient(uploadUri);
await blockBlobClient.UploadAsync(fileStreamSource, new BlobUploadOptions());
// Notify IoT Hub that the file upload is complete
var successfulFileUploadCompletionNotification = new FileUploadCompletionNotification
{
// Mandatory. Must be the same value as the correlation id returned in the sas uri response
CorrelationId = sasUri.CorrelationId,
// Mandatory. Will be present when service client receives this file upload notification
IsSuccess = true,
// Optional, user defined status code. Will be present when service client receives this file upload notification
StatusCode = 200,
// Optional, user-defined status description. Will be present when service client receives this file upload notification
StatusDescription = "Success"
};
// Notify IoT Hub that the file upload is complete
await _deviceClient.CompleteFileUploadAsync(successfulFileUploadCompletionNotification);
fileUploadTime.Stop();
Console.WriteLine($"File upload completed successfully.");
Console.WriteLine($"Time to upload file: {fileUploadTime.Elapsed}.");
Console.WriteLine($"Deleting Test File.");
fileStreamSource.Dispose();
File.Delete(testFilePath);
Console.WriteLine("press enter to continue.");
}
/// <summary>
/// This method is used to receive file upload notifications
/// </summary>
private async void ReceiveFileUploadNotificationAsync()
{
var connectionString = ConfigurationManager.AppSettings["ConnectionStringIotHubOwner"];
// Create service client to receive file upload notifications
ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(connectionString);
// Get file upload notification receiver
var notificationReceiver = serviceClient.GetFileNotificationReceiver();
Console.WriteLine("\nReceiving file upload notification from service");
while (true)
{
// Wait for file upload notification
var fileUploadNotification = await notificationReceiver.ReceiveAsync();
if (fileUploadNotification == null) continue;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Received file upload notification: {0}", string.Join(", ", fileUploadNotification.BlobName));
Console.ResetColor();
await notificationReceiver.CompleteAsync(fileUploadNotification);
}
}
}
ERROR
{"Message":"ErrorCode:IotHubUnauthorizedAccess;Unauthorized","ExceptionMessage":"Tracking ID:2d210xxxxxxxxe3afdc-G:0-TimeStamp:08/04/2023 15:48:25"}
at Microsoft.Azure.Devices.Client.Transport.HttpClientHelper.<ExecuteAsync>d__22.MoveNext()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.Azure.Devices.Client.Transport.HttpClientHelper.<PostAsync>d__18`2.MoveNext()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.Azure.Devices.Client.Transport.HttpTransportHandler.<GetFileUploadSasUriAsync>d__15.MoveNext()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at RemoteIoTDeviceConsoleApp.Class.Data.FileOperations.<UploadFile>d__3.MoveNext() in E:\zzzz\xxxx\xxx\xxx\Class\Data\FileOperations.cs:line 53
The error message "ErrorCode:IotHubUnauthorizedAccess;Unauthorized" you're encountering indicates an authorization issue when trying to access the IoT Hub service from your device using X.509 certificate authentication.Giving you an example sample of File Upload in IoT Hub using X509 Certs with GetFileUploadSasUriAsync.
var cert = new X509Certificate2("C:\\Tools\\cert\\MyCert.cer");
var auth = new DeviceAuthenticationWithX509Certificate("mydevice", cert);
var DClnt = DeviceClient.Create("HostName", auth);
private const string CertificateThumbprint = "E9DB5307C576E23009";
private const string IotHubHostName = "sampath.azure-devices.net";
private const string DeviceId = "fa";
private const string FilePath = "C:/Users/Hello.js";
static async Task Main(string[] args)
{
try
{
var auth = new DeviceAuthenticationWithX509Certificate(DeviceId, GetCertificateByThumbprint(CertificateThumbprint));
var deviceClient = DeviceClient.Create(IotHubHostName, auth, TransportType.Http1);
await SendToBlobSample(deviceClient);
}
catch (Exception ex)
{
Console.WriteLine("{0}\n", ex.Message);
if (ex.InnerException != null)
{
Console.WriteLine(ex.InnerException.Message + "\n");
}
}
}
static async Task SendToBlobSample(DeviceClient deviceClient)
{
var fileStreamSource = new FileStream(FilePath, FileMode.Open);
var fileName = Path.GetFileName(FilePath);
Console.WriteLine("Uploading File: {0}", fileName);
var watch = System.Diagnostics.Stopwatch.StartNew();
var fileUploadSasUriRequest = new FileUploadSasUriRequest
{
BlobName = fileName
};
FileUploadSasUriResponse sasUri = await deviceClient.GetFileUploadSasUriAsync(fileUploadSasUriRequest);
Uri uploadUri = sasUri.GetBlobUri();
var blockBlobClient = new BlockBlobClient(uploadUri);
await blockBlobClient.UploadAsync(fileStreamSource, new BlobUploadOptions());
watch.Stop();
Console.WriteLine("Time to upload file: {0}ms\n", watch.ElapsedMilliseconds);
Console.WriteLine("File uploaded to Azure Blob Storage: {0}", fileName);
var successfulFileUploadCompletionNotification = new FileUploadCompletionNotification
{
CorrelationId = sasUri.CorrelationId,
IsSuccess = true,
StatusCode = 200,
StatusDescription = "Success"
};
await deviceClient.CompleteFileUploadAsync(successfulFileUploadCompletionNotification);
}
static X509Certificate2 GetCertificateByThumbprint(string thumbprint)
{
using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
if (certificates.Count > 0)
{
return certificates[0];
}
else
{
throw new Exception("Certificate not found.");
}
}
}