Azure / Azure/azure-powershell

Set-AzIoTDeviceProvisioningServiceCertificate incorrectly passes the certificate as the query parameter 'certificate.name' instead of the body's member certificate.rawBytes

Open
#26,669 6 comments 0 reactions 0 assignees View on GitHub
bug customer-reported DeviceProvisioningServices Service Attention
Dominant language
C#
Stars
4.8k
Forks
4.3k
Avg merge
3d 14h
Merged PRs (30d)
54

Description

### Description

When I invoke:
```
Set-AzIoTDeviceProvisioningServiceCertificate -ResourceGroupName MyResourceGroup -Name MyDPS -CertificateName $certName -Etag $azDPSValidationCode.Etag -Path "${smCertName}-validation-cert.pem"
```

I see the Restful API call is taking the body of the certificate and assigning it to the certificate.name query parameter.

The following was referencing git commit 174cc610c9a820198fe8b4d3d22293057e2cfa9c.

Working back from the code which assembles the query string, I see:
In `DeviceProvisioningServices.Management.Sdk/Generated/DpsCertificateOperations.cs`:
```
1312 public async Task> VerifyCertificateWithHttpMessagesAsync(string certificateName, string ifMatch, string resourceGroupName, string provisioningServiceName, string certificatename = default(string), byte[] certificaterawBytes = default(byte[]), bool? certificateisVerified = default(bool?), string certificatepurpose = default(string), System.DateTime? certificatecreated = default(System.DateTime?), System.DateTime? certificatelastUpdated = default(System.DateTime?), bool? certificatehasPrivateKey = default(bool?), string certificatenonce = default(string), string certificate = default(string), Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken))
...
1374 if (certificatename != null)
1375 {
1376 _queryParameters.Add(string.Format("certificate.name={0}", System.Uri.EscapeDataString(certificatename)));
1377 }
1378 if (certificaterawBytes != null)
1379 {
1380 _queryParameters.Add(string.Format("certificate.rawBytes={0}", System.Uri.EscapeDataString(Rest.Serialization.SafeJsonConvert.SerializeObject(certificaterawBytes, Client.SerializationSettings).Trim('"'))));
1381 }
```

This tells me that the passed in parameter 5 (certificatename), not to be confused with the parameter 1 (certificateName), is being passed in as a string. What I intend to do is pass in parameter 6 (certificaterawBytes) as the body of my certificate.

Tracking back to the call location, I see:
DeviceProvisioningServices.Management.Sdk/Generated/DpsCertificateOperationsExtensions.cs:
```
449 public static CertificateResponse VerifyCertificate(this IDpsCertificateOperations operations, string certificateName, string ifMatch, string resourceGroupName, string provisioningServiceName, string certificatename = default(string), byte[] certificaterawBytes = default(byte[]), bool? certificateisVerified = default(bool?), string certificatepurpose = default(string), System.DateTime? certificatecreated = default(System.DateTime?), System.DateTime? certificatelastUpdated = default(System.DateTime?), bool? certificatehasPrivateKey = default(bool?), string certificatenonce = default(string), string certificate = default(string))
450 {
451 return operations.VerifyCertificateAsync(certificateName, ifMatch, resourceGroupName, provisioningServiceName, certificatename, certificaterawBytes, certificateisVerified, certificatepurpose, certificatecreated, certificatelastUpdated, certificatehasPrivateKey, certificatenonce, certificate).GetAwaiter().GetResult();
452 }
...

509 public static async Task VerifyCertificateAsync(this IDpsCertificateOperations operations, string certificateName, string ifMatch, string resourceGroupName, string provisioningServiceName, string certificatename = default(string), byte[] certificaterawBytes = default(byte[]), bool? certificateisVerified = default(bool?), string certificatepurpose = default(string), System.DateTime? certificatecreated = default(System.DateTime?), System.DateTime? certificatelastUpdated = default(System.DateTime?), bool? certificatehasPrivateKey = default(bool?), string certificatenonce = default(string), string certificate = default(string), CancellationToken cancellationToken = default(CancellationToken))
510 {
511 using (var _result = await operations.VerifyCertificateWithHttpMessagesAsync(certificateName, ifMatch, resourceGroupName, provisioningServiceName, certificatename, certificaterawBytes, certificateisVerified, certificatepurpose, certificatecreated, certificatelastUpdated, certificatehasPrivateKey, certificatenonce, certificate, null, cancellationToken).ConfigureAwait(false))
512 {
513 return _result.Body;
514 }
515 }
516
517 }
518 }
```

