it-swarm.dev

Bir Web API'sinde kimliğini doğrulamak ve yetkilendirmek için bir istemci sertifikası nasıl kullanılır

Bir Web API kullanarak cihazların kimliğini doğrulamak ve yetkilendirmek için bir istemci sertifikası kullanmaya çalışıyorum ve olası çözümle ilgili sorunları çözmek için basit bir konsept kanıtı geliştirdim. İstemci sertifikasının web uygulaması tarafından alınmadığı bir sorunla karşılaşıyorum. Bu sorunu bildiren çok sayıda insan, bu soru ve cevap dahil ancak hiçbirinin yanıtı yok. Umudum bu konuyu canlandırmak için daha fazla ayrıntı sağlamak ve umarım konuma bir cevap almaktır. Diğer çözümlere açığım. Ana gereksinim, C # ile yazılmış bağımsız bir işlemin bir Web API'sini çağırabilmesi ve bir istemci sertifikası kullanarak kimliğini doğrulayabilmesidir. 

Bu POC içindeki Web API'si çok basittir ve sadece tek bir değer döndürür. HTTPS'nin kullanıldığını ve bir istemci sertifikası bulunduğunu doğrulamak için bir öznitelik kullanır. 

public class SecureController : ApiController
{
    [RequireHttps]
    public string Get(int id)
    {
        return "value";
    }

}

RequireHttpsAttribute kodu:

public class RequireHttpsAttribute : AuthorizationFilterAttribute 
{ 
    public override void OnAuthorization(HttpActionContext actionContext) 
    { 
        if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps) 
        { 
            actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden) 
            { 
                ReasonPhrase = "HTTPS Required" 
            }; 
        } 
        else 
        {
            var cert = actionContext.Request.GetClientCertificate();
            if (cert == null)
            {
                actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
                {
                    ReasonPhrase = "Client Certificate Required"
                }; 

            }
            base.OnAuthorization(actionContext); 
        } 
    } 
}

Bu POC'da sadece müşteri sertifikasının kullanılabilirliğini kontrol ediyorum. Bu işe yaradığında, sertifika listesini doğrulamak için sertifikadaki bilgileri kontrol edebilirim.

İşte bu web uygulaması için SSL için IIS içindeki ayar.

 enter image description here

İsteği bir istemci sertifikasıyla gönderen müşterinin kodu. Bu bir konsol uygulamasıdır.

    private static async Task SendRequestUsingHttpClient()
    {
        WebRequestHandler handler = new WebRequestHandler();
        X509Certificate certificate = GetCert("ClientCertificate.cer");
        handler.ClientCertificates.Add(certificate);
        handler.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(ValidateServerCertificate);
        handler.ClientCertificateOptions = ClientCertificateOption.Manual;
        using (var client = new HttpClient(handler))
        {
            client.BaseAddress = new Uri("https://localhost:44398/");
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            HttpResponseMessage response = await client.GetAsync("api/Secure/1");
            if (response.IsSuccessStatusCode)
            {
                string content = await response.Content.ReadAsStringAsync();
                Console.WriteLine("Received response: {0}",content);
            }
            else
            {
                Console.WriteLine("Error, received status code {0}: {1}", response.StatusCode, response.ReasonPhrase);
            }
        }
    }

    public static bool ValidateServerCertificate(
      object sender,
      X509Certificate certificate,
      X509Chain chain,
      SslPolicyErrors sslPolicyErrors)
    {
        Console.WriteLine("Validating certificate {0}", certificate.Issuer);
        if (sslPolicyErrors == SslPolicyErrors.None)
            return true;

        Console.WriteLine("Certificate error: {0}", sslPolicyErrors);

        // Do not allow this client to communicate with unauthenticated servers.
        return false;
    }

