.NET의 HttpWebRequest / Response와 함께 자체 서명 된 인증서 사용


80

자체 서명 된 SSL 인증서를 사용하는 API에 연결하려고합니다. .NET의 HttpWebRequest 및 HttpWebResponse 개체를 사용하여 그렇게하고 있습니다. 그리고 다음과 같은 예외가 발생합니다.

기본 연결이 닫혔습니다. SSL / TLS 보안 채널에 대한 신뢰 관계를 설정할 수 없습니다.

이것이 의미하는 바를 이해합니다. 그리고 .NET이 경고하고 연결을 닫아야한다고 느끼는 이유를 이해 합니다. 그러나이 경우에는 어쨌든 API에 연결하고 싶습니다. man-in-the-middle 공격은 저주합니다.

그렇다면이 자체 서명 된 인증서에 대한 예외를 추가하려면 어떻게해야합니까? 아니면 HttpWebRequest / Response에게 인증서의 유효성을 전혀 확인하지 않도록 지시하는 방법입니까? 어떻게할까요?

답변:


81

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

아마도 아래에서 올바른 방법을 선호 한 사람 일 것입니다. 어쨌든,이 해킹은 급격히 작동하지만, 이러한 종류의 예외를 코딩해서는 안됩니다. 확인을 모두 함께 비활성화하거나 (바로 아래 제안을 통해) 실제로 컴퓨터에 인증서를 신뢰하도록 지시하십시오 .. .
BrainSlugs83

4
@ BrainSlugs83 : 비활성화도 확실히 옵션이지만 시스템 수준 루트 권한 저장소에 인증서를 추가하는 것은 관리자 만 수행 할 수 있습니다. 내 솔루션은 어느 쪽이든 작동합니다.
devstuff

그리고 나는 그것을 완전히 이해하지만, 당신은 물었고, 그것은 누군가가 당신의 대답에 반대 투표를 한 이유에 대한 나의 추측입니다. 더 많은 작업이 있더라도 아래 IMHO wgthom의 대답은 여전히 ​​가장 정확한 답변입니다.
BrainSlugs83

btw, 조심해, ServerCertificateValidationCallback이 STATIC이고 심지어 threadlocal이라고 생각합니다. 내가 틀리지 않다면 일단 설정되면 당신이 그것을 지울 때까지 설정된 상태로 유지됩니다. 한 연결에만 사용하고 다른 모든 연결에는 사용하지 않으려면 병렬 요청에 매우주의하십시오.
quetzalcoatl

3
이것이 가장 좋은 방법입니다. sslPolicyErrors에 대한 검사를 제거하면 실제로 API 인증서가 항상 예상되는 인증서인지 확인할 수 있습니다. 한 가지 주목할 점은 위 코드의 인증서 지문이 const 바이트 배열이라는 것입니다. 이것은 작성된대로 컴파일되지 않습니다. 대신 정적 읽기 전용 바이트 배열을 시도하십시오. 컴파일러는 new () 연산자가 필요하기 때문에 이것에 대해 질식합니다.
Centijo 2014

92

인증서 유효성 검사를 모두 비활성화하려면 ServicePointManager에서 ServerCertificateValidationCallback을 다음과 같이 변경할 수 있습니다.

ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

이는 모든 인증서 (유효하지 않거나 만료되었거나 자체 서명 된 인증서 포함)의 유효성을 검사합니다.


2
개발 머신에 대한 빠른 테스트에 적합합니다. 감사.
Nate

2
이것이 영향을 미치는 범위-appdomain의 모든 것? apppool의 모든 것? 기계에있는 모든 것?
codeulike 2011-08-18

29
하지만 조심하세요! RL 경험에 따르면이 개발 해킹은 종종 릴리스 제품 에 영향을줍니다. 세상에서 가장 위험한 코드
Doomjunky

4
이것은 개발에 유용한 해킹이므로 주변에 #if DEBUG #endif 문을 두는 것이이 작업을 더 안전하게 만들고 프로덕션에서 끝나는 것을 막기 위해 최소한해야 할 일입니다.
AndyD

3
이 사람이이 대답을 제거하지 않는 한, 우리는 잘못된 대답이 정답보다 훨씬 더 많은 표를 받는다는 재미있는 사실을 보게 될 것입니다.
Lex Li

47

.NET 4.5에서는 HttpWebRequest 자체에 따라 SSL 유효성 검사를 재정의 할 수 있습니다 (모든 요청에 ​​영향을주는 전역 대리자를 통하지 않음).

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

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

1
이것을 찬성하십시오; 이것은 4.5로 업그레이드 할 가치가 있습니다!
Lynn Crumbling

1
@FlorianWinter 예, 사용자 devstuff의 논리를 채택해야합니다
Summer-Time

43

로컬 컴퓨터의 신뢰할 수있는 루트 인증 기관에 자체 서명 된 인증서 추가

MMC를 관리자로 실행하여 인증서를 가져올 수 있습니다.

방법 : MMC 스냅인을 사용하여 인증서보기


4
IMHO 이것이 가장 올바른 방법입니다. 사람들은 너무 게으 르기 때문에하지 말아야 할 것들에 대해 특별한 예외로 코딩합니다.
BrainSlugs83 2011

4
이 방법이 Windows Mobile 6.5에서 작동합니까? 7은 어때? 제 경우에는 개발 버전을 실행하려고 계획 한 모든 모바일 장치에 로컬 인증서를 추가 할 필요가 없었습니다. 이 경우 좋은 예외는 배포를 훨씬 쉽게 만듭니다. 게으름이나 효율성, 당신이 말해줍니다.
Dominic Scheirlinck

