private static X509Certificate generateTestCertificate(@Nullable final String subjectCommonName, final boolean isValid, final KeyPair issuerKeyPair, final KeyPair subjectKeyPair, int indexAwayFromEndEntity) throws Exception
    {
        // Issuer and Subject.
        final String subjectName = (subjectCommonName != null ? subjectCommonName : asSemiUniqueName(subjectKeyPair.getPublic()));
        final String issuerName = issuerKeyPair == subjectKeyPair ? subjectName : asSemiUniqueName(issuerKeyPair.getPublic());
        final X500NameBuilder subjectBuilder = new X500NameBuilder();
        subjectBuilder.addRDN(BCStyle.CN, subjectName);
        final X500Name subject = subjectBuilder.build();
        final X500NameBuilder issuerBuilder = new X500NameBuilder();
        issuerBuilder.addRDN(BCStyle.CN, issuerName);
        final X500Name issuer = issuerBuilder.build();

        // Validity
        final Instant notBefore;
        final Instant notAfter;
        if ( isValid )
        {
            notBefore = Instant.now().minus(30, ChronoUnit.DAYS); // 30 days ago
            notAfter  = Instant.now().plus(99, ChronoUnit.DAYS); // 99 days from now.
        }
        else
        {
            // Generate a certificate for which the validate period has expired.
            notBefore = Instant.now().minus(40, ChronoUnit.DAYS); // 40 days ago
            notAfter  = Instant.now().minus(10, ChronoUnit.DAYS); // 10 days ago
        }

        // The new certificate should get a unique serial number.
        final BigInteger serial = BigInteger.valueOf( Math.abs( new SecureRandom().nextInt() ) );

        final X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(
                issuer,
                serial,
                Date.from(notBefore),
                Date.from(notAfter),
                subject,
                subjectKeyPair.getPublic()
        );

        // When this certificate is used to sign another certificate, basic constraints need to be set.
        if ( indexAwayFromEndEntity > 0 )
        {
            builder.addExtension( Extension.basicConstraints, true, new BasicConstraints( indexAwayFromEndEntity - 1 ) );
        }

        // add subjectAlternativeName extension that includes all relevant names.
        builder.addExtension(Extension.subjectAlternativeName, false,
            new GeneralNames(new GeneralName(GeneralName.dNSName, subjectName)));

        // Add keyIdentifiers extensions
        final JcaX509ExtensionUtils utils = new JcaX509ExtensionUtils();
        builder.addExtension(Extension.subjectKeyIdentifier, false, utils.createSubjectKeyIdentifier(subjectKeyPair.getPublic()));
        builder.addExtension(Extension.authorityKeyIdentifier, false, utils.createAuthorityKeyIdentifier(issuerKeyPair.getPublic()));

        // Build the certificate.
        final ContentSigner contentSigner = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).build( issuerKeyPair.getPrivate() );
        final X509CertificateHolder certificateHolder = builder.build( contentSigner );

        // Verify the signature.
        final ContentVerifierProvider verifierProvider = new JcaContentVerifierProviderBuilder().build(issuerKeyPair.getPublic());
        if (!certificateHolder.isSignatureValid(verifierProvider)) {
            throw new GeneralSecurityException("Certificate signature not valid");
        }

        return new JcaX509CertificateConverter().setProvider( "BC" ).getCertificate( certificateHolder );
    }
Generates a chain of certificates, identical to {@link #generateValidCertificateChain()}, with one exception:
one certificate in the chain (identified by its number in the chain, provided by the second argument) is expired.

@param subjectCommonName The CN value to use in the end-entity certificate.
@param certificateInChainThatIsExpired certificate position in chain that is expired (zero-based).
@return an array of certificates. Never null, never an empty array.
xmppserver/src/test/java/org/jivesoftware/openfire/keystore/KeystoreTestUtils.java