[英]WebApi HttpClient not sending client certificate
我正在嘗試使用客戶端證書通過 ssl 和客戶端身份驗證來保護我的 RESTful WebApi 服務。
去測試; 我生成了一個自簽名證書並放置在本地機器、受信任的根證書頒發機構文件夾中,並且我生成了一個“服務器”和“客戶端”證書。 到服務器的標准 https 工作沒有問題。
但是,我在服務器中有一些代碼來驗證證書,當我使用提供我的客戶端證書的測試客戶端進行連接時,它永遠不會被調用,並且測試客戶端返回 403 Forbidden 狀態。
這意味着服務器在到達我的驗證代碼之前未能通過我的證書。 但是,如果我啟動 fiddler,它知道需要客戶端證書並要求我向 My Documents\\Fiddler2 提供一個證書。 我給了它與我在測試客戶端中使用的相同的客戶端證書,我的服務器現在可以工作並收到了我期望的客戶端證書! 這意味着 WebApi 客戶端沒有正確發送證書,我下面的客戶端代碼與我發現的其他示例幾乎相同。
static async Task RunAsync()
{
try
{
var handler = new WebRequestHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ClientCertificates.Add(GetClientCert());
handler.ServerCertificateValidationCallback += Validate;
handler.UseProxy = false;
using (var client = new HttpClient(handler))
{
client.BaseAddress = new Uri("https://hostname:10001/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
var response = await client.GetAsync("api/system/");
var str = await response.Content.ReadAsStringAsync();
Console.WriteLine(str);
}
} catch(Exception ex)
{
Console.Write(ex.Message);
}
}
任何想法為什么它可以在 fiddler 中工作而不是我的測試客戶端?
編輯:這是 GetClientCert() 的代碼
private static X509Certificate GetClientCert()
{
X509Store store = null;
try
{
store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
var certs = store.Certificates.Find(X509FindType.FindBySubjectName, "Integration Client Certificate", true);
if (certs.Count == 1)
{
var cert = certs[0];
return cert;
}
}
finally
{
if (store != null)
store.Close();
}
return null;
}
授予測試代碼不處理空證書,但我正在調試以確保找到正確的證書。
有兩種類型的證書。 第一個是從服務器所有者發送給您的公共 .cer 文件。 這個文件只是一長串字符。 第二個是密鑰庫證書,這是您創建的自簽名證書並將 cer 文件發送到您正在調用的服務器並安裝它。 根據您擁有多少安全性,您可能需要將其中一個或兩個添加到客戶端(在您的情況下為 Handler)。 我只看到在安全性非常安全的一台服務器上使用的密鑰庫證書。 此代碼從 bin/deployed 文件夾中獲取兩個證書:
#region certificate Add
// KeyStore is our self signed cert
// TrustStore is cer file sent to you.
// Get the path where the cert files are stored (this should handle running in debug mode in Visual Studio and deployed code) -- Not tested with deployed code
string executableLocation = Path.GetDirectoryName(AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory);
#region Add the TrustStore certificate
// Get the cer file location
string pfxLocation = executableLocation + "\\Certificates\\TheirCertificate.cer";
// Add the certificate
X509Certificate2 theirCert = new X509Certificate2();
theirCert.Import(pfxLocation, "Password", X509KeyStorageFlags.DefaultKeySet);
handler.ClientCertificates.Add(theirCert);
#endregion
#region Add the KeyStore
// Get the location
pfxLocation = executableLocation + "\\Certificates\\YourCert.pfx";
// Add the Certificate
X509Certificate2 YourCert = new X509Certificate2();
YourCert.Import(pfxLocation, "PASSWORD", X509KeyStorageFlags.DefaultKeySet);
handler.ClientCertificates.Add(YourCert);
#endregion
#endregion
此外 - 您需要處理證書錯誤(注意:這很糟糕 - 它說所有證書問題都可以)您應該更改此代碼以處理特定的證書問題,例如名稱不匹配。 這是我要做的事情。 有很多關於如何做到這一點的例子。
此代碼位於您的方法頂部
// Ignore Certificate errors need to fix to only handle
ServicePointManager.ServerCertificateValidationCallback = MyCertHandler;
類中某處的方法
private bool MyCertHandler(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors error)
{
// Ignore errors
return true;
}
上面的代碼是否在Fiddler運行的同一個用戶帳戶中運行? 如果沒有,它可能有權訪問證書文件,但不能訪問正確的私鑰。 同樣,你的GetClientCert()
函數返回什么? 具體來說,它是否設置了PrivateKey
屬性?
在您使用的代碼中store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
.
客戶端證書不是從LocalMachine
,您應該使用StoreLocation.CurrentUser
。
檢查MMC -> File -> Add or Remove Snap-ins -> Certificates -> My user account
您將看到 fiddler 使用的證書。 如果您從My user account
刪除它並且只在Computer account
導入它,您將看到 Fiddler 也無法提取它。
旁注是在查找證書時,您還必須解決文化問題。
例子:
var certificateSerialNumber= "83 c6 62 0a 73 c7 b1 aa 41 06 a3 ce 62 83 ae 25".ToUpper().Replace(" ", string.Empty);
//0 certs
var certs = store.Certificates.Find(X509FindType.FindBySerialNumber, certificateSerialNumber, true);
//null
var cert = store.Certificates.Cast<X509Certificate>().FirstOrDefault(x => x.GetSerialNumberString() == certificateSerialNumber);
//1 cert
var cert1 = store.Certificates.Cast<X509Certificate>().FirstOrDefault(x =>
x.GetSerialNumberString().Equals(certificateSerialNumber, StringComparison.InvariantCultureIgnoreCase));
try this.
證書應與當前用戶存儲。 或者授予完全權限並從文件中讀取,因為它是一個控制台應用程序。
// Load the client certificate from a file.
X509Certificate x509 = X509Certificate.CreateFromCertFile(@"c:\user.cer");
從用戶存儲中讀取。
private static X509Certificate2 GetClientCertificate()
{
X509Store userCaStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
try
{
userCaStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificatesInStore = userCaStore.Certificates;
X509Certificate2Collection findResult = certificatesInStore.Find(X509FindType.FindBySubjectName, "localtestclientcert", true);
X509Certificate2 clientCertificate = null;
if (findResult.Count == 1)
{
clientCertificate = findResult[0];
}
else
{
throw new Exception("Unable to locate the correct client certificate.");
}
return clientCertificate;
}
catch
{
throw;
}
finally
{
userCaStore.Close();
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.