package org.apereo.cas.oidc.token; import com.google.common.base.Throwables; import com.google.common.cache.LoadingCache; import org.apache.commons.lang3.StringUtils; import org.apereo.cas.services.OidcRegisteredService; import org.jose4j.jwa.AlgorithmConstraints; import org.jose4j.jwe.JsonWebEncryption; import org.jose4j.jwk.RsaJsonWebKey; import org.jose4j.jws.AlgorithmIdentifiers; import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; import org.jose4j.lang.JoseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Optional; import java.util.UUID; /** * This is {@link OidcIdTokenSigningAndEncryptionService}. * * @author Misagh Moayyed * @since 5.1.0 */ public class OidcIdTokenSigningAndEncryptionService { private static final Logger LOGGER = LoggerFactory.getLogger(OidcIdTokenSigningAndEncryptionService.class); private final LoadingCache<String, Optional<RsaJsonWebKey>> defaultJsonWebKeystoreCache; private final LoadingCache<OidcRegisteredService, Optional<RsaJsonWebKey>> serviceJsonWebKeystoreCache; private final String issuer; public OidcIdTokenSigningAndEncryptionService(final LoadingCache<String, Optional<RsaJsonWebKey>> defaultJsonWebKeystoreCache, final LoadingCache<OidcRegisteredService, Optional<RsaJsonWebKey>> serviceJsonWebKeystoreCache, final String issuer) { this.defaultJsonWebKeystoreCache = defaultJsonWebKeystoreCache; this.serviceJsonWebKeystoreCache = serviceJsonWebKeystoreCache; this.issuer = issuer; } /** * Sign id token claim string. * * @param svc the service * @param claims the claims * @return the string * @throws JoseException the jose exception */ public String encode(final OidcRegisteredService svc, final JwtClaims claims) throws JoseException { try { LOGGER.debug("Attempting to produce id token generated for service [{}]", svc); final JsonWebSignature jws = new JsonWebSignature(); final String jsonClaims = claims.toJson(); jws.setPayload(jsonClaims); LOGGER.debug("Generated claims to put into id token are [{}]", jsonClaims); jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.NONE); jws.setAlgorithmConstraints(AlgorithmConstraints.NO_CONSTRAINTS); String innerJwt = svc.isSignIdToken() ? signIdToken(svc, jws) : jws.getCompactSerialization(); if (svc.isEncryptIdToken() && StringUtils.isNotBlank(svc.getIdTokenEncryptionAlg()) && StringUtils.isNotBlank(svc.getIdTokenEncryptionEncoding())) { innerJwt = encryptIdToken(svc, jws, innerJwt); } return innerJwt; } catch (final Exception e) { LOGGER.error(e.getMessage(), e); throw Throwables.propagate(e); } } private String encryptIdToken(final OidcRegisteredService svc, final JsonWebSignature jws, final String innerJwt) throws Exception { LOGGER.debug("Service [{}] is set to encrypt id tokens", svc); final JsonWebEncryption jwe = new JsonWebEncryption(); jwe.setAlgorithmHeaderValue(svc.getIdTokenEncryptionAlg()); jwe.setEncryptionMethodHeaderParameter(svc.getIdTokenEncryptionEncoding()); final Optional<RsaJsonWebKey> jwks = this.serviceJsonWebKeystoreCache.get(svc); if (!jwks.isPresent()) { throw new IllegalArgumentException("Service " + svc.getServiceId() + " with client id " + svc.getClientId() + " is configured to encrypt id tokens, yet no JSON web key is available"); } final RsaJsonWebKey jsonWebKey = jwks.get(); LOGGER.debug("Found JSON web key to encrypt the id token: [{}]", jsonWebKey); if (jsonWebKey.getPublicKey() == null) { throw new IllegalArgumentException("JSON web key used to sign the id token has no associated public key"); } jwe.setKey(jsonWebKey.getPublicKey()); jwe.setKeyIdHeaderValue(jws.getKeyIdHeaderValue()); jwe.setContentTypeHeaderValue("JWT"); jwe.setPayload(innerJwt); return jwe.getCompactSerialization(); } private String signIdToken(final OidcRegisteredService svc, final JsonWebSignature jws) throws Exception { final Optional<RsaJsonWebKey> jwks = defaultJsonWebKeystoreCache.get(this.issuer); if (!jwks.isPresent()) { throw new IllegalArgumentException("Service " + svc.getServiceId() + " with client id " + svc.getClientId() + " is configured to sign id tokens, yet no JSON web key is available"); } final RsaJsonWebKey jsonWebKey = jwks.get(); LOGGER.debug("Found JSON web key to sign the id token: [{}]", jsonWebKey); if (jsonWebKey.getPrivateKey() == null) { throw new IllegalArgumentException("JSON web key used to sign the id token has no associated private key"); } prepareJsonWebSignatureForIdTokenSigning(svc, jws, jsonWebKey); return jws.getCompactSerialization(); } private void prepareJsonWebSignatureForIdTokenSigning(final OidcRegisteredService svc, final JsonWebSignature jws, final RsaJsonWebKey jsonWebKey) { LOGGER.debug("Service [{}] is set to sign id tokens", svc); jws.setKey(jsonWebKey.getPrivateKey()); jws.setAlgorithmConstraints(AlgorithmConstraints.DISALLOW_NONE); if (StringUtils.isBlank(jsonWebKey.getKeyId())) { jws.setKeyIdHeaderValue(UUID.randomUUID().toString()); } else { jws.setKeyIdHeaderValue(jsonWebKey.getKeyId()); } LOGGER.debug("Signing id token with key id header value [{}]", jws.getKeyIdHeaderValue()); jws.setAlgorithmHeaderValue(getJsonWebKeySigningAlgorithm()); LOGGER.debug("Signing id token with algorithm [{}]", jws.getAlgorithmHeaderValue()); } public String getJsonWebKeySigningAlgorithm() { return AlgorithmIdentifiers.RSA_USING_SHA256; } }