/// <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); } }