如何确保证书不会更改?

时间:2015-11-11 07:46:58

标签: c# certificate x509certificate

我想到了一个问题。 是否有任何常见的方法使应用程序仅在系统中安装了证书时运行。我希望通过我的自签名证书颁发和验证这样的证书吗?

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

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

我很抱歉,如果它不正确和/或明确的问题,但我很乐意为此提供帮助。

1 个答案:

答案 0 :(得分:4)

确实是非常好的问题。

最终用户总是有可能使用您的主题名称和发行人创建有效的证书链,而另一个用于发行人证书,直到根目录。

他们所做的就是使用颁发者证书的私钥对这些证书进行签名。

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

在我的源代码中,颁发者证书将添加到具有密钥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();
}