Bu test uygulamasını çalıştırdığımda, RequireHttpsAttribute içine girdiğini ve herhangi bir müşteri sertifikası bulamadığını belirten “Müşteri Sertifikası Gerekli” ifadesinin bir nedeni ile 403 Yasak durum kodunu geri alıyorum. Bunu bir hata ayıklayıcı aracılığıyla çalıştırma Sertifikanın yüklendiğini ve WebRequestHandler'a eklendiğini doğruladım. Sertifika, yüklenmekte olan bir CER dosyasına verilir. Özel anahtarlı sertifikanın tamamı, Web uygulama sunucusu için Yerel Makinenin Kişisel ve Güvenilir Kök mağazalarında bulunur. Bu test için müşteri ve web uygulaması aynı makinede çalıştırılıyor.

Bu Web API yöntemini Fiddler kullanarak arayabilir, aynı müşteri sertifikasını ekleyebilirim ve iyi çalışıyor. Fiddler kullanırken RequireHttpsAttribute içindeki testleri geçer ve başarılı bir 200 durum kodu döndürür ve beklenen değeri döndürür.

HttpClient'in talepte bir müşteri sertifikası gönderemediği ve bir çözüm bulamadığı herhangi biri aynı sorunu yaşadı mı?

Güncelleme 1:

Ayrıca, sertifikayı özel anahtarı içeren sertifika deposundan almayı da denedim. İşte nasıl aldım:

    private static X509Certificate2 GetCert2(string hostname)
    {
        X509Store myX509Store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
        myX509Store.Open(OpenFlags.ReadWrite);
        X509Certificate2 myCertificate = myX509Store.Certificates.OfType<X509Certificate2>().FirstOrDefault(cert => cert.GetNameInfo(X509NameType.SimpleName, false) == hostname);
        return myCertificate;
    }

Bu sertifikanın doğru alındığını ve istemci sertifika koleksiyonuna eklendiğini doğruladım. Ancak, sunucu kodunun herhangi bir müşteri sertifikası almadığı durumlarda da aynı sonuçları aldım.

Burada eksiksiz olması için sertifikayı bir dosyadan almak için kullanılan kod şudur:

    private static X509Certificate GetCert(string filename)
    {
        X509Certificate Cert = X509Certificate.CreateFromCertFile(filename);
        return Cert;

    }

Sertifikayı bir dosyadan aldığınızda, X509Certificate türünde bir nesne döndürdüğünü ve sertifika deposundan aldığınızda X509Certificate2 türünde olduğunu fark edeceksiniz. X509CertificateCollection.Add Method, bir tür X509Certificate bekliyor.

Güncelleme 2: Hala bunu anlamaya çalışıyorum ve birçok farklı seçenek denedim, ama boşuna. 

  • Web uygulamasını yerel ana bilgisayar yerine Ana bilgisayar adına çalışacak şekilde değiştirdim.
  • Web uygulamasını SSL gerektirecek şekilde ayarladım
  • Sertifikanın Müşteri Kimlik Doğrulaması için ayarlandığını ve güvenilir kökte olduğunu doğruladım.
  • Müşteri sertifikasını Fiddler'da test etmenin yanı sıra, Chrome'da da doğruladım.

Bu seçeneklerin denenmesi sırasında bir noktada çalışmaya başladı. Sonra neyin işe yaradığını görmek için değişiklikleri yedeklemeye başladım. Çalışmaya devam etti. Sonra bunun gerekli olduğunu doğrulamak için sertifikayı güvenilir kökünden çıkarmayı denedim ve çalışmayı durdurdu ve sertifikayı güvenilir köke geri koymama rağmen çalışmaya devam edemiyorum. Artık Chrome, benim de kullandığı gibi bir sertifika istemeyecek ve Chrome'da başarısız olacak, ancak hala Fiddler'da çalışıyor. Kaçırdığım bazı sihirli konfigürasyonlar olmalı.

