001/* 002 * This file is part of the Keycloak Moodle authenticator 003 * Copyright (C) 2024 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 Lesser General Public License as published 007 * by 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 Lesser General Public 013 * License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.keycloak.moodleauth.moodle.service; 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 @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}