четверг, 18 ноября 2021 г.

.NET Ошибка: Не удалось создать защищенный канал ssl/tls на Windows Server 2008 R2 / 2012 R2(The request was aborted: Could not create SSL/TLS secure channel) TLS 1.2

Ситуация: 

С недавних пор наши приложения на .net 4.5 в функционал которых, кроме прочего, входит загрузка страниц с сайтов. Перестали работать на серверах Windows Werver 2008 R2 и Windows Server 2012 R2

Ошибка

Не удалось создать защищенный канал ssl/tls
или она же на английском
The request was aborted: Could not create SSL/TLS secure channel

Причем эти же приложения работали нормально на машинах разработчиков на Windows 10.


Разбор ситуации. Что не помогло

Сразу скажу - много форумов читали, много советов перепробовали - нам
не помогли такие действия:

игры с настройкой протокола в приложении

ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(AlwaysGoodCertificate);

ServicePointManager.Expect100Continue = true;

ServicePointManager.DefaultConnectionLimit = 9999;

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

игры с переходом на более свежий фреймворк: 

пробовали и .NET 4.6.1 и .NET 4.7.2


Но попробуйте, может у вас другой случай (я думаю, в случае другой конфигурации машины - могут помочь)

Вот советы, которые нам не помогли

Disable SSL fallback and use only TLS for outbound connections in .NET? (Poodle mitigation)
https://stackoverflow.com/questions/26389899/disable-ssl-fallback-and-use-only-tls-for-outbound-connections-in-net-poodle

How to specify SSL protocol to use for WebClient class
https://stackoverflow.com/questions/30491716/how-to-specify-ssl-protocol-to-use-for-webclient-class

.Net WebClient: Could not create SSL/TLS secure channel
https://www.aspsnippets.com/Articles/Net-WebClient-Could-not-create-SSLTLS-secure-channel.aspx

Запрос был прерван: Не удалось создать защищенный канал SSL/TLS
https://answer-id.com/ru/74496884

Запрос был прерван: не удалось создать безопасный канал SSL / TLS
https://qastack.ru/programming/2859790/the-request-was-aborted-could-not-create-ssl-tls-secure-channel

Не удалось создать защищенный канал SSL/TLS только на сервере Windows Server 2012
https://coderoad.ru/59975964/%D0%9D%D0%B5-%D1%83%D0%B4%D0%B0%D0%BB%D0%BE%D1%81%D1%8C-%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D1%82%D1%8C-%D0%B7%D0%B0%D1%89%D0%B8%D1%89%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9-%D0%BA%D0%B0%D0%BD%D0%B0%D0%BB-SSL-TLS-%D1%82%D0%BE%D0%BB%D1%8C%D0%BA%D0%BE-%D0%BD%D0%B0-%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80%D0%B5-Windows-Server


How to enable TLS 1.2 on the site servers and remote site systems
https://docs.microsoft.com/en-us/mem/configmgr/core/plan-design/security/enable-tls-1-2-server

How to enable TLS 1.2 on clients
https://docs.microsoft.com/en-us/mem/configmgr/core/plan-design/security/enable-tls-1-2-client


Разбор ситуации 2: Варианты решения

Продолжили наши разбирательства, и, думаем, нашли причину ошибки и
2 возможных варианта решения:

Наиболее вероятная причина ошибки

Старые операционки не поддерживают новые наборы шифрования протокола TLS 1.2

Суть наших умозаключений 

похоже, что проблема в наборах шифрования (Cipher Suites)

попытался их внимательнее поразбирать с помощью статьи
https://docs.microsoft.com/en-us/answers/questions/227738/windows-server-2012-r2-tls-12-cipher-suites.html

не помогло.

Продолжили разбирательство

Вот Cipher Suites для разных операционных систем
https://docs.microsoft.com/en-us/windows/win32/secauthn/cipher-suites-in-schannel

по наводкам из статьи установил

IISCrypto
https://www.nartac.com/Products/IISCrypto

Увидел: на локальной машине есть такой набор 
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

он же используется и на веб сайте с которого хотим загружать страницы

это я посмотрел через сайт
https://www.ssllabs.com/ssltest/analyze.html

Вот раздел с наборами шифрования