Ayrıca bağlayıcıda "Müşteri Sertifikasını Pazarla" seçeneğini etkinleştirmeyi de denedim, ancak Chrome hala bir müşteri sertifikası için bana sormayacak. İşte "netsh http show sslcert" kullanan ayarlar

 IP:port                 : 0.0.0.0:44398
 Certificate Hash        : 429e090db21e14344aa5d75d25074712f120f65f
 Application ID          : {4dc3e181-e14b-4a21-b022-59fc669b0914}
 Certificate Store Name  : MY
 Verify Client Certificate Revocation    : Disabled
 Verify Revocation Using Cached Client Certificate Only    : Disabled
 Usage Check    : Enabled
 Revocation Freshness Time : 0
 URL Retrieval Timeout   : 0
 Ctl Identifier          : (null)
 Ctl Store Name          : (null)
 DS Mapper Usage    : Disabled
 Negotiate Client Certificate    : Enabled

İşte kullanıyorum müşteri sertifikası:

 enter image description here

 enter image description here

 enter image description here

Sorunun ne olduğu konusunda şaşkınım. Bunu çözmeme yardım edebilecek biri için bir ödül ekliyorum.

30
Kevin Junghans

İzleme, sorunun ne olduğunu bulmama yardımcı oldu (Bu öneri için Fabian'a teşekkür ederim). Daha fazla test yaparak, istemci sertifikasının başka bir sunucuda çalışmasını sağlayabileceğimi gördüm (Windows Server 2012). Bunu geliştirme makinemde (Pencere 7) test ediyordum, böylece bu işlemin hatalarını giderebildim. Böylece, izlemeyi çalıştıran ve çalıştıramayan bir IIS Sunucusu ile karşılaştırarak izleyen satırdaki ilgili satırları belirleyebildim. İşte müşteri sertifikasının çalıştığı bir logun bir kısmı. Bu, göndermeden hemen önceki ayardır

System.Net Information: 0 : [17444] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=CredentialsNeeded).
System.Net Information: 0 : [17444] SecureChannel#54718731 - We have user-provided certificates. The server has not specified any issuers, so try all the certificates.
System.Net Information: 0 : [17444] SecureChannel#54718731 - Selected certificate:

İstemci sertifikasının başarısız olduğu makinede izleme günlüğü nasıl göründüğü.

System.Net Information: 0 : [19616] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=CredentialsNeeded).
System.Net Information: 0 : [19616] SecureChannel#54718731 - We have user-provided certificates. The server has specified 137 issuer(s). Looking for certificates that match any of the issuers.
System.Net Information: 0 : [19616] SecureChannel#54718731 - Left with 0 client certificates to choose from.
System.Net Information: 0 : [19616] Using the cached credential handle.

Sunucuyu belirten çizgiye odaklanarak 137 vericiyi buldum bunu buldum Sorunumla benzer görünen Q&A . Benim için çözüm, sertifikamın güvenilir kök içerisinde olması nedeniyle cevap olarak işaretlenmemiş. Cevap altındaki kayıt defterini güncellediğiniz yerde. Değeri sadece kayıt defteri anahtarına ekledim.

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet Control\SecurityProviders\Schannel\

Değer adı: SendTrustedIssuerList Değer türü: REG_DWORD Değer verisi: 0 (Yanlış)

Bu değeri kayıt defterine ekledikten sonra Windows 7 makinemde çalışmaya başladı. Bu bir Windows 7 sorunu gibi görünüyor.

7
Kevin Junghans

Güncelle:

Microsoft'tan örnek:

https://docs.Microsoft.com/en-us/Azure/app-service/app-service-web-configure-tls-mutual-auth#special-considerations-for-certificate-validation

Orijinal

Müşteri sertifikasını bu şekilde çalıştırıp belirli bir Kök CA'nın belirli bir sertifika olduğu gibi yayınladığını kontrol etmemdir.

İlk önce <src>\.vs\config\applicationhost.config dosyasını düzenledim ve bu değişikliği yaptım: <section name="access" overrideModeDefault="Allow" />

Bu, <system.webServer> içinde web.config dosyasını düzenlememe ve IIS Express'te istemci sertifikası gerektiren aşağıdaki satırları eklememe izin veriyor. Not: Bunu geliştirme amacıyla düzenledim, üretimde geçersiz kılmaya izin verme.

Üretim için, IIS'yi kurmak üzere böyle bir kılavuz izleyin:

