生成用于滚动密钥的所有权证明令牌,并以编程方式更新证书
可以使用在 application 和 servicePrincipal 资源上定义的 addKey 和 removeKey 方法,以编程方式滚动过期密钥。
作为这些方法的请求验证的一部分,在调用方法之前,会验证现有密钥的所有权证明 (PoP) 。 证明由自签名 JSON Web 令牌 (JWT) 表示。 必须使用应用程序现有有效证书之一的私钥对此令牌进行签名。 令牌的建议生存期为 10 分钟。
本文提供 C# 中的代码示例,演示如何:
- 使用现有有效证书计算客户端断言。
- 使用生成的客户端断言密钥生成 PoP 令牌。
- 使用 PoP 令牌使用 addKey 方法将新证书上传到应用或服务主体对象。
- 使用 PoP 令牌使用 removeKey 方法从应用或服务主体对象中删除证书。
重要
由于证书尚未添加或现有证书已过期而没有任何现有 有效 证书的应用程序不能使用此服务操作。 请改用 Update 应用程序 操作来更新 keyCredentials 属性。 有关详细信息,请参阅 使用 Microsoft Graph 向应用添加证书。
先决条件
- 在目标应用或服务主体上拥有有效的客户端证书。
- 需要有效的现有 vertificate 的详细信息才能生成客户端断言密钥和 PoP 令牌。
- 出于测试目的,可以使用自签名证书。 若要了解如何创建自签名证书,请参阅 创建自签名公共证书对应用程序进行身份验证。
- 导出证书及其私钥的格式
.pfx
。 或者,可以将脚本更新为仅需要没有私钥的公共证书。
- 需要有效的现有 vertificate 的详细信息才能生成客户端断言密钥和 PoP 令牌。
- 客户端 ID (API) 上称为 appId ,对象 ID (生成 PoP 令牌的应用程序或服务主体的 API) 上调用 ID 。
示例代码
令牌应包含以下声明:
-
aud:受众必须是
00000002-0000-0000-c000-000000000000
。 - iss:颁发者应该是启动请求 的应用程序 或 servicePrincipal 对象的 ID。
- nbf:不是在时间之前。
- exp:到期时间应为 nbf + 10 分钟的值。
using System;
using System.Security.Cryptography.X509Certificates;
using System.Net;
using System.Net.Http;
using Microsoft.IdentityModel.Tokens;
namespace SampleCertCall
{
class Program
{
static void Main(string[] args)
{
//=============================
// Global variables which will be used to store app registation info, you can use appsettings.json to store such data
//=============================
string clientId = "Enter_the_Application_Id_Here"; //client ID or appId of the target app or service principal
string tenantID = "Enter_the_Tenant_Id_Here"; // Tenant ID value
string scopes = "https://graph.microsoft.com/.default"; // The "https://graph.microsoft.com/.default" is required in the client credentials flow, see the consent documentation (https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc#the-default-scope)
string objectId = "Enter_the_Object_Id_Here"; // The object ID is the identifier of the app or service principal you want to work with. Depending on the endpoint you use, it can be either the application objectId (https://graph.microsoft.com/v1.0/applications)) or the service principal objectId (https://graph.microsoft.com/v1.0/ServicePrincipals)).
string api = "Graph_API/ENDPOINT"; // Choose the graph endpoint you need to use, depending on whether you are working with (https://graph.microsoft.com/v1.0/applications) or (https://graph.microsoft.com/v1.0/servicePrincipals)
string aud_POP = "00000002-0000-0000-c000-000000000000"; // audience for client assertion must always be 00000002-0000-0000-c000-000000000000
string aud_ClientAssertion = "https://login.microsoftonline.com/{YOUR_TENANT_ID_HERE}/v2.0"; // audience for PoP must always be in the format https://login.microsoftonline.com/{YOUR_TENANT_ID_HERE}/v2.0
// pfxFilePath -> Use an existing valid cert used/uploaded to the app or service principal to generate access token and PoP token.
string pfxFilePath = "Current_Active_Certificate_Path"; // Replace the file path with the location of your certificate.
string password = "Current_Active_Certificate_Password"; // If applicable, replace the password value with your certificate password.
X509Certificate2 signingCert = null;
try
{
if (!password.IsNullOrEmpty())
signingCert = new X509Certificate2(pfxFilePath, password);
else
signingCert = new X509Certificate2(pfxFilePath);
}
catch (System.Security.Cryptography.CryptographicException ex)
{
Console.WriteLine("Check the old/uploaded certificate {CertificateDiskPath}, you need to add a correct certificate path and/or password for this sample to work\n" + ex.Message);
Environment.Exit(-1);
}
// newCerFilePath -> This is the new cert which will be uploaded. The cert can also be stored in Azure Key Vault.
string newCerFilePath = "New_Certificate_Path"; // Replace the file path with the location of your new certificate to be uploaded using the Graph API.
string newCertPassword = "New_Certificate_Password"; // If applicable, replace the password value with your new certificate password.
X509Certificate2 newCert = null;
try
{
if (newCertPassword != "")
newCert = new X509Certificate2(newCerFilePath, newCertPassword);
else
newCert = new X509Certificate2(newCerFilePath);
}
catch (System.Security.Cryptography.CryptographicException ex)
{
Console.WriteLine("Check the new certificate {NewCertificateDiskPath}, you need to add a correct certificate path and/or password for this sample to work\n" + ex.Message);
Environment.Exit(-1);
}
//========================
//Get acessToken via client assertion
//========================
var client_assertion = Helper.GenerateClientAssertion(aud_ClientAssertion, clientId, signingCert);
var token = Helper.GenerateAccessTokenWithClientAssertion(client_assertion, clientId, tenantID);
//========================
//Get PoP Token
//========================
var poP = Helper.GeneratePoPToken(objectId, aud_POP, signingCert);
// Get the new certificate info which will be uploaded via Microsoft Graph API call
var key = Helper.GetCertificateKey(newCert);
var graphClient = Helper.GetGraphClient(scopes, tenantID, clientId, signingCert);
int choice = -1;
while (choice != 0)
{
Console.WriteLine("\n=================================================");
Console.WriteLine("Please choose one of the following options:");
Console.WriteLine("=================================================");
Console.WriteLine("0. Exit");
Console.WriteLine("1. Display access token");
Console.WriteLine("2. Display client assertion");
Console.WriteLine("3. Display PoP token");
Console.WriteLine("4. Display certificate Info");
Console.WriteLine("5. Upload certificate using Graph SDK");
Console.WriteLine("6. Upload certificate using Graph API");
Console.WriteLine("7. Delete certificate using Graph SDK");
Console.WriteLine("8. Delete certificate using Graph API");
Console.WriteLine("\nEnter the choose number here:");
choice = Int32.TryParse(Console.ReadLine(), out choice) ? choice : -1;
HttpStatusCode code;
KeyCredential response;
string certID;
Guid val;
// Process user choice
switch (choice)
{
case 0:
// Exit the program
Console.WriteLine("\nGoodbye...\n");
break;
case 1:
// Display access token
Console.WriteLine("\n\"Access Token Value is:\"\n__________________");
Console.WriteLine($"Access Token: {token}");
Console.WriteLine("__________________\n");
break;
case 2:
// Display client assertion
Console.WriteLine("\n\"Client Assertion Token Value is\"\n__________________");
Console.WriteLine($"client_assertion: {client_assertion}");
Console.WriteLine("__________________\n");
break;
case 3:
// Display client assertion
Console.WriteLine("\n\"Proof of Possession Token Value is\"\n__________________");
Console.WriteLine($"PoP token: {poP}");
Console.WriteLine("__________________\n");
break;
case 4:
// Display certificate key
Helper.DisplayCertificateInfo(newCert);
break;
case 5:
// Call the addKey SDK using Graph SDK
if (newCertPassword != "")
{
response = GraphSDK.AddKeyWithPassword_GraphSDKAsync(poP, objectId, key, newCertPassword, graphClient).GetAwaiter().GetResult();
}
else
{
response = GraphSDK.AddKey_GraphSDKAsync(poP, objectId, key, graphClient).GetAwaiter().GetResult();
}
if (response != null)
{
Console.WriteLine("\n______________________");
Console.WriteLine("Uploaded Successfully!");
Console.WriteLine("______________________\n");
}
else
{
Console.WriteLine("\n______________________");
Console.WriteLine("Something went wrong!");
Console.WriteLine("______________________\n");
}
break;
case 6:
// Call the addKey API directly without using SDK
if (!password.IsNullOrEmpty())
{
code = GraphAPI.AddKeyWithPassword(poP, objectId, api, token, key, newCertPassword);
}
else
{
code = GraphAPI.AddKey(poP, objectId, api, token, key);
}
if (code == HttpStatusCode.OK)
{
Console.WriteLine("\n______________________");
Console.WriteLine("Uploaded Successfully!");
Console.WriteLine("______________________\n");
}
else
{
Console.WriteLine("\n______________________");
Console.WriteLine("Something went wrong!");
Console.WriteLine("HTTP Status code is " + code);
Console.WriteLine("______________________\n");
}
break;
case 7:
// Call the removeKey API using Graph SDK
Console.WriteLine("\nEnter certificate ID that you want to delete:");
certID = Console.ReadLine();
if (Guid.TryParse(certID, out val))
{
var res = GraphSDK.RemoveKey_GraphSDKAsync(poP, objectId, certID, graphClient).GetAwaiter().GetResult();
if (res)
{
Console.WriteLine("\n______________________");
Console.WriteLine("Cert Deleted Successfully!");
Console.WriteLine("_____________________\n");
}
else
{
Console.WriteLine("\n______________________");
Console.WriteLine("Something Went Wrong!");
Console.WriteLine("ERROR: Unable to delete certificate");
Console.WriteLine("______________________\n");
}
}
else
{
Console.WriteLine("\n______________________");
Console.WriteLine("ERROR: Invalid Certificate ID");
Console.WriteLine("______________________\n");
}
break;
case 8:
// Call the removeKey API directly without using API
Console.WriteLine("\nEnter certificate ID that you want to delete:");
certID = Console.ReadLine();
try
{
if (Guid.TryParse(certID, out val))
{
code = GraphAPI.RemoveKey(poP, objectId, api, certID, token);
if (code == HttpStatusCode.NoContent)
{
Console.WriteLine("\n______________________");
Console.WriteLine("Cert Deleted Successfully!");
Console.WriteLine("______________________\n");
}
else
{
Console.WriteLine("\n______________________");
Console.WriteLine("Something went wrong!");
Console.WriteLine("HTTP Status code is " + code);
Console.WriteLine("______________________\n");
}
}
else
{
Console.WriteLine("\n------------------------------");
Console.WriteLine("ERROR: Invalid Certificate ID");
Console.WriteLine("______________________________\n");
}
}
catch (HttpRequestException ex)
{
Console.WriteLine(ex.InnerException.Message);
Console.WriteLine("\n______________________");
Console.WriteLine("ERROR: " + ex.Message);
Console.WriteLine("______________________\n");
}
break;
default:
Console.WriteLine("\n______________________");
Console.WriteLine("Invalid choice");
Console.WriteLine("______________________\n");
break;
}
}
}
}
}
还可以使用 Azure KeyVault 中的签名生成证明。 请务必注意,JWT 标头和有效负载中不得包含填充字符“=”,否则将返回 Authentication_MissingOrMalformed 错误。
相关内容
拥有 PoP 令牌后,可以将其用于:
若要详细了解客户端断言,请参阅 Microsoft标识平台应用程序身份验证证书凭据。