That, in turn is called from:
Cmdlet/IotDpsCertificates/SetAzureRmIotDeviceProvisioningServiceCertificate.cs
```
162 certificate = Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(certificate));
163
164 VerificationCodeRequest verificationCodeRequest = new VerificationCodeRequest();
165 verificationCodeRequest.Certificate = certificate;
166
167 CertificateResponse certificateResponse = this.IotDpsClient.DpsCertificate.VerifyCertificate(this.CertificateName, this.Etag,this.ResourceGroupName, this.Name, verificationCodeRequest.Certificate);
168 this.WriteObject(IotDpsUtils.ToPSCertificateResponse(certificateResponse));
```

And just for completness:
DeviceProvisioningServices.Management.Sdk/Generated/Models/VerificationCodeRequest.cs
```
19 public partial class VerificationCodeRequest
20 {
...
49 [JsonProperty(PropertyName = "certificate")]
50 public string Certificate { get; set; }
```

Conclusion:

No matter what I pass to the `Set-AzIoTDeviceProvisioningServiceCertificate` command, it will be converted to a string in SetAzureRmIoTDeviceProvisioningServiceCertificate by lines 162-167.

That, in-turn will cause VerifyCertificate to receive what I am passing as a certificate in the certificatename parameter, which is in-turn passed to VerifyCertificateAsync as the certificatename parameter, which in turn is passed to VerifyCertificateWithHttpMessagesAsync as its certificatename parameter, resulting in the wrong query string.

### Issue script & Debug output