Cipher Suites
# TLS 1.3 (server has no preference)
TLS_AES_128_GCM_SHA256 (0x1301)   ECDH x25519 (eq. 3072 bits RSA)   FS128
TLS_CHACHA20_POLY1305_SHA256 (0x1303)   ECDH x25519 (eq. 3072 bits RSA)   FS256
TLS_AES_256_GCM_SHA384 (0x1302)   ECDH x25519 (eq. 3072 bits RSA)   FS256
# TLS 1.2 (suites in server-preferred order)
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)   ECDH x25519 (eq. 3072 bits RSA)   FS256
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca8)   ECDH x25519 (eq. 3072 bits RSA)   FS256
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)   ECDH x25519 (eq. 3072 bits RSA)   FS128


но, к сожалению, наборы не поддерживается в виндовс сервер 2012
https://docs.microsoft.com/en-us/windows/win32/secauthn/tls-cipher-suites-in-windows-8-1

и тем более в Windows Server 2008
https://docs.microsoft.com/en-us/windows/win32/secauthn/tls-cipher-suites-in-windows-7

а поддерживается только в Windows 10 (Windows Server 2016+)
https://docs.microsoft.com/en-us/windows/win32/secauthn/tls-cipher-suites-in-windows-10-v1607


Поизучали возможность обновления этих пакетов

How to Update Your Windows Server Cipher Suite for Better Security
https://www.howtogeek.com/221080/how-to-update-your-windows-server-cipher-suite-for-better-security/

Но, похоже, тут только возможность упорядочивания их, но новый пакет в ОС добавить вручную нельзя.

Вот более подробный разбор этой темы
Windows Server 2012 R2 TLS 1.2 Cipher Suites
https://docs.microsoft.com/en-us/answers/questions/227738/windows-server-2012-r2-tls-12-cipher-suites.html

вроде бы неудача...
но, в форумах часто проскальзывает что "хром работает, а .NET Framework приложения - нет". Причина в том, что баузер хром использует свои пакеты шифрования а не полагается ОС.
Мы зацепились за эту идею.

В одном из проектов мы использовали компонент - внедренный браузер на Chromium

пакет в Nuget: CefSharp.Winforms, CefSharp.Common.

Попробовали загрузить страницу через этот механизм - работает!

Решения

то есть у вас есть 2 варианта решения

1. переход на свежий Windows Server 2016

2. использование пакета CefSharp но предупреждаю, он увеличивает размер приложения (в нашем случае на 100+ МБ)

Выбор за вами :)

Если нужны будут детальные инструкции по CefSharp -напишите в комментах: сделаю отдельную статью.

Материалы по теме

Как узнать версию TLS на сайте
https://ru.wikihow.com/%D1%83%D0%B7%D0%BD%D0%B0%D1%82%D1%8C-%D0%B2%D0%B5%D1%80%D1%81%D0%B8%D1%8E-TLS-%D0%BD%D0%B0-%D1%81%D0%B0%D0%B9%D1%82%D0%B5

Страница для анализа SSL на нужном сайте(домене)
https://www.ssllabs.com/ssltest/analyze.html

How to Update Your Windows Server Cipher Suite for Better Security
https://www.howtogeek.com/221080/how-to-update-your-windows-server-cipher-suite-for-better-security/

Cipher Suites in TLS/SSL (Schannel SSP)
https://docs.microsoft.com/en-us/windows/win32/secauthn/cipher-suites-in-schannel

Demystifying Schannel
https://techcommunity.microsoft.com/t5/core-infrastructure-and-security/demystifying-schannel/ba-p/259233


 





  

суббота, 12 декабря 2020 г.

Разбор архитектуры, применение паттернов проектирования и рефакторинг по работающему проекту

Преимущества рефакторинга по живой системе по сравнению с рефакторингом по "книжным задачам"

1. не надо вникать в чужую предметную область - работаешь в своей.

2. не надо выдумывать тестовое окружение. Берем состояние кода на текущий момент времени и разбираем его. Код написан предшественниками, коллегами или даже самим программистом, но стихийно и/или давно.

3. Польза более явная, т.к. улучшенный код сразу приносит результат в виде ускорения работы системы и/или выделения четких границ модулей и/или уменьшения количества поддерживаемого кода.

