private byte[] Encode(X509SignatureGenerator signatureGenerator, HashAlgorithmName hashAlgorithm) { // State validation should be runtime checks if/when this becomes public API Debug.Assert(Subject != null); Debug.Assert(PublicKey != null); // Under a public API model we could allow these to be null for a self-signed case. Debug.Assert(SerialNumber != null); Debug.Assert(Issuer != null); if (SignatureAlgorithm != null) { ValidateSignatureAlgorithm(); } List <byte[][]> encodedFields = new List <byte[][]>(); byte version = Version; if (version != 0) { byte[][] encodedVersion = DerEncoder.ConstructSegmentedSequence( DerEncoder.SegmentedEncodeUnsignedInteger(new[] { version })); encodedVersion[0][0] = DerSequenceReader.ContextSpecificConstructedTag0; encodedFields.Add(encodedVersion); } encodedFields.Add(DerEncoder.SegmentedEncodeUnsignedInteger(SerialNumber)); // SignatureAlgorithm: Use the specified value, or ask the generator (without mutating the class) byte[] signatureAlgorithm = SignatureAlgorithm ?? signatureGenerator.GetSignatureAlgorithmIdentifier(hashAlgorithm); encodedFields.Add(signatureAlgorithm.WrapAsSegmentedForSequence()); // For public API allowing self-sign ease-of-use, this could be (Issuer ?? Subject). encodedFields.Add(Issuer.RawData.WrapAsSegmentedForSequence()); encodedFields.Add( DerEncoder.ConstructSegmentedSequence( EncodeValidityField(NotBefore, nameof(NotBefore)), EncodeValidityField(NotAfter, nameof(NotAfter)))); encodedFields.Add(Subject.RawData.WrapAsSegmentedForSequence()); encodedFields.Add(PublicKey.SegmentedEncodeSubjectPublicKeyInfo()); // Issuer and Subject Unique ID values would go here, if they were supported. if (Extensions.Count > 0) { Debug.Assert(version >= 2); List <byte[][]> encodedExtensions = new List <byte[][]>(Extensions.Count); // extensions[3] Extensions OPTIONAL // // Since this doesn't say IMPLICIT, it will look like // // A3 [length] // 30 [length] // First Extension // Second Extension // ... // An interesting quirk of skipping null values here is that // Extensions.Count == 0 => no extensions // Extensions.ContainsOnly(null) => empty extensions list HashSet <string> usedOids = new HashSet <string>(Extensions.Count); foreach (X509Extension extension in Extensions) { if (extension == null) { continue; } if (!usedOids.Add(extension.Oid.Value)) { throw new InvalidOperationException( SR.Format(SR.Cryptography_CertReq_DuplicateExtension, extension.Oid.Value)); } encodedExtensions.Add(extension.SegmentedEncodedX509Extension()); } byte[][] extensionField = DerEncoder.ConstructSegmentedSequence( DerEncoder.ConstructSegmentedSequence(encodedExtensions)); extensionField[0][0] = DerSequenceReader.ContextSpecificConstructedTag3; encodedFields.Add(extensionField); } return(DerEncoder.ConstructSequence(encodedFields)); }
private byte[] Build( X500DistinguishedName issuerName, X509SignatureGenerator generator, BigInteger crlNumber, DateTimeOffset nextUpdate, DateTimeOffset thisUpdate, HashAlgorithmName hashAlgorithm, X509AuthorityKeyIdentifierExtension authorityKeyIdentifier) { ArgumentNullException.ThrowIfNull(issuerName); ArgumentNullException.ThrowIfNull(generator); 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)); ArgumentNullException.ThrowIfNull(authorityKeyIdentifier); byte[] signatureAlgId = generator.GetSignatureAlgorithmIdentifier(hashAlgorithm); { AlgorithmIdentifierAsn signatureAlgorithmAsn; // Deserialization also does validation of the value (except for Parameters, // which have to be validated separately). signatureAlgorithmAsn = AlgorithmIdentifierAsn.Decode(signatureAlgId, AsnEncodingRules.DER); if (signatureAlgorithmAsn.Parameters.HasValue) { Helpers.ValidateDer(signatureAlgorithmAsn.Parameters.GetValueOrDefault().Span); } } AsnWriter writer = (_writer ??= new AsnWriter(AsnEncodingRules.DER)); writer.Reset(); // TBSCertList using (writer.PushSequence()) { // version v2(1) writer.WriteInteger(1); // signature (AlgorithmIdentifier) writer.WriteEncodedValue(signatureAlgId); // issuer writer.WriteEncodedValue(issuerName.RawData); // thisUpdate WriteX509Time(writer, thisUpdate); // nextUpdate WriteX509Time(writer, nextUpdate); // revokedCertificates (don't write down if empty) if (_revoked.Count > 0) { // SEQUENCE OF using (writer.PushSequence()) { foreach (RevokedCertificate revoked in _revoked) { // Anonymous CRL Entry type using (writer.PushSequence()) { writer.WriteInteger(revoked.Serial); WriteX509Time(writer, revoked.RevocationTime); if (revoked.Extensions is not null) { writer.WriteEncodedValue(revoked.Extensions); } } } } } // extensions [0] EXPLICIT Extensions using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 0))) { // Extensions (SEQUENCE OF) using (writer.PushSequence()) { // Authority Key Identifier Extension using (writer.PushSequence()) { writer.WriteObjectIdentifier(authorityKeyIdentifier.Oid !.Value !); if (authorityKeyIdentifier.Critical) { writer.WriteBoolean(true); } byte[] encodedAkid = authorityKeyIdentifier.RawData; Helpers.ValidateDer(encodedAkid); writer.WriteOctetString(encodedAkid); } // CRL Number Extension using (writer.PushSequence()) { writer.WriteObjectIdentifier(Oids.CrlNumber); using (writer.PushOctetString()) { writer.WriteInteger(crlNumber); } } } } } byte[] tbsCertList = writer.Encode(); writer.Reset(); byte[] signature = generator.SignData(tbsCertList, hashAlgorithm); // CertificateList using (writer.PushSequence()) { writer.WriteEncodedValue(tbsCertList); writer.WriteEncodedValue(signatureAlgId); writer.WriteBitString(signature); } byte[] crl = writer.Encode(); return(crl); }
/// <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.RsaRsa: if (_rsaPadding == null) { throw new InvalidOperationException(SR.Cryptography_CertReq_RSAPaddingRequired); } RSA rsa = issuerCertificate.GetRSAPrivateKey(); key = rsa; generator = X509SignatureGenerator.CreateForRSA(rsa, _rsaPadding); break; case Oids.Ecc: 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(); } }