Sử dụng chứng chỉ tự ký với HttpWebRequest / Response của .NET


80

Tôi đang cố gắng kết nối với một API sử dụng chứng chỉ SSL tự ký. Tôi đang làm như vậy bằng cách sử dụng các đối tượng HttpWebRequest và HttpWebResponse của .NET. Và tôi nhận được một ngoại lệ rằng:

Kết nối cơ bản đã bị đóng: Không thể thiết lập mối quan hệ tin cậy cho kênh bảo mật SSL / TLS.

Tôi hiểu điều này có nghĩa là gì. Và tôi hiểu tại sao .NET cảm thấy nó nên cảnh báo tôi và đóng kết nối. Nhưng trong trường hợp này, dù sao thì tôi cũng chỉ muốn kết nối với API, các cuộc tấn công man-in-the-middle thật đáng nguyền rủa.

Vì vậy, làm cách nào để thêm một ngoại lệ cho chứng chỉ tự ký này? Hay là cách tiếp cận để cho biết HttpWebRequest / Response không xác thực chứng chỉ? Làm thế nào tôi sẽ làm điều đó?

Câu trả lời:


81

@Domster: điều đó hoạt động, nhưng bạn có thể muốn thực thi một chút bảo mật bằng cách kiểm tra xem băm chứng chỉ có khớp với những gì bạn mong đợi hay không. Vì vậy, một phiên bản mở rộng trông giống như thế này (dựa trên một số mã trực tiếp mà chúng tôi đang sử dụng):

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;
}

Có lẽ ai đó thích cách chính xác dưới đây. Dù sao thì, bản hack này cũng hoạt động khá hiệu quả, nhưng có lẽ bạn không nên mã hóa những loại ngoại lệ này trong ... hoặc chỉ cần vô hiệu hóa tất cả kiểm tra cùng nhau (thông qua gợi ý ngay bên dưới) hoặc thực sự hướng dẫn máy tính của bạn tin tưởng vào chứng chỉ .. .
BrainSlugs83

3
@ BrainSlugs83: Vô hiệu hóa chắc chắn cũng là một tùy chọn, nhưng việc thêm chứng chỉ vào kho lưu trữ cơ quan quản lý gốc cấp máy chỉ có thể được thực hiện bởi quản trị viên. Giải pháp của tôi hoạt động theo cả hai cách.
devstuff,

Và tôi hoàn toàn hiểu điều đó, nhưng bạn đã hỏi, và đó vẫn là suy đoán của tôi về lý do tại sao ai đó lại bỏ phiếu cho câu trả lời của bạn. Và bất kể nó là công việc nhiều hơn, câu trả lời của IMHO wgthom dưới đây vẫn là câu trả lời đúng nhất.
BrainSlugs83

btw, hãy cẩn thận, tôi nghĩ rằng ServerCertificateValidationCallback là STATIC và thậm chí không phải là threadlocal. nếu tôi không sai, thì sau khi đặt, nó vẫn được đặt cho đến khi bạn xóa nó. nếu bạn muốn sử dụng nó cho duy nhất và không phải là một kết nối trong tất cả những người khác, phải rất cẩn thận với yêu cầu song song ..
Quetzalcoatl

3
Đây là cách tốt nhất để làm điều này. Nếu bạn loại bỏ kiểm tra đối với sslPolicyErrors, bạn thực sự có thể đảm bảo chứng chỉ API luôn là chứng chỉ mong đợi. Một điều cần lưu ý là tệp tham chiếu chứng chỉ trong đoạn mã trên là một mảng const byte. Điều này sẽ không biên dịch như đã viết. Thay vào đó, hãy thử một mảng byte chỉ đọc tĩnh. Trình biên dịch mắc kẹt vì nó yêu cầu toán tử new ().
Centijo

92

Hóa ra, nếu bạn chỉ muốn tắt hoàn toàn xác thực chứng chỉ, bạn có thể thay đổi ServerCertificateValidationCallback trên ServicePointManager, như sau:

ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

Điều này sẽ xác nhận tất cả các chứng chỉ (bao gồm cả chứng chỉ không hợp lệ, hết hạn hoặc tự ký).


2
Hoàn hảo cho một số thử nghiệm nhanh chống lại các máy phát triển. Cảm ơn.
Nate

2
Điều này ảnh hưởng đến phạm vi nào - mọi thứ trong miền ứng dụng? mọi thứ trên apppool? mọi thứ trên máy?
codeulike

29
Nhưng hãy cẩn thận! Các chương trình kinh nghiệm RL rằng hacks sự phát triển này thường làm cho nó cách vào sản phẩm phát hành: Các mã nguy hiểm nhất trên thế giới
Doomjunky

4
Đây là một bản hack hữu ích trong quá trình phát triển, vì vậy việc đặt câu lệnh #if DEBUG #endif xung quanh nó là điều ít nhất bạn nên làm để làm cho điều này an toàn hơn và ngăn điều này kết thúc trong quá trình sản xuất.
AndyD ngày

