如何在.NET中加载证书请求并从中创建证书

如何解决如何在.NET中加载证书请求并从中创建证书

我想从序列化的表格中加载证书请求(CSR)并对其进行签名。 在纯.NET中有可能吗?

CSR如下:

-----BEGIN CERTIFICATE REQUEST-----
MIIDejCCAmICAQAwZTE0MDIGCgmSJom....
-----END CERTIFICATE REQUEST-----

它是使用.NET 4.7.2 CertificateRequest生成的,类似于以下问题的答案: Generate and Sign Certificate Request using pure .net Framework

然后将序列化的CSR发送到服务器,该服务器需要创建证书-问题是如何做到的。

解决方法

您真的要这样做吗?

解析认证请求(俗称“证书签名请求”或CSR)并盲目签名是一种非常非常糟糕的操作习惯。

如果您想成为证书颁发机构,甚至是私有证书颁发机构,则应阅读并理解CA / Browser论坛当前(截至阅读本指南时)位于https://cabforum.org/baseline-requirements-documents/的所有基本要求。也许您是故意地决定某个不适用于您的事物,但是至少它是故意的。

至少您应该检查该请求:

  • 不授予自己CA的权限(提示,颁发pathLenConstraint为0的签名证书来帮助阻止它),除非您当然打算创建从属CA(但可能不是)。
  • 仅使用批准的密钥用法和扩展的密钥用法值。
  • 仅使用批准的使用者名称和“使用者备用名称”扩展名值(如果请求没有EKU扩展名或包含TLS服务器用法)。
  • 不定义会干扰您CA的扩展(权限密钥标识符,授权信息访问,颁发者备用名称,CRL分发点等)
  • 未定义您不理解的任何扩展名(例如证书透明性“ poison”扩展名)/未授权该请求。

确定真的要这样做吗?

如果您坚持要...