https://medium.com/@hafizmohammedg/configuring-client-certificates-on-iis-95aef4174ddb

web.config:

<security>
  <access sslFlags="Ssl,SslNegotiateCert,SslRequireCert" />
</security>

API Kontrolörü:

[RequireSpecificCert]
public class ValuesController : ApiController
{
    // GET api/values
    public IHttpActionResult Get()
    {
        return Ok("It works!");
    }
}

Özellik:

public class RequireSpecificCertAttribute : AuthorizationFilterAttribute
{
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps)
        {
            actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
            {
                ReasonPhrase = "HTTPS Required"
            };
        }
        else
        {
            X509Certificate2 cert = actionContext.Request.GetClientCertificate();
            if (cert == null)
            {
                actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
                {
                    ReasonPhrase = "Client Certificate Required"
                };

            }
            else
            {
                X509Chain chain = new X509Chain();

                //Needed because the error "The revocation function was unable to check revocation for the certificate" happened to me otherwise
                chain.ChainPolicy = new X509ChainPolicy()
                {
                    RevocationMode = X509RevocationMode.NoCheck,
                };
                try
                {
                    var chainBuilt = chain.Build(cert);
                    Debug.WriteLine(string.Format("Chain building status: {0}", chainBuilt));

                    var validCert = CheckCertificate(chain, cert);

                    if (chainBuilt == false || validCert == false)
                    {
                        actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
                        {
                            ReasonPhrase = "Client Certificate not valid"
                        };
                        foreach (X509ChainStatus chainStatus in chain.ChainStatus)
                        {
                            Debug.WriteLine(string.Format("Chain error: {0} {1}", chainStatus.Status, chainStatus.StatusInformation));
                        }
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.ToString());
                }
            }

            base.OnAuthorization(actionContext);
        }
    }

    private bool CheckCertificate(X509Chain chain, X509Certificate2 cert)
    {
        var rootThumbprint = WebConfigurationManager.AppSettings["rootThumbprint"].ToUpper().Replace(" ", string.Empty);

        var clientThumbprint = WebConfigurationManager.AppSettings["clientThumbprint"].ToUpper().Replace(" ", string.Empty);

        //Check that the certificate have been issued by a specific Root Certificate
        var validRoot = chain.ChainElements.Cast<X509ChainElement>().Any(x => x.Certificate.Thumbprint.Equals(rootThumbprint, StringComparison.InvariantCultureIgnoreCase));

        //Check that the certificate thumbprint matches our expected thumbprint
        var validCert = cert.Thumbprint.Equals(clientThumbprint, StringComparison.InvariantCultureIgnoreCase);

        return validRoot && validCert;
    }
}

Daha sonra başka bir web projesinde test edilen API gibi müşteri sertifikası ile arayabilir.

[RoutePrefix("api/certificatetest")]
public class CertificateTestController : ApiController
{

    public IHttpActionResult Get()
    {
        var handler = new WebRequestHandler();
        handler.ClientCertificateOptions = ClientCertificateOption.Manual;
        handler.ClientCertificates.Add(GetClientCert());
        handler.UseProxy = false;
        var client = new HttpClient(handler);
        var result = client.GetAsync("https://localhost:44331/api/values").GetAwaiter().GetResult();
        var resultString = result.Content.ReadAsStringAsync().GetAwaiter().GetResult();
        return Ok(resultString);
    }

    private static X509Certificate GetClientCert()
    {
        X509Store store = null;
        try
        {
            store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
            store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);

            var certificateSerialNumber= "‎81 c6 62 0a 73 c7 b1 aa 41 06 a3 ce 62 83 ae 25".ToUpper().Replace(" ", string.Empty);

            //Does not work for some reason, could be culture related
            //var certs = store.Certificates.Find(X509FindType.FindBySerialNumber, certificateSerialNumber, true);

            //if (certs.Count == 1)
            //{
            //    var cert = certs[0];
            //    return cert;
            //}

            var cert = store.Certificates.Cast<X509Certificate>().FirstOrDefault(x => x.GetSerialNumberString().Equals(certificateSerialNumber, StringComparison.InvariantCultureIgnoreCase));

            return cert;
        }
        finally
        {
            store?.Close();
        }
    }
}
5
Ogglas

