File: System\Security\Cryptography\X509Certificates\CertificateRequest.Load.cs
Web Access
Project: src\src\libraries\System.Security.Cryptography\src\System.Security.Cryptography.csproj (System.Security.Cryptography)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using System.Diagnostics;
using System.Formats.Asn1;
using System.Security.Cryptography.Asn1;
using System.Security.Cryptography.X509Certificates.Asn1;
using Internal.Cryptography;
 
namespace System.Security.Cryptography.X509Certificates
{
    public sealed partial class CertificateRequest
    {
        private const CertificateRequestLoadOptions AllOptions =
            CertificateRequestLoadOptions.SkipSignatureValidation |
            CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions;
 
        public static CertificateRequest LoadSigningRequestPem(
            string pkcs10Pem,
            HashAlgorithmName signerHashAlgorithm,
            CertificateRequestLoadOptions options = CertificateRequestLoadOptions.Default,
            RSASignaturePadding? signerSignaturePadding = null)
        {
            ArgumentNullException.ThrowIfNull(pkcs10Pem);
 
            return LoadSigningRequestPem(
                pkcs10Pem.AsSpan(),
                signerHashAlgorithm,
                options,
                signerSignaturePadding);
        }
 
        public static CertificateRequest LoadSigningRequestPem(
            ReadOnlySpan<char> pkcs10Pem,
            HashAlgorithmName signerHashAlgorithm,
            CertificateRequestLoadOptions options = CertificateRequestLoadOptions.Default,
            RSASignaturePadding? signerSignaturePadding = null)
        {
            // Since ML-DSA (and others) don't require a hash algorithm, but we don't
            // know what signature algorithm is being used until the call to Create,
            // we can't check here.
 
            if ((options & ~AllOptions) != 0)
            {
                throw new ArgumentOutOfRangeException(nameof(options), options, SR.Argument_InvalidFlag);
            }
 
            foreach ((ReadOnlySpan<char> contents, PemFields fields) in PemEnumerator.Utf16(pkcs10Pem))
            {
                if (contents[fields.Label].SequenceEqual(PemLabels.Pkcs10CertificateRequest))
                {
                    byte[] rented = ArrayPool<byte>.Shared.Rent(fields.DecodedDataLength);
 
                    if (!Convert.TryFromBase64Chars(contents[fields.Base64Data], rented, out int bytesWritten) ||
                        bytesWritten != fields.DecodedDataLength)
                    {
                        Debug.Fail("Base64Decode failed, but PemEncoding said it was legal");
                        throw new UnreachableException();
                    }
 
                    try
                    {
                        return LoadSigningRequest(
                            rented.AsSpan(0, bytesWritten),
                            permitTrailingData: false,
                            signerHashAlgorithm,
                            out _,
                            options,
                            signerSignaturePadding);
                    }
                    finally
                    {
                        ArrayPool<byte>.Shared.Return(rented);
                    }
                }
            }
 
            throw new CryptographicException(
                SR.Format(SR.Cryptography_NoPemOfLabel, PemLabels.Pkcs10CertificateRequest));
        }
 
        public static CertificateRequest LoadSigningRequest(
            byte[] pkcs10,
            HashAlgorithmName signerHashAlgorithm,
            CertificateRequestLoadOptions options = CertificateRequestLoadOptions.Default,
            RSASignaturePadding? signerSignaturePadding = null)
        {
            ArgumentNullException.ThrowIfNull(pkcs10);
 
            return LoadSigningRequest(
                pkcs10,
                permitTrailingData: false,
                signerHashAlgorithm,
                out _,
                options,
                signerSignaturePadding);
        }
 
        public static CertificateRequest LoadSigningRequest(
            ReadOnlySpan<byte> pkcs10,
            HashAlgorithmName signerHashAlgorithm,
            out int bytesConsumed,
            CertificateRequestLoadOptions options = CertificateRequestLoadOptions.Default,
            RSASignaturePadding? signerSignaturePadding = null)
        {
            return LoadSigningRequest(
                pkcs10,
                permitTrailingData: true,
                signerHashAlgorithm,
                out bytesConsumed,
                options,
                signerSignaturePadding);
        }
 
