# Bug Reports ## `@azure/keyvault-certificates` — `CertificateClient.importCertificate` silently drops `policy.contentType` **Package:** `@azure/keyvault-certificates` **Confirmed versions:** 4.10.3, current `main` branch **Source file:** `sdk/keyvault/keyvault-certificates/src/index.ts` **Serializer file:** `sdk/keyvault/keyvault-certificates/src/models/models.ts` --- ### Summary `CertificateClient.importCertificate()` accepts `ImportCertificateOptions.policy.contentType` as part of its public API but never sends it to Azure. The value is silently dropped due to a key name mismatch between the high-level client and the generated REST serializer. As a result, Azure falls back to the existing stored policy for that certificate name. When that policy specifies a different format than the bytes being imported (e.g. importing PFX into a certificate previously stored as PEM), Azure rejects the request with: > `The specified PEM X.509 certificate content is in an unexpected format.` --- ### Root Cause In `index.ts`, `importCertificate` builds the parameters object for the low-level generated client by spreading `updatedOptions`: ```typescript const result = await this.client.importCertificate( certificateName, { base64EncodedCertificate, preserveCertOrder: updatedOptions.preserveCertificateOrder, ...updatedOptions, // contributes: policy, password, tags, ... }, updatedOptions, ); ``` The spread adds the property as `policy` (the public API name from `ImportCertificateOptions`). In `models/models.ts`, the generated serializer reads a **different** key name: ```typescript export function certificateImportParametersSerializer(item: CertificateImportParameters): any { return { value: item["base64EncodedCertificate"], pwd: item["password"], policy: !item["certificatePolicy"] // reads "certificatePolicy", not "policy" ? item["certificatePolicy"] : certificatePolicySerializer(item["certificatePolicy"]), attributes: !item["certificateAttributes"] ? item["certificateAttributes"] : certificateAttributesSerializer(item["certificateAttributes"]), tags: item["tags"], preserveCertOrder: item["preserveCertOrder"], }; } ``` `item["certificatePolicy"]` is always `undefined` because the spread only populated `item["policy"]`. The REST body is sent with `policy: null`, regardless of what the caller specified. **Note:** `password` is not affected — it is read as `item["password"]` which matches the spread key and is correctly transmitted. **Note:** The Python SDK does not have this bug — it uses a different serialization architecture. --- ### Effect `ImportCertificateOptions.policy` is effectively a no-op. Any value passed is ignored. The observable consequence: - Importing a certificate for the first time works, because Azure auto-detects the format from the bytes (PEM headers are recognisable; PFX ASN.1 magic bytes may also be detected). - Importing a **new version** of an existing certificate in a **different format** fails: Azure validates the incoming bytes against the stored policy's `content_type`, which no longer matches. --- ### Reproduction A self-contained runnable script is provided in [`docs/bug-reproduce.ts`](bug-reproduce.ts): ```sh KEYVAULT_NAME= npx tsx docs/bug-reproduce.ts ``` The script generates a self-signed certificate, imports it as PEM (Step 1 — succeeds), then attempts to re-import it as PFX with `policy.contentType: "application/x-pkcs12"` (Step 2 — fails with the error above, confirming the bug). The workaround is demonstrated in [`docs/bug-workaround.ts`](bug-workaround.ts): ```sh KEYVAULT_NAME= npx tsx docs/bug-workaround.ts ``` This calls `updateCertificatePolicy()` before the PFX import to pre-set the stored `content_type`, allowing the import to succeed. --- ### Fix In `sdk/keyvault/keyvault-certificates/src/index.ts`, explicitly map the public `policy` field to the internal `certificatePolicy` key after the spread: ```typescript // BEFORE const result = await this.client.importCertificate( certificateName, { base64EncodedCertificate, preserveCertOrder: updatedOptions.preserveCertificateOrder, ...updatedOptions, }, updatedOptions, ); // AFTER const result = await this.client.importCertificate( certificateName, { base64EncodedCertificate, preserveCertOrder: updatedOptions.preserveCertificateOrder, ...updatedOptions, certificatePolicy: updatedOptions.policy, // map public name → serializer key }, updatedOptions, ); ``` No changes required to the serializer, the public `ImportCertificateOptions` interface, or any other file. --- ### Test Place in `sdk/keyvault/keyvault-certificates/test/` alongside the existing import tests. The test intercepts the outgoing HTTP request and asserts that `policy.secret_props.content_type` is present in the body. ```typescript import { assert } from "@azure/test-utils"; import { CertificateClient } from "../../src/index.js"; import { createTestCredential } from "@azure-tools/test-credential"; import { HttpClient, PipelineRequest, PipelineResponse } from "@azure/core-rest-pipeline"; function makeFakeResponse(): PipelineResponse { return { status: 200, headers: { get: () => "application/json", set: () => {}, has: () => true, delete: () => {}, toJSON: () => ({}) } as any, bodyAsText: JSON.stringify({ id: "https://vault.azure.net/certificates/MyCert/abc123", cer: Buffer.alloc(0).toString("base64"), attributes: { enabled: true }, policy: { secret_props: { contentType: "application/x-pkcs12" }, issuer: { name: "Unknown" }, }, }), request: null as any, }; } it("importCertificate sends policy.contentType in the REST body", async () => { let capturedBody: any; const fakeHttpClient: HttpClient = { sendRequest: async (request: PipelineRequest): Promise => { capturedBody = JSON.parse(request.body as string); return makeFakeResponse(); }, }; const client = new CertificateClient( "https://fakevault.vault.azure.net", createTestCredential(), { httpClient: fakeHttpClient }, ); const pfxBytes = Buffer.from("fakepfxbytes"); await client.importCertificate("MyCert", pfxBytes, { password: "secret", policy: { contentType: "application/x-pkcs12" }, }); assert.isDefined( capturedBody?.policy?.secret_props?.contentType, "policy.secret_props.contentType must be present in the REST body", ); assert.strictEqual( capturedBody.policy.secret_props.contentType, "application/x-pkcs12", "contentType must match the value passed in ImportCertificateOptions.policy", ); }); ``` --- ### Contribution Steps 1. Search [azure-sdk-for-js issues](https://github.com/Azure/azure-sdk-for-js/issues) for `importCertificate policy contentType` — file a new issue if none exists. 2. Fork `Azure/azure-sdk-for-js` on GitHub. 3. Clone your fork locally. 4. Apply the one-line fix in `sdk/keyvault/keyvault-certificates/src/index.ts`. 5. Add the test above to the existing import test suite. 6. Open a PR referencing the issue, with this document as the description basis.