```PowerShell
PS /home/holtr/src/SystemManager/Certificate_Signing_Service/Powershell_scripts> $resp = Set-AzIoTDeviceProvisioningServiceCertificate -ResourceGroupName IoTHub-cu-ft-rg -Name Telemetry-cu-ft-dps -CertificateName $smCertName -Etag $azDPSValidationCode.Etag -Path "${smCertName}-validation-cert.pem"
DEBUG: 2:31:28 PM - [ConfigManager] Got nothing from [DisplaySecretsWarning], Module = [], Cmdlet = []. Returning default value [True].
DEBUG: 2:31:28 PM - SetAzureRmIoTDeviceProvisioningServiceCertificate begin processing with ParameterSet 'ResourceSet'.
DEBUG: 2:31:28 PM - using account id 'XXXXX.XXXX@XXXXXXXXXXXXX.com'...
DEBUG: 2:31:28 PM - [ConfigManager] Got nothing from [DisplayBreakingChangeWarning], Module = [], Cmdlet = []. Returning default value [True].
DEBUG: [Common.Authentication]: Authenticating using Account: 'XXXXX.XXXX@XXXXXXXXXXXXX.com', environment: 'AzureCloud', tenant: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
DEBUG: 2:31:28 PM - [ConfigManager] Got nothing from [DisableInstanceDiscovery], Module = [], Cmdlet = []. Returning default value [False].
DEBUG: 2:31:28 PM - [ConfigManager] Got [False] from [EnableLoginByWam], Module = [], Cmdlet = [].
DEBUG: 2:31:28 PM - [SilentAuthenticator] Calling SharedTokenCacheCredential.GetTokenAsync - TenantId:'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', Scopes:'https://management.core.windows.net//.default', AuthorityHost:'https://login.microsoftonline.com/', UserId:'XXXXX.XXXX@XXXXXXXXXXXXX.com'
DEBUG: SharedTokenCacheCredential.GetToken invoked. Scopes: [ https://management.core.windows.net//.default ] ParentRequestId:
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z - 0248ebf2-4f2e-4980-bf8f-b2a3f899b24a] IsLegacyAdalCacheEnabled: yes
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z - 0248ebf2-4f2e-4980-bf8f-b2a3f899b24a] [Region discovery] Not using a regional authority.
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z - 0248ebf2-4f2e-4980-bf8f-b2a3f899b24a] [Region discovery] Not using a regional authority.
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z - 0248ebf2-4f2e-4980-bf8f-b2a3f899b24a] IsLegacyAdalCacheEnabled: yes
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z - 0248ebf2-4f2e-4980-bf8f-b2a3f899b24a] IsLegacyAdalCacheEnabled: yes
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z] Found 1 cache accounts and 0 broker accounts
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z] Returning 1 accounts
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z - e30ded8c-25da-44e3-bb18-49133209dc35] MSAL MSAL.CoreCLR with assembly version '4.65.0.0'. CorrelationId(e30ded8c-25da-44e3-bb18-49133209dc35)
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z - e30ded8c-25da-44e3-bb18-49133209dc35] === AcquireTokenSilent Parameters ===
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z - e30ded8c-25da-44e3-bb18-49133209dc35] LoginHint provided: False
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z - e30ded8c-25da-44e3-bb18-49133209dc35] Account provided: True
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z - e30ded8c-25da-44e3-bb18-49133209dc35] ForceRefresh: False
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z - e30ded8c-25da-44e3-bb18-49133209dc35]
=== Request Data ===
Authority Provided? - True
Scopes - https://management.core.windows.net//.default
Extra Query Params Keys (space separated) -
ApiId - AcquireTokenSilent
IsConfidentialClient - False
SendX5C - False
LoginHint ? False
IsBrokerConfigured - False
HomeAccountId - False
CorrelationId - e30ded8c-25da-44e3-bb18-49133209dc35
UserAssertion set: False
LongRunningOboCacheKey set: False
Region configured:

DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z - e30ded8c-25da-44e3-bb18-49133209dc35] === Token Acquisition (SilentRequest) started:
Scopes: https://management.core.windows.net//.default
Authority Host: login.microsoftonline.com
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z - e30ded8c-25da-44e3-bb18-49133209dc35] [Region discovery] Not using a regional authority.
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z - e30ded8c-25da-44e3-bb18-49133209dc35] Access token is not expired. Returning the found cache entry. [Current time (11/12/2024 14:31:28) - Expiration Time (11/12/2024 15:16:45 +00:00) - Extended Expiration Time (11/12/2024 15:16:45 +00:00)]
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z - e30ded8c-25da-44e3-bb18-49133209dc35] Returning access token found in cache. RefreshOn exists ? False
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z - e30ded8c-25da-44e3-bb18-49133209dc35] [Region discovery] Not using a regional authority.
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z - e30ded8c-25da-44e3-bb18-49133209dc35]
=== Token Acquisition finished successfully:
DEBUG: False MSAL 4.65.0.0 MSAL.CoreCLR .NET 8.0.10 Linux [2024-11-12 14:31:28Z - e30ded8c-25da-44e3-bb18-49133209dc35] AT expiration time: 11/12/2024 3:16:45 PM +00:00, scopes: https://management.core.windows.net//.default https://management.core.windows.net//user_impersonation. source: Cache
DEBUG: SharedTokenCacheCredential.GetToken succeeded. Scopes: [ https://management.core.windows.net//.default ] ParentRequestId: ExpiresOn: 2024-11-12T15:16:45.0000000+00:00
DEBUG: [Common.Authentication]: Received token with LoginType 'User', Tenant: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', UserId: 'XXXXX.XXXX@XXXXXXXXXXXXX.com'
DEBUG: ============================ HTTP REQUEST ============================

HTTP Method:
POST

Absolute Uri:
https://management.azure.com/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/IoTHub-cu-ft-rg/providers/Microsoft.Devices/provisioningServices/Telemetry-cu-ft-dps/certificates/SM-DPS-20241110-20441112/verify?certificate.name=-----BEGIN CERTIFICATE-----%0AMIIC1zCCAb%2BgAwIBAgIBATANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDExhTTS1EUFMtMjAyNDEx%0D%0AMTAtMjA0NDExMTIwHhcNMjQxMTEyMDAwMDAwWhcNMjQxMTE5MDAwMDAwWjA7MTkwNwYDVQQDEzBD%0D%0AMkY4OEVFNDUwMzYxMzk3NzkzRkM1NDY4M0Q2M0ZGRDMxQ0NEQTM1MDhFRjZEODcwggEiMA0GCSqG%0D%0ASIb3DQEBAQUAA4IBDwAwggEKAoIBAQDXHIOxXEnqur9xTof743j4i%2F74vG%2FZKVx070dNVmeH%2BomQ%0D%0AfRpgzJQsOh%2Fvmwrg%2FUZCpUzGclGbNNPJwuzdffNSNb%2Bpfc%2FZcjdv75lp8iPMtv%2BMFvH3csfgJ2e1%0D%0Acu1qNt70oIoc8Kfn%2FNDyEpsYtQCn3IfA4Rzt5VWanlt4pTZ3exgblGjdzkVTbiTu%2F1sUM%2F3ONlPX%0D%0A7XUvu6PtJrJ7aJ%2BkjRUOQgZzuenvaMlQszGIIzF2kILo2oNI95%2F7dgwClMA385O6YKpaVEeK3WVO%0D%0AMMZQj29F5c3uNCrrelcKMVMHwvbbdUO2JZoyszz9OetHH6z9U%2F5hkAOUP1hJ3HQBdw85AgMBAAEw%0D%0ADQYJKoZIhvcNAQELBQADggEBADzCwtYGX3D7JvXouSf6OOjN0bmz7ZM2XFX6YRhtLTeeMsEgu8UY%0D%0AKoGWxcMoXu9uxuw3MZCso%2BRuJDvkMUHo%2Ff5xhYLYcwA31mfGVYS2DEdjIzIgUktjNu6masju63pS%0D%0Aet%2BAhf4Tl8NasxEvJVAzDvg52kz%2FchlDOEaSd9slCsxfqnVVlrwJx5aioBN3DS4LlIX940AQeV0D%0D%0A7sNcunqP6ypPTXtna5IHILCxTs7X0%2BrygieubeBQ5nN%2FJeyleEWr35yc%2FK2xm8H6XS%2BD5JRRLQU6%0D%0APhzuOiz1pOrlqDEnnjSSMVS93fLxy0zVQ9kkHOSfPaBNuyaSA4wN55oN3iaEXeQ%3D%0A-----END CERTIFICATE-----%0A%0A&api-version=2017-11-15

Headers:
If-Match : ImIyMDBjZWMxLTAwMDAtMDMwMC0wMDAwLTY3MzM2NjU2MDAwMCI=
Accept-Language : en-US
x-ms-client-request-id : cb29fad8-aa2f-4575-8941-62ad752ade91

Body:
{}

DEBUG: ============================ HTTP RESPONSE ============================

Status Code:
BadRequest

Headers:
Cache-Control : no-cache
Pragma : no-cache
x-ms-ratelimit-remaining-subscription-writes: 199
x-ms-ratelimit-remaining-subscription-global-writes: 2999
x-ms-request-id : c8712f18-68d6-4a4a-94f2-fdcece22b58c
x-ms-correlation-request-id : c8712f18-68d6-4a4a-94f2-fdcece22b58c
x-ms-routing-request-id : NORTHCENTRALUS:20241112T143129Z:c8712f18-68d6-4a4a-94f2-fdcece22b58c
Strict-Transport-Security : max-age=31536000; includeSubDomains
X-Content-Type-Options : nosniff
X-Cache : CONFIG_NOCACHE
X-MSEdge-Ref : Ref A: 0E4270D18E81464288C136D004CDE333 Ref B: CH1AA2020614025 Ref C: 2024-11-12T14:31:28Z
Date : Tue, 12 Nov 2024 14:31:28 GMT

Body:
{
"code": 400059,
"httpStatusCode": "BadRequest",
"message": "Request body validation failed. If you contact a support representative please include this correlation identifier: ae22b3b4-2fed-49dc-a85e-71b0da2eb7b6, timestamp: 2024-11-12 14:31:29Z, errorcode: IH400059."
}

DEBUG: 2:31:29 PM - [ConfigManager] Got nothing from [EnableErrorRecordsPersistence], Module = [], Cmdlet = []. Returning default value [False].
Set-AzIoTDeviceProvisioningServiceCertificate: Operation returned an invalid status code 'BadRequest'
DEBUG: 2:31:29 PM - [ConfigManager] Got nothing from [DisplayBreakingChangeWarning], Module = [], Cmdlet = []. Returning default value [True].
DEBUG: 2:31:29 PM - [ConfigManager] Got nothing from [DisplayRegionIdentified], Module = [], Cmdlet = []. Returning default value [True].
DEBUG: 2:31:29 PM - [ConfigManager] Got nothing from [CheckForUpgrade], Module = [], Cmdlet = []. Returning default value [True].
DEBUG: AzureQoSEvent: Module: Az.DeviceProvisioningServices:0.10.3; CommandName: Set-AzIoTDeviceProvisioningServiceCertificate; PSVersion: 7.4.6; IsSuccess: False; Duration: 00:00:01.0126382; SanitizeDuration: 00:00:00; Exception: Operation returned an invalid status code 'BadRequest';
DEBUG: 2:31:29 PM - [ConfigManager] Got nothing from [EnableDataCollection], Module = [], Cmdlet = []. Returning default value [True].
DEBUG: 2:31:29 PM - SetAzureRmIoTDeviceProvisioningServiceCertificate end processing.
PS /home/holtr/src/SystemManager/Certificate_Signing_Service/Powershell_scripts> $smCertName
SM-DPS-20241110-20441112
```