3
Trừ khi anh chàng này loại bỏ câu trả lời này, chúng ta sẽ thấy một sự thật hài hước rằng một câu trả lời sai nhận được nhiều phiếu bầu hơn câu trả lời đúng.
Lex Li

47

Lưu ý rằng trong .NET 4.5, bạn có thể ghi đè xác thực SSL trên chính HttpWebRequest (và không phải thông qua ủy quyền toàn cầu ảnh hưởng đến tất cả các yêu cầu):

http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.servercertificatevalidationcallback.aspx

HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.ServerCertificateValidationCallback = delegate { return true; };

1
Xin vui lòng ủng hộ điều này; cái này đáng để nâng cấp lên 4,5 cho!
Lynn sụp đổ

1
@FlorianWinter Có, bạn phải chấp nhận logic từ công cụ phát triển của người dùng
Summer-Time

43

Thêm chứng chỉ tự ký vào Tổ chức phát hành chứng chỉ gốc đáng tin cậy của máy tính cục bộ

Bạn có thể nhập chứng chỉ bằng cách chạy MMC với tư cách Quản trị viên.

Cách thực hiện: Xem Chứng chỉ với MMC Snap-in


4
IMHO đây là cách chính xác nhất; mọi người chỉ quá lười biếng nên họ viết mã trong những trường hợp ngoại lệ đặc biệt cho những thứ mà họ có thể không nên.
BrainSlugs83

4
Phương pháp đó có hoạt động với Windows Mobile 6.5 không? Làm thế nào về 7? Trong trường hợp của tôi, tôi không muốn phải thêm chứng chỉ cục bộ vào mọi thiết bị di động mà tôi định chạy phiên bản phát triển. Một ngoại lệ tốt, trong trường hợp này, làm cho việc triển khai dễ dàng hơn rất nhiều. Bạn cho tôi biết sự lười biếng hay hiệu quả.
Dominic Scheirlinck,

3
@domster Bạn đang sử dụng chứng chỉ SSL vì một lý do - để xác minh điểm cuối. Nếu bạn phát triển mã hoạt động cụ thể xung quanh đó, bạn không kiểm tra nó đúng cách và có nguy cơ rò rỉ mã đó vào môi trường sống. Nếu cài đặt chứng chỉ trên máy khách thực sự là quá nhiều công việc, tại sao không chỉ trả tiền cho chứng chỉ từ một tổ chức phát hành được tất cả các thiết bị tin cậy?
Cơ bản

1
@Basic Nếu tôi nhớ trường hợp cụ thể này, tôi sẽ cần một số chứng chỉ ký tự đại diện (có nửa tá TLD mà nó đang kết nối, tất cả đều nằm trong tầm kiểm soát của chúng tôi). Đó là một chi phí khó có thể biện minh cho một môi trường phát triển. Trong trường hợp này, mã duy nhất được "làm việc xung quanh" và không được kiểm tra là một ngoại lệ sẽ không được ném ở nơi mà nó sẽ có. Bạn nên thử nghiệm đường dẫn ngoại lệ cụ thể đó bất kể bạn có đang sử dụng giải pháp này hay không. Và, cuối cùng, nếu bạn không thể giữ cho mã phát triển không được sản xuất, bạn sẽ gặp vấn đề lớn hơn nhiều so với xác thực SSL.
Dominic Scheirlinck 14/12/12

đối với ứng dụng web, hãy nhớ tái chế nhóm ứng dụng của bạn hoặc khởi động lại trang web của bạn. cá nhân tôi chỉ biên dịch lại, và sau đó nó hoạt động. đối với nội dung wsdl của chúng tôi, xác minh chứng chỉ dường như xảy ra khi khởi tạo và lưu vào bộ nhớ cache.
sonjz

34

Phạm vi của lệnh gọi lại xác thực được sử dụng trong câu trả lời của Domster có thể được giới hạn trong một yêu cầu cụ thể bằng cách sử dụng tham số người gửi trên ServerCertificateValidationCallbackđại biểu. Lớp phạm vi đơn giản sau đây sử dụng kỹ thuật này để tạm thời kết nối một lệnh gọi lại xác thực chỉ thực thi cho một đối tượng yêu cầu nhất định.

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;
    }
}

Lớp trên có thể được sử dụng để bỏ qua tất cả các lỗi chứng chỉ cho một yêu cầu cụ thể như sau:

var request = WebRequest.Create(uri);
using (new ServerCertificateValidationScope(request, delegate { return true; }))
{
    request.GetResponse();
}

6
Câu trả lời này cần nhiều lượt bình chọn hơn :) Đó là câu trả lời hợp lý nhất để bỏ qua xác thực chứng chỉ cho một yêu cầu duy nhất bằng cách sử dụng đối tượng HttpWebRequest.
MikeJansen 16/10/12

