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}