Self-signed client X.509 certificate for mTLS

How to generate a self-client client X.509 certificate for simple mutual TLS (mTLS) authentication at the token endpoint of an OAuth 2.0 server, or other purposes where a self-signed certificate is needed.

import java.security.cert.*;
import java.util.*;
import com.nimbusds.jose.jwk.*;
import com.nimbusds.jose.jwk.gen.*;
import com.nimbusds.jose.util.*;
import com.nimbusds.oauth2.sdk.util.*;

// Generate an RSA key pair in JWK format
RSAKey rsaJWK = new RSAKeyGenerator(2048)
    .keyID("1")
    .generate();

// Set certificate valid not-before and not-after times
Date now = new Date();
Date nbf = new Date(now.getTime() - 1000L);
Date exp = new Date(now.getTime() + 3600_000L * 24 * 30); // expires in 30 days

// Create the certificate
X509Certificate cert = X509CertificateUtils.generateSelfSigned(
    new Issuer("example.com"), // issuer and subject same
    nbf,                       // valid not before date
    exp,                       // valid not after date
    rsaJWK.toRSAPublicKey(),   // to be included in the certificate
    rsaJWK.toRSAPrivateKey()); // to sign the certificate

// To embed the certificate into the JWK as "x5c" (X.509 certificate chain
// parameter). This JWK can now be used in the OAuth 2.0 client registration
// request
rsaJWK = new RSAKey.Builder(rsaJWK)
    .x509CertChain(Collections.singletonList(Base64.encode(cert.getEncoded())))
    .build();

// Put the JWK into a JWK set for registration with an OAuth 2.0 server
JWKSet clientJWKSet = new JWKSet(rsaJWK);
System.out.println(clientJWKSet);

Note that the X.509 certificate created by the X509CertificateUtils is minimal and doesn't include any extensions, etc. For OAuth 2.0 mTLS client authentication and certificate-bound tokens that is sufficient.

{
  "version"            : "v3",
  "serial number"      : "1C 44 2C 20 B8 60 6E C9",
  "signature algorithm": "SHA256withRSA",
  "issuer"             : "CN=example.com",
  "not before"         : "2020-02-24 09:27:37.000 EET",
  "not after"          : "2021-02-24 10:27:38.000 EET",
  "subject"            : "CN=example.com",
  "subject public key" : "RSA"
}

An RSA JWK with embedded certificate chain for the public key:

