[英]Using a self-signed certificate with .NET's HttpWebRequest/Response
我正在嘗試連接到使用自簽名 SSL 證書的 API。 我這樣做是使用 .NET 的 HttpWebRequest 和 HttpWebResponse 對象。 我得到了一個例外:
底層連接已關閉:無法為 SSL/TLS 安全通道建立信任關系。
我明白這意味着什么。 我明白為什么.NET 覺得它應該警告我並關閉連接。 但在這種情況下,無論如何我只想連接到 API,中間人攻擊該死。
那么,我該如何為這個自簽名證書添加一個例外呢? 或者是告訴 HttpWebRequest/Response 根本不驗證證書的方法? 我該怎么做?
事實證明,如果您只想完全禁用證書驗證,您可以更改 ServicePointManager 上的 ServerCertificateValidationCallback,如下所示:
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
這將驗證所有證書(包括無效、過期或自簽名證書)。
@Domster:可行,但您可能希望通過檢查證書哈希是否與您期望的匹配來強制執行一些安全性。 所以擴展版本看起來有點像這樣(基於我們正在使用的一些實時代碼):
static readonly byte[] apiCertHash = { 0xZZ, 0xYY, ....};
/// <summary>
/// Somewhere in your application's startup/init sequence...
/// </summary>
void InitPhase()
{
// Override automatic validation of SSL server certificates.
ServicePointManager.ServerCertificateValidationCallback =
ValidateServerCertficate;
}
/// <summary>
/// Validates the SSL server certificate.
/// </summary>
/// <param name="sender">An object that contains state information for this
/// validation.</param>
/// <param name="cert">The certificate used to authenticate the remote party.</param>
/// <param name="chain">The chain of certificate authorities associated with the
/// remote certificate.</param>
/// <param name="sslPolicyErrors">One or more errors associated with the remote
/// certificate.</param>
/// <returns>Returns a boolean value that determines whether the specified
/// certificate is accepted for authentication; true to accept or false to
/// reject.</returns>
private static bool ValidateServerCertficate(
object sender,
X509Certificate cert,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
// Good certificate.
return true;
}
log.DebugFormat("SSL certificate error: {0}", sslPolicyErrors);
bool certMatch = false; // Assume failure
byte[] certHash = cert.GetCertHash();
if (certHash.Length == apiCertHash.Length)
{
certMatch = true; // Now assume success.
for (int idx = 0; idx < certHash.Length; idx++)
{
if (certHash[idx] != apiCertHash[idx])
{
certMatch = false; // No match
break;
}
}
}
// Return true => allow unauthenticated server,
// false => disallow unauthenticated server.
return certMatch;
}
請注意,在 .NET 4.5 中,您可以覆蓋每個 HttpWebRequest 本身的 SSL 驗證(而不是通過影響所有請求的全局委托):
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.ServerCertificateValidationCallback = delegate { return true; };
使用ServerCertificateValidationCallback
委托上的 sender 參數,可以將Domster 回答中使用的驗證回調的范圍限制為特定請求。 下面的簡單作用域類使用這種技術來臨時連接一個僅對給定請求對象執行的驗證回調。
public class ServerCertificateValidationScope : IDisposable
{
private readonly RemoteCertificateValidationCallback _callback;
public ServerCertificateValidationScope(object request,
RemoteCertificateValidationCallback callback)
{
var previous = ServicePointManager.ServerCertificateValidationCallback;
_callback = (sender, certificate, chain, errors) =>
{
if (sender == request)
{
return callback(sender, certificate, chain, errors);
}
if (previous != null)
{
return previous(sender, certificate, chain, errors);
}
return errors == SslPolicyErrors.None;
};
ServicePointManager.ServerCertificateValidationCallback += _callback;
}
public void Dispose()
{
ServicePointManager.ServerCertificateValidationCallback -= _callback;
}
}
上面的類可用於忽略特定請求的所有證書錯誤,如下所示:
var request = WebRequest.Create(uri);
using (new ServerCertificateValidationScope(request, delegate { return true; }))
{
request.GetResponse();
}
只是基於devstuff 的回答,包括主題和發行人...歡迎評論...
public class SelfSignedCertificateValidator
{
private class CertificateAttributes
{
public string Subject { get; private set; }
public string Issuer { get; private set; }
public string Thumbprint { get; private set; }
public CertificateAttributes(string subject, string issuer, string thumbprint)
{
Subject = subject;
Issuer = issuer;
Thumbprint = thumbprint.Trim(
new char[] { '\u200e', '\u200f' } // strip any lrt and rlt markers from copy/paste
);
}
public bool IsMatch(X509Certificate cert)
{
bool subjectMatches = Subject.Replace(" ", "").Equals(cert.Subject.Replace(" ", ""), StringComparison.InvariantCulture);
bool issuerMatches = Issuer.Replace(" ", "").Equals(cert.Issuer.Replace(" ", ""), StringComparison.InvariantCulture);
bool thumbprintMatches = Thumbprint == String.Join(" ", cert.GetCertHash().Select(h => h.ToString("x2")));
return subjectMatches && issuerMatches && thumbprintMatches;
}
}
private readonly List<CertificateAttributes> __knownSelfSignedCertificates = new List<CertificateAttributes> {
new CertificateAttributes( // can paste values from "view cert" dialog
"CN = subject.company.int",
"CN = issuer.company.int",
"f6 23 16 3d 5a d8 e5 1e 13 58 85 0a 34 9f d6 d3 c8 23 a8 f4")
};
private static bool __createdSingleton = false;
public SelfSignedCertificateValidator()
{
lock (this)
{
if (__createdSingleton)
throw new Exception("Only a single instance can be instanciated.");
// Hook in validation of SSL server certificates.
ServicePointManager.ServerCertificateValidationCallback += ValidateServerCertficate;
__createdSingleton = true;
}
}
/// <summary>
/// Validates the SSL server certificate.
/// </summary>
/// <param name="sender">An object that contains state information for this
/// validation.</param>
/// <param name="cert">The certificate used to authenticate the remote party.</param>
/// <param name="chain">The chain of certificate authorities associated with the
/// remote certificate.</param>
/// <param name="sslPolicyErrors">One or more errors associated with the remote
/// certificate.</param>
/// <returns>Returns a boolean value that determines whether the specified
/// certificate is accepted for authentication; true to accept or false to
/// reject.</returns>
private bool ValidateServerCertficate(
object sender,
X509Certificate cert,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true; // Good certificate.
Dbg.WriteLine("SSL certificate error: {0}", sslPolicyErrors);
return __knownSelfSignedCertificates.Any(c => c.IsMatch(cert));
}
}
添加可能的幫助給其他人...如果您希望它提示用戶安裝自簽名證書,您可以使用此代碼(從上面修改)。
不需要管理員權限,安裝到本地用戶信任的配置文件:
private static bool ValidateServerCertficate(
object sender,
X509Certificate cert,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
// Good certificate.
return true;
}
Common.Helpers.Logger.Log.Error(string.Format("SSL certificate error: {0}", sslPolicyErrors));
try
{
using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
store.Add(new X509Certificate2(cert));
store.Close();
}
return true;
}
catch (Exception ex)
{
Common.Helpers.Logger.Log.Error(string.Format("SSL certificate add Error: {0}", ex.Message));
}
return false;
}
這對我們的應用程序來說似乎很有效,如果用戶按“否”,則通信將無法進行。
更新:2015-12-11 - 將 StoreName.Root 更改為 StoreName.My - My 將安裝到本地用戶存儲中,而不是 Root。 即使您“以管理員身份運行”,某些系統上的 root 也無法工作
要記住的一件事是,擁有 ServicePointManager.ServerCertificateValidationCallback 似乎並不意味着未完成 CRL 檢查和服務器名稱驗證,它僅提供了一種覆蓋其結果的方法。 因此,您的服務可能仍需要一段時間才能獲得 CRL,之后您只會知道它未通過某些檢查。
我遇到了與 OP 相同的問題,其中 Web 請求會拋出那個確切的異常。 我認為所有設置都正確,證書已安裝,我可以在機器存儲中找到它,並將其附加到 Web 請求,並且我已禁用請求上下文中的證書驗證。
原來我是在我的用戶帳戶下運行的,並且證書已安裝到機器存儲中。 這導致 Web 請求拋出此異常。 為了解決這個問題,我必須以管理員身份運行或將證書安裝到用戶存儲並從那里讀取它。
看起來 C# 能夠在機器存儲中找到證書,即使它不能與 Web 請求一起使用,這會導致一旦發出 Web 請求就會拋出 OP 的異常。
首先 - 我很抱歉,因為我使用了@devstuff 描述的解決方案。 但是,我找到了一些方法來改進它。
這是我的修改:
private static X509Certificate2 caCertificate2 = null;
/// <summary>
/// Validates the SSL server certificate.
/// </summary>
/// <param name="sender">An object that contains state information for this validation.</param>
/// <param name="cert">The certificate used to authenticate the remote party.</param>
/// <param name="chain">The chain of certificate authorities associated with the remote certificate.</param>
/// <param name="sslPolicyErrors">One or more errors associated with the remote certificate.</param>
/// <returns>Returns a boolean value that determines whether the specified certificate is accepted for authentication; true to accept or false to reject.</returns>
private static bool ValidateServerCertficate(
object sender,
X509Certificate cert,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
// Good certificate.
return true;
}
// If the following line is not added, then for the self-signed cert an error will be (not tested with let's encrypt!):
// "A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider. (UntrustedRoot)"
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
// convert old-style cert to new-style cert
var returnedServerCert2 = new X509Certificate2(cert);
// This part is very important. Adding known root here. It doesn't have to be in the computer store at all. Neither do certificates.
chain.ChainPolicy.ExtraStore.Add(caCertificate2);
// 1. Checks if ff the certs are OK (not expired/revoked/etc)
// 2. X509VerificationFlags.AllowUnknownCertificateAuthority will make sure that untrusted certs are OK
// 3. IMPORTANT: here, if the chain contains the wrong CA - the validation will fail, as the chain is wrong!
bool isChainValid = chain.Build(returnedServerCert2);
if (!isChainValid)
{
string[] errors = chain.ChainStatus
.Select(x => String.Format("{0} ({1})", x.StatusInformation.Trim(), x.Status))
.ToArray();
string certificateErrorsString = "Unknown errors.";
if (errors != null && errors.Length > 0)
{
certificateErrorsString = String.Join(", ", errors);
}
Log.Error("Trust chain did not complete to the known authority anchor. Errors: " + certificateErrorsString);
return false;
}
// This piece makes sure it actually matches your known root
bool isValid = chain.ChainElements
.Cast<X509ChainElement>()
.Any(x => x.Certificate.RawData.SequenceEqual(caCertificate2.GetRawCertData()));
if (!isValid)
{
Log.Error("Trust chain did not complete to the known authority anchor. Thumbprints did not match.");
}
return isValid;
}
設置證書:
caCertificate2 = new X509Certificate2("auth/ca.crt", "");
var clientCertificate2 = new X509Certificate2("auth/client.pfx", "");
傳遞委托方法
ServerCertificateValidationCallback(ValidateServerCertficate)
client.pfx
是用 KEY 和 CERT 生成的,如下所示:
openssl pkcs12 -export -in client.crt -inkey client.key -out client.pfx
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.