Files
hcs/Hcs.Client/Client/Api/Request/X509Tools.cs

173 lines
7.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Org.BouncyCastle.Asn1;
using System;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace Hcs.Client.Api.Request
{
internal class X509Tools
{
private const string PRIVATE_KEY_USAGE_PERIOD = "2.5.29.16";
public static string GetFullnameWithExpirationDateStr(X509Certificate2 x509cert)
{
var (фамилия, имя, отчество) = GetFullname(x509cert);
return фамилия + " " + имя + " " + отчество +
" до " + GetNotAfterDate(x509cert).ToString("dd.MM.yyyy");
}
/// <summary>
/// Возвращает массив из трех строк, содержащих соответственно Фамилию, Имя и Отчество
/// полученных из данных сертификата. Если сертификат не содержит ФИО, то возвращается массив
/// из трех пустых строк. Это не точный метод определять имя, он предполагает, что
/// поля SN, G, CN содержат ФИО в определенном порядке, что правдоподобно но не обязательно.
/// </summary>
private static (string Фамилия, string Имя, string Отчество) GetFullname(X509Certificate2 x509cert)
{
string фам = "", имя = "", отч = "";
// Сначала ищем поля surname (SN) и given-name (G)
var sn = DecodeSubjectField(x509cert, "SN");
var g = DecodeSubjectField(x509cert, "G");
if (!string.IsNullOrEmpty(sn) && !string.IsNullOrEmpty(g))
{
фам = sn;
var gParts = g.Split(' ');
if (gParts != null && gParts.Length >= 1)
{
имя = gParts[0];
}
if (gParts != null && gParts.Length >= 2)
{
отч = gParts[1];
}
}
else
{
// Иначе берем три первых слова из common name (CN), игнорируя кавычки
var cn = DecodeSubjectField(x509cert, "CN");
if (!string.IsNullOrEmpty(cn))
{
cn = new StringBuilder(cn).Replace("\"", "").ToString();
char[] separators = [' ', ';'];
var cnParts = cn.Split(separators);
if (cnParts != null && cnParts.Length >= 1)
{
фам = cnParts[0];
}
if (cnParts != null && cnParts.Length >= 2)
{
имя = cnParts[1];
}
if (cnParts != null && cnParts.Length >= 3)
{
отч = cnParts[2];
}
}
}
return (фам, имя, отч);
}
/// <summary>
/// Возвращает значение поля с именем @subName включенного в различимое имя Subject
/// </summary>
private static string DecodeSubjectField(X509Certificate2 x509cert, string subName)
{
// Чтобы посмотреть все поля сертификата
//System.Diagnostics.Trace.WriteLine("x509decode = " + x509cert.SubjectName.Decode(
//X500DistinguishedNameFlags.UseNewLines));
// Декодируем различимое имя на отдельные строки через переводы строк для надежности разбора
var decoded = x509cert.SubjectName.Decode(X500DistinguishedNameFlags.UseNewLines);
char[] separators = ['\n', '\r'];
var parts = decoded.Split(separators, StringSplitOptions.RemoveEmptyEntries);
if (parts == null)
{
return null;
}
// Каждая часть начинается с имени и отделяется от значения символом равно
foreach (var part in parts)
{
if (part.Length <= subName.Length + 1)
{
continue;
}
if (part.StartsWith(subName) && part[subName.Length] == '=')
{
return part.Substring(subName.Length + 1);
}
}
return null;
}
/// <summary>
/// Возвращает дату окончания действия сертификата
/// </summary>
private static DateTime GetNotAfterDate(X509Certificate2 x509cert)
{
// Сначала пытаемся определить срок первичного ключа, а затем уже самого сертификата
var датаОкончания = GetPrivateKeyUsageEndDate(x509cert);
if (датаОкончания != null)
{
return (DateTime)датаОкончания;
}
return x509cert.NotAfter;
}
private static DateTime? GetPrivateKeyUsageEndDate(X509Certificate2 x509cert)
{
foreach (var ext in x509cert.Extensions)
{
if (ext.Oid.Value == PRIVATE_KEY_USAGE_PERIOD)
{
// Дата начала с индексом 0, дата окончания с индексом 1
return ParseAsn1Datetime(ext, 1);
}
}
return null;
}
/// <summary>
/// Разбирает значение типа дата из серии значений ASN1 присоединенных к расширению
/// </summary>
private static DateTime? ParseAsn1Datetime(X509Extension ext, int valueIndex)
{
try
{
var asnObject = (new Asn1InputStream(ext.RawData)).ReadObject();
if (asnObject == null)
{
return null;
}
var asnSequence = Asn1Sequence.GetInstance(asnObject);
if (asnSequence.Count <= valueIndex)
{
return null;
}
var asn = (Asn1TaggedObject)asnSequence[valueIndex];
var asnStr = Asn1OctetString.GetInstance(asn, false);
var s = Encoding.UTF8.GetString(asnStr.GetOctets());
var year = int.Parse(s.Substring(0, 4));
var month = int.Parse(s.Substring(4, 2));
var day = int.Parse(s.Substring(6, 2));
var hour = int.Parse(s.Substring(8, 2));
var minute = int.Parse(s.Substring(10, 2));
var second = int.Parse(s.Substring(12, 2));
return new DateTime(year, month, day, hour, minute, second);
}
catch (System.Exception)
{
return null;
}
}
}
}