skip to Main Content

When I call graphAPI from my Powershell script it first removes all keyCredentials(certificates) from the Enterprise Application Service Principal in Azure AD, then uploads my custom certificate. How can I retain the certificates that are currently installed on the application and ALSO upload my new certificate in an inactive state?

Here is the body.


{
    "keyCredentials": [
        {
            "customKeyIdentifier":
            "endDateTime": 
            "keyId": 
            "startDateTime":
            "type": "X509CertAndPassword",
            "usage": "Sign",
            "key":
            "displayName": 
        },
        {
            "customKeyIdentifier": 
            "endDateTime": 
            "keyId": 
            "startDateTime": 
            "type": "AsymmetricX509Cert",
            "usage": "Verify",
            "key": 
            "displayName": 
        }
    ],
    "passwordCredentials": [
        {
            "customKeyIdentifier": 
            "keyId": 
            "endDateTime": 
            "startDateTime": 
            "secretText": 
        }
    ]
}'

Each key has a value I just am removing them for privacy.

Here is the call to graphAPI

$response = Invoke-RestMethod -Method Patch -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/{AppID}" -Headers $global:Header -Body $certBody

All of the information is correct because it uploads the custom certificate correctly. I just want it to leave the other certs alone.

2

Answers


  1. Chosen as BEST ANSWER

    After some research, and discussions with Microsoft, the way to use this method and retain the certificates is to query the service principal's key credentials using a regular GET call to https://graph.microsoft.com/v1.0/servicePrincipals/{id}. Then, create a new JSON payload with both the CURRENT key credentials from the GET call and the NEW key credentials. You can then make a PATCH call, using the same URI, with that payload and it will successfully update the service principal certificates without deleting the current certificates.

    Couple of things to note:

    1. The "customKeyIdentifier" field has a character limit and since it is recommended to use a hash of the thumbprint I tried SHA256 but it created too long of a hash, so I used SHA1. Since this isn't as secure another random generation could work as long as it is within the limit, though a GUID didn't work and I suspect it was a character type issue with the dashes. I have not done enough testing to find the limit, though my identifier was 40 characters and it was successful.

    2. For the private key you can use the following command in Powershell to get the correct decoded private key. Here is where I found this command. (This is the only method I found useful as OpenSSL didn't seem to output the correct private key Azure is looking for)

      $fileContentBytes = get-content '<PATH_TO_PFX_FILE>' -asbytestream
      $privKey = [system.convert]::tobase64string($fileContentBytes)
      

    Here is an example of a successful JSON payload with existing certs and a new cert. The password credentials section should contain the passphrase for the new cert as well as the keyID of the private key. This specific service principal has two current certificates.

    Please note that while the current certs only have a "AsymmetricX509Cert" for the type, the new cert has both "AsymmetricX509Cert" and "X509CertandPassword" using the public key and private key respectively.

    {
      "keyCredentials": [
        {
          "customKeyIdentifier": "<HASH_OF_THUMBPRINT>",
          "displayName": "<CN>",
          "endDateTime": "<EXPIRY_DATE>",
          "key": null <EXISTING_CERT>,
          "keyId": "<GUID>",
          "startDateTime": "<START_DATE>",
          "type": "AsymmetricX509Cert",
          "usage": "Verify"
        },
        {
          "customKeyIdentifier": "<HASH_OF_THUMBPRINT>",
          "displayName": "<CN>",
          "endDateTime": "<EXPIRY_DATE>",
          "key": null <EXISTING_CERT>,
          "keyId": "<GUID>",
          "startDateTime": "<START_DATE>",
          "type": "AsymmetricX509Cert",
          "usage": "Sign"
        },
        {
          "customKeyIdentifier": "<HASH_OF_THUMBPRINT>",
          "displayName": "<CN>",
          "endDateTime": "<EXPIRY_DATE>",
          "key": null <EXISTING_CERT>,
          "keyId": "<GUID>",
          "startDateTime": "<START_DATE>",
          "type": "AsymmetricX509Cert",
          "usage": "Verify"
        },
        {
          "customKeyIdentifier": "<HASH_OF_THUMBPRINT>",
          "displayName": "<CN>",
          "endDateTime": "<EXPIRY_DATE>",
          "key": null <EXISTING_CERT>,
          "keyId": "<GUID>",
          "startDateTime": "<START_DATE>",
          "type": "AsymmetricX509Cert",
          "usage": "Sign"
        },
        {
          "customKeyIdentifier": "<HASH_OF_THUMBPRINT>",
          "endDateTime": "<EXPIRY_DATE>",
          "type": "X509CertandPassword",
          "key": "<NEWCERT-PrivateKey>",
          "displayName": "<CN>",
          "startDateTime": "<START_DATE>",
          "keyId": "<GUID>",
          "usage": "Sign"
        },
        {
          "customKeyIdentifier": "<HASH_OF_THUMBPRINT>",
          "endDateTime": "<EXPIRY_DATE>",
          "type": "AsymmetricX509Cert",
          "key": "<NEWCERT-PublicKey>",
          "displayName": "<CN>",
          "startDateTime": "<START_DATE>",
          "keyId": "<GUID>",
          "usage": "Verify"
        }
      ],
      "passwordCredentials": [
        {
          "endDateTime": "<EXPIRY_DATE>",
          "secretText": "<PASSPHRASE_FOR_NEW_CERT>",
          "startDateTime": "<START_DATE>",
          "keyId": "<KEY_ID_OF_PRIVATE_KEY>",
          "customKeyIdentifier": "<HASH_OF_THUMBPRINT_OF_NEW_CERT>"
        }
      ]
    }
    

  2. Use addKey instead of the Update method to add additional keyCredentials:

    POST /servicePrincipals/{id}/addKey versus PATCH /servicePrincipals/{id}

    But be aware that:

    ServicePrincipals that don’t have any existing valid certificates (i.e.: no certificates have been added yet, or all certificates have expired), won’t be able to use this service action. Update servicePrincipal can be used to perform an update instead.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search