4. Код всегда с тобой в исходном коде проекта. Всегда можно подсмотреть, если забыл, как были сделаны те или иные вещи.


Недостатки

Есть риск внесения ошибок. Поэтому рефакторинг я предпочитаю делать маленькими шагами и с повышенным вниманием к тестам.

вторник, 6 октября 2020 г.

ASP.NET MVC: одинаковый IP адрес клиента (Request.UserHostAddress) после настройки переадресации с HTTP на HTTPS

Ситуация и проблема 

Есть приложение ASP.NET MVC. В приложении записывааются IP адреса клиентов для статистики через 

Request.UserHostAddress

В какой то момент понадобилась переадресация с http на https, которая была сделана через рерайт в веб-конфиге. И значение Request.UserHostAddress стало показывать всегда один и тот же адрес (адрес сервера хостинга).

Решение

Использовать поле 

Request.Headers["X-Forwarded-For"] 

в нем, в моем случае, через запятую были 2 IP-адреса. Взяли из них первый.

Код получился примерно такой

var IPv4Address = "";

var headerValueXForwerdedFor = request.Headers["X-Forwarded-For"];

if (string.IsNullOrWhiteSpace(headerValueXForwerdedFor))

{

  IPv4Address = request?.UserHostAddress;

}

else

{

  var XForwardedForIpAddress = headerValueXForwerdedFor;

  var commaindex = headerValueXForwerdedFor.IndexOf(',');

  if (commaindex > 0)

  {

  XForwardedForIpAddress = headerValueXForwerdedFor.Substring(0, commaindex);

  }  

   IPv4Address = XForwardedForIpAddress;

 }


Что помогло: 

статья со StackOwerflow

https://stackoverflow.com/questions/15297620/request-userhostaddress-return-ip-address-of-load-balancer

суббота, 12 сентября 2020 г.

Курсы программирования для детей

Достаточно большая подборка курсов программирования для детей.

Есть онлайн и оффлайн форматы

Курсы по таким языкам и технологиям

  • C#
  • C/C++
  • Code
  • HTML, CSS
  • Java
  • JavaScript
  • Python
  • Scratch
  • Unity

четверг, 23 апреля 2020 г.

Установка 2-х SSL сертификатов на 2-х сайтах на одном Windows Server

Задача

Нужно установить 2 SSL - сертификата на одном сервере (Windows Server)

Решение

В IIS/сайт/Привязки сайта
нужно установить галочку "требовать обозначение имени сервера"



Решение взял из статьи
https://qna.habr.com/q/434575

воскресенье, 12 апреля 2020 г.

C# Как убрать порт по умолчанию при генерации адреса с помощью UriBuilder

Проблема


есть такой код по преобраpованию ссылки

var uriBuilder = new UriBuilder(absoluteUri);
//(код модифицирующий url с помощью механизмов UriBuilder)
...
uriBuilder.ToString()

uriBuilder.ToString() генерирует ссылку с явным указанием порта, даже если порта во входной строке не было

Пример
вход: https://a.b.com/test
выход:https://a.b.com:443/test

Что помогло

Статья
How to remove the port number from a url string
https://stackoverflow.com/questions/2819336/how-to-remove-the-port-number-from-a-url-string

Решение

написал такой вспомогательный метод

private static string GetUriBuilderAsStringWithoutDefaultPort(UriBuilder uriBuilder)
        {
            if (uriBuilder.Uri.IsDefaultPort) uriBuilder.Port = -1;
            return uriBuilder.ToString();
        }

и теперь вместо UriBuilder.ToString() вызваю GetUriBuilderAsStringWithoutDefaultPort(uriBuilder)


C# пример SEO оформления страниц сайта с пейджингом (с учетом рекомендаций Google)

Задача


Оформить дружелюбно для поиска Google страницы сайта с пейджингом

Теория

На английском
SEO Guide to Google Webmaster Recommendations for Pagination
https://moz.com/blog/seo-guide-to-google-webmaster-recommendations-for-pagination

На русском
Постраничная верстка rel=«next|prev»
https://habr.com/ru/post/128746/

Сухая выжимка.
Для страниц каталога , которые реализованы в виде пейджинга рекомендуется прописывать в заголовке страницы

