如何确保证书没有更改

本文关键字:何确 保证书 | 更新日期: 2023-09-27 18:21:32

我想到了一个问题。有没有什么常见的方法可以让应用程序只在系统中安装了证书的情况下运行。我想通过我自己签署的证书来颁发和验证这样的证书吗?

我可以从存储中按其名称获取证书,但我如何确保这样的证书由我的自签名证书签名,并且没有人颁发具有相同名称的证书并替换本地存储中的证书?

或者换句话说,我如何确保在本地存储中签署证书的证书不是伪造的?

如果这个问题不正确或不清楚,我很抱歉,但我很乐意得到帮助。

如何确保证书没有更改

这个问题确实很好。

最终用户总是有可能使用您的使用者名称创建一个有效的证书链,并作为颁发者,为颁发者证书创建另一个证书链,直到根。

他们不能做的是用颁发者证书的私钥签署这些证书。

因此,下面的代码从当前用户的个人证书存储加载应用程序证书,然后从资源加载颁发者的颁发者证书,并使用颁发者证书的公钥验证安装在客户端计算机上的应用程序证书上的签名。

在我的源代码中,使用密钥IssuerCertificate将颁发者证书添加到资源中

事实上,我很喜欢提出这样的解决方案。

在代码中,我提到了一个编码ASN.1。如果您需要,请在此处查看

