Как программно определить, какой сертификат использовался для подписи данного сертификата?

В моем коде C# у меня есть объект X509Certificate2, который представляет SSL-сертификат (из локального хранилища или из успешного HTTP-запроса через SSL). Сертификат подписан каким-то промежуточным сертификатом, который может присутствовать в локальном хранилище, а может и нет, поэтому использование X509Chain.Build(), вероятно, не сработает.

Изображение средства просмотра сертификатов Firefox (поскольку у меня пока нет пригодного кода):

введите описание изображения здесь

В разделе «Подробности» в «Иерархии сертификатов» я вижу это:

  • DigiCert High Assurance EV Root CA
    • DigiCert SHA2 Extended Validation Server CA
      • github.com

Мой объект представляет «github.com», самую нижнюю строку в цепочке. Мне нужно программно идентифицировать среднюю линию ("DigiCert SHA2 Extended Validation Server CA").

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


person sharptooth    schedule 24.02.2016    source источник
comment
Поскольку вы задали мета-вопрос о закрытии, я подробно отвечу там. Пожалуйста, дайте мне несколько минут, чтобы ответить.   -  person Wai Ha Lee    schedule 24.02.2016
comment
Я ответил. Простите за опоздание.   -  person Wai Ha Lee    schedule 24.02.2016
comment
Что не так со свойством IssuerName?   -  person erickson    schedule 25.02.2016
comment
@erickson Ничего, кроме того, что он не должен совпадать с предметом сертификата подписавшего, и даже если он совпадает, нет никакого способа узнать, является ли это точно правильным сертификатом или просто каким-то сертификатом с тем же предметом.   -  person sharptooth    schedule 25.02.2016
comment
@sharptooth, ты не прав. Проверка подписи подтвердит это.   -  person Crypt32    schedule 25.02.2016
comment
Кстати, IssuerName в листовом сертификате должно совпадать с SubjectName эмитента.   -  person Crypt32    schedule 25.02.2016
comment
@CryptoGuy Как осуществляется такая проверка подписи?   -  person sharptooth    schedule 25.02.2016
comment
взяв открытый ключ кандидата-эмитента и проверив подпись целевого сертификата. Вы расшифровываете подпись и получаете подписанное хеш-значение. Затем вы вычисляете хэш для целевого сертификата. Из обоих совпадений хэшей -- кандидат является допустимым эмитентом, в противном случае -- нет.   -  person Crypt32    schedule 25.02.2016
comment
@sharptooth вы можете найти полный алгоритм сопоставления в RFC 5280 (раздел 6.1) . По сути, вы начинаете с сопоставления эмитента/субъекта, проверяете подпись, а затем проверяете, что атрибуты разрешают такое использование. Это нормально, когда в якорях доверия есть несколько потенциальных кандидатов.   -  person Bruno    schedule 25.02.2016


Ответы (2)


В данном конкретном случае (github.com) будет работать X509Chain.Build, поскольку конечный сертификат содержит информацию о расположении сертификата издателя (в расширении доступа к информации о полномочиях).

Но иногда это может не сработать (например, с сертификатами Thawte, поскольку Thawte не предоставляет явной информации о расположении сертификата эмитента). И если сертификат установлен в локальном хранилище сертификатов, нет возможности автоматически найти издателя.

Вариант 1 — SSL-соединение

Однако если вы работаете с SSL-сертификатом и можете установить сеанс SSL, вы можете получить сертификат, добавив прослушиватель к свойству ServicePointManager.ServerCertificateValidationCallback: https://msdn.microsoft.com/en-us/library/system.net.servicepointmanager.servercertificatevalidationcallback.aspx

Делегат RemoteCertificateValidationCallback содержит несколько параметров, один из них chain, который содержит цепочку SSL-сертификатов, возвращенную сервером. И если на удаленном сервере есть сертификат эмитента, то он будет представлен там в коллекции ChainElements. Этот объект обычно содержит несколько элементов:

-Leaf Certificate
    -Issuer Certificate
        -(Optional Issuer certs when available)

