alsutton/enterprisepasswordsafe

View on GitHub
src/main/java/com/enterprisepasswordsafe/database/PasswordBase.java

Summary

Maintainability
B
5 hrs
Test Coverage
F
54%
/*
 * Copyright (c) 2017 Carbon Security Ltd. <opensource@carbonsecurity.co.uk>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package com.enterprisepasswordsafe.database;

import com.enterprisepasswordsafe.engine.accesscontrol.AccessControl;
import com.enterprisepasswordsafe.engine.utils.DateFormatter;
import com.enterprisepasswordsafe.engine.utils.IDGenerator;
import com.enterprisepasswordsafe.engine.utils.PasswordUtils;

import java.io.IOException;
import java.security.*;
import java.util.*;

/**
 * Base class for objects relating to passwords.
 */
public abstract class PasswordBase
    implements Comparable<PasswordBase>, AccessControledObject {

    /**
     * Number of millisecons in a day.
     */
    private static final long MILLIS_IN_A_DAY = 24 * 60 * 60 * 1000;

    /**
     * The fields needed to construct a PasswordBase object from a ResultSet.
     */

    public static final String PASSWORD_BASE_FIELDS =
        " pass.password_id, pass.password_data ";

    /**
     * The number of ResultSet fields used by this object.
     */

    public static final int PASSWORD_BASE_FIELDS_COUNT = 2;

    /**
     * The state used when the expiry state of a password is unkown.
     */

    public static final int EXPIRY_UNKNOWN = -1;

    /**
     * The state used when the password has not expired.
     */

    public static final int EXPIRY_PASSED = 0;

    /**
     * The state used when the password is about to expire.
     */

    public static final int EXPIRY_WARN = 1;

    /**
     * The state used when the password has expired.
     */

    public static final int EXPIRY_OK = 2;

    /**
     * The encyrption algorythm used to encrypt passwords in the database.
     */

    public static final int PASSWORD_KEY_SIZE = 1024;

    /**
     * The algorythm used for encryption in version 1.
     */

    public static final String V1_PASSWORD_ALGORITHM = "RSA";

    /**
     * The ID for this password.
     */

    private String passwordId;

    /**
     * The username associated with this password.
     */

    private String username;

    /**
     * The password itself.
     */

    private String password;

    /**
     * The system on which the password resides.
     */

    private String location;

    /**
     * Notes associated with the password.
     */
    private String notes;

    /**
     * Whether or not the password is enabled.
     */

    private boolean enabled;

    /**
     * The expiry date for the password.
     */

    private long expiry = Long.MAX_VALUE;

    /**
     * The key used to modify the password.
     */
    private PrivateKey modifyKey;

    /**
     * The key used to read the password.
     */
    private PublicKey readKey;

    /**
     * Flag to say if the password is modifyable or not.
     */
    private boolean isModifiable;

    /**
     * The custom fields
     */

    private Map<String,String> customFields;

    /**
     * Constructor. Creates empty object ready for population.
     *
     * @throws NoSuchAlgorithmException
     */

    public PasswordBase() throws NoSuchAlgorithmException {
        passwordId = IDGenerator.getID();
        generateKeys();
    }

    /**
     * Constructor. Creates empty object ready for population.
     */

    public PasswordBase(final String id) {
        passwordId = id;
    }

    /**
     * Creates a new instance of Password.
     *
     * @param newUsername
     *            The username to create the password with.
     * @param newPassword
     *            The password itself.
     * @param newLocation
     *            The location to associate with the password.
     * @param newNotes
     *            The notes to associate with the password.
     * @param newExpiry
     *            The expiry date for the password.
     *
     * @throws NoSuchAlgorithmException Thrown if the encryption algorithm is unavailable.
     */

    public PasswordBase(final String newUsername, final String newPassword,
            final String newLocation, final String newNotes,
            final long newExpiry)
        throws NoSuchAlgorithmException {
        this(null, newUsername, newPassword, newLocation, newNotes, newExpiry);
    }

    /**
     * Creates a new instance of Password.
     *
     * @param newPasswordId
     *            The id of the password.
     * @param newUsername
     *            The username to create the password with.
     * @param newPassword
     *            The password itself.
     * @param newLocation
     *            The location to associate with the password.
     * @param newNotes
     *            The notes to associate with the password.
     * @param newExpiry
     *            The expiry date for the password.
     *
     * @throws NoSuchAlgorithmException Thrown if the encryption algorithm is unavailable.
     */

    public PasswordBase(final String newPasswordId, final String newUsername,
            final String newPassword, final String newLocation,
            final String newNotes, final long newExpiry)
            throws NoSuchAlgorithmException {
        if (newPasswordId != null) {
            passwordId = newPasswordId;
        } else {
            passwordId = IDGenerator.getID();
            generateKeys();
        }

        setUsername(newUsername);
        setPassword(newPassword);
        setNotes(newNotes);
        setExpiry(newExpiry);

        location = Objects.requireNonNullElse(newLocation, "");

        isModifiable = false;
    }

    /**
     * Creates a new instance of Password.
     *
     * @param passwordId The ID for the password.
     * @param data The password data.
     * @param ac The access control for the password.
     * @param props The properties relating to the password.
     *
     */

    public PasswordBase(final String passwordId, final byte[] data, final AccessControl ac, final Properties props)
            throws IOException, GeneralSecurityException {
        this.passwordId = passwordId;
        PasswordUtils.decrypt(this, ac, data, props);
    }

    /**
     * Creates a new instance of Password.
     *
     * @param passwordId The ID for the password.
     * @param data The password data.
     * @param ac The access control for the password.
     */

    public PasswordBase(final String passwordId, final byte[] data, final AccessControl ac)
            throws IOException, GeneralSecurityException {
        this(passwordId, data, ac, null);
    }

    /**
     * Decrypts this password using a given uac if it is encrypted.
     *
     * @param ac The access control to use to decrypt the password.
     */

    public final void decrypt(final AccessControl ac) {
        modifyKey = ac.getModifyKey();
        readKey = ac.getReadKey();

        isModifiable = (modifyKey != null);
    }

    /**
     * Generates the read and modify keys.
     *
     * @throws NoSuchAlgorithmException Thrown if the encryption algorithm is unavailable.
     */

    public final void generateKeys()
        throws NoSuchAlgorithmException {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance(V1_PASSWORD_ALGORITHM);
        kpg.initialize(PASSWORD_KEY_SIZE);
        KeyPair keys = kpg.generateKeyPair();

        modifyKey = keys.getPrivate();
        readKey = keys.getPublic();
    }

    /**
     * Gets the expiry state for the password.
     *
     * @return The expiry state.
     *
     */

    public final String getTimeToExpire() {
        long theExpiry = getExpiry();
        if (theExpiry == Long.MAX_VALUE) {
            return "Never Expires";
        }

        long today = DateFormatter.getToday();
        if( today == theExpiry ) {
            return "Expiring Today";
        }

        if (today > theExpiry) {
            return "Expired";
        }

        Calendar now = Calendar.getInstance();
        long expiryDistance = theExpiry - now.getTimeInMillis();
        expiryDistance /= MILLIS_IN_A_DAY;
        expiryDistance++;
        if (expiryDistance == 0) {
            return "Expiring Today";
        }
        if (expiryDistance == 1) {
            return "Expires Tomorrow";
        }

        return "Expires in " + expiryDistance + " days";
    }

    /**
     * Checks to see if the password has expired.
     *
     * @return true if the password has expired, false if not.
     */

    public final boolean isExpired() {
        return getExpiry() < DateFormatter.getToday();
    }

    /**
     * Compares this password to another object.
     *
     * @param otherPassword
     *            The other object.
     * @return < 0 if the other object should be considered less than this, 0 if
     *         it should be considered equal, or > 0 if it should be considered
     *         greater than.
     */

    @Override
    public final int compareTo(final PasswordBase otherPassword) {
        int compareValue = username.compareTo(otherPassword.username);
        if (compareValue == 0) {
            compareValue = location.compareTo(otherPassword.location);
        }
        if (compareValue == 0) {
            compareValue = passwordId.compareTo(otherPassword.passwordId);
        }

        return compareValue;
    }

    /**
     * Get the hash code for this object. The passwordId should be unique for
     * all passwords, therefore we'll use it for the hash.
     *
     * @return The hash code for this object.
     */

    @Override
    public final int hashCode() {
        return passwordId.hashCode();
    }

    /**
     * Test this object for equality with another object.
     *
     * @param otherObject
     *            The object to compare this one to.
     *
     * @return true if the objects are equal, false if not.
     */

    @Override
    public final boolean equals(final Object otherObject) {
        return (otherObject instanceof PasswordBase)
                && passwordId.equals(((PasswordBase) otherObject).passwordId);
    }

    /**
     * Get the ID for this password.
     *
     * @return The ID for the password.
     */

    @Override
    public final String getId() {
        return passwordId;
    }

    /**
     * Set the ID for this password
     */

    public void setId(final String id) {
        passwordId = id;
    }

    /**
     * Get the username for this password.
     *
     * @return The username for this password.
     */

    public final String getUsername() {
        return username;
    }

    /**
     * Get the password for this password.
     *
     * @return The password for this password.
     */

    public final String getPassword() {
        return password;
    }

    /**
     * Get the notes for this password.
     *
     * @return The notes for this password.
     */

    public final String getNotes() {
        return notes;
    }

    /**
     * Get the location for this password.
     *
     * @return The location for this password.
     */

    public final String getLocation() {
        return location;
    }

    /**
     * Get the expiry for this password.
     *
     * @return The expiry for this password.
     */

    public final long getExpiry() {
        return expiry;
    }

    /**
     * Sets whether or not the password is enabled.
     *
     * @param enabled true if the password should be considered enabled, false if not.
     */

    public void setEnabled(final boolean enabled) {
        this.enabled = enabled;
    }

    /**
     * @return Returns the isEnabled.
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     * Gets the expiry date in a readable format.
     */

    public final String getExpiryInHumanForm() {
        return DateFormatter.convertToString( getExpiry() );
    }

    /**
     * Get the modification key for this password.
     *
     * @return The modification key for this password.
     */

    @Override
    public final PrivateKey getModifyKey() {
        return modifyKey;
    }

    /**
     * Get the read key for this password.
     *
     * @return The read key for this password.
     */

    @Override
    public final PublicKey getReadKey() {
        return readKey;
    }

    /**
     * @param newExpiry The expiry to set.
     */
    public final void setExpiry(final long newExpiry) {
        expiry = newExpiry;
    }

    /**
     * @param newLocation The location to set.
     */
    public final void setLocation(final String newLocation) {
        location = Objects.requireNonNullElse(newLocation, "");

    }

    /**
     * @param newNotes The notes to set.
     */
    public final void setNotes(final String newNotes) {
        notes = newNotes;
    }

    /**
     * @param newPassword The password to set.
     */
    public final void setPassword(final String newPassword) {
        password = newPassword;
    }

    /**
     * @param newUsername The username to set.
     */
    public final void setUsername(final String newUsername) {
        username = newUsername;
    }

    /**
     * Add a custom field.
     *
     * @param name The name of the custom field.
     * @param value The value for the custom field.
     */

    public final void setCustomField(final String name, final String value) {
        synchronized(this) {
            if(customFields == null) {
                customFields = new HashMap<>();
            }
            customFields.put(name, value);
        }
    }

    /**
     * Delete a custom field.
     *
     * @param name The name of the field to remove.
     */

    public final void deleteCustomField(final String name) {
        if(customFields != null) {
            customFields.remove(name);
        }
    }

    /**
     * Get a custom field value
     *
     * @param name The name of the custom field.
     *
     * @return The custom field value.
     */

    public final String getCustomField(final String name) {
        synchronized(this) {
            if(customFields == null) {
                return null;
            }
            return customFields.get(name);
        }
    }

    /**
     * Get all the custom fields
     *
     * @return The map of custom fields.
     */

    public Map<String,String> getAllCustomFields() {
        return customFields;
    }

    /**
     * Add all the custom fields to a map.
     *
     * @param fields The Map to add the fields to.
     */

    public void addAllCustomFields(final Map<String,String> fields) {
        for(Map.Entry<String,String> entry : fields.entrySet()) {
            setCustomField(entry.getKey(), entry.getValue());
        }
    }


    /**
     * Whether or not the password expires.
     *
     * @return true if the password has an expiry date, false if not.
     */

    public final boolean expires() {
        return (expiry != Long.MAX_VALUE);
    }

    /**
     * Return if the password can be modified using the access control specified.
     *
     * @return Returns the isModifiable.
     */
    public final boolean isModifiable() {
        return isModifiable;
    }

    /**
     * toString method, prepares the content of this object in a human readable
     * form.
     *
     * @return The string represnting this object
     */

    @Override
    public String toString() {
        return getUsername() + '@' + getLocation();
    }

}