{
  "kty": "RSA",
  "kid": "1",
  "x5c": [
    "MIICrTCCAZWgAwIBAgIJAIikWUuaclJzMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNVBAMTC2V4YW1wbGUuY29tMB4XDTIwMDIyNTA3NDkyNVoXDTIwMDIyNTA4NDkyNlowFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCg528pfSG4HiDPtDAaPk5j5pwFcmmh0GU/10H6ckhgxLHBCs4zICM18ByF6NYeyp09JVydsOOIJhSy/7201izIuqZ29XR8Act0e+1gj0xXjAw0ab+tTD24yFp5OPGPUT3IZO+fiM40B+7yfluin4IDNKuUgNucxIPr2bN7cxoKhikHQ80sIP6aPxpflMBX/ASV4lqMiLimarPw4CT3v7YBXBS5DJA/7vudN9GRjC99h1HVcqOmPLrleMjAmlSPCK6sB3sjd50GP4dHhyHkjGr2M/ptlo2HSY7fwCTyfY9mChICD84uVG+jYrLIcbl4RcE8qf5TIPSiifiB2l046H3TAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABNeuVIWt9sEGlYs/syIA7csmHwJ7yO0W99KGQS6NakRyy6QflCUiyRpDVsL8Pwte58Afjl7NBHgbT4nmqrDLsdELzHs3VjrFOfIpLQ7yWjNQdEXOlM3R+htI9psTMMF8TpgfILM3Nr/FofkgtXkRtwl51jUwibkT1wJnCE+9Gzpa6kZB1uQ3Kf8gCOI27BsU+6NtGVR0058oI9tCtcUK/KDEtodIN7wWLTb77noU+SV5MbTdkfhdHNo15Ba9H0TF+E5n007TJorvQQMHDmavp8ERacvwLeoSVFtE52WTYOe+gIowf1FaSNj2mZ7h3noOGen0FmRjIwlD5dwx6ZEeSQ="
  ],
  "n": "oOdvKX0huB4gz7QwGj5OY-acBXJpodBlP9dB-nJIYMSxwQrOMyAjNfAchejWHsqdPSVcnbDjiCYUsv-9tNYsyLqmdvV0fAHLdHvtYI9MV4wMNGm_rUw9uMhaeTjxj1E9yGTvn4jONAfu8n5bop-CAzSrlIDbnMSD69mze3MaCoYpB0PNLCD-mj8aX5TAV_wEleJajIi4pmqz8OAk97-2AVwUuQyQP-77nTfRkYwvfYdR1XKjpjy65XjIwJpUjwiurAd7I3edBj-HR4ch5Ixq9jP6bZaNh0mO38Ak8n2PZgoSAg_OLlRvo2KyyHG5eEXBPKn-UyD0oon4gdpdOOh90w",
  "e": "AQAB",
  "d": "Udwso9DznLZHPySO7GsSeqTSb7r7nsVlHYuoF4CY7hJj1LBoo34QK9rSyBXjCZtPT9J4QgnCWHv3P_H4U846zoyXMCJEdup61fJOnDeLQWcQtoGucL4_EIlt7gpYau-MHS2s818oeTQoDtrWR_AatwLO4CiGkkUwUIdbVs0CTKORuMsOKcGKa-KgFOtRgTsb0xiAnLleaDtv40iWMy8-15WfkZ5MTsgdaASajQVUAMvsQ-QxTWsPA76OjdJPH5yZqSUaxoMibRySfpInN51yCurnrTV7pmrdcfq1KTF1_WjrJPikHpFgypmZZUsPYL8EdklSTP1bINkX26mzHf0d4Q",
  "p": "2N30aM_x4vpNqykgAphA9kjhhTZiFvi9KCslFEz6_eQS_I9uAJuK3sWilIV0oeKoMsPZTmxE1A1s7nhCP3q3fy4BEx5pGcV4RaGd_MBNq9yXpzJ5vXMCkIlmOxk4FVOT3Sm4m8pZI04pXyxQFjWoNJulO90viNrpHpgknWLMqak",
  "q": "vfBNewTy5UjGPPyZZIqrxL5zb_4pP63wrnivnQ2uRL0D4EHWOimOHCJ9TzddvNLgIG_V05A0dmatGpNGVsHzGtclOfhTxsWCDMiv5kafQvmCYl79mlGPJwY1ARVHJD2e2XpJZhW_I2TadQxlSojhqMgpp9S9ApMAZ_8K-B5XcRs",
  "dp": "riMLbl4LTMbQNu_-1BaNkzVSYTUZ7ngs62Q5keN-ZwAMfuBs4_ABwn_P3JKM5LCrfpfkliQ54EwnfBT0nSRc07KNCl0Q57C4srDju2Bu_eFTpN3TA1ymYojxneLSNc22nZAyvGXuzXqlndZnOG49coDIXRluYeXl6rsgK4B59oE",
  "dq": "RwXmUwvkr6voxMFHsnrQA_-bNtN5JSCrkPH76ORGt9ld66tyqckEJK0Y4lg3qvXHbRmkgTm5BFUcgYV0ldhsSSsN9oFPAlK0cIdSju71epvGD3apqdy5hMQjacZFFd4c2gGKFKwpDtkVfxMlhRtuFijSurkn_CBv1HNf7Kub0uU",
  "qi": "LrtCDf18lMaB35KxrvTrpLQIVIjEGnjt3KF-VZGTUMHSBeHMIecCoWuLq_E30qECK2zn_onlwbSBMo7J18poasv0BPAgeRm5IaclAn0I6rKOnk5DY26MB0Wpd1YTnHT11MtTuq59IpKhWvfiLZOm_fj7HyhKcEtpyoNy_TVLiok"
}

The certificate can carry and be signed with an EC key (instead of RSA):

import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;

// Generate an EC key pair in JWK format
ECKey ecJWK = new ECKeyGenerator(Curve.P_256)
    .keyID("2")
    .generate();

// To get the public key as std Java class
ecJWK.toECPublicKey();

// To get the private key as std Java class
ecJWK.toECPrivateKey();