001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2022 Michael N. Lipp
004 * 
005 * This program is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU General Public License as published by 
007 * the Free Software Foundation; either version 3 of the License, or 
008 * (at your option) any later version.
009 * 
010 * This program is distributed in the hope that it will be useful, but 
011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
013 * for more details.
014 * 
015 * You should have received a copy of the GNU General Public License along 
016 * with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jgrapes.util;
020
021import java.lang.ref.Reference;
022import java.lang.ref.ReferenceQueue;
023import java.lang.ref.WeakReference;
024import java.util.Arrays;
025import java.util.Optional;
026
027/**
028 * Stores a password in such a way that it can be cleared. Automatically 
029 * clears the storage if an object of this type becomes weakly reachable.
030 */
031public class Password {
032
033    private static ReferenceQueue<Password> toBeCleared
034        = new ReferenceQueue<>();
035    private static Thread purger;
036    private static final char[] EMPTY_PASSWORD = new char[0];
037
038    @SuppressWarnings("PMD.AvoidFieldNameMatchingTypeName")
039    private char[] password;
040    @SuppressWarnings({ "PMD.SingularField", "unused" })
041    private final WeakReference<Password> passwordRef;
042
043    /**
044     * Instantiates a new password representation.
045     *
046     * @param password the password
047     */
048    @SuppressWarnings({ "PMD.UseVarargs", "PMD.AssignmentToNonFinalStatic",
049        "PMD.ArrayIsStoredDirectly" })
050    public Password(char[] password) {
051        synchronized (Password.class) {
052            if (purger == null) {
053                purger = new Thread(() -> {
054                    while (true) {
055                        try {
056                            Reference<? extends Password> passwordRef
057                                = toBeCleared.remove();
058                            Optional.ofNullable(passwordRef.get())
059                                .ifPresent(Password::clear);
060                            passwordRef.clear();
061                        } catch (InterruptedException e) {
062                            break;
063                        }
064                    }
065                });
066                purger.setName("PasswordPurger");
067                purger.setDaemon(true);
068                purger.start();
069            }
070        }
071        this.password = password;
072        passwordRef = new WeakReference<>(this, toBeCleared);
073    }
074
075    /**
076     * Clear the stored password.
077     */
078    public void clear() {
079        for (int i = 0; i < password.length; i++) {
080            password[i] = 0;
081        }
082        // Don't even remember its length.
083        password = EMPTY_PASSWORD;
084    }
085
086    /**
087     * Returns the stored password. This is returns a reference to the
088     * internally used array.
089     *
090     * @return the char[]
091     */
092    @SuppressWarnings("PMD.MethodReturnsInternalArray")
093    public char[] password() {
094        return password;
095    }
096
097    /**
098     * Compare to a given string.
099     *
100     * @param value the value to compare to
101     * @return true, if successful
102     */
103    public boolean compareTo(String value) {
104        if (value == null) {
105            return false;
106        }
107
108        return Arrays.equals(value.toCharArray(), password);
109    }
110
111    /**
112     * Compare to a given char array.
113     *
114     * @param value the value to compare to
115     * @return true, if successful
116     */
117    @SuppressWarnings("PMD.UseVarargs")
118    public boolean compareTo(char[] value) {
119        if (value == null) {
120            return false;
121        }
122        return Arrays.equals(value, password);
123    }
124
125    @Override
126    public boolean equals(Object other) {
127        if (!(other instanceof Password)) {
128            return false;
129        }
130        return compareTo(((Password) other).password);
131    }
132
133    /**
134     * Passwords shouldn't be used in sets or as keys. To avoid
135     * disclosing any information about the password, this method
136     * always returns 0.
137     *
138     * @return 0
139     */
140    @Override
141    public int hashCode() {
142        return 0;
143    }
144
145    /**
146     * Return "`(hidden)`". Should prevent the password from appearing
147     * unintentionally in outputs.
148     *
149     * @return the string
150     */
151    @Override
152    public String toString() {
153        return "(hidden)";
154    }
155}