Итак, вам нужно проверить две вещи:

  1. Если ChainElements содержит как минимум два элемента (скажем, конечный сертификат и предлагаемый эмитент).
  2. Если первый элемент коллекции ChainElements не имеет статуса NotSignatureValid в коллекции ChainelementStatus.

Вы можете добавить следующий фрагмент кода в делегат RemoteCertificateValidationCallback для выполнения этих проверок:

X509Certificate2 issuer = null;
if (
    chain.ChainElements.Count > 1 &&
    !chain.ChainElements[0].ChainElementStatus.Any(x => x.Status == X509ChainStatusFlags.NotSignatureValid)) {
    issuer = chain.ChainElements[1].Certificate;
}

Если после выполнения этого фрагмента кода переменная issuer будет равна null, то вы не сможете автоматически определить, кто является эмитентом вашего сертификата. Этот процесс потребует некоторых дополнительных исследований. И это не null, тогда переменная issuer будет содержать фактический сертификат эмитента.

Вариант 2 — поиск локального хранилища сертификатов

Хорошо, согласно вашим комментариям, вы хотите определить, установлен ли сертификат эмитента в локальном хранилище сертификатов или нет. Прочитав ваш вопрос, я не понял. Почему мы должны угадывать, что вы на самом деле ищете? В конце концов, я все еще не уверен, знаете ли вы/понимаете, чего хотите достичь.

Если вы хотите узнать, установлен ли эмитент в локальном хранилище, вы можете использовать следующий алгоритм:

1) используйте X509Certificate2Collection.Find и найти сертификаты-кандидаты по имени субъекта

2) найдите в списке кандидатов (полученном на шаге 1) те, у которых значение идентификатора ключа субъекта совпадает со значением идентификатора ключа органа сертификации сертификата в субъекте.

X509Certificate2Collection certs = new X509Certificate2Collection();
// grab candidates from CA and Root stores
foreach (var storeName in new[] { StoreName.CertificateAuthority, StoreName.Root }) {
    X509Store store = new X509Store(storeName, StoreLocation.CurrentUser);
    store.Open(OpenFlags.ReadOnly);
    certs.AddRange(store.Certificates);
    store.Close();
}
certs = certs.Find(X509FindType.FindBySubjectDistinguishedName, cert.Issuer, false);
if (certs.Count == 0) {
    Console.WriteLine("Issuer is not installed in the local certificate store.");
    return;
}
var aki = cert.Extensions["2.5.29.35"];
if (aki == null) {
    Console.WriteLine("Issuer candidates: ");
    foreach (var candidate in certs) {
        Console.WriteLine(candidate.Thumbprint);
    }
    return;
}
var match = Regex.Match(aki.Format(false), "KeyID=(.+)", RegexOptions.IgnoreCase);
if (match.Success) {
    var keyid = match.Groups[1].Value.Replace(" ", null).ToUpper();
    Console.WriteLine("Issuer candidates: ");
    foreach (var candidate in certs.Find(X509FindType.FindBySubjectKeyIdentifier, keyid, false)) {
        Console.WriteLine(candidate.Thumbprint);
    }
} else {
    // if KeyID is not presented in the AKI extension, attempt to get serial number from AKI:
    match = Regex.Match(aki.Format(false), "Certificate SerialNumber=(.+)", RegexOptions.IgnoreCase);
    var serial = match.Groups[1].Value.Replace(" ", null);
    Console.WriteLine("Issuer candidates: ");
    foreach (var candidate in certs.Find(X509FindType.FindBySerialNumber, serial, false)) {
        Console.WriteLine(candidate.Thumbprint);
    }
}

предполагая, что переменная cert хранит сертификат в теме (для которой ищется эмитент). У этого подхода есть проблемы, поскольку он не проверяет подпись и может возвращать ложные срабатывания.

person Crypt32    schedule 24.02.2016

Я спросил: «Что не так со свойством IssuerName?»

