Skip to content

Commit

Permalink
File upload SAS URI operation requires certificate to be copied (Azur…
Browse files Browse the repository at this point in the history
…e#2350)

* Supply cert and proxy to file upload operation

* Code cleanup
  • Loading branch information
David R. Williamson authored Apr 15, 2022
1 parent 8e93bed commit 41e8eb4
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 185 deletions.
4 changes: 2 additions & 2 deletions azureiot.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29709.97
# Visual Studio Version 17
VisualStudioVersion = 17.1.32328.378
MinimumVisualStudioVersion = 15.0.26124.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "iothub", "iothub", "{537FA828-124D-4C70-9FC1-89301D9E776D}"
EndProject
Expand Down
21 changes: 16 additions & 5 deletions e2e/test/iothub/FileUploadE2ETests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net;
using System.Security.Cryptography.X509Certificates;
Expand Down Expand Up @@ -52,6 +51,14 @@ public async Task FileUpload_GetFileUploadSasUri_Http_x509_NoFileTransportSettin
await GetSasUriAsync(Client.TransportType.Http1, smallFileBlobName, true).ConfigureAwait(false);
}

[LoggedTestMethod]
[TestCategory("LongRunning")]
public async Task FileUpload_GetFileUploadSasUri_Mqtt_x509_NoFileTransportSettingSpecified()
{
string smallFileBlobName = await GetTestFileNameAsync(FileSizeSmall).ConfigureAwait(false);
await GetSasUriAsync(Client.TransportType.Mqtt, smallFileBlobName, true).ConfigureAwait(false);
}

[LoggedTestMethod]
[TestCategory("LongRunning")]
[Obsolete]
Expand Down Expand Up @@ -200,10 +207,14 @@ private async Task UploadFileAsync(Client.TransportType transport, string filena

private async Task GetSasUriAsync(Client.TransportType transport, string blobName, bool useX509auth = false)
{
using TestDevice testDevice = await TestDevice.GetTestDeviceAsync(
Logger,
_devicePrefix,
useX509auth ? TestDeviceType.X509 : TestDeviceType.Sasl).ConfigureAwait(false);
using TestDevice testDevice = await TestDevice
.GetTestDeviceAsync(
Logger,
_devicePrefix,
useX509auth
? TestDeviceType.X509
: TestDeviceType.Sasl)
.ConfigureAwait(false);

DeviceClient deviceClient;
X509Certificate2 cert = null;
Expand Down
39 changes: 22 additions & 17 deletions iothub/device/src/AuthenticationMethodFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,43 +11,48 @@ namespace Microsoft.Azure.Devices.Client
/// </summary>
public sealed class AuthenticationMethodFactory
{
internal static IAuthenticationMethod GetAuthenticationMethod(IotHubConnectionStringBuilder iotHubConnectionStringBuilder)
internal static IAuthenticationMethod GetAuthenticationMethod(IotHubConnectionStringBuilder csBuilder)
{
if (iotHubConnectionStringBuilder.SharedAccessKeyName != null)
if (csBuilder.SharedAccessKeyName != null)
{
return new DeviceAuthenticationWithSharedAccessPolicyKey(
iotHubConnectionStringBuilder.DeviceId, iotHubConnectionStringBuilder.SharedAccessKeyName, iotHubConnectionStringBuilder.SharedAccessKey);
csBuilder.DeviceId,
csBuilder.SharedAccessKeyName,
csBuilder.SharedAccessKey);
}
else if (iotHubConnectionStringBuilder.SharedAccessKey != null)
else if (csBuilder.SharedAccessKey != null)
{
if (iotHubConnectionStringBuilder.ModuleId != null)
if (csBuilder.ModuleId != null)
{
return new ModuleAuthenticationWithRegistrySymmetricKey(
iotHubConnectionStringBuilder.DeviceId, iotHubConnectionStringBuilder.ModuleId, iotHubConnectionStringBuilder.SharedAccessKey);
csBuilder.DeviceId,
csBuilder.ModuleId,
csBuilder.SharedAccessKey);
}
else
{
return new DeviceAuthenticationWithRegistrySymmetricKey(
iotHubConnectionStringBuilder.DeviceId, iotHubConnectionStringBuilder.SharedAccessKey);
csBuilder.DeviceId,
csBuilder.SharedAccessKey);
}
}
else if (iotHubConnectionStringBuilder.SharedAccessSignature != null)
else if (csBuilder.SharedAccessSignature != null)
{
return iotHubConnectionStringBuilder.ModuleId != null
return csBuilder.ModuleId != null
? new ModuleAuthenticationWithToken(
iotHubConnectionStringBuilder.DeviceId,
iotHubConnectionStringBuilder.ModuleId,
iotHubConnectionStringBuilder.SharedAccessSignature)
csBuilder.DeviceId,
csBuilder.ModuleId,
csBuilder.SharedAccessSignature)
: (IAuthenticationMethod)new DeviceAuthenticationWithToken(
iotHubConnectionStringBuilder.DeviceId,
iotHubConnectionStringBuilder.SharedAccessSignature);
csBuilder.DeviceId,
csBuilder.SharedAccessSignature);
}
else if (iotHubConnectionStringBuilder.UsingX509Cert)
else if (csBuilder.UsingX509Cert)
{
return new DeviceAuthenticationWithX509Certificate(iotHubConnectionStringBuilder.DeviceId, iotHubConnectionStringBuilder.Certificate);
return new DeviceAuthenticationWithX509Certificate(csBuilder.DeviceId, csBuilder.Certificate);
}