3
@domster 끝점을 확인하기 위해 SSL 인증서를 사용하고 있습니다. 특별히 작동하는 코드를 개발하는 경우 제대로 테스트하지 못하고 해당 코드가 실제 환경으로 유출 될 위험이 있습니다. 클라이언트에 인증서를 설치하는 것이 너무 많은 작업이라면 모든 장치에서 신뢰하는 발급자의 인증서에 대해 비용을 지불하는 것이 어떻습니까?
Basic

1
@Basic이 특정한 경우를 기억한다면 여러 개의 와일드 카드 인증서가 필요했을 것입니다 (우리가 제어하는 ​​6 개의 TLD가 연결되어있었습니다). 이는 개발 환경에 대해 정당화하기 어려운 비용입니다. 이 경우 "작업"되고 테스트되지 않는 유일한 코드는 그렇지 않은 경우 예외가 발생하지 않는다는 것입니다. 이 해결 방법을 사용하는지 여부에 관계없이 특정 예외 경로를 테스트해야합니다. 마지막으로, 개발 코드를 프로덕션에서 제외 할 수 없다면 SSL 유효성 검사보다 훨씬 더 큰 문제가 있습니다.
Dominic Scheirlinck

웹 앱의 경우 앱 풀을 재활용하거나 웹 사이트를 다시 시작하십시오. 개인적으로 방금 재 컴파일 한 다음 작동했습니다. wsdl 항목의 경우 인증서 확인이 초기화시 발생하고 캐시 된 것으로 보입니다.
sonjz 2015-08-12

35

Domster의 응답에 사용되는 유효성 검사 콜백의 범위 는 ServerCertificateValidationCallback대리자 의 sender 매개 변수를 사용하여 특정 요청으로 제한 될 수 있습니다 . 다음 간단한 범위 클래스는이 기술을 사용하여 주어진 요청 객체에 대해서만 실행되는 유효성 검사 콜백을 일시적으로 연결합니다.

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

6
이 답변에는 더 많은 찬성 투표가 필요합니다. :) HttpWebRequest 객체를 사용하는 단일 요청에 대한 인증서 유효성 검사를 건너 뛰는 것이 가장 합리적인 대답입니다.
MikeJansen 2010 년

이 항목을 추가했는데 여전히 요청이 중단되었습니다. SSL / TLS 보안 채널을 만들 수 없습니다.
vikingben

7
이것은 다중 스레드 환경에서 문제를 실제로 해결하지 못합니다.
Hans

1
maaan !!!, 5 년 된 게시물이 내 하루를 저장합니다. 유효하지 않은 인증서로 오래된 위성 모뎀 기기에 연결하는 데 문제가 있습니다 !! 감사합니다!!
WindyHen

혼란스럽고 / 약간 걱정됩니다! SslPolicyErrors.None이전 콜백이 없었던 경우 반환 된다는 것은 '모두 수락'정책으로 기본 정책을 재정의한다는 것을 의미하지 않습니까? cf. 이 질문과 다양한 답변 : stackoverflow.com/q/9058096 . 내가 왜 틀렸는 지,이 코드는 괜찮다는 말을 듣게되어 매우 기쁩니다!
MikeBeaton

3

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

3

다른 사람에게 가능한 도움으로 추가하려면 ... 사용자에게 자체 서명 된 인증서를 설치하라는 메시지를 표시하려면이 코드를 사용할 수 있습니다 (위에서 수정 됨).

관리자 권한이 필요하지 않으며 로컬 사용자에게 신뢰할 수있는 프로필을 설치합니다.

    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는 루트가 아닌 로컬 사용자 저장소에 설치됩니다. 일부 시스템에서는 "관리자 권한으로 실행"하더라도 루트가 작동하지 않습니다.


Compact Framework winCE에서 작동한다면 멋질 것입니다. store.Add (..)를 사용할 수 없습니다.
Dawit

1

한 가지 명심해야 할 점은 ServicePointManager.ServerCertificateValidationCallback을 사용한다고해서 CRL 확인 및 서버 이름 유효성 검사가 완료되지 않았 음을 의미하는 것이 아니라 결과를 재정의하는 수단 만 제공한다는 것입니다. 따라서 서비스가 CRL을 얻는 데 여전히 시간이 걸릴 수 있으며 나중에 일부 검사에 실패했음을 알 수 있습니다.


1

나는 웹 요청이 정확한 예외를 던지는 OP와 동일한 문제에 직면했습니다. 나는 모든 것이 올바르게 설정되었고, 인증서가 설치되었고, 컴퓨터 저장소에서 그것을 찾아서 웹 요청에 첨부 할 수 있었고, 요청 컨텍스트에서 인증서 확인을 비활성화했습니다.

내 사용자 계정으로 실행 중이며 인증서가 컴퓨터 저장소에 설치되어 있음이 밝혀졌습니다. 이로 인해 웹 요청에서이 예외가 발생했습니다. 문제를 해결하려면 관리자로 실행하거나 인증서를 사용자 저장소에 설치하고 거기에서 읽어야했습니다.

웹 요청과 함께 사용할 수 없더라도 C #이 컴퓨터 저장소에서 인증서를 찾을 수있는 것처럼 보이며 이로 인해 웹 요청이 발행되면 OP 예외가 발생합니다.


Windows 서비스의 경우 각 서비스마다 별도의 인증서 구성을 설정할 수 있습니다. 데스크톱 앱이 아니라 서비스를 작성하는 경우 특히 서비스 데몬에 대한 CA 인증서를 MMC에서 가져올 수 있습니다. 사용자 계정과 컴퓨터 계정의 차이점은 무엇입니까? 컴퓨터 계정의 모든 것이 사용자에게 자동으로 적용된다고 생각했습니다.
ArticIceJuice jul.

1

우선 @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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.