Закрепление корневого сертификата в C#/.NET

Я хочу реализовать привязку сертификата/открытого ключа в своем приложении С#. Я уже видел много решений, которые напрямую закрепляют сертификат сервера, например. в этот вопрос. Однако, чтобы быть более гибким, я хочу закрепить только корневой сертификат. Сертификат, который сервер получает при настройке, подписан промежуточным ЦС, который сам подписан корнем.

До сих пор я реализовал сервер, который загружает свой собственный сертификат, закрытый ключ, промежуточный сертификат и корневой сертификат из файла PKCS#12 (.pfx). Я создал файл с помощью следующей команды:

openssl pkcs12 -export -inkey privkey.pem -in server_cert.pem -certfile chain.pem -out outfile.pfx

Файл цепь.pem содержит корневой и промежуточный сертификаты.

Сервер загружает этот сертификат и хочет аутентифицировать себя по отношению к клиенту:

// certPath is the path to the .pfx file created before
var cert = new X509Certificate2(certPath, certPass)
var clientSocket = Socket.Accept();
var sslStream = new SslStream(
    new NetworkStream(clientSocket),
    false
);

try {
    sslStream.AuthenticateAsServer(cert, false, SslProtocols.Tls12, false);
} catch(Exception) {
     // Error during authentication
}

Теперь клиент хочет аутентифицировать сервер:

public void Connect() {
    var con = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    con.Connect(new IPEndPoint(this.address, this.port));
    var sslStream = new SslStream(
        new NetworkStream(con),
        false,
        new RemoteCertificateValidationCallback(ValidateServerCertificate),
        null
    );
    sslStream.AuthenticateAsClient("serverCN");
}

public static bool ValidateServerCertificate(
    object sender,
    X509Certificate certificate,
    X509Chain chain,
    SslPolicyErrors sslPolicyErrors
)
{
    // ??
}

Теперь проблема в том, что сервер отправляет клиенту только свой собственный сертификат. Также параметр цепь не содержит дополнительной информации. Это как-то правдоподобно, поскольку сертификат X509Certificate2 (в коде сервера) содержит только сертификат сервера и не содержит информации о промежуточном или корневом сертификате. Однако клиент не может проверить всю цепочку, поскольку (как минимум) отсутствует промежуточный сертификат.

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

Поэтому кто-нибудь знает возможность заставить SslStream отправлять всю цепочку на аутентификацию или реализовать функционал другим способом? Или мне нужно упаковать сертификаты по-другому?

Спасибо!

Редактировать: Я сделал несколько других тестов, чтобы обнаружить проблему. Как было предложено в комментариях, я создал X509Store, содержащий все сертификаты. После этого я создал X509Chain, используя сертификат моего сервера и магазин. На самом сервере новая цепочка содержит все сертификаты правильно, но не в функции ValidateServerCertificate..

X509Chain содержит цепочку центров сертификации, связанных с сертификатом сервера, вы можете видеть это в RemoteCertificateValidationCallback. Но вы используете свой собственный сертификат pfx, попробуйте сначала перечислить его и импортировать в хранилище сервера. Есть статьи stackoverflow.com/questions/9141198/… и stackoverflow.com/questions/5036590/…
Pavel Anikhouski 13.03.2019 12:08

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

PraMiD 13.03.2019 14:10
ChainElements свойство chain объекта в вашем ValidateServerCertificate методе должно иметь полную цепочку
Pavel Anikhouski 13.03.2019 14:15

Я знаю, но ChainElements содержит только один элемент на клиенте. Может быть, что-то не так с тем, как я создаю файл PKCS#12? Вы знаете способ проверить это.

PraMiD 13.03.2019 14:53

Я не создавал файлы pfx самостоятельно, просто использую существующие. Согласно этой статье stackoverflow.com/questions/6307886/…, вы должны включить корневой ЦС и промежуточные сертификаты в команду openssl.

Pavel Anikhouski 13.03.2019 15:27

Спасибо за ссылку. Однако я не могу указать несколько параметров -in, так как openssl будет смотреть только на последний. В опубликованной команде openssl, которую я использовал для создания pfx-файла, я указал дополнительные сертификаты с помощью параметра -certfile. X509Certificate2Collection также может прочитать все три из них, но они не содержатся в цепочке на стороне клиента. Я добавил дополнительную информацию о тестах, которые я сделал в исходный пост, возможно, это будет полезно.

