All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fi.evolver.azure.entraid.EntraIdCertificateSignedJwtAssertionFactory Maven / Gradle / Ivy

The newest version!
package fi.evolver.azure.entraid;

import java.io.ByteArrayInputStream;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.crypto.factories.DefaultJWSSignerFactory;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.util.Base64URL;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;

/**
 * A factory used to create Microsoft Entra JWT assertion signed with a
 * certificate.
 *
 * @author Rujun Chen
 * @see 
 *      Certificate credentials
 * @since 2022-02-18
 */
public class EntraIdCertificateSignedJwtAssertionFactory {

	private final JWSSigner signer;
	private final JWSHeader header;
	private final JWTClaimsSet templateClaims;

	/**
	 * @param file     Path of certificate file. The file should contain encrypted
	 *                 private key and certificate. And the file name should have
	 *                 ".pfx" as suffix.
	 * @param password The password of the encrypted private key in certificate
	 *                 file.
	 * @throws EntraIdAssertionException if failed to create factory.
	 */
	public EntraIdCertificateSignedJwtAssertionFactory(
			String privateKeyPEM,
			String certificatePEM,
			String tenantId,
			String clientId) throws EntraIdAssertionException {

		try {
			byte[] decodedKey = Base64.getDecoder().decode(privateKeyPEM);
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
			PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
			byte[] decodedCert = Base64.getDecoder().decode(certificatePEM);
			CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
			X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(decodedCert));
			PublicKey publicKey = x509Certificate.getPublicKey();
			signer = createJWSSigner(publicKey, privateKey);
			header = createJWSHeader(x509Certificate);
			templateClaims = createTemplateJWTClaims(tenantId, clientId);
		} catch (InvalidKeySpecException | CertificateException | NoSuchAlgorithmException | JOSEException exception) {
			throw new EntraIdAssertionException("Failed to create factory.", exception);
		}
	}

	/**
	 * Create JWT assertion
	 *
	 * @throws EntraIdAssertionException If failed to create assertion.
	 */
	public String createJwtAssertion() throws EntraIdAssertionException {
		JWTClaimsSet claims = createJWTClaimsSet();
		SignedJWT signedJwt = new SignedJWT(header, claims);
		try {
			signedJwt.sign(signer);
		} catch (JOSEException exception) {
			throw new EntraIdAssertionException("Failed to sign JWT.", exception);
		}
		return signedJwt.serialize();
	}

	private static JWSSigner createJWSSigner(PublicKey publicKey, PrivateKey privateKey) throws JOSEException {
		// Microsoft Entra ID currently supports only RSA.
		// Refs:
		// https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-self-signed-certificate
		JWK jwk = new RSAKey.Builder((RSAPublicKey) publicKey)
				.privateKey(privateKey)
				.keyID(UUID.randomUUID().toString()).build();
		return new DefaultJWSSignerFactory().createJWSSigner(jwk);
	}

	@SuppressWarnings("deprecation")
	private static JWSHeader createJWSHeader(X509Certificate x509Certificate) throws CertificateEncodingException, NoSuchAlgorithmException {
		return new JWSHeader.Builder(JWSAlgorithm.RS256)
				.type(JOSEObjectType.JWT)
				.x509CertThumbprint(Base64URL.encode(getX5t(x509Certificate))).build();
	}

	private static byte[] getX5t(X509Certificate cert) throws NoSuchAlgorithmException, CertificateEncodingException {
		MessageDigest digest = MessageDigest.getInstance("SHA-1");
		byte[] der = cert.getEncoded();
		digest.update(der);
		return digest.digest();
	}

	private static JWTClaimsSet createTemplateJWTClaims(String tenantId, String clientId) {
		return new JWTClaimsSet.Builder()
				.audience("https://login.microsoftonline.com/%s/v2.0".formatted(tenantId))
				.issuer(clientId).subject(clientId).build();
	}

	private JWTClaimsSet createJWTClaimsSet() {
		Date currentTime = new Date();
		return new JWTClaimsSet.Builder(templateClaims)
				.expirationTime(Date.from(currentTime.toInstant().plusSeconds(300))) // 5 minutes after currentTime.
				.jwtID(UUID.randomUUID().toString()).notBeforeTime(currentTime).issueTime(currentTime).build();
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy