如何解决如何在.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”扩展名)/未授权该请求。
您确定您真的要这样做吗?
- .NET没有内置的支持,您应该验证阅读主题备用名称。 (不要使用字符串解析,请使用类似System.Formats.Asn1.AsnReader的东西)
- 您可能还希望向您发出的请求中添加“授权信息访问”扩展,“授权密钥标识符”扩展,以及可能的“ CRL分发点”扩展,两者均没有内置支持。
- .NET不具有编写CRL或读取OCSP请求或生成OCSP响应的内置支持,因此您需要自己进行撤销。
- 您需要处理很多操作流程(请参阅CA / Browser论坛基准要求)
如果您坚持要...
此代码使用新的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 举报,一经查实,本站将立刻删除。