此代码使用新的System.Formats.Asn1程序包(特别是在.NET Framework 4.8和更高版本的5.0.0-preview.8.20407.11 [应为2020年11月为5.0.0稳定版本)上进行了测试。针对.NET Framework 4.7.2构建的可执行文件。

它确实验证了私钥持有证明的签名是有效的,并在此过程中将自身限制为RSA-SSA-PKCS1_v1.5签名(无ECDSA,无RSA-SSA-PSS)。 (当然)可以添加其他算法。

此代码不提供任何类型的操作策略。呼叫者应验证是否仅使用了适当的扩展名(包括“关键”位是适当的),名称是否均适当,以及,除了“可以解码且主题公钥可以验证请求签名”。

API的怪异之处在于,您需要告诉解码例程对请求进行签名时最终打算使用哪种哈希算法,因为CertificateRequest要求在构造函数中使用它来简化后续的签名调用。

好的,我认为这是足够的免责声明,以及代码中的其他免责声明。因此,这里有足够的代码可以成为一个“糟糕的” CA。

internal static class CertificationRequestDecoder
{
    private const string BadPemRequest = "Input is not a PEM-encoded Certification Request.";

    /// <summary>
    ///   Load a CertificateRequest from a PEM-encoded Certification Request
    ///   (a.k.a. Certificate Signing Request,CSR)
    /// </summary>
    /// <param name="pem">The PEM-encoded Certification Request</param>
    /// <param name="signatureHashAlgorithm">
    ///   The hash algorithm to be used with the CA signature.
    /// </param>
    /// <returns>
    ///   A certificate request object containing the same data as the signing request.
    /// </returns>
    /// <exception cref="ArgumentNullException"><paramref name="pem"/> is <c>null</c>.</exception>
    /// <exception cref="ArgumentException">
    ///   <paramref name="pem"/> is not a well-formed PEM encoding for a Certification Request.
    /// </exception>
    /// <exception cref="AsnContentException">
    ///   <paramref name="pem"/> does not contain a well-formed Certification Request.
    /// </exception>
    /// <exception cref="InvalidOperationException">
    ///   The request contains unsupported elements.
    /// </exception>
    /// <exception cref="CryptographicException">
    ///   The Certification Request signature is invalid.
    /// </exception>
    /// <seealso cref="DecodeDer(ReadOnlyMemory{byte},HashAlgorithmName"/>
    internal static CertificateRequest DecodePem(
        string pem,HashAlgorithmName signatureHashAlgorithm)
    {
        if (pem == null)
            throw new ArgumentNullException(nameof(pem));

        // This PEM reader is overly lax. It should check for a newline at the end of preEB
        // and another at the beginning of postEB,but it skips it for Unix/Windows newline
        // reasons.
        //
        // After all,this is just a sample,right?
        const string PreEB = "-----BEGIN CERTIFICATE REQUEST-----";
        const string PostEB = "-----END CERTIFICATE REQUEST-----";

        int startIdx = pem.IndexOf(PreEB,StringComparison.Ordinal);
        int endIdx = pem.IndexOf(PostEB,StringComparison.Ordinal);

        if (startIdx < 0 || endIdx < 0)
            throw new ArgumentException(BadPemRequest,nameof(pem));

        if (startIdx != 0 && !string.IsNullOrWhiteSpace(pem.Substring(0,startIdx)))
            throw new ArgumentException(BadPemRequest,nameof(pem));

        if (endIdx < startIdx || !string.IsNullOrWhiteSpace(pem.Substring(endIdx + PostEB.Length)))
            throw new ArgumentException(BadPemRequest,nameof(pem));

        byte[] der;

        try
        {
            int base64Start = startIdx + PreEB.Length;
            string base64 = pem.Substring(base64Start,endIdx - base64Start);

            der = Convert.FromBase64String(base64);
        }
        catch (FormatException e)
        {
            throw new ArgumentException(BadPemRequest,nameof(pem),e);
        }

        return DecodeDer(der,signatureHashAlgorithm);
    }

    internal static CertificateRequest DecodeDer(
        byte[] der,HashAlgorithmName signatureHashAlgorithm)
    {
        if (der == null)
            throw new ArgumentNullException(nameof(der));

        return DecodeDer(der.AsMemory(),signatureHashAlgorithm);
    }

    /// <summary>
    ///   Load a CertificateRequest from a DER-encoded Certification Request
    ///   (a.k.a. Certificate Signing Request,CSR)
    /// </summary>
    /// <param name="der">The DER-encoded Certification Request.</param>
    /// <param name="signatureHashAlgorithm">
    ///   The hash algorithm to be used with the CA signature.
    /// </param>
    /// <returns>
    ///   A certificate request object containing the same data as the signing request.
    /// </returns>
    /// <exception cref="FormatException">
    ///   <paramref name="der"/> is not well-formed.
    /// </exception>
    /// <exception cref="InvalidOperationException">
    ///   The request contains unsupported elements.
    /// </exception>
    /// <exception cref="CryptographicException">
    ///   The Certification Request signature is invalid.
    /// </exception>
    /// <remarks>
    ///   This routine does not perform any sort of operational policy.
    ///   The caller is responsible for verifying that only valid extensions
    ///   are used,that the subject name is appropriate,and any other operational
    ///   concerns.
    /// </remarks>
    internal static CertificateRequest DecodeDer(
        ReadOnlyMemory<byte> der,HashAlgorithmName signatureHashAlgorithm)
    {
        AsnReader reader = new AsnReader(der,AsnEncodingRules.DER);
        AsnReader certificationRequest = reader.ReadSequence();
        reader.ThrowIfNotEmpty();

        byte[] encodedRequestInfo = certificationRequest.PeekEncodedValue().ToArray();
        AsnReader certificationRequestInfo = certificationRequest.ReadSequence();
        AsnReader algorithm = certificationRequest.ReadSequence();
        byte[] signature = certificationRequest.ReadBitString(out int unused);
        
        if (unused != 0)
        {
            throw new InvalidOperationException("The signature was not complete bytes.");
        }

        certificationRequest.ThrowIfNotEmpty();

        string algorithmOid = algorithm.ReadObjectIdentifier();
        HashAlgorithmName hashAlg;
        RSASignaturePadding signaturePadding = RSASignaturePadding.Pkcs1;

        // This only supports RSA.
        // Other algorithms could be added.
        switch (algorithmOid)
        {
            case "1.2.840.113549.1.1.5":
                hashAlg = HashAlgorithmName.SHA1;
                break;
            case "1.2.840.113549.1.1.11":
                hashAlg = HashAlgorithmName.SHA256;
                break;
            case "1.2.840.113549.1.1.12":
                hashAlg = HashAlgorithmName.SHA384;
                break;
            case "1.2.840.113549.1.1.13":
                hashAlg = HashAlgorithmName.SHA512;
                break;
            default:
                throw new InvalidOperationException(
                    $"No support for signature algorithm '{algorithmOid}'");
        }

        // Since only RSA-SSA-PKCS1 made it here,we know the parameters are missing,or NULL.
        if (algorithm.HasData)
        {
            algorithm.ReadNull();
        }

        algorithm.ThrowIfNotEmpty();

        CertificateRequest certReq =
            DecodeCertificationRequestInfo(certificationRequestInfo,signatureHashAlgorithm);

        RSA pubKey = GetRSA(certReq.PublicKey);

        if (pubKey == null)
        {
            throw new InvalidOperationException("Requested public key was not an RSA key.");
        }

        if (!pubKey.VerifyData(encodedRequestInfo,signature,hashAlg,signaturePadding))
        {
            throw new CryptographicException();
        }

        return certReq;
    }

    private static CertificateRequest DecodeCertificationRequestInfo(
        AsnReader certReqInfo,HashAlgorithmName signatureHashAlgorithm)
    {
        //https://tools.ietf.org/html/rfc2986#section-4.1
        // CertificationRequestInfo::= SEQUENCE {
        //     version INTEGER { v1(0) } (v1,...),//     subject Name,//     subjectPKInfo SubjectPublicKeyInfo{ { PKInfoAlgorithms } },//     attributes[0] Attributes{ { CRIAttributes } }
        // }

        // As of Sept 2020,there's not a V2 request format.
        if (!certReqInfo.TryReadInt32(out int version) || version != 0)
        {
            throw new InvalidOperationException("Only V1 requests are supported.");
        }

        byte[] encodedSubject = certReqInfo.ReadEncodedValue().ToArray();
        X500DistinguishedName subject = new X500DistinguishedName(encodedSubject);

        AsnReader spki = certReqInfo.ReadSequence();
        AsnReader reqAttrs =certReqInfo.ReadSetOf(new Asn1Tag(TagClass.ContextSpecific,0));
        certReqInfo.ThrowIfNotEmpty();

        // https://tools.ietf.org/html/rfc3280#section-4.1
        // SubjectPublicKeyInfo::= SEQUENCE {
        //     algorithm AlgorithmIdentifier,//     subjectPublicKey     BIT STRING
        // }

        AsnReader pubKeyAlg = spki.ReadSequence();
        string algOid = pubKeyAlg.ReadObjectIdentifier();
        byte[] algParams;

        if (pubKeyAlg.HasData)
        {
            algParams = pubKeyAlg.ReadEncodedValue().ToArray();
            pubKeyAlg.ThrowIfNotEmpty();
        }
        else
        {
            algParams = new byte[] { 0x05,0x00 };
        }

        byte[] keyBytes = spki.ReadBitString(out int unusedBitCount);

        if (unusedBitCount != 0)
        {
            throw new InvalidOperationException(
                "The subjectPublicKey field was not made of full bytes.");
        }

        PublicKey publicKey = new PublicKey(
            new Oid(algOid,null),new AsnEncodedData(algParams),new AsnEncodedData(keyBytes));

        CertificateRequest request = new CertificateRequest(
            subject,publicKey,signatureHashAlgorithm);

        if (reqAttrs.HasData)
        {
            // This decode routine only supports one extension: the PKCS#9 extensionRequest

            // https://tools.ietf.org/html/rfc2985
            // extensionRequest ATTRIBUTE ::= {
            //     WITH SYNTAX ExtensionRequest
            //     SINGLE VALUE TRUE
            //     ID pkcs-9-at-extensionRequest
            // }
            //
            // ExtensionRequest::= Extensions

            // https://www.itu.int/ITU-T/formal-language/itu-t/x/x501/2012/InformationFramework.html
            // Attribute{ATTRIBUTE: SupportedAttributes} ::= SEQUENCE {
            //    type ATTRIBUTE.&id({SupportedAttributes}),//    values SET SIZE(0..MAX) OF ATTRIBUTE.&Type({SupportedAttributes}{@type}),//    valuesWithContext SIZE(1..MAX) OF
            //      SEQUENCE {
            //        value ATTRIBUTE.&Type({SupportedAttributes}{@type}),//        contextList SET SIZE(1..MAX) OF Context,//        ...
            //      } OPTIONAL,//    ...
            // }

            // https://tools.ietf.org/html/rfc5280#section-4.1
            // Extensions::= SEQUENCE SIZE(1..MAX) OF Extension
            //
            // Extension::= SEQUENCE  {
            //     extnID OBJECT IDENTIFIER,//     critical BOOLEAN DEFAULT FALSE,//     extnValue OCTET STRING
            //       --contains the DER encoding of an ASN.1 value
            //       --corresponding to the extension type identified
            //       --by extnID
            // }

            AsnReader attribute = reqAttrs.ReadSequence();
            string attrType = attribute.ReadObjectIdentifier();
            AsnReader attrValues = attribute.ReadSetOf();

            if (attrType != "1.2.840.113549.1.9.14")
            {
                throw new InvalidOperationException(
                    $"Certification Request attribute '{attrType}' is not supported.");
            }

            // No contexts are defined for the extensionRequest attribute,// so valuesWithContext can't exist.
            attribute.ThrowIfNotEmpty();

            // The attribute is single-value,so it must be present
            // and there mustn't be a second one.
            AsnReader extensions = attrValues.ReadSequence();
            attrValues.ThrowIfNotEmpty();

            while (extensions.HasData)
            {
                AsnReader extension = extensions.ReadSequence();
                string extnId = extension.ReadObjectIdentifier();
                bool critical = false;
                byte[] extnValue;

                if (extension.PeekTag().HasSameClassAndValue(Asn1Tag.Boolean))
                {
                    critical = extension.ReadBoolean();
                }

                extnValue = extension.ReadOctetString();
                extension.ThrowIfNotEmpty();

                X509Extension ext = new X509Extension(
                    extnId,extnValue,critical);

                if (CryptoConfig.CreateFromName(extnId) is X509Extension typedExtn)
                {
                    typedExtn.CopyFrom(ext);
                    ext = typedExtn;
                }

                request.CertificateExtensions.Add(ext);
            }
        }

        return request;
    }

    private static RSA GetRSA(PublicKey certReqPublicKey)
    {
        try
        {
            return certReqPublicKey.Key as RSA;
        }
        catch (CryptographicException)
        {
        }
        catch (PlatformNotSupportedException)
        {
        }

        // The try will fail on .NET Framework with any RSA key whose public exponent
        // is bigger than uint.MaxValue,because RSACryptoServiceProvider (Windows CAPI)
        // doesn't support them.

        if (certReqPublicKey.Oid.Value != "1.2.840.113549.1.1.1")
        {
            throw new InvalidOperationException(
                $"The public key algorithm '{certReqPublicKey.Oid.Value}' is not supported.");
        }

        byte[] encodedParams = certReqPublicKey.EncodedParameters.RawData;

        if (encodedParams != null && encodedParams.Length != 0)
        {
            if (encodedParams.Length != 2 ||
                encodedParams[0] != 0x05 ||
                encodedParams[1] != 0x00)
            {
                throw new InvalidOperationException(
                    "Invalid algorithm parameters for an RSA key.");
            }
        }

        AsnReader encodedKey = new AsnReader(
            certReqPublicKey.EncodedKeyValue.RawData,AsnEncodingRules.DER);

        // https://tools.ietf.org/html/rfc3447#appendix-A.1.1
        // RSAPublicKey::= SEQUENCE {
        //     modulus INTEGER,--n
        //     publicExponent INTEGER   --e
        // }

        AsnReader rsaPublicKey = encodedKey.ReadSequence();
        BigInteger modulus = rsaPublicKey.ReadInteger();
        BigInteger publicExponent = rsaPublicKey.ReadInteger();
        rsaPublicKey.ThrowIfNotEmpty();

        byte[] n = modulus.ToByteArray();
        byte[] e = publicExponent.ToByteArray();

        if (n[n.Length - 1] == 0)
        {
            Array.Resize(ref n,n.Length - 1);
        }

        if (e[e.Length - 1] == 0)
        {
            Array.Resize(ref e,e.Length - 1);
        }

        Array.Reverse(n);
        Array.Reverse(e);
        
        RSAParameters rsaParameters = new RSAParameters
        {
            Modulus = n,Exponent = e,};

        RSACng rsaCng = new RSACng();
        rsaCng.ImportParameters(rsaParameters);
        return rsaCng;
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?