### Environment data

```PowerShell
Name Value
---- -----
PSVersion 7.4.6
PSEdition Core
GitCommitId 7.4.6
OS CBL-Mariner/Linux
Platform Unix
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
```

### Module versions

```PowerShell
ModuleType Version PreRelease Name ExportedCommands
---------- ------- ---------- ---- ----------------
Script 3.0.5 Az.Accounts {Add-AzEnvironment, Clear-AzConfig, Clear-AzContext, Clear-AzDefault…}
Script 0.10.3 Az.DeviceProvisioningServices {Add-AzIoTDeviceProvisioningServiceAccessPolicy, Add-AzIoTDeviceProvisioningServiceCertificate, Add-AzIoTDeviceProvisioningServiceEnrollment, Add-AzIoTDeviceProvisioningServiceEnrollmentGroup…}
Script 4.1.1 Az.Functions {Get-AzFunctionApp, Get-AzFunctionAppAvailableLocation, Get-AzFunctionAppPlan, Get-AzFunctionAppSetting…}
Script 6.2.0 Az.KeyVault {Add-AzKeyVaultCertificate, Add-AzKeyVaultCertificateContact, Add-AzKeyVaultKey, Add-AzKeyVaultManagedStorageAccount…}
```

### Error output

```PowerShell

```

Contributor guide

Open the contributing guide

Research direction

Start at Cmdlet/IotDpsCertificates/SetAzureRmIotDeviceProvisioningServiceCertificate.cs, especially the call around lines 162-167, then trace the matching overloads in the generated DpsCertificateOperations files. Verify that the certificate value is routed to the rawBytes request member rather than the certificate name parameter, and confirm the cmdlet sends the expected request.

Written by the indexing model from the issue text.

Assessment

Tech stack
azure, csharp, powershell
Domain
api, cli
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.