throw new InvalidOperationException("Unsupported Authentication Method {0}".FormatInvariant(iotHubConnectionStringBuilder));
throw new InvalidOperationException($"Unsupported authentication method in '{csBuilder}'.");
}

/// <summary>
Expand Down
55 changes: 34 additions & 21 deletions iothub/device/src/ClientFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Linq;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using Microsoft.Azure.Devices.Client.Exceptions;
Expand Down Expand Up @@ -122,12 +123,14 @@ internal static InternalClient Create(
throw new ArgumentException("No certificate was found. To use certificate authentication certificate must be present.");
}

InternalClient dc = CreateFromConnectionString(
InternalClient internalClient = CreateFromConnectionString(
connectionStringBuilder.ToString(),
authenticationMethod,
PopulateCertificateInTransportSettings(connectionStringBuilder, transportType),
null,
options);

dc.Certificate = connectionStringBuilder.Certificate;
internalClient.Certificate = connectionStringBuilder.Certificate;

// Install all the intermediate certificates in the chain if specified.
if (connectionStringBuilder.ChainCertificates != null)
Expand All @@ -143,7 +146,7 @@ internal static InternalClient Create(
}
}

return dc;
return internalClient;
}

return CreateFromConnectionString(connectionStringBuilder.ToString(), authenticationMethod, transportType, null, options);
Expand Down Expand Up @@ -296,7 +299,7 @@ internal static InternalClient CreateFromConnectionString(
throw new ArgumentException("Connection string must not contain DeviceId keyvalue parameter", nameof(connectionString));
}

return CreateFromConnectionString(connectionString + ";" + DeviceId + "=" + deviceId, transportType, options);
return CreateFromConnectionString($"{connectionString};{DeviceId}={deviceId}", transportType, options);
}