PraMiD 13.03.2019 15:49
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
1 331
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

SslStream никогда не будет отправлять всю цепочку (за исключением самовыданных сертификатов). Соглашение состоит в том, чтобы отправлять все, кроме корня, потому что другая сторона либо уже имеет корень и доверяет ему, либо не имеет (таким образом/или не доверяет корню), и в любом случае это была пустая трата полосы пропускания.

Но SslStream может отправлять промежуточные сообщения только тогда, когда он их понимает.

var cert = new X509Certificate2(certPath, certPass);

При этом извлекается только сертификат конечного объекта (с закрытым ключом), любые другие сертификаты в PFX отбрасываются. Если вы хотите загрузить все необходимые сертификаты, используйте X509Certificate2Collection.Import. Но... это тебе тоже не поможет. SslStream принимает только сертификат конечного объекта, он ожидает, что система сможет построить для него функционирующую цепочку.

Чтобы построить функционирующую цепочку, ваши промежуточные и корневые сертификаты должны принадлежать одному из следующих типов:

  • Предоставляется как ручной ввод через X509Chain.ChainPolicy.ExtraStore
    • Поскольку рассматриваемая цепочка построена SslStream, вы не можете сделать это здесь.
  • ТекущийПользователь\Мой X509Store
  • *LocalMachine\Мой X509Store
  • CurrentUser\CA X509Store
  • **LocalMachine\CA X509Store
  • CurrentUser\Root X509Store
  • **LocalMachine\Root X509Store
  • *LocalMachine\ThirdPartyRoot X509Store
  • Расположение http (не-s), указанное в расширении идентификатора доступа к центру сертификации в сертификате.

Магазины, отмеченные *, не существуют в .NET Core в Linux. Магазины, отмеченные **, существуют в Linux, но не могут быть изменены приложением .NET.

Этого также недостаточно для довольно, потому что (по крайней мере, для SslStream в Linux и, возможно, macOS в .NET Core) он по-прежнему отправляет промежуточные звенья только в том случае, если он построил цепочку, которой доверяет. Таким образом, сервер должен фактически доверять корневому сертификату, чтобы отправлять промежуточные звенья. (Или клиент должен доверять корню для клиентского сертификата)


С другой стороны действуют те же правила. Разница в том, что в обратном вызове вы можете перестроить цепочку, чтобы добавить дополнительные сертификаты.

private static bool IsExpectedRootPin(X509Chain chain)
{
    X509Certificate2 lastCert = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
    return lastCert.RawBytes.SequenceEquals(s_pinnedRootBytes);
}

private static bool ValidateServerCertificate(
    object sender,
    X509Certificate certificate,
    X509Chain chain,
    SslPolicyErrors sslPolicyErrors
)
{
    if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0)
    {
        // No cert, or name mismatch (or any future errors)
        return false;
    }

    if (IsExpectedRootPin(chain))
    {
        return true;
    }

    chain.ChainPolicy.ExtraStore.Add(s_intermediateCert);
    chain.ChainPolicy.ExtraStore.Add(s_pinnedRoot);
    chain.ChainPolicy.VerificationFlags |= X509VerificationFlags.AllowUnknownCertificateAuthority;

    if (chain.Build(chain.ChainElements[0].Certificate))
    {
        return IsExpectedRootPin(chain);
    }

    return false;
}

Конечно, проблема с этим подходом заключается в том, что вам также необходимо понимать и предоставлять промежуточное звено на удаленной стороне. Реальное решение этой проблемы состоит в том, что промежуточные звенья должны быть доступны на конечной точке распространения HTTP, а выданные сертификаты должны содержать расширение доступа к информации о полномочиях, чтобы можно было найти их динамически.

Спасибо, это почти сводило меня с ума. Я использовал коллекцию для загрузки сертификатов в новое хранилище для сертификатов, а не в определенное системой... Поэтому решение представляет собой комбинацию комментария @pavel-anikhouski и этого ответа. Сначала мы должны загрузить сертификаты в хранилище Системный с помощью X509Certificate2Collection, а затем использовать сертификат для аутентификации. В результате клиент сможет увидеть/проверить всю цепочку.

PraMiD 13.03.2019 16:06

Другие вопросы по теме