Закрепление корневого сертификата в C # /. NET - PullRequest
1 голос
/ 13 марта 2019

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

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

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

Файл chain.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
)
{
    // ??
}

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

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

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

Спасибо!

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

1 Ответ

2 голосов
/ 13 марта 2019

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

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

var cert = new X509Certificate2(certPath, certPass);

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

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

  • Предоставляется как ручной ввод через X509Chain.ChainPolicy.ExtraStore.
    • Так как рассматриваемая цепочка построена SslStream, вы не можете сделать это здесь.
  • CurrentUser \ My X509Store
  • * LocalMachine \ My 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, а выданные сертификаты должны иметь расширение доступа к информации об авторитете, чтобы иметь возможность определять их динамически.

...