Tôi đã thêm điều này và vẫn nhận được Yêu cầu đã bị hủy bỏ: Không thể tạo kênh bảo mật SSL / TLS.
vikingben

7
Điều này không thực sự giải quyết được vấn đề trong môi trường đa luồng.
Hans

1
maaan !!!, một bài viết cách đây 5 năm lưu ngày của tôi, tôi gặp sự cố khi kết nối với thiết bị-modem-vệ tinh cũ với chứng chỉ không hợp lệ !! Cảm ơn bạn!!
WindyHen

3

Chỉ xây dựng câu trả lời từ devstuff để bao gồm chủ đề và nhà phát hành ... các ý kiến ​​được hoan nghênh ...

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));            
    }
}

3

Để thêm trợ giúp có thể cho người khác ... Nếu bạn muốn nó nhắc người dùng cài đặt chứng chỉ tự ký, bạn có thể sử dụng mã này (được sửa đổi từ trên).

Không yêu cầu quyền quản trị viên, cài đặt cho các cấu hình đáng tin cậy của người dùng cục bộ:

    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;
    }

Điều này dường như hoạt động tốt cho ứng dụng của chúng tôi và nếu người dùng nhấn không, giao tiếp sẽ không hoạt động.

Cập nhật: 2015-12-11 - Đã thay đổi StoreName.Root thành StoreName.My - My sẽ cài đặt vào cửa hàng người dùng cục bộ, thay vì Root. Root trên một số hệ thống sẽ không hoạt động, ngay cả khi bạn "chạy với tư cách quản trị viên"


Điều này sẽ thật tuyệt vời nếu nó hoạt động trên Compact Framework winCE. store.Add (..) không có sẵn.
Dawit

1

Một điều cần lưu ý là việc có ServicePointManager.ServerCertificateValidationCallback dường như không có nghĩa là kiểm tra CRL và xác thực tên máy chủ không được thực hiện, nó chỉ cung cấp một phương tiện để ghi đè kết quả của chúng. Vì vậy, dịch vụ của bạn có thể vẫn mất một khoảng thời gian để nhận được CRL, sau đó bạn sẽ chỉ biết rằng nó đã thất bại trong một số lần kiểm tra.


1

Tôi đã gặp phải vấn đề tương tự như OP trong đó yêu cầu web sẽ đưa ra ngoại lệ chính xác đó. Tôi nghĩ mọi thứ đã được thiết lập đúng như tôi nghĩ, chứng chỉ đã được cài đặt, tôi có thể định vị nó trong cửa hàng máy tốt và đính kèm nó vào yêu cầu web và tôi đã tắt xác minh chứng chỉ theo ngữ cảnh yêu cầu.

Hóa ra là tôi đang chạy trong tài khoản người dùng của mình và chứng chỉ đã được cài đặt vào cửa hàng máy. Điều này khiến yêu cầu web ném ra ngoại lệ này. Để giải quyết vấn đề, tôi phải chạy với tư cách quản trị viên hoặc cài đặt chứng chỉ vào cửa hàng người dùng và đọc nó từ đó.

Có vẻ như C # có thể tìm thấy chứng chỉ trong cửa hàng máy mặc dù nó không thể được sử dụng với một yêu cầu web và điều này dẫn đến ngoại lệ của OP sẽ được ném ra sau khi yêu cầu web được đưa ra.


Đối với các dịch vụ Windows, bạn có thể thiết lập các cấu hình chứng chỉ riêng biệt cho mỗi dịch vụ. Nếu bạn đang viết không phải là một ứng dụng dành cho máy tính để bàn mà là một dịch vụ, thì chứng chỉ CA có thể được nhập vào MMC cho daemon dịch vụ cụ thể. Sự khác biệt giữa tài khoản người dùng và tài khoản máy là gì? Tôi nghĩ rằng mọi thứ trong tài khoản máy sẽ tự động áp dụng cho người dùng.
ArticIceJuice,

1

Trước hết - tôi xin lỗi, vì tôi đã sử dụng giải pháp được mô tả bởi @devstuff. Tuy nhiên, tôi đã tìm ra một số cách để cải thiện nó.

  • thêm xử lý chứng chỉ tự ký
  • so sánh bằng dữ liệu thô của chứng chỉ
  • thực tế xác nhận cơ quan cấp chứng chỉ
  • một số nhận xét bổ sung và cải tiến

Đây là sửa đổi của tôi:

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;
}

thiết lập chứng chỉ:

caCertificate2 = new X509Certificate2("auth/ca.crt", "");
var clientCertificate2 = new X509Certificate2("auth/client.pfx", "");

truyền phương pháp đại biểu

ServerCertificateValidationCallback(ValidateServerCertficate)

client.pfx được tạo bằng KEY và CERT như vậy:

openssl pkcs12 -export -in client.crt -inkey client.key -out client.pfx
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.