Ответ был таким: «Ничего, кроме того, что он не должен совпадать с субъектом сертификата подписавшего, и даже если он совпадает, нет никакого способа узнать, является ли это в точности правильным сертификатом или просто сертификатом с таким же субъектом».

Это неправильно.

PKIX 6.1 говорит: "для всех x в {1, ..., n -1}, субъект сертификата x является издателем сертификата x+1."

В подразделах это поясняется указанием: «Назначить имя субъекта сертификата рабочему_имя_издателя», а затем для следующего сертификата в цепочке проверять, что «имя издателя сертификата является рабочим_имя_издателя».

Вы можете запутаться, потому что у вас может быть много сертификатов эмитента с одинаковым именем, но разными ключами. В этом случае правильный ключ подписи можно определить, сопоставив идентификатор ключа субъекта на идентификатор ключа полномочий субъекта. Однако совпадения идентификаторов ключей недостаточно: сначала должны совпадать имена.

Непонятно, почему вы пытаетесь сделать это вручную. Вы должны иметь возможность предоставить все доступные промежуточные звенья вашему библиотека построения пути и позволяет найти допустимую цепочку, если она существует.

person erickson    schedule 25.02.2016
comment
ExtraStore полезно, если у вас есть правильный сертификат, что не всегда так. Как я уже упоминал в случае с Thawte, клиентам не обязательно иметь промежуточный сертификат Thawte CA для успешной работы в веб-браузере, потому что этот сертификат доставляется веб-сервером и удаляется после проверки сертификата. - person Crypt32; 25.02.2016
comment
@CryptoGuy Я не вижу смысла в вашем комментарии. Если сервер отправляет вам промежуточный файл, он у вас есть, и вы можете поместить его в ExtraStore. Является ли это правильным сертификатом, вы проверяете путем проверки. Можешь яснее изложить свою мысль? - person erickson; 25.02.2016
comment
Я отредактировал свой ответ, добавив некоторый код и уточнив свою точку зрения. Надеюсь, это прояснит. - person Crypt32; 25.02.2016
comment
Выглядит многообещающе. Я не использую библиотеку построения пути, потому что при построении цепочки доверия промежуточные сертификаты могут поступать либо из хранилища, либо из центра (через AIA или что-то еще), и я пытаюсь найти сертификат в местный магазин. - person sharptooth; 26.02.2016
comment
@sharptooth из локального магазина или [...] запрос через SSL: то, что вы пытаетесь сделать, не обязательно имеет смысл (или, по крайней мере, вам нужно быть осторожным с тем, что вы повторно пытаюсь сделать). Проверка сертификата в вашем локальном хранилище с другими требует дополнительной спецификации. В частности, вам нужно будет не только сопоставить эмитентов и подписи для построения цепочки, но также сопоставить дату/время достоверности и атрибуты назначения. В принципе, в противном случае у вас может быть несколько кандидатов на совпадения. Библиотеки построения пути, как правило, делают все это за вас в контексте соединения SSL. - person Bruno; 26.02.2016
comment
@sharptooth Вы не можете использовать найти метод в вашей коллекции сертификатов? Сначала выберите по имени, затем извлеките идентификатор ключа центра сертификации из дочернего сертификата и сузьте первые результаты поиска эмитентов с этим идентификатором ключа субъекта. Я не вижу API для прямого извлечения идентификатора ключа аутентификации, поэтому вам, возможно, придется самостоятельно проанализировать структуру ASN.1 этого расширения. - person erickson; 26.02.2016
comment
@sharptooth, было бы лучше, если бы вы сказали нам конечную цель, которую хотите достичь. Глядя на stackoverflow.com/questions/35647719/, похоже, что вопрос, который вы задали здесь, не тот, который вы ищете. - person Crypt32; 27.02.2016
comment
@CryptoGuy На самом деле мне нужно и то, и другое: убедиться, что правильный сертификат находится в хранилище сервера (это будет проверка на стороне сервера), и убедиться, что сертификат обслуживается (это будет проверка на стороне клиента). - person sharptooth; 29.02.2016