/// <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"/> 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, ReadOnlySpan <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.IsEmpty) { 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(); } }
private byte[] Build( X509Certificate2 issuerCertificate, BigInteger crlNumber, DateTimeOffset nextUpdate, DateTimeOffset thisUpdate, HashAlgorithmName hashAlgorithm, RSASignaturePadding?rsaSignaturePadding) { ArgumentNullException.ThrowIfNull(issuerCertificate); if (!issuerCertificate.HasPrivateKey) { throw new ArgumentException( SR.Cryptography_CertReq_IssuerRequiresPrivateKey, nameof(issuerCertificate)); } if (crlNumber < 0) { throw new ArgumentOutOfRangeException(nameof(crlNumber), SR.ArgumentOutOfRange_NeedNonNegNum); } if (nextUpdate <= thisUpdate) { throw new ArgumentException(SR.Cryptography_CRLBuilder_DatesReversed); } ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); // 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 the CRL 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]; var subjectKeyIdentifier = (X509SubjectKeyIdentifierExtension?)issuerCertificate.Extensions[Oids.SubjectKeyIdentifier]; 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.CrlSign) == 0) { throw new ArgumentException( SR.Cryptography_CRLBuilder_IssuerKeyUsageInvalid, nameof(issuerCertificate)); } AsymmetricAlgorithm?key = null; string keyAlgorithm = issuerCertificate.GetKeyAlgorithm(); X509SignatureGenerator generator; try { switch (keyAlgorithm) { case Oids.Rsa: if (rsaSignaturePadding is null) { throw new ArgumentException(SR.Cryptography_CertReq_RSAPaddingRequired); } RSA?rsa = issuerCertificate.GetRSAPrivateKey(); key = rsa; generator = X509SignatureGenerator.CreateForRSA(rsa !, rsaSignaturePadding); 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)); } X509AuthorityKeyIdentifierExtension akid; if (subjectKeyIdentifier is not null) { akid = X509AuthorityKeyIdentifierExtension.CreateFromSubjectKeyIdentifier(subjectKeyIdentifier); } else { akid = X509AuthorityKeyIdentifierExtension.CreateFromIssuerNameAndSerialNumber( issuerCertificate.IssuerName, issuerCertificate.SerialNumberBytes.Span); } return(Build( issuerCertificate.SubjectName, generator, crlNumber, nextUpdate, thisUpdate, hashAlgorithm, akid)); } finally { key?.Dispose(); } }