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
027import org.jgrapes.core.Components;
028
029/**
030 * Stores a password in such a way that it can be cleared. Automatically 
031 * clears the storage if an object of this type becomes weakly reachable.
032 */
033public class Password {
034
035    private static ReferenceQueue<Password> toBeCleared
036        = new ReferenceQueue<>();
037    private static Thread purger;
038    private static final char[] EMPTY_PASSWORD = new char[0];
039
040    @SuppressWarnings("PMD.AvoidFieldNameMatchingTypeName")
041    private char[] password;
042    @SuppressWarnings({ "PMD.SingularField", "unused" })
043    private final WeakReference<Password> passwordRef;
044
045    /**
046     * Instantiates a new password representation.
047     *
048     * @param password the password
049     */
050    @SuppressWarnings({ "PMD.UseVarargs", "PMD.AssignmentToNonFinalStatic",
051        "PMD.ArrayIsStoredDirectly" })
052    public Password(char[] password) {
053        synchronized (Password.class) {
054            if (purger == null) {
055                purger = (Components.useVirtualThreads() ? Thread.ofVirtual()
056                    : Thread.ofPlatform()).name("PasswordPurger").start(() -> {
057                        while (true) {
058                            try {
059                                Reference<? extends Password> passwordRef
060                                    = toBeCleared.remove();
061                                Optional.ofNullable(passwordRef.get())
062                                    .ifPresent(Password::clear);
063                                passwordRef.clear();
064                            } catch (InterruptedException e) {
065                                break;
066                            }
067                        }
068                    });
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    @SuppressWarnings("PMD.SimplifyBooleanReturns")
104    public boolean compareTo(String value) {
105        if (value == null) {
106            return false;
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", "PMD.SimplifyBooleanReturns" })
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    @SuppressWarnings("PMD.SimplifyBooleanReturns")
127    public boolean equals(Object other) {
128        if (!(other instanceof Password)) {
129            return false;
130        }
131        return compareTo(((Password) other).password);
132    }
133
134    /**
135     * Passwords shouldn't be used in sets or as keys. To avoid
136     * disclosing any information about the password, this method
137     * always returns 0.
138     *
139     * @return 0
140     */
141    @Override
142    public int hashCode() {
143        return 0;
144    }
145
146    /**
147     * Return "`(hidden)`". Should prevent the password from appearing
148     * unintentionally in outputs.
149     *
150     * @return the string
151     */
152    @Override
153    public String toString() {
154        return "(hidden)";
155    }
156}