如何在.NET中加载证书请求并从中创建证书 [英] How to load a certificate request and create a certificate from it in .NET

查看:61
本文介绍了如何在.NET中加载证书请求并从中创建证书的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I want to load a Certificate Request (CSR) from its serialized form and sign it. Is this possible in pure .NET?

CSR如下:

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

它是使用.NET 4.7.2 CertificateRequest 生成的,类似于以下问题的答案:使用纯.net框架生成并签名证书请求

It was generated using .NET 4.7.2 CertificateRequest, similar to the answer in this question: Generate and Sign Certificate Request using pure .net Framework

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

The serialized CSR is then sent to a server, which needs to create the certificate - the question is how to do that.

推荐答案

您真的要这样做吗?

解析认证请求(俗称证书签名请求"或CSR)并盲目签名是一种非常非常糟糕的操作做法.

如果您想成为证书颁发机构,甚至是私有证书颁发机构,则应阅读并理解CA/Browser论坛当前(截至阅读本指南时)的所有内容,网址为

If you want to be a Certificate Authority, even a private one, you should read and understand everything in the CA/Browser Forum's current (as of whenever you read this) Baseline Requirements document at https://cabforum.org/baseline-requirements-documents/. Maybe you intentionally decide something doesn't apply to you, but then at least it's intentional.

至少您应该检查该请求:

At minimum you should be checking that the request:

  • 不授予自己CA的权限(提示,颁发pathLenConstraint为0的签名证书来帮助阻止此签名),除非您当然打算创建从属CA(但可能不是).
  • 仅使用批准的密钥用法和扩展的密钥用法值.
  • 仅使用批准的使用者名称和使用者备用名称"扩展名值(如果请求没有EKU扩展名或包含TLS服务器用法).
  • 不定义会干扰您CA的扩展(权限密钥标识符,授权信息访问,颁发者备用名称,CRL分发点等)
  • 未定义您不理解的任何扩展名(例如证书透明性"poison"扩展名)/未授权该请求.
  • .NET没有内置支持来读取主题备用名,您应该对此进行验证.(不要使用字符串解析,请使用诸如System.Formats.Asn1.AsnReader之类的东西)
  • 您可能还想向您发出的请求中添加一个Authority Information Access扩展,Authority Key Identifier扩展以及CRL分发点扩展,对此也没有内置的支持.
    • .NET doesn't have built-in support for reading Subject Alternative Names, which you're supposed to verify. (Don't use string parsing, use something like System.Formats.Asn1.AsnReader)
    • You probably also want to add an Authority Information Access extension, Authority Key Identifier extension, and probably CRL Distribution Point extension to the requests you issue, there's no built-in support for that either.
      • https://github.com/dotnet/runtime/blob/8b8c390755189d45efc0c407992cb7c006b802b5/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/CertificateAuthority.cs does have examples of all of this (for the tests of X509Chain).
      • https://github.com/dotnet/runtime/blob/8b8c390755189d45efc0c407992cb7c006b802b5/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/RevocationResponder.cs (again, from the tests for X509Chain).

      此代码使用新的 System.Formats.Asn1 包(具体来说,它是通过针对.NET Framework 4.7.2构建的可执行文件在.NET Framework 4.8上使用版本5.0.0-preview.8.20407.11(应该是2020年11月的稳定版本5.0.0)进行测试的.

      This code uses the new System.Formats.Asn1 package (specifically, it was tested with version 5.0.0-preview.8.20407.11 [which should be stable version 5.0.0 in November 2020] on .NET Framework 4.8 from an executable built targeting .NET Framework 4.7.2).

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

      It does verify that the proof-of-private-key-possession signature is valid, and in doing so limits itself to RSA-SSA-PKCS1_v1.5 signatures (no ECDSA, no RSA-SSA-PSS). Adding other algorithms is (of course) possible.

      此代码不提供任何类型的操作策略.呼叫者有责任验证仅使用了适当的扩展名(包括关键"位是适当的),名称都适当,并且,除了它可以解码和主题公钥"之外的所有其他内容验证请求签名".

      This code DOES NOT provide any sort of operational policy. It's up to the caller to verify that only appropriate extensions are used (including that "critical" bits are appropriate), that names are all appropriate, and, well, anything else aside from "it can be decoded and the subject public key verifies the request signature".

      API的奇特之处在于,您需要告诉解码例程对请求进行签名时最终打算使用哪种哈希算法,因为CertificateRequest要求在构造函数中使用它,才能使后续的签名调用更加容易.

      There's an API oddity in that you need to tell the decode routine what hash algorithm you eventually intend to use when signing the request, because CertificateRequest requires it in the constructor to make subsequent signing calls easier.

      好的,我认为这是足够的免责声明,以及代码中的其他免责声明.因此,这里有足够的代码成为可怕的"代码.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;
          }
      }
      

      这篇关于如何在.NET中加载证书请求并从中创建证书的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