reede, 25. jaanuar 2019

Windows Serveri seadistamine Telia X-Tee turvaserverisse SOAP andmete saatmiseks C# veebipäringutega


Kui nüüd on ette tulnud infosüsteemi liidestus X-Teega ja vaja hakata andmeid vahetama läbi Telia XTee turvaserveri tuleb Windows Serveri seadistuses teha serveri poolse ASP.NET koodi jaoks järgmist.

Kontrollida, et serveris oleks TLS 1.2 protokoll lubatud, Telia XTee nõuab TLS 1.2 olemasolu ja ilma selleta ühendust ei tehta. Vastavad TLS 1.2 registrivõtmed DisabledByDefault(0) ja Enabled(1) lisada
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/SecurityProviders/SCHANNEL/Protocols kataloogi. Sõltuvalt Windows Serveri versionist võib TLS 1.2 olla juba vaikimisi lubatud.



Et kliendi sertifikaadiga autentimine toimiks tuleb võimaldada FIPS, ehk Group Policy alt seadistada
lubatuks Use FIPS compliant algorithms for encryption, hashing, and signing seadistus



Edasi tuleb genereerida avaliku ja privaatvõtme paar ning privaatvõti laadida Windows Serveri sertifikaadihoidlasse Local Computer / Personal

Kuidas genereerida sertifikaate

Selleks, et C# kood sertifikaadiga autentida saaks kas siis .NET rakendusest või SQL SERVER CLR C# meetodiga, peab vastaval kontol, mille all rakendus töötab olema ligipääs sertifikaadihoidlasse laetud privaatvõtmele. Juurdepääsu saab anda ainult Local Computer / Personal hoidlas olevatele privaatvõtmega sertifikaatidele

Local Computer / Personal sertifikaadihoidla avamiseks tuleb C# kasutada StoreName.My parameetrit

X509Store certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);


Hoidlast räsi järgi otsimiseks X509FindType.FindByThumbprint seadistust. Privaatvõtme serdi räsi saab teada meetodiga X509Certificate2.GetSerialNumberString();

Koodijupp, mis SQL SERVER CLR C# funktsioonina on realiseeritud, selleks, et TLS 1.2 töötaks peab projektis olema kasutatud vähemalt .NET Target Frameworki versioon 4.5 või kõrgem

TLS 1.2 otsene rakendamine ServicePointManager-i kasutades määratakse
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

TLS 1.2 ühendust saab luua kas System.Net.Http.HttpClient või System.Net.Security.SslStream klasse kasutades


System.Net.Http.HttpClient näide

    /// <summary>
    ///   Telia XTee turvaserveri kaudu kliendi sertifikaadiga TLS 1.2 protokolli abil SOAP päring
    /// </summary>
    /// <param name="Url">XTee turvaserveri https URL</param>
    /// <param name="envelope">SOAP päringu XML sisu stringina</param>
    /// <param name="serthash">Local Computer / Personal sertifikaadihoidlas oleva sertifikaadi räsi</param>
    /// <returns></returns>
    [Microsoft.SqlServer.Server.SqlFunction]
    public static SqlChars XTeeSOAPRequest(SqlString Url, SqlString envelope, SqlString serthash)
    {
        XmlDocument soapXml = new XmlDocument();
        try
        {
            soapXml.LoadXml((string)envelope);
        }
        catch (SystemException ex)
        {
            throw new SystemException("soapXml.LoadXml ERROR:"+ex.Message+"  SISU:"+(string)envelope);
        }
        string soapResult = "";

     
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
     
        HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create((string)Url);
     
        string chash = (string)serthash;
        if (!String.IsNullOrEmpty(chash) )
        {
         
            X509Store certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
            certStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

         
            X509Certificate2Collection serts = certStore.Certificates.Find(X509FindType.FindByThumbprint, chash, true);
            if (serts.Count == 0)
            {
                serts = certStore.Certificates.Find(X509FindType.FindByThumbprint, chash, false);
            }
            if (serts.Count != 1)
            {
                throw new SystemException("Sertifikaati räsiga " + chash + "  ei ole hoidlas Local Computer - Personal");
            }
            X509Certificate2 cert = serts[0];
         
            certStore.Close();
            webRequest.ClientCertificates.Clear();
            webRequest.ClientCertificates.Add(cert);
            if (webRequest.ClientCertificates.Count != 1)
            {
                throw new SystemException("HttpWebRequest sertifikaatide tõrge, vale kogus " + cert.IssuerName.Name);
            }

        }
     
        webRequest.ContentType = "text/xml;charset=\"utf-8\"";
        webRequest.Accept = "text/xml";
        webRequest.Method = "POST";
        webRequest.UserAgent = "KAMPUS";
        using (Stream stream = webRequest.GetRequestStream())
        {
            soapXml.Save(stream);
        }

        using (WebResponse response = webRequest.GetResponse())
        {
            using (StreamReader rd = new StreamReader(response.GetResponseStream()))
            {
                soapResult = rd.ReadToEnd();
             
            }
        }

        SqlChars sd = new SqlChars(((SqlString)soapResult));
        return sd; //siin on vastus, SOAP XML
    }

Rakenduse kontole tuleb anda ligipääs privaatvõtmele, Local Computer / Personal sertifikaadihoidlas tuleb vastava privaatvõtme serdi peale teha hiire paremklõps ja valida



Ning seada Read õigused kontole



Mis konto all ASP.NET veebirakendus töötab saab teada C# funktsiooniga
System.Security.Principal.WindowsIdentity.GetCurrent().Name
MS SQL SERVER-i konto Services rakendusest



Veatõrgete jälgimiseks on ASP.NET rakendusel soovitav peale seada System.Diagnostic trace logimine
https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/ty48b824(v=vs.100)

Logi veateade The credentials supplied to the package were not recognized  viitab konto õiguste puudumisele privaatvõtmega sertifikaadile ligipääsul

.NET error The request was aborted: Could not create SSL/TLS secure channel viitab privaatvõtmega sertifikaadi puudumisele , FIPS keelamisele või TLS 1.2 mittelubamisele.

Kui ikka segane, pane serveris Wireshark käima


ja jälgi, et nii server teeks Client Certificate küsimise ja klient vastaks ka Certificate vastusega, kui Handshake Protocol Certificates / Certificates Lenght : 0 siis kliendi poolt sertifikaati kaasa ei pandud, kusjuures see kliendi ärakorjamine tehakse Windows Serveri poolt ilma midagi kobisedes ning C# koodi poolt HttpWebRequest.ClientCertificates.Add(cert);

meetod ei ütle kaa midagi paha, et sertifikaati külge panemine on mõttetu, kuna alumised protokollid ei pruugi TLS 1.2 toetada, sertifikaat võetakse küljest Windows Serveri poolt ja veateade mis välja antakse võib olla selline

Server.ClientProxy.SslAuthenticationFailed Client (....) specifies HTTPS but did not supply TLS certificate

