package org.apereo.cas.util.cipher; import com.google.common.base.Throwables; import org.apache.commons.lang3.StringUtils; import org.apereo.cas.CipherExecutor; import org.apereo.cas.util.EncodingUtils; import org.jose4j.jwe.ContentEncryptionAlgorithmIdentifiers; import org.jose4j.jwe.JsonWebEncryption; import org.jose4j.jwe.KeyManagementAlgorithmIdentifiers; import org.jose4j.jwk.JsonWebKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Serializable; import java.nio.charset.StandardCharsets; import java.security.Key; import java.util.HashMap; import java.util.Map; /** * The {@link BaseStringCipherExecutor} is the default * implementation of {@link CipherExecutor}. It provides * a facade API to encrypt, sign, and verify values. * * @author Misagh Moayyed * @since 4.1 */ public abstract class BaseStringCipherExecutor extends AbstractCipherExecutor<Serializable, String> { private static final Logger LOGGER = LoggerFactory.getLogger(BaseStringCipherExecutor.class); private static final int ENCRYPTION_KEY_SIZE = 256; private static final int SIGNING_KEY_SIZE = 512; private String contentEncryptionAlgorithmIdentifier; private Key secretKeyEncryptionKey; private BaseStringCipherExecutor() { } /** * Instantiates a new cipher. * <p>Note that in order to customize the encryption algorithms, * you will need to download and install the JCE Unlimited Strength Jurisdiction * Policy File into your Java installation.</p> * * @param secretKeyEncryption the secret key encryption; must be represented as a octet sequence JSON Web Key (JWK) * @param secretKeySigning the secret key signing; must be represented as a octet sequence JSON Web Key (JWK) */ public BaseStringCipherExecutor(final String secretKeyEncryption, final String secretKeySigning) { this(secretKeyEncryption, secretKeySigning, ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256); } /** * Instantiates a new cipher. * * @param secretKeyEncryption the key for encryption * @param secretKeySigning the key for signing * @param contentEncryptionAlgorithmIdentifier the content encryption algorithm identifier */ public BaseStringCipherExecutor(final String secretKeyEncryption, final String secretKeySigning, final String contentEncryptionAlgorithmIdentifier) { super(); if (StringUtils.isBlank(contentEncryptionAlgorithmIdentifier)) { LOGGER.debug("contentEncryptionAlgorithmIdentifier is not defined"); return; } String secretKeyToUse = secretKeyEncryption; if (StringUtils.isBlank(secretKeyToUse)) { LOGGER.warn("Secret key for encryption is not defined for [{}]; CAS will attempt to auto-generate the encryption key", getName()); secretKeyToUse = EncodingUtils.generateJsonWebKey(ENCRYPTION_KEY_SIZE); LOGGER.warn("Generated encryption key [{}] of size [{}] for [{}]. The generated key MUST be added to CAS settings.", secretKeyToUse, ENCRYPTION_KEY_SIZE, getName()); } String signingKeyToUse = secretKeySigning; if (StringUtils.isBlank(signingKeyToUse)) { LOGGER.warn("Secret key for signing is not defined for [{}]. CAS will attempt to auto-generate the signing key", getName()); signingKeyToUse = EncodingUtils.generateJsonWebKey(SIGNING_KEY_SIZE); LOGGER.warn("Generated signing key [{}] of size [{}] for [{}]. The generated key MUST be added to CAS settings.", signingKeyToUse, SIGNING_KEY_SIZE, getName()); } setSigningKey(signingKeyToUse); this.secretKeyEncryptionKey = prepareJsonWebTokenKey(secretKeyToUse); this.contentEncryptionAlgorithmIdentifier = contentEncryptionAlgorithmIdentifier; LOGGER.debug("Initialized cipher encryption sequence via [{}]", contentEncryptionAlgorithmIdentifier); } @Override public String encode(final Serializable value) { final String encoded = encryptValue(value); final String signed = new String(sign(encoded.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8); return signed; } @Override public String decode(final Serializable value) { try { final byte[] encoded = verifySignature(value.toString().getBytes(StandardCharsets.UTF_8)); if (encoded != null && encoded.length > 0) { return decryptValue(new String(encoded, StandardCharsets.UTF_8)); } return null; } catch (final Exception e) { throw new IllegalArgumentException(e.getMessage(), e); } } /** * Prepare json web token key. * * @param secret the secret * @return the key */ private static Key prepareJsonWebTokenKey(final String secret) { try { final Map<String, Object> keys = new HashMap<>(2); keys.put("kty", "oct"); keys.put(EncodingUtils.JSON_WEB_KEY, secret); final JsonWebKey jwk = JsonWebKey.Factory.newJwk(keys); return jwk.getKey(); } catch (final Exception e) { throw new IllegalArgumentException(e.getMessage(), e); } } /** * Encrypt the value based on the seed array whose length was given during afterPropertiesSet, * and the key and content encryption ids. * * @param value the value * @return the encoded value */ private String encryptValue(final Serializable value) { try { final JsonWebEncryption jwe = new JsonWebEncryption(); jwe.setPayload(serializeValue(value)); jwe.enableDefaultCompression(); jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.DIRECT); jwe.setEncryptionMethodHeaderParameter(this.contentEncryptionAlgorithmIdentifier); jwe.setKey(this.secretKeyEncryptionKey); LOGGER.debug("Encrypting via [{}]", this.contentEncryptionAlgorithmIdentifier); return jwe.getCompactSerialization(); } catch (final Exception e) { throw new RuntimeException("Ensure that you have installed JCE Unlimited Strength Jurisdiction Policy Files. " + e.getMessage(), e); } } /** * Serialize value as string. * * @param value the value * @return the string */ protected String serializeValue(final Serializable value) { return value.toString(); } /** * Decrypt value based on the key created during afterPropertiesSet. * * @param value the value * @return the decrypted value */ private String decryptValue(final String value) { try { final JsonWebEncryption jwe = new JsonWebEncryption(); jwe.setKey(this.secretKeyEncryptionKey); jwe.setCompactSerialization(value); LOGGER.debug("Decrypting value..."); return jwe.getPayload(); } catch (final Exception e) { throw Throwables.propagate(e); } } }