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

com.erigir.wrench.ape.service.TokenService Maven / Gradle / Ivy

There is a newer version: 2.2.16+16
Show newest version
package com.erigir.wrench.ape.service;

import com.erigir.wrench.CrockfordBase32;
import com.erigir.wrench.QuietObjectMapper;
import com.erigir.wrench.ZipUtils;
import com.erigir.wrench.ape.exception.InvalidTokenException;
import com.erigir.wrench.ape.model.CustomerToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * As of version 1.2.0, you may specify more than one key for the token service - it accepts a list
 * If you do so, any new tokens are encrypted with the first key, but the other keys are checked for
 * decrypting tokens.  This is to simplify the process of key rotation - just add a new key to the start
 * of the list and restart the server.  After the expiration time has passed any old keys can
 * be removed from the list as they will be invalid anyway.
 * Created by cweiss on 7/18/15.
 */
public class TokenService {
    private static final Logger LOG = LoggerFactory.getLogger(TokenService.class);
    private static final String CIPHER = "AES/ECB/PKCS5Padding";
    private List encryptionKeys;
    private QuietObjectMapper objectMapper;
    private CrockfordBase32 base32 = new CrockfordBase32();

    public String createToken(String keyValue, Long expires, Map otherData) {
        LOG.debug("Creating token {} / {} / {}", keyValue, expires, otherData);
        Objects.requireNonNull(keyValue);
        Objects.requireNonNull(expires);

        // We always encrypt with the first key
        String encryptionKey = encryptionKeys.get(0);
        CustomerToken token = new CustomerToken(keyValue, expires, otherData);
        String raw = objectMapper.writeValueAsString(token);
        byte[] compressed = ZipUtils.zipData(raw.getBytes());

        try {
            byte[] encrypted = cipher(Cipher.ENCRYPT_MODE, encryptionKey).doFinal(compressed);
            String rval = base32.encodeToString(encrypted);
            return rval;
        } catch (Exception e) {
            throw new IllegalStateException("Shouldnt happen, error on encode: (key size was " + encryptionKey.length() + " and msg size was " + raw.length() + ") " + e, e);
        }
    }

    public void validateToken(CustomerToken token)
            throws InvalidTokenException {
        if (token == null) {
            throw new InvalidTokenException("Supplied string was not a valid token");
        }
        if (token.getExpires() == null || System.currentTimeMillis() > token.getExpires()) {
            throw new InvalidTokenException("Token expired at " + token.getExpires() + " and it is " + System.currentTimeMillis());
        }
    }

    private Cipher cipher(int mode, String key) {
        try {
            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), CIPHER.substring(0, 3));
            Cipher nCipher = Cipher.getInstance(CIPHER);
            //IvParameterSpec iv = new IvParameterSpec("0000000000000000".getBytes());
            nCipher.init(mode, keySpec);//,iv);
            return nCipher;
        } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException e) {
            // Like these would be handled at runtime... right?
            throw new RuntimeException("Error creating cipher", e);
        }
    }

    public CustomerToken extractAndValidateToken(String tokenString)
            throws InvalidTokenException {
        CustomerToken token = extractToken(tokenString);
        validateToken(token);
        return token;
    }

    public CustomerToken extractToken(String tokenString) {
        CustomerToken rval = null;
        for (int i = 0; i < encryptionKeys.size() && rval == null; i++) {
            rval = innerExtractToken(tokenString, encryptionKeys.get(i));
        }
        return rval;
    }

    private CustomerToken innerExtractToken(String tokenString, String key) {
        try {
            byte[] zippedData;
            byte[] encryptedText = base32.decode(tokenString.getBytes());
            zippedData = cipher(Cipher.DECRYPT_MODE, key).doFinal(encryptedText);
            byte[] unzippedData = ZipUtils.unzipData(zippedData);
            CustomerToken output = objectMapper.readValue(unzippedData, CustomerToken.class);
            return output;
        } catch (Exception e) {
            LOG.trace("Unable to decrypt token with this key");
            return null;
        }
    }

    public void setEncryptionKeys(List encryptionKeys) {
        this.encryptionKeys = encryptionKeys;
    }

    /**
     * Here for backwards compatibility with previous versions
     *
     * @param encryptionKey A single key that tokenservice will decrypt with
     */
    public void setEncryptionKey(String encryptionKey) {
        this.encryptionKeys = (encryptionKey == null) ? null : Collections.singletonList(encryptionKey);
    }

    public void setObjectMapper(QuietObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy