.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.