如何确保证书没有被更改?

How to make sure certificate is not changed?

我想到了一个问题。 只有当系统中安装了证书时,是否有任何通用的方法来制作应用程序 运行。而我想要这样的证书由我的自签名证书颁发和验证?

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

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

如果问题不正确或不明确,我很抱歉,但我很乐意就此问题提供帮助。

确实是个好问题。

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

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

因此,下面的代码从当前用户的个人证书库中加载应用程序证书,然后从资源中加载颁发者的颁发者证书,并验证安装在客户端机器上的应用程序证书上的签名颁发者证书的 public 密钥。

在我的源代码中,颁发者证书使用密钥 IssuerCertificate

添加到资源中

我其实很喜欢提出这样的解决方案。

在代码中我提到了编码 ASN.1。 Check it here如果你需要

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();
}