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

com.github.mtakaki.credentialstorage.resources.CredentialResource Maven / Gradle / Ivy

package com.github.mtakaki.credentialstorage.resources;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import org.apache.commons.lang3.StringUtils;

import com.codahale.metrics.annotation.Timed;
import com.github.mtakaki.credentialstorage.CredentialStorageConfiguration;
import com.github.mtakaki.credentialstorage.database.CredentialDAO;
import com.github.mtakaki.credentialstorage.database.model.Credential;
import com.github.mtakaki.credentialstorage.encryption.EncryptionUtil;
import com.github.mtakaki.dropwizard.circuitbreaker.jersey.CircuitBreaker;
import com.google.common.base.Optional;
import com.google.common.cache.Cache;

import io.dropwizard.hibernate.UnitOfWork;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

import jodd.petite.meta.PetiteBean;
import lombok.AllArgsConstructor;

/**
 * Resource that handles the basic credential CRUD operations.
 *
 * @author mtakaki
 *
 */
@Path("/credential")
@Api("/credential")
@Produces(MediaType.APPLICATION_JSON)
@AllArgsConstructor
@PetiteBean
public class CredentialResource {
    private final CredentialDAO credentialDAO;
    private final Cache encryptionUtil;
    private final CredentialStorageConfiguration configuration;

    @GET
    @ApiOperation(
        value = "Retrieves the credential pair for the given public key",
        notes = "Returns a symetrical key, encrypted using the given assymetrical public key. "
                + "The symetrical key should be used to decrypt the credential pair.")
    @Timed
    @CircuitBreaker
    @UnitOfWork
    public Optional getByKey(@HeaderParam("X-Auth-RSA") final String userPublicKey) {
        if (StringUtils.isBlank(userPublicKey)) {
            return Optional.absent();
        }

        return this.credentialDAO.getCredentialByKey(userPublicKey);
    }

    @POST
    @ApiOperation(
        value = "Stores the given credential pair into the database.",
        notes = "The credential pair is encrypted using a symmetric algorithm. "
                + "The symmetrical key is encrypted using the public assymetrical key and stored in the database.")
    @Consumes(MediaType.APPLICATION_JSON)
    @Timed
    @CircuitBreaker
    @UnitOfWork
    public Response storeCredential(@HeaderParam("X-Auth-RSA") final String userPublicKey,
            @Valid final Credential credential) throws ExecutionException, InvalidKeyException,
                    NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException,
                    BadPaddingException, UnsupportedEncodingException {
        if (StringUtils.isBlank(userPublicKey)) {
            return Response.status(Status.BAD_REQUEST).build();
        }

        final Optional savedCredentialOptional = this.credentialDAO
                .getCredentialByKey(userPublicKey);
        this.fillUpEncryptAndSaveCredential(userPublicKey,
                savedCredentialOptional.isPresent() ? savedCredentialOptional.get() : credential,
                credential);

        return Response.created(URI.create("/credential/" + userPublicKey)).build();
    }

    @PUT
    @ApiOperation(
        value = "Updates the credential pair stored under the given public asymmetrical key.",
        notes = "The credential pair is re-encrypted with a symetric algorithm and its key is stored and encrypted using the given assymetrical public key.")
    @Consumes(MediaType.APPLICATION_JSON)
    @Timed
    @CircuitBreaker
    @UnitOfWork
    public Response updateCredential(@HeaderParam("X-Auth-RSA") final String userPublicKey,
            @Valid final Credential credential) throws ExecutionException, InvalidKeyException,
                    NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException,
                    BadPaddingException, UnsupportedEncodingException {
        if (StringUtils.isBlank(userPublicKey)) {
            return Response.status(Status.BAD_REQUEST).build();
        }

        // As this is an update, we need to query and verify the credentials
        // exist in the database.
        final Optional savedCredentialOptional = this.credentialDAO
                .getCredentialByKey(userPublicKey);
        if (!savedCredentialOptional.isPresent()) {
            return Response.status(Status.NOT_FOUND).build();
        }

        final Credential savedCredential = savedCredentialOptional.get();

        this.fillUpEncryptAndSaveCredential(userPublicKey, savedCredential, credential);

        return Response.ok().build();
    }