Kontrolli FIPS seadistust ja konto õigusi privaatvõtmega sertifikaadile. Kontode puhul tähele panna, et isegi kui ASP.NET rakenduses certStore.Certificates.Find(X509FindType.FindByThumbprint ... leiab privaatvõtmega sertifikaadi ülesse, siis veebipäringu tegemisel ei pruugi see õigus enam toimida vaid vaja on otsest Read õiguse andmist ASP.NET või MS SQL SERVER kontole

TLS 1.2 programmeerimine sõltub tugevalt kasutatavast .NET Framework versioonist, mida kõrgem seda parem. https://docs.microsoft.com/en-us/dotnet/framework/network-programming/tls

Telia Xtee turvaserveri poolne veateade Failed to get signing info for member ... Signer.InternalError: Member .... has no suitable certificates tähendab seda, et sa ei saada õiget privaatvõtme sertifikaati, näiteks saadad avalikku võtit kliendi sertifikaadina.

XTee test turvaserveriga kontrolli, et Sertifitseerimiskeskuse test SK serdid oleks laetud test OCSP’sse (https://demo.sk.ee/upload_cert).


Teine võimalus, kuidas privaatvõtmega kliendi sertifikaati kasutades TLS 1.2 SOAP päringut teha on kasutada System.Net.Security.SslStream klassi, võib anda rohkem veateateid välja

    /// <summary>
    /// Telia XTee turvaserveri kaudu kliendi sertifikaadiga TLS 1.2 protokolli abil SOAP päring TcpClient ja SSLStream kaudu
    /// </summary>
    /// <param name="Url">XTee turvaserveri https URL</param>
    /// <param name="envelope">SOAP päringu XML sisu stringina</param>
    /// <param name="serthash">Local Computer / Personal sertifikaadihoidlas oleva sertifikaadi räsi</param>
    /// <param name="serverIp">XTee turvaserveri IP</param>
    /// <param name="servesertname">XTee turvaserveri sertifikaadi nimi</param>
    /// <returns></returns>
    [Microsoft.SqlServer.Server.SqlFunction]
    public static SqlChars XTeeSOAPSslRequest(SqlString Url, SqlString envelope, SqlString serthash, SqlString serverIp, SqlString servesertname)
    {
        string inputMessage = "";
        string kataloog = (string)tempdir;
        try
        {
            string ServerHostName = (string)serverIp; 
            int ServerPort = 443;
            string ServerCertificateName = (string)servesertname;
            string ymbrik = (string)envelope;
         

            string chash = (string)serthash;

            X509Store certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
            certStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
            X509Certificate2Collection serts = certStore.Certificates.Find(X509FindType.FindByThumbprint, chash, true);
            if (serts.Count == 0)
            {
                serts = certStore.Certificates.Find(X509FindType.FindByThumbprint, chash, false);
            }
            if (serts.Count != 1)
            {
                throw new SystemException("Sertifikaati räsiga " + chash + "  ei ole hoidlas Local Computer - Personal");
            }
            X509Certificate2 cert = serts[0];
            X509CertificateCollection clientCertificateCollection = new X509CertificateCollection(new X509Certificate[] { serts[0] });
            certStore.Close();
            using (TcpClient client = new TcpClient(ServerHostName, ServerPort))
            {
                using (SslStream sslStream = new SslStream(client.GetStream(), false, App_CertificateValidation))
                {

                    try
                    {
                        sslStream.AuthenticateAsClient(ServerCertificateName, clientCertificateCollection, SslProtocols.Tls12, false);
                    }
                    catch (SystemException ex)
                    {
                        throw new SystemException("SsslStream.AuthenticateAsClient viga(Kontrolli sertifikaadile ligipääsu õigusi) " + ex.Message);
                    }
                    if (sslStream.LocalCertificate == null)
                    {
                        throw new SystemException("SSL LocalCertificate ei ole " + chash);
                    }

                    if (sslStream.RemoteCertificate == null)
                    {
                        throw new SystemException("SSL RemoteCertificate ei ole");
                    }
                    string url = (string)Url;
                    string host = url.Replace("https://", "");
                    host = host.Replace("http://", "");
                    string outputMessage = @"POST " + url + @" HTTP/1.1
Accept: text/xml
Content-Type: text/xml; charset=UTF-8
Content-Length: " + ymbrik.Length + @"
Host: " + host + @"
Connection: Keep-Alive
User-Agent: KAMPUS

" + ymbrik;
                 
                    byte[] outputBuffer = Encoding.UTF8.GetBytes(outputMessage);
                    sslStream.Write(outputBuffer);

                    byte[] inputBuffer = new byte[8096];
                    int inputBytes = 0;
                    while (inputBytes == 0)
                    {
                        inputBytes = sslStream.Read(inputBuffer, 0, inputBuffer.Length);
                    }
                    inputMessage = Encoding.UTF8.GetString(inputBuffer, 0, inputBytes);
                }  //using
            }
        }
        catch (SystemException ex)
        {         
            throw new SystemException(ex.GetType().Name + " " + ex.Message);
        }
        SqlChars sd = new SqlChars(((SqlString)inputMessage));
        return sd;
    }


 private static bool App_CertificateValidation(Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
    {
        if (sslPolicyErrors == SslPolicyErrors.None)
        { return true; }
        if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors)
        { return true; } //siin võid ka false tagastada
     
        return false;
    }

Logi veateade AcquireCredentialsHandle() failed with error 0X8009030D. Exception in HttpWebRequest#62571264:: - The request was aborted: Could not create SSL/TLS secure channel. viitab konto õiguste puudumisele privaatvõtmega sertifikaadile, ilmneb see sslStream.AuthenticateAsClient(ServerCertificateName, clientCertificateCollection, SslProtocols.Tls12, false); meetodi juures.

Rakenduse sujuvamaks töötamiseks on tark teha eraldi testfunktsioon System.Net.Security.SslStream AuthenticateAsClient meetodi väljakutsega, kui see annab tõrke on tegu konto ligipääsu õigustega privaatvõtmega sertifikaadile.