Ejemplo n.º 1
0
        /// <summary>
        /// Create a certificate using the established subject, key, and optional extensions using
        /// the provided certificate as the issuer.
        /// </summary>
        /// <param name="issuerCertificate">
        ///   An X509Certificate2 instance representing the issuing Certificate Authority (CA).
        /// </param>
        /// <param name="notBefore">
        ///   The oldest date and time where this certificate is considered valid.
        ///   Typically <see cref="DateTimeOffset.UtcNow"/>, plus or minus a few seconds.
        /// </param>
        /// <param name="notAfter">
        ///   The date and time where this certificate is no longer considered valid.
        /// </param>
        /// <param name="serialNumber">
        ///   The serial number to use for the new certificate. This value should be unique per issuer.
        ///   The value is interpreted as an unsigned (big) integer in big endian byte ordering.
        /// </param>
        /// <returns>
        ///   An <see cref="X509Certificate2"/> with the specified values. The returned object will
        ///   not assert <see cref="X509Certificate2.HasPrivateKey" />.
        /// </returns>
        /// <exception cref="ArgumentNullException"><paramref name="issuerCertificate"/> is null.</exception>
        /// <exception cref="ArgumentException">
        ///   The <see cref="X509Certificate2.HasPrivateKey"/> value for <paramref name="issuerCertificate"/> is false.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   The type of signing key represented by <paramref name="issuerCertificate"/> could not be determined.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="notAfter"/> represents a date and time before <paramref name="notBefore"/>.
        /// </exception>
        /// <exception cref="ArgumentException"><paramref name="serialNumber"/> is null or has length 0.</exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="issuerCertificate"/> has a different key algorithm than the requested certificate.
        /// </exception>
        /// <exception cref="InvalidOperationException">
        ///   <paramref name="issuerCertificate"/> is an RSA certificate and this object was created via a constructor
        ///   which does not accept a <see cref="RSASignaturePadding"/> value.
        /// </exception>
        public X509Certificate2 Create(
            X509Certificate2 issuerCertificate,
            DateTimeOffset notBefore,
            DateTimeOffset notAfter,
            byte[] serialNumber)
        {
            if (issuerCertificate == null)
            {
                throw new ArgumentNullException(nameof(issuerCertificate));
            }
            if (!issuerCertificate.HasPrivateKey)
            {
                throw new ArgumentException(SR.Cryptography_CertReq_IssuerRequiresPrivateKey, nameof(issuerCertificate));
            }
            if (notAfter < notBefore)
            {
                throw new ArgumentException(SR.Cryptography_CertReq_DatesReversed);
            }
            if (serialNumber == null || serialNumber.Length < 1)
            {
                throw new ArgumentException(SR.Arg_EmptyOrNullArray, nameof(serialNumber));
            }

            if (issuerCertificate.PublicKey.Oid.Value != PublicKey.Oid.Value)
            {
                throw new ArgumentException(
                          SR.Format(
                              SR.Cryptography_CertReq_AlgorithmMustMatch,
                              issuerCertificate.PublicKey.Oid.Value,
                              PublicKey.Oid.Value),
                          nameof(issuerCertificate));
            }

            DateTime notBeforeLocal = notBefore.LocalDateTime;

            if (notBeforeLocal < issuerCertificate.NotBefore)
            {
                throw new ArgumentException(
                          SR.Format(
                              SR.Cryptography_CertReq_NotBeforeNotNested,
                              notBeforeLocal,
                              issuerCertificate.NotBefore),
                          nameof(notBefore));
            }

            DateTime notAfterLocal = notAfter.LocalDateTime;

            // Round down to the second, since that's the cert accuracy.
            // This makes one method which uses the same DateTimeOffset for chained notAfters
            // not need to do the rounding locally.
            long notAfterLocalTicks = notAfterLocal.Ticks;
            long fractionalSeconds  = notAfterLocalTicks % TimeSpan.TicksPerSecond;

            notAfterLocalTicks -= fractionalSeconds;
            notAfterLocal       = new DateTime(notAfterLocalTicks, notAfterLocal.Kind);

            if (notAfterLocal > issuerCertificate.NotAfter)
            {
                throw new ArgumentException(
                          SR.Format(
                              SR.Cryptography_CertReq_NotAfterNotNested,
                              notAfterLocal,
                              issuerCertificate.NotAfter),
                          nameof(notAfter));
            }

            // Check the Basic Constraints and Key Usage extensions to help identify inappropriate certificates.
            // Note that this is not a security check. The system library backing X509Chain will use these same criteria
            // to determine if a chain is valid; and a user can easily call the X509SignatureGenerator overload to
            // bypass this validation.  We're simply helping them at signing time understand that they've
            // chosen the wrong cert.
            var basicConstraints = (X509BasicConstraintsExtension)issuerCertificate.Extensions[Oids.BasicConstraints2];
            var keyUsage         = (X509KeyUsageExtension)issuerCertificate.Extensions[Oids.KeyUsage];

            if (basicConstraints == null)
            {
                throw new ArgumentException(SR.Cryptography_CertReq_BasicConstraintsRequired, nameof(issuerCertificate));
            }
            if (!basicConstraints.CertificateAuthority)
            {
                throw new ArgumentException(SR.Cryptography_CertReq_IssuerBasicConstraintsInvalid, nameof(issuerCertificate));
            }
            if (keyUsage != null && (keyUsage.KeyUsages & X509KeyUsageFlags.KeyCertSign) == 0)
            {
                throw new ArgumentException(SR.Cryptography_CertReq_IssuerKeyUsageInvalid, nameof(issuerCertificate));
            }

            AsymmetricAlgorithm key = null;
            string keyAlgorithm     = issuerCertificate.GetKeyAlgorithm();
            X509SignatureGenerator generator;

            try
            {
                switch (keyAlgorithm)
                {
                case Oids.Rsa:
                    if (_rsaPadding == null)
                    {
                        throw new InvalidOperationException(SR.Cryptography_CertReq_RSAPaddingRequired);
                    }

                    RSA rsa = issuerCertificate.GetRSAPrivateKey();
                    key       = rsa;
                    generator = X509SignatureGenerator.CreateForRSA(rsa, _rsaPadding);
                    break;

                case Oids.EcPublicKey:
                    ECDsa ecdsa = issuerCertificate.GetECDsaPrivateKey();
                    key       = ecdsa;
                    generator = X509SignatureGenerator.CreateForECDsa(ecdsa);
                    break;

                default:
                    throw new ArgumentException(
                              SR.Format(SR.Cryptography_UnknownKeyAlgorithm, keyAlgorithm),
                              nameof(issuerCertificate));
                }

                return(Create(issuerCertificate.SubjectName, generator, notBefore, notAfter, serialNumber));
            }
            finally
            {
                key?.Dispose();
            }
        }
        public static void HybridCertificateRequestTest()
        {
            const string TestCN       = "CN=Test";
            const string LeafCN       = "CN=Leaf";
            var          serialNumber = new byte[20];
            var          rand         = new Random();

            rand.NextBytes(serialNumber);
            serialNumber[0] = 0x80;

            using (var rsa1 = RSA.Create())
                using (var ecdsa2 = ECDsa.Create()) {
                    var request1 = new CertificateRequest(TestCN, rsa1, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
                    request1.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, false));
                    Assert.NotNull(request1.PublicKey);
                    Assert.NotNull(request1.CertificateExtensions);
                    Assert.NotEmpty(request1.CertificateExtensions);
                    Assert.Equal(TestCN, request1.SubjectName.Name);

                    var ca = request1.CreateSelfSigned(DateTimeOffset.UtcNow,
                                                       DateTimeOffset.UtcNow + TimeSpan.FromDays(1));
                    Assert.Equal(ca.Subject, TestCN);

                    var request2 = new CertificateRequest(LeafCN, ecdsa2, HashAlgorithmName.SHA512);
                    request2.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false));
                    Assert.NotNull(request2.PublicKey);
                    Assert.NotNull(request2.CertificateExtensions);
                    Assert.NotEmpty(request2.CertificateExtensions);
                    Assert.Equal(LeafCN, request2.SubjectName.Name);

                    var signer = X509SignatureGenerator.CreateForRSA(rsa1, RSASignaturePadding.Pkcs1);
                    var leaf   = request2.Create(ca.SubjectName, signer, DateTimeOffset.UtcNow,
                                                 DateTimeOffset.UtcNow + TimeSpan.FromHours(1), serialNumber);
                    Assert.Equal(leaf.Subject, LeafCN);
                    Assert.Equal(new SerialNumber(serialNumber).ToString(), leaf.SerialNumber);
                    Assert.NotNull(leaf.Extensions);
                    Assert.Single(leaf.Extensions);
                    Assert.NotNull(leaf.GetBasicConstraintsExtension());

                    var buffer = request2.CreateSigningRequest();

                    var info = buffer.ToCertificationRequestInfo();
                    Assert.True(info.GetPublicKey().SameAs(ecdsa2.ToPublicKey()));
                    Assert.Equal(LeafCN, new X500DistinguishedName(info.Subject.GetEncoded()).Name);

                    var csr = buffer.ToCertificateRequest(SignatureType.PS256);
                    //  var pubk = csr.PublicKey.Key;
                    //  Assert.True(pubk.ToPublicKey().SameAs(rsa2.ToPublicKey()));
                    Assert.Equal(LeafCN, csr.SubjectName.Name);

                    var leaf2 = csr.Create(ca.SubjectName, signer, DateTimeOffset.UtcNow,
                                           DateTimeOffset.UtcNow + TimeSpan.FromHours(1), serialNumber);
                    Assert.True(leaf2.GetECDsaPublicKey().ToPublicKey().SameAs(ecdsa2.ToPublicKey()));
                    Assert.NotNull(leaf2.PublicKey);
                    Assert.NotNull(leaf2.Extensions);
                    Assert.Single(leaf2.Extensions);
                    Assert.NotNull(leaf2.GetBasicConstraintsExtension());
                    Assert.Equal(leaf.Subject, leaf2.Subject);
                    Assert.Equal(leaf.SerialNumber, leaf2.SerialNumber);
                }
        }