    /**
     * Will encrypt the given credential using the incoming credential data and
     * will save it to the database. It will generate new symmetric keys every
     * time this method is called and it will be used to encrypt the
     * credentials.
     *
     * @param userPublicKey
     *            The incoming public key used to encrypt the symmetric key.
     * @param credential
     *            The credential object that will be updated and saved to the
     *            database.
     * @param incomingCredential
     *            The incoming credential payload. It will be used as the source
     *            of data.
     * @throws NoSuchAlgorithmException
     *             Thrown if either AES or RSA algorithms are not available.
     * @throws NoSuchPaddingException
     *             Thrown if the padding algorithm is not available.
     * @throws InvalidKeyException
     *             Thrown if the incoming symmetric key is invalid. We don't
     *             have any way of validating it beforehand.
     * @throws IllegalBlockSizeException
     *             Thrown if the data is too long to be encrypted.
     * @throws BadPaddingException
     *             Thrown if the padding data is incorrect.
     * @throws UnsupportedEncodingException
     *             Thrown if we can't convert the plain text strings to UTF-8
     *             bytes.
     * @throws ExecutionException
     *             Thrown if we fail to create the {@link EncryptionUtil} from
     *             within the cache.
     */
    private void fillUpEncryptAndSaveCredential(final String userPublicKey,
            final Credential credential, final Credential incomingCredential)
                    throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
                    IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException,
                    ExecutionException {
        final EncryptionUtil encryptionUtil = this.getEncryptionUtilFromCache(userPublicKey);
        final SecretKey symetricKey = encryptionUtil.generateSymmetricKey();

        // The asymmetric key is stored as it is. At this point there is not
        // security threat to store it like this.
        credential.setKey(userPublicKey);
        // The symmetric key is stored encrypted using the asymmetric public
        // key. This can only be decrypted using the private keys, so not even
        // us can decrypt it later.
        credential.setSymmetricKey(encryptionUtil.encrypt(symetricKey));

        credential.setPrimary(encryptionUtil.encrypt(symetricKey, incomingCredential.getPrimary()));
        if (StringUtils.isNotBlank(incomingCredential.getSecondary())) {
            credential.setSecondary(
                    encryptionUtil.encrypt(symetricKey, incomingCredential.getSecondary()));
        }

        this.credentialDAO.save(credential);
    }

    /**
     * Retrieves an {@link EncryptionUtil} from the cache or creates a new one
     * if it cannot be found.
     *
     * @param userPublicKey
     *            The incoming user's public key.
     * @return An {@link EncryptionUtil} ready to be used for encryption.
     * @throws ExecutionException
     *             Thrown if we can't create a new {@link EncryptionUtil}.
     */
    private EncryptionUtil getEncryptionUtilFromCache(final String userPublicKey)
            throws ExecutionException {
        return this.encryptionUtil.get(userPublicKey,
                new Callable() {
                    @Override
                    public EncryptionUtil call() throws Exception {
                        return new EncryptionUtil(userPublicKey,
                                CredentialResource.this.configuration.getSymmetricKeySize());
                    }
                });
    }

    @DELETE
    @ApiOperation("Deletes a credential pair from the database.")
    @Consumes(MediaType.APPLICATION_JSON)
    @Timed
    @CircuitBreaker
    @UnitOfWork
    public Response deleteCredential(@HeaderParam("X-Auth-RSA") final String userPublicKey) {
        if (StringUtils.isBlank(userPublicKey)) {
            return Response.status(Status.BAD_REQUEST).build();
        }

        if (this.credentialDAO.deleteByKey(userPublicKey)) {
            return Response.ok().build();
        } else {
            return Response.status(Status.NOT_FOUND).build();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy