001// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org> 002// 003// SPDX-License-Identifier: Apache-2.0 004 005package org.pgpainless.util; 006 007import javax.annotation.Nonnull; 008import javax.annotation.Nullable; 009import java.util.Arrays; 010 011import static org.pgpainless.util.BCUtil.constantTimeAreEqual; 012 013public class Passphrase { 014 015 public final Object lock = new Object(); 016 017 private final char[] chars; 018 private boolean valid = true; 019 020 /** 021 * Passphrase for keys etc. 022 * 023 * @param chars may be null for empty passwords. 024 */ 025 public Passphrase(@Nullable char[] chars) { 026 if (chars == null) { 027 this.chars = null; 028 } else { 029 char[] trimmed = removeTrailingAndLeadingWhitespace(chars); 030 if (trimmed.length == 0) { 031 this.chars = null; 032 } else { 033 this.chars = trimmed; 034 } 035 } 036 } 037 038 /** 039 * Return a copy of the passed in char array, with leading and trailing whitespace characters removed. 040 * 041 * @param chars char array 042 * @return copy of char array with leading and trailing whitespace characters removed 043 */ 044 private static char[] removeTrailingAndLeadingWhitespace(char[] chars) { 045 int i = 0; 046 while (i < chars.length && isWhitespace(chars[i])) { 047 i++; 048 } 049 int j = chars.length - 1; 050 while (j >= i && isWhitespace(chars[j])) { 051 j--; 052 } 053 054 char[] trimmed = new char[chars.length - i - (chars.length - 1 - j)]; 055 System.arraycopy(chars, i, trimmed, 0, trimmed.length); 056 057 return trimmed; 058 } 059 060 /** 061 * Return true, if the passed in char is a whitespace symbol (space, newline, tab). 062 * 063 * @param xar char 064 * @return true if whitespace 065 */ 066 private static boolean isWhitespace(char xar) { 067 return xar == ' ' || xar == '\n' || xar == '\t'; 068 } 069 070 /** 071 * Create a {@link Passphrase} from a {@link String}. 072 * 073 * @param password password 074 * @return passphrase 075 */ 076 public static Passphrase fromPassword(@Nonnull String password) { 077 return new Passphrase(password.toCharArray()); 078 } 079 080 /** 081 * Overwrite the char array with spaces and mark the {@link Passphrase} as invalidated. 082 */ 083 public void clear() { 084 synchronized (lock) { 085 if (chars != null) { 086 Arrays.fill(chars, ' '); 087 } 088 valid = false; 089 } 090 } 091 092 /** 093 * Return a copy of the underlying char array. 094 * A return value of {@code null} represents no password. 095 * 096 * @return passphrase chars. 097 * 098 * @throws IllegalStateException in case the password has been cleared at this point. 099 */ 100 public @Nullable char[] getChars() { 101 synchronized (lock) { 102 if (!valid) { 103 throw new IllegalStateException("Passphrase has been cleared."); 104 } 105 106 if (chars == null) { 107 return null; 108 } 109 110 char[] copy = new char[chars.length]; 111 System.arraycopy(chars, 0, copy, 0, chars.length); 112 return copy; 113 } 114 } 115 116 /** 117 * Return true if the passphrase has not yet been cleared. 118 * 119 * @return valid 120 */ 121 public boolean isValid() { 122 synchronized (lock) { 123 return valid; 124 } 125 } 126 127 /** 128 * Return true if the passphrase represents no password. 129 * 130 * @return empty 131 */ 132 public boolean isEmpty() { 133 synchronized (lock) { 134 return valid && chars == null; 135 } 136 } 137 138 /** 139 * Represents a {@link Passphrase} instance that represents no password. 140 * 141 * @return empty passphrase 142 */ 143 public static Passphrase emptyPassphrase() { 144 return new Passphrase(null); 145 } 146 147 @Override 148 public int hashCode() { 149 if (getChars() == null) { 150 return 0; 151 } 152 return new String(getChars()).hashCode(); 153 } 154 155 @Override 156 public boolean equals(Object obj) { 157 if (obj == null) { 158 return false; 159 } 160 if (this == obj) { 161 return true; 162 } 163 if (!(obj instanceof Passphrase)) { 164 return false; 165 } 166 Passphrase other = (Passphrase) obj; 167 return (getChars() == null && other.getChars() == null) || 168 constantTimeAreEqual(getChars(), other.getChars()); 169 } 170}