【问题标题】:How to make sure certificate is not changed?如何确保证书不被更改?
【发布时间】:2016-02-12 06:51:10
【问题描述】:

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

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

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

如果问题不正确和/或不清楚,我很抱歉,但我很乐意就此获得帮助。

【问题讨论】:

  • 但是我如何确保我从商店获得的软密钥最终由我的 {root |中间} 证书 客户的受信任的根证书颁发机构存储中的公钥?换句话说,我如何确保 [public cert B] 由 [private cert A] 使用 [public cert A] 签名???
  • 我会在 30 分钟内更新我的答案
  • 不能但会。你可能想顺便检查一下充气城堡c#库。
  • @Anatolyevich。我想出了解决方案。我希望这能解决你的问题。实际上,这是一个非常公平的问题,我希望更轻松地完成这样一个微不足道的任务。您仍然可以使用从 Java 移植到 C# 的 BouncyCastle 库。

标签: c# certificate x509certificate


【解决方案1】:

确实是非常好的问题。

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

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

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

在我的源代码中,颁发者证书使用密钥 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();
}

【讨论】:

  • 刚刚浏览了您发布的代码。似乎这正是我想要的。非常感谢您的帮助。
  • 而且我觉得我是证书方面的新手.. 这段代码如何确保具有预期主题名称的证书完全由系统中的预期发行者名称证书签名?通过personalCertificateStore.Certificates.Find(X509FindType.FindBySubjectName, expectedSubjectName, true)?
  • 其实,我要更新我的答案。我现在正在开会,但会尽快更新以真正检查证书是否已使用 X590Chain 类更改
  • 我刚刚检查了当用户创建具有相同名称的个人证书,导入到存储中,最后存储在受信任的根证书颁发机构 (TRCA) 中有 2 个具有相同名称的证书时的场景。作为结果,用户可以颁发假证书,用以前创建的证书对其进行签名,它将被软件接受,因为是的,存储中有原始证书..并且假证书是有效的(因为它的颁发者公钥是在 TRCA 中导入的。有没有办法从子证书中获取颁发者证书的某种指纹数据?
  • 我很感激。期待您的来信。
猜你喜欢
  • 2012-12-15
  • 1970-01-01
  • 1970-01-01
  • 2012-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-06
  • 2018-05-31
相关资源
最近更新 更多