<link rel="prev" href="http://www.example.com/article?story=abc&page=2" /> <link rel="next" href="http://www.example.com/article?story=abc&page=4" />


причем   link rel="prev" не нужен на первой странице, а
link rel="next" не нужен на последней

Решение на C#

public static class UtilsSeo
    {
        private const string pageParamName = "page";

        public static string FindUrlPagePrev(
            string url,
            int? pageNumber,
            int pageSize,
            int itemTotalCount
        )
        {
            var pageNumberStrongDefined = GetStrongDefinedPageNumber(pageNumber);

            if (pageNumberStrongDefined <= 1) return null;

            if (pageNumberStrongDefined > GetMaxPageNumber(pageSize, itemTotalCount)) return null;

            return devuaUtils2014.Urls.UrlParamAddOrChange(url, pageParamName, (pageNumberStrongDefined - 1).ToString());
        }

        private static int GetStrongDefinedPageNumber(int? pageNumber)
        {
            var pageNumberStrongDefined = pageNumber ?? 0;
            if (pageNumberStrongDefined <= 0) pageNumberStrongDefined = 1;
            return pageNumberStrongDefined;
        }

        public static bool IsLastPage(
            int? pageNumber,
            int pageSize,
            int itemTotalCount
            )
        {
           return GetStrongDefinedPageNumber(pageNumber) >= GetMaxPageNumber(pageSize, itemTotalCount);
        }

        private static double GetMaxPageNumber(int pageSize, int itemTotalCount)
        {
            return Math.Ceiling( (double)itemTotalCount / (double)pageSize );
        }

        public static string FindUrlPageNext(
            string url,
            int? pageNumber,
            int pageSize,
            int itemTotalCount
        )
        {
            if (IsLastPage(pageNumber, pageSize, itemTotalCount)) return null;

            var pageNumberStrongDefined = GetStrongDefinedPageNumber(pageNumber);
            
            return devuaUtils2014.Urls.UrlParamAddOrChange(url, pageParamName,
                (pageNumberStrongDefined + 1).ToString());
        }

    }

Утилиты

namespace devuaUtils2014
{
    public static class Urls
    {
        public static string UrlParamRemove(string absoluteUri, string paramName)
        {
            var uriBuilder = new UriBuilder(absoluteUri);
            var query = HttpUtility.ParseQueryString(uriBuilder.Query);
            query.Remove(paramName);
            uriBuilder.Query = query.ToString();
            return GetUriBuilderAsStringWithoutDefaultPort(uriBuilder);
        }

        private static string GetUriBuilderAsStringWithoutDefaultPort(UriBuilder uriBuilder)
        {
            if (uriBuilder.Uri.IsDefaultPort)
            {
                uriBuilder.Port = -1;
            }
            return uriBuilder.ToString();
        }

        public static string UrlParamChange(string absoluteUri, string paramName, string paramValue)
        {
            var uriBuilder = new UriBuilder(absoluteUri);
            var query = HttpUtility.ParseQueryString(uriBuilder.Query);
            query[paramName] = paramValue;
            uriBuilder.Query = query.ToString();
            
            return GetUriBuilderAsStringWithoutDefaultPort(uriBuilder);
        }


        public static string UrlParamAdd(string absoluteUri, string paramName, string paramValue)
        {
            var uriBuilder = new UriBuilder(absoluteUri);
            var query = HttpUtility.ParseQueryString(uriBuilder.Query);
            query.Add(paramName,paramValue); //[paramName] = paramValue;
            uriBuilder.Query = query.ToString();
            
            return GetUriBuilderAsStringWithoutDefaultPort(uriBuilder);
        }

        public static string UrlParamAddOrChange(string absoluteUri, string paramName, string paramValue)
        {
            var uriBuilder = new UriBuilder(absoluteUri);
            var query = HttpUtility.ParseQueryString(uriBuilder.Query);
            var res = "";
            if (string.IsNullOrEmpty(query[paramName]))
            {
                res = UrlParamAdd(absoluteUri, paramName, paramValue);
            }
            else
            {
                res = UrlParamChange(absoluteUri, paramName, paramValue);
            } 
            return res;
        }
    }
}

Пример
https://childcourse.com.ua/course?page=2