/// <summary>
Expand All @@ -306,8 +309,10 @@ internal static InternalClient CreateFromConnectionString(
/// <param name="transportSettings">Prioritized list of transports and their settings</param>
/// <param name="options">The options that allow configuration of the device client instance during initialization.</param>
/// <returns>InternalClient</returns>
internal static InternalClient CreateFromConnectionString(string connectionString,
ITransportSettings[] transportSettings, ClientOptions options = default)
internal static InternalClient CreateFromConnectionString(
string connectionString,
ITransportSettings[] transportSettings,
ClientOptions options = default)
{
return CreateFromConnectionString(connectionString, null, transportSettings, null, options);
}
Expand All @@ -317,10 +322,14 @@ internal static InternalClient CreateFromConnectionString(string connectionStrin
/// </summary>
/// <param name="connectionString">Connection string for the IoT hub (without DeviceId)</param>
/// <param name="deviceId">Id of the device</param>
/// <param name="transportSettings">Prioritized list of transportTypes and their settings</param>
/// <param name="transportSettings">Prioritized list of transport types and their settings</param>
/// <param name="options">The options that allow configuration of the device client instance during initialization.</param>
/// <returns>InternalClient</returns>
internal static InternalClient CreateFromConnectionString(string connectionString, string deviceId, ITransportSettings[] transportSettings, ClientOptions options = default)
internal static InternalClient CreateFromConnectionString(
string connectionString,
string deviceId,
ITransportSettings[] transportSettings,
ClientOptions options = default)
{
if (connectionString == null)
{
Expand All @@ -337,7 +346,7 @@ internal static InternalClient CreateFromConnectionString(string connectionStrin
throw new ArgumentException("Connection string must not contain DeviceId keyvalue parameter", nameof(connectionString));
}

return CreateFromConnectionString(connectionString + ";" + DeviceId + "=" + deviceId, transportSettings, options);
return CreateFromConnectionString($"{connectionString};{DeviceId}={deviceId}", transportSettings, options);
}

internal static InternalClient CreateFromConnectionString(
Expand Down Expand Up @@ -430,8 +439,9 @@ internal static InternalClient CreateFromConnectionString(

// Clients that derive their authentication method from AuthenticationWithTokenRefresh will need to specify
// the token time to live and renewal buffer values through the corresponding AuthenticationWithTokenRefresh
// implementation constructors instead.
if (!(builder.AuthenticationMethod is AuthenticationWithTokenRefresh))
// implementation constructors instead, and these values are irrelevant for cert-based auth.
if (!(builder.AuthenticationMethod is AuthenticationWithTokenRefresh)
&& !(builder.AuthenticationMethod is DeviceAuthenticationWithX509Certificate))
{
builder.SasTokenTimeToLive = options?.SasTokenTimeToLive ?? default;
builder.SasTokenRenewalBuffer = options?.SasTokenRenewalBuffer ?? default;
Expand Down Expand Up @@ -501,17 +511,20 @@ internal static InternalClient CreateFromConnectionString(
/// </summary>
private static void EnsureOptionsIsSetup(X509Certificate2 cert, ref ClientOptions options)
{
if (options?.FileUploadTransportSettings == null)
if (options == null)
{
var fileUploadTransportSettings = new Http1TransportSettings { ClientCertificate = cert };
if (options == null)
{
options = new ClientOptions { FileUploadTransportSettings = fileUploadTransportSettings };
}
else
{
options.FileUploadTransportSettings = fileUploadTransportSettings;
}
options = new ClientOptions();
}

if (options.FileUploadTransportSettings == null)
{
options.FileUploadTransportSettings = new Http1TransportSettings();
}

if (cert != null
&& options.FileUploadTransportSettings.ClientCertificate == null)
{
options.FileUploadTransportSettings.ClientCertificate = cert;
}
}

Expand Down
6 changes: 3 additions & 3 deletions iothub/device/src/ClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ public class ClientOptions

/// <summary>
/// The transport settings to use for all file upload operations, regardless of what protocol the device
/// client is configured with. All file upload operations take place over https.
/// If FileUploadTransportSettings is not provided, then file upload operations will use the client certificates configured
/// in the transport settings set for the non-file upload operations.
/// client is configured with. All file upload operations take place over https.
/// If FileUploadTransportSettings is not provided, then file upload operations will use the same client certificates
/// configured in the transport settings set for client connect.
/// </summary>
public Http1TransportSettings FileUploadTransportSettings { get; set; } = new Http1TransportSettings();

Expand Down
Loading

0 comments on commit 41e8eb4

Please sign in to comment.