        private static unsafe CertificateRequest LoadSigningRequest(
            ReadOnlySpan<byte> pkcs10,
            bool permitTrailingData,
            HashAlgorithmName signerHashAlgorithm,
            out int bytesConsumed,
            CertificateRequestLoadOptions options,
            RSASignaturePadding? signerSignaturePadding)
        {
            // Since ML-DSA (and others) don't require a hash algorithm, but we don't
            // know what signature algorithm is being used until the call to Create,
            // we can't check here.
 
            if ((options & ~AllOptions) != 0)
            {
                throw new ArgumentOutOfRangeException(nameof(options), options, SR.Argument_InvalidFlag);
            }
 
            bool skipSignatureValidation =
                (options & CertificateRequestLoadOptions.SkipSignatureValidation) != 0;
 
            bool unsafeLoadCertificateExtensions =
                (options & CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions) != 0;
 
            try
            {
                ValueAsnReader outer = new ValueAsnReader(pkcs10, AsnEncodingRules.DER);
                int encodedLength = outer.PeekEncodedValue().Length;
 
                ValueAsnReader pkcs10Asn = outer.ReadSequence();
                CertificateRequest req;
 
                if (!permitTrailingData)
                {
                    outer.ThrowIfNotEmpty();
                }
 
                ReadOnlySpan<byte> encodedRequestInfo = pkcs10Asn.PeekEncodedValue();
                ValueCertificationRequestInfoAsn requestInfo;
                ValueAlgorithmIdentifierAsn algorithmIdentifier;
                ReadOnlySpan<byte> signature;
                int signatureUnusedBitCount;
 
                ValueCertificationRequestInfoAsn.Decode(ref pkcs10Asn, out requestInfo);
                ValueAlgorithmIdentifierAsn.Decode(ref pkcs10Asn, out algorithmIdentifier);
 
                if (!pkcs10Asn.TryReadPrimitiveBitString(out signatureUnusedBitCount, out signature))
                {
                    throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
                }
 
                pkcs10Asn.ThrowIfNotEmpty();
 
                if (requestInfo.Version < 0)
                {
                    throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
                }
 
                // They haven't bumped from v0 to v1 as of 2022.
                const int MaxSupportedVersion = 0;
 
                if (requestInfo.Version != MaxSupportedVersion)
                {
                    throw new CryptographicException(
                        SR.Format(
                            SR.Cryptography_CertReq_Load_VersionTooNew,
                            requestInfo.Version,
                            MaxSupportedVersion));
                }
 
                PublicKey publicKey = PublicKey.DecodeSubjectPublicKeyInfo(ref requestInfo.SubjectPublicKeyInfo);
 
                if (!skipSignatureValidation)
                {
                    // None of the supported signature algorithms support signatures that are not full bytes.
                    // So, shortcut the verification on the bit length
                    if (signatureUnusedBitCount != 0 ||
                        !VerifyX509Signature(encodedRequestInfo, signature, publicKey, ref algorithmIdentifier))
                    {
                        throw new CryptographicException(SR.Cryptography_CertReq_SignatureVerificationFailed);
                    }
                }
 
                X500DistinguishedName subject = new X500DistinguishedName(requestInfo.Subject);
 
                req = new CertificateRequest(
                    subject,
                    publicKey,
                    signerHashAlgorithm,
                    signerSignaturePadding);
 
                bool foundCertExt = false;
 
                foreach (ValueAttributeAsn attr in requestInfo.GetAttributes(AsnEncodingRules.DER))
                {
                    if (attr.AttrType == Oids.Pkcs9ExtensionRequest)
                    {
                        if (foundCertExt)
                        {
                            throw new CryptographicException(
                                SR.Cryptography_CertReq_Load_DuplicateExtensionRequests);
                        }
 
                        foundCertExt = true;
 
                        scoped ReadOnlySpan<byte> firstAttrValue = default;
                        bool foundAttrValue = false;
 
                        foreach (ReadOnlySpan<byte> values in attr.GetAttrValues(AsnEncodingRules.DER))
                        {
                            if (foundAttrValue)
                            {
                                throw new CryptographicException(
                                    SR.Cryptography_CertReq_Load_DuplicateExtensionRequests);
                            }
 
                            firstAttrValue = values;
                            foundAttrValue = true;
                        }
 
                        if (!foundAttrValue)
                        {
                            throw new CryptographicException(SR.Cryptography_CertReq_Load_DuplicateExtensionRequests);
                        }
 
                        ValueAsnReader extsReader = new ValueAsnReader(
                            firstAttrValue,
                            AsnEncodingRules.DER);
 
                        ValueAsnReader exts = extsReader.ReadSequence();
                        extsReader.ThrowIfNotEmpty();
 
                        // Minimum length is 1, so do..while
                        do
                        {
                            ValueX509ExtensionAsn.Decode(ref exts, out ValueX509ExtensionAsn extAsn);
 
                            if (unsafeLoadCertificateExtensions)
                            {
                                X509Extension ext = new X509Extension(
                                    extAsn.ExtnId,
                                    extAsn.ExtnValue,
                                    extAsn.Critical);
 
                                X509Extension? rich =
                                    X509Certificate2.CreateCustomExtensionIfAny(extAsn.ExtnId);
 
                                if (rich is not null)
                                {
                                    rich.CopyFrom(ext);
                                    req.CertificateExtensions.Add(rich);
                                }
                                else
                                {
                                    req.CertificateExtensions.Add(ext);
                                }
                            }
                        } while (exts.HasData);
                    }
                    else
                    {
                        bool anyAttrValues = false;
 
                        foreach (ReadOnlySpan<byte> val in attr.GetAttrValues(AsnEncodingRules.DER))
                        {
                            req.OtherRequestAttributes.Add(new AsnEncodedData(attr.AttrType, val));
                            anyAttrValues = true;
                        }
 
                        if (!anyAttrValues)
                        {
                            throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
                        }
                    }
                }
 
                bytesConsumed = encodedLength;
                return req;
            }
            catch (AsnContentException e)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
            }
        }
 
        private static bool VerifyX509Signature(
            ReadOnlySpan<byte> toBeSigned,
            ReadOnlySpan<byte> signature,
            PublicKey publicKey,
            ref readonly ValueAlgorithmIdentifierAsn algorithmIdentifier)
        {
            RSA? rsa = publicKey.GetRSAPublicKey();
            ECDsa? ecdsa = publicKey.GetECDsaPublicKey();
            MLDsa? mldsa = publicKey.GetMLDsaPublicKey();
            SlhDsa? slhDsa = publicKey.GetSlhDsaPublicKey();
 
            try
            {
                HashAlgorithmName hashAlg;
 
                if (algorithmIdentifier.Algorithm == Oids.RsaPss)
                {
                    if (rsa is null || !algorithmIdentifier.HasParameters)
                    {
                        return false;
                    }
 
                    ValuePssParamsAsn.Decode(
                        algorithmIdentifier.Parameters,
                        AsnEncodingRules.DER,
                        out ValuePssParamsAsn pssParams);
 
                    RSASignaturePadding padding = pssParams.GetSignaturePadding();
                    hashAlg = HashAlgorithmName.FromOid(pssParams.HashAlgorithm.Algorithm);
 
                    return rsa.VerifyData(
                        toBeSigned,
                        signature,
                        hashAlg,
                        padding);
                }
 
                switch (algorithmIdentifier.Algorithm)
                {
                    case Oids.RsaPkcs1Sha256:
                    case Oids.ECDsaWithSha256:
                        hashAlg = HashAlgorithmName.SHA256;
                        break;
                    case Oids.RsaPkcs1Sha384:
                    case Oids.ECDsaWithSha384:
                        hashAlg = HashAlgorithmName.SHA384;
                        break;
                    case Oids.RsaPkcs1Sha512:
                    case Oids.ECDsaWithSha512:
                        hashAlg = HashAlgorithmName.SHA512;
                        break;
                    case Oids.RsaPkcs1Sha1:
                    case Oids.ECDsaWithSha1:
                        hashAlg = HashAlgorithmName.SHA1;
                        break;
                    case Oids.MLDsa44:
                    case Oids.MLDsa65:
                    case Oids.MLDsa87:
                        hashAlg = default;
                        break;
                    case string oid when Helpers.IsSlhDsaOid(oid):
                        hashAlg = default;
                        break;
                    default:
                        throw new NotSupportedException(
                            SR.Format(SR.Cryptography_UnknownKeyAlgorithm, algorithmIdentifier.Algorithm));
                }
 
                // All remaining supported algorithms have no defined parameters
                if (!algorithmIdentifier.HasNullEquivalentParameters())
                {
                    return false;
                }
 
                switch (algorithmIdentifier.Algorithm)
                {
                    case Oids.RsaPkcs1Sha256:
                    case Oids.RsaPkcs1Sha384:
                    case Oids.RsaPkcs1Sha512:
                    case Oids.RsaPkcs1Sha1:
                        if (rsa is null)
                        {
                            return false;
                        }
 
                        return rsa.VerifyData(toBeSigned, signature, hashAlg, RSASignaturePadding.Pkcs1);
                    case Oids.ECDsaWithSha256:
                    case Oids.ECDsaWithSha384:
                    case Oids.ECDsaWithSha512:
                    case Oids.ECDsaWithSha1:
                        if (ecdsa is null)
                        {
                            return false;
                        }
 
                        return ecdsa.VerifyData(toBeSigned, signature, hashAlg, DSASignatureFormat.Rfc3279DerSequence);
                    case Oids.MLDsa44:
                    case Oids.MLDsa65:
                    case Oids.MLDsa87:
                        if (mldsa is null)
                        {
                            return false;
                        }
 
                        return mldsa.VerifyData(toBeSigned, signature);
 
                    case string oid when Helpers.IsSlhDsaOid(oid):
                        if (slhDsa is null)
                        {
                            return false;
                        }
 
                        return slhDsa.VerifyData(toBeSigned, signature);
 
                    default:
                        Debug.Fail(
                            $"Algorithm ID {algorithmIdentifier.Algorithm} was in the first switch, but not the second");
                        return false;
                }
            }
            catch (AsnContentException)
            {
                return false;
            }
            catch (CryptographicException)
            {
                return false;
            }
            finally
            {
                rsa?.Dispose();
                ecdsa?.Dispose();
                mldsa?.Dispose();
                slhDsa?.Dispose();
            }
        }
    }
}