static void Main(string[] args)
{
    string expectedSubjectName = "My Application";
    X509Certificate2 issuerCertificate = new X509Certificate2(Resource1.IssuerCertificate);
    string expectedIssuerName = issuerCertificate.Subject;
    bool result = VerifyCertificateIssuer(expectedSubjectName, expectedIssuerName, issuerCertificate);
}
private static void ThrowCertificateNotFoundException(string expectedSubjectName, string expectedIssuerName, bool isThumbprintMismatch)
{
    if (isThumbprintMismatch)
    {
        // Notification for possible certificate forgery
    }
    throw new SecurityException("A certificate with subject name " + expectedSubjectName + " issued by " + expectedIssuerName + " is required to run this application");
}
private static X509Certificate2 GetCertificate(string expectedSubjectName, string expectedIssuerName)
{
    X509Store personalCertificateStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
    personalCertificateStore.Open(OpenFlags.ReadOnly);
    X509CertificateCollection certificatesBySubjectName = personalCertificateStore.Certificates.Find(X509FindType.FindBySubjectName, expectedSubjectName, true);
    if (certificatesBySubjectName.Count == 0)
    {
        ThrowCertificateNotFoundException(expectedSubjectName, expectedIssuerName, false);
    }
    X509Certificate2 matchingCertificate = null;
    foreach (X509Certificate2 certificateBySubjectName in certificatesBySubjectName)
    {
        if (certificateBySubjectName.Issuer == expectedIssuerName)
        {
            matchingCertificate = certificateBySubjectName;
            break;
        }
    }
    if (matchingCertificate == null)
    {
        ThrowCertificateNotFoundException(expectedSubjectName, expectedIssuerName, false);
    }
    return matchingCertificate;
}
private static bool VerifyCertificateIssuer(string expectedSubjectName, string expectedIssuerName, X509Certificate2 issuerCertificate)
{
    X509Certificate2 matchingCertificate = GetCertificate(expectedSubjectName, expectedIssuerName);
    X509Chain chain = new X509Chain();
    chain.Build(matchingCertificate);
    // bool x = Encoding.ASCII.GetString(chain.ChainElements[1].Certificate.RawData) == Encoding.ASCII.GetString(issuerCertificate.RawData);
    byte[] certificateData = matchingCertificate.RawData;
    MemoryStream asn1Stream = new MemoryStream(certificateData);
    BinaryReader asn1StreamReader = new BinaryReader(asn1Stream);
    // The der encoded certificate structure is like this:
    // Root Sequence
    //     Sequence (Certificate Content)
    //     Sequence (Signature Algorithm (like SHA256withRSAEncryption)
    //     Sequence (Signature)
    // We need to decode the ASN.1 content to get
    //     Sequence 0 (which is the actual certificate content that is signed by the issuer, including the sequence definition and tag number and length)
    //     Sequence 2 (which is the signature. X509Certificate2 class does not give us that information. The year is 2015)
    // Read the root sequence (ignore)
    byte leadingOctet = asn1StreamReader.ReadByte();
    ReadTagNumber(leadingOctet, asn1StreamReader);
    ReadDataLength(asn1StreamReader);
    // Save the current position because we will need it for including the sequence header with the certificate content
    int sequence0StartPosition = (int)asn1Stream.Position;
    leadingOctet = asn1StreamReader.ReadByte();
    ReadTagNumber(leadingOctet, asn1StreamReader);
    int sequence0ContentLength = ReadDataLength(asn1StreamReader);
    int sequence0HeaderLength = (int)asn1Stream.Position - sequence0StartPosition;
    sequence0ContentLength += sequence0HeaderLength;
    byte[] sequence0Content = new byte[sequence0ContentLength];
    asn1Stream.Position -= 4;
    asn1StreamReader.Read(sequence0Content, 0, sequence0ContentLength);
    // Skip sequence 1 (signature algorithm) since we don't need it and assume that we know it because we own the issuer certificate
    // This sequence, containing the algorithm used during the signing process IS HIDDEN FROM US BY DEFAULT. The year is 2015.
    // What should have been done for real is, to get the algorithm ID (hash algorithm and asymmetric algorithm) and to use those
    // algorithms during the verification process
    leadingOctet = asn1StreamReader.ReadByte();
    ReadTagNumber(leadingOctet, asn1StreamReader);
    int sequence1ContentLength = ReadDataLength(asn1StreamReader);
    byte[] sequence1Content = new byte[sequence1ContentLength];
    asn1StreamReader.Read(sequence1Content, 0, sequence1ContentLength);
    // Read sequence 2 (signature)
    // The actual signature of the certificate IS HIDDEN FROM US BY DEFAULT. The year is 2015.
    leadingOctet = asn1StreamReader.ReadByte();
    ReadTagNumber(leadingOctet, asn1StreamReader);
    int sequence2ContentLength = ReadDataLength(asn1StreamReader);
    byte unusedBits = asn1StreamReader.ReadByte();
    sequence2ContentLength -= 1;
    byte[] sequence2Content = new byte[sequence2ContentLength];
    asn1StreamReader.Read(sequence2Content, 0, sequence2ContentLength);
    // At last, we have the data that is signed and the signature.
    bool verificationResult = ((RSACryptoServiceProvider)issuerCertificate.PublicKey.Key)
    .VerifyData
    (
        sequence0Content,
        CryptoConfig.MapNameToOID("SHA256"),
        sequence2Content
    );
    return verificationResult;
}
private static byte[] ReadTagNumber(byte leadingOctet, BinaryReader inputStreamReader)
{
    List<byte> byts = new List<byte>();
    byte temporaryByte;
    if ((leadingOctet & 0x1F) == 0x1F) // More than 1 byte is used to specify the tag number
    {
        while (((temporaryByte = inputStreamReader.ReadByte()) & 0x80) > 0)
        {
            byts.Add((byte)(temporaryByte & 0x7F));
        }
        byts.Add(temporaryByte);
    }
    else
    {
        byts.Add((byte)(leadingOctet & 0x1F));
    }
    return byts.ToArray();
}
private static int ReadDataLength(BinaryReader inputStreamReader)
{
    byte leadingOctet = inputStreamReader.ReadByte();
    if ((leadingOctet & 0x80) > 0)
    {
        int subsequentialOctetsCount = leadingOctet & 0x7F;
        int length = 0;
        for (int i = 0; i < subsequentialOctetsCount; i++)
        {
            length <<= 8;
            length += inputStreamReader.ReadByte();
        }
        return length;
    }
    else
    {
        return leadingOctet;
    }
}
private static byte[] GetTagNumber(byte leadingOctet, BinaryReader inputStreamReader, ref int readBytes)
{
    List<byte> byts = new List<byte>();
    byte temporaryByte;
    if ((leadingOctet & 0x1F) == 0x1F) // More than 1 byte is used to specify the tag number
    {
        while (((temporaryByte = inputStreamReader.ReadByte()) & 0x80) > 0)
        {
            readBytes++;
            byts.Add((byte)(temporaryByte & 0x7F));
        }
        byts.Add(temporaryByte);
    }
    else
    {
        byts.Add((byte)(leadingOctet & 0x1F));
    }
    return byts.ToArray();
}