Describe the bug
I am using BlobClient.UploadAsync to upload an image loaded into a stream to our Azure CDN storage service, but I get an error when I try to do this.
Expected behavior
Blobs are uploaded to Azure storage that contain the data from the stream.
Actual behavior (include Exception or Stack Trace)
When this function executes, I get an error saying "System.PlatformNotSupportedException: System.Security.Cryptography.Algorithms is not supported on this platform." This happens whether I use the latest stable v12.6.0 release or the v12.7.0-preview.1 release of Azure.Storage.Blobs.
This may be related to the fact that HMAC encryption is not supported (https://www.gitmemory.com/issue/dotnet/runtime/40076/692559015)
If that's the case, why is this built-in Azure library trying to implement this encryption algorithm, and how can I upload BLOBs without this happening using the SDK?
Here is the stack trace:
System.PlatformNotSupportedException: System.Security.Cryptography.Algorithms is not supported on this platform.
at System.Security.Cryptography.HMACSHA256..ctor(Byte[] key)
at Azure.Storage.StorageSharedKeyCredential.ComputeHMACSHA256(String message)
at Azure.Storage.StorageSharedKeyCredential.ComputeSasSignature(StorageSharedKeyCredential credential, String message)
at Azure.Storage.StorageSharedKeyCredentialInternals.ComputeSasSignature(StorageSharedKeyCredential credential, String message)
at Azure.Storage.StorageSharedKeyPipelinePolicy.OnSendingRequest(HttpMessage message)
at Azure.Core.Pipeline.HttpPipelineSynchronousPolicy.ProcessAsync(HttpMessage message, ReadOnlyMemory1 pipeline)
at Azure.Core.Pipeline.HttpPipelineSynchronousPolicy.ProcessAsync(HttpMessage message, ReadOnlyMemory1 pipeline)
at Azure.Core.Pipeline.RetryPolicy.ProcessAsync(HttpMessage message, ReadOnlyMemory1 pipeline, Boolean async)
at Azure.Core.Pipeline.RetryPolicy.ProcessAsync(HttpMessage message, ReadOnlyMemory1 pipeline, Boolean async)
at Azure.Core.Pipeline.HttpPipelineSynchronousPolicy.ProcessAsync(HttpMessage message, ReadOnlyMemory1 pipeline)
at Azure.Core.Pipeline.HttpPipelineSynchronousPolicy.ProcessAsync(HttpMessage message, ReadOnlyMemory1 pipeline)
at Azure.Core.Pipeline.HttpPipelineSynchronousPolicy.ProcessAsync(HttpMessage message, ReadOnlyMemory1 pipeline)
at Azure.Storage.Blobs.BlobRestClient.BlockBlob.UploadAsync(ClientDiagnostics clientDiagnostics, HttpPipeline pipeline, Uri resourceUri, Stream body, Int64 contentLength, String version, Nullable1 timeout, Byte[] transactionalContentHash, String blobContentType, String blobContentEncoding, String blobContentLanguage, Byte[] blobContentHash, String blobCacheControl, IDictionary2 metadata, String leaseId, String blobContentDisposition, String encryptionKey, String encryptionKeySha256, Nullable1 encryptionAlgorithm, String encryptionScope, Nullable1 tier, Nullable1 ifModifiedSince, Nullable1 ifUnmodifiedSince, Nullable1 ifMatch, Nullable1 ifNoneMatch, String ifTags, String requestId, String blobTagsString, Boolean async, String operationName, CancellationToken cancellationToken)
at Azure.Storage.Blobs.Specialized.BlockBlobClient.UploadInternal(Stream content, BlobHttpHeaders blobHttpHeaders, IDictionary2 metadata, IDictionary2 tags, BlobRequestConditions conditions, Nullable1 accessTier, IProgress`1 progressHandler, String operationName, Boolean async, CancellationToken cancellationToken)
at Azure.Storage.Blobs.Specialized.BlockBlobClient.<>c__DisplayClass48_0.<
--- End of stack trace from previous location ---
at Azure.Storage.PartitionedUploader`2.
at Azure.Storage.Blobs.BlobClient.StagedUploadInternal(Stream content, BlobUploadOptions options, Boolean async, CancellationToken cancellationToken)
at {the Razor page where I call UploadAsync}
To Reproduce
Use blobClient.UploadAsync and pass a stream as the argument.
Environment:
Azure.Storage.Blobs 12.7.0-preview.1
Visual Studio 16.8.0 Preview 3.2
Target framework: .NET 5.0
Thanks for the feedback! We are routing this to the appropriate team for follow-up. cc @Wmengmsft, @MehaKaushik, @shurd
Thanks for the feedback! We are routing this to the appropriate team for follow-up. cc @xgithubtriage.
Thank you for your feedback. Tagging and routing to the team best able to assist.
Hi @Multivac222, thanks for reporting this, we will investigate.
@jaschrep-msft.
Am I doing something abnormal here, maybe? It's strange that no one else has reported this. Is it unusual to upload blobs directly from streams?
Apologies for missing this thread until now.
It looks from the stack trace you're failing to sign the request using your storage shared key. This is needed to sign every service request if you are using shared key auth. It appears .NET 5 RC1 does not support most hashing functions, and I'm having trouble finding exact releases where they will.
If .NET 5 is a requirement for you, your only option seems to be using SAS authentication (though you need hashing functions to create one in the first place, you won't need them to use the SAS). I would assume acquiring an Identity token also involves some hashing, which would place oauth off the table as well.
@jaschrep-msft
Thank you for your reply, but I'm afraid it produces more questions:
By the "request", do you mean the request produced by the Azure.Storage.Blobs.BlobClient.UploadAsync function, which is what I am using to upload the blob? How does one ensure that the request that UploadAsync creates is signed, and how do I make sure that this signing is completed using SAS authentication? It seems that it is the UploadAsync function that is attempting to use an unsupported encryption algorithm. Does that mean I need to change the algorithm that UploadAsync is using if I want to use it in .NET 5, and if so, how can I do this? The documentation for this class says nothing about an ability to select a specific encryption algorithm that I could find.
This is not a matter of encrypting the uploaded data. The SDK does not encrypt blob data unless you explicitly opt into it, which you have not specified you are. This is a matter of authenticating the HTTP(S) request itself to the storage cloud service. To test this, you can make any call while using shared key credentials, such as BlobContainerClient.GetProperties(), and see that the same error is thrown.
To answer quick one off points:
In general, .NET 5 appears not to be ready to support many security aspects of web apps, including ones storage shared key authorization requires to function.
Hi, @Multivac222. We've been looking into our .NET 5 story in general, and the latest GA Blobs SDK seems to work out of the box for us. We cannot reproduce your issue in a .NET 5 console app. Are you able to create a simple reproduction that we could look into? We would appreciate this if possible.
Could you also give us more information about your environment? All we really see from this issue is your .NET version, Visual Studio version, and Blobs SDK version; it would be good to know more about the environment you encounter this on. I also notice all the stated versions you give are preview versions. I wonder if moving to stable release versions removes the issue.
Hello @jaschrep-msft. I am using BlobClient's UploadAsync in the client-side part of a hosted Blazor WASM application to upload images directly from a client's machine to an Azure CDN service. Since we last spoke, I have upgraded to the stable version of .NET Core 5 and all associated Nuget packages, and this problem still persists.
So it sounds like the failing code is running in a browser, then. Many cryptography algorithms are unsupported in this environment because their use implies you are putting secrets into an insecure environment. To quote a member of the .NET team: "Blazor WebAssembly apps are public clients that can't keep secrets" (found here). The solution put forward by that comment appears to be Comsos-specific, using short-lived bearer tokens with small authorization scopes.
This issue was my starting point for finding most of this information regarding lack of browser support for crypto algorithms. There appears to be a fair amount of documentation for general auth practices in a Blazor environment that I'm afraid I'm not intimately familiar with at the moment.
Some additional perspective to add regarding your environment:
This is very similar to what JavaScript SDK users encounter when trying to use JavaScript in the browser instead of a Node app. The workflow in this article recommended to such users is one that uses SAS tokens, where the secure web service holds the secret key and generates a SAS token with limited scope and time to send back to the client app for their storage operations.
I have the same issue when using BlobClient's UploadAsync(Stream) in my blazor project.
My blazor project is created by https://github.com/staticwebdev/blazor-starter template.
And it migrated to .NET5.
VS for Mac 8.8 2913
.NET5.0
Azure.Storage.Blobs 12.7.0
Microsoft.AspNetCore.Components.WebAssembly5.0.0
Here's a resource that explains various ways to authenticate to storage: https://docs.microsoft.com/en-us/azure/storage/common/storage-auth .
For scenarios that run in browser (i.e. on client side, in environment you don't control) usage of Shared Key/Connection String is dangerous and shouldn't be attempted for security reasons. It is effectively exposing these secrets to public and anybody who intercepts them gains superuser access to your storage account.
For mentioned scenarios I suggest to explore the following options:
Running into this bug as well.
Are you saying that if we send the image to a Web API controller, and then try the Azure.Blob.Storage methods from there, they will work? Or will those also require the SAS workaround?
Anyone try the SAS workaround?
Assuming that Web API controller is hosted on the "server-side" like Blazor Server running on ASP.NET Core then any Storage Auth mechanism should work from there including Shared Key.
I.e. the data flow would be Blazor Web Assembly -> Blazor Server (or any other backend) -> Azure Storage.
I can confirm that it Azure Blob Storage works fine on the server (API or Blazor Server). I don't think this is a bug, as much as a security feature.