Aslında benzer bir sorunum vardı; burada birçok güvenilir kök sertifikamız vardı. Yeni kurulan web sunucumuzun üzerinde bir sıkıntı vardı. Kökünüz Z harfiyle başladı, böylece listenin sonunda sona erdi. 

Sorun şu ki, IIS müşteriye sadece ilk yirmi şeye güvenilen kökleri gönderdi ve bizimkiler de dahil olmak üzere geri kalanını kesdi. Birkaç yıl önceydi, aracın adını hatırlayamıyorum ... IIS takım grubunun bir parçasıydı, ama Fiddler de yapmalıydı. Hatayı fark ettikten sonra, ihtiyacımız olmayan çok fazla güvenilir kök kaldırdık. Bu deneme yanılma yapıldı, ne sildiğinize dikkat edin.

Temizlikten sonra her şey bir cazibe gibi çalıştı.

1
Brandtware

HttpClient'in tam müşteri sertifikasına erişimi olduğundan emin olun (özel anahtar dahil). 

Özel bir anahtar bulunmadığı varsayımına yol açan "ClientCertificate.cer" dosyasıyla GetCert'i çağırıyorsunuz; bunun yerine pencerelerin içinde bir pfx dosyası olması gerekir. Sertifikaya Windows sertifika deposundan erişmek ve parmak izini kullanarak aramak daha da iyi olabilir.

Parmak izini kopyalarken dikkatli olun: Sertifika yönetiminde görüntülerken yazdırılamayan bazı karakterler var (dizeyi notepad ++ 'a kopyalayın ve görüntülenen dizenin uzunluğunu kontrol edin).

1
Daniel Nachtrub

Kaynak koduna bakarak da özel anahtarla ilgili bir sorun olması gerektiğini düşünüyorum.

Yaptığı şey aslında geçirilen sertifikanın X509Certificate2 tipinde olup olmadığını ve özel anahtara sahip olup olmadığını kontrol etmektir. 

Özel anahtarı bulamazsa, sertifikayı CurrentUser deposunda ve ardından LocalMachine deposunda bulmaya çalışır. Sertifikayı bulursa, özel anahtarın mevcut olup olmadığını kontrol eder.

(bakınız SecureChannnel sınıfından source code , yöntem EnsurePrivateKey)

Bu nedenle, hangi dosyayı içe aktardığınıza (.cer - özel anahtar olmadan veya .pfx - özel anahtarla) ve hangi mağazada doğru dosyayı bulamayacağınıza ve Request.ClientCertificate doldurulmaz.

Bu hata ayıklamayı denemek için Ağ İzleme özelliğini etkinleştirebilirsiniz. Size şöyle bir çıktı verecek:

  • Sertifika deposunda eşleşen bir sertifika bulmaya çalışıyorum
  • Sertifika LocalMachine deposunda veya CurrentUser deposunda bulunamıyor.
0
Fabian

Geçenlerde benzer bir sorunla karşılaştım ve Fabian'ın tavsiyelerini takip ederek beni çözüme ulaştırdım. Müşteri güvencesiyle ortaya çıktı, iki şeyden emin olmalısın:

  1. Özel anahtar aslında sertifikanın bir parçası olarak ihraç ediliyor.

  2. Uygulamayı çalıştıran uygulama havuzu kimliği, söz konusu özel anahtara erişebilir.

Bizim durumumuzda:

  1. Özel anahtarın gönderildiğinden emin olmak için dışa aktar onay kutusunu işaretlerken pfx dosyasını yerel sunucu deposuna alın.
  2. MMC konsolunu kullanarak, kullanılan hizmet hesabına sertifika için özel anahtara erişim izni verin.

Diğer cevaplarda açıklanan güvenilen kök sorunu geçerli bir sorundur, bizim durumumuzda sorun değildi.

0