001package com.nimbusds.oauth2.sdk.auth; 002 003 004import java.nio.charset.Charset; 005import java.security.SecureRandom; 006import java.util.Date; 007 008import com.nimbusds.jose.util.Base64URL; 009import net.jcip.annotations.Immutable; 010import org.apache.commons.lang3.ArrayUtils; 011import org.apache.commons.lang3.StringUtils; 012 013 014/** 015 * Secret or password. The secret should be {@link #erase erased} when no 016 * longer in use. 017 */ 018@Immutable 019public class Secret { 020 021 022 /** 023 * The default byte length of generated secrets. 024 */ 025 public static final int DEFAULT_BYTE_LENGTH = 32; 026 027 028 /** 029 * The secure random generator. 030 */ 031 private static final SecureRandom SECURE_RANDOM = new SecureRandom(); 032 033 034 /** 035 * The secret value. 036 */ 037 private byte[] value; 038 039 040 /** 041 * Optional expiration date. 042 */ 043 private final Date expDate; 044 045 046 /** 047 * Creates a new secret with the specified value. 048 * 049 * @param value The secret value. Must not be {@code null} or empty 050 * string. 051 */ 052 public Secret(final String value) { 053 054 this(value, null); 055 } 056 057 058 /** 059 * Creates a new secret with the specified value and expiration date. 060 * 061 * @param value The secret value. Must be UTF-8 encoded, not 062 * {@code null} or empty string. 063 * @param expDate The expiration date, {@code null} if not specified. 064 */ 065 public Secret(final String value, final Date expDate) { 066 067 if (StringUtils.isBlank(value)) 068 throw new IllegalArgumentException("The value must not be null or empty string"); 069 070 this.value = value.getBytes(Charset.forName("utf-8")); 071 072 this.expDate = expDate; 073 } 074 075 076 /** 077 * Creates a new secret with a randomly generated value of the 078 * specified byte length, Base64URL-encoded. 079 * 080 * @param byteLength The byte length of the secret value to generate. 081 * Must be greater than one. 082 */ 083 public Secret(final int byteLength) { 084 085 this(byteLength, null); 086 } 087 088 089 /** 090 * Creates a new secret with a randomly generated value of the 091 * specified byte length, Base64URL-encoded, and the specified 092 * expiration date. 093 * 094 * @param byteLength The byte length of the secret value to generate. 095 * Must be greater than one. 096 * @param expDate The expiration date, {@code null} if not 097 * specified. 098 */ 099 public Secret(final int byteLength, final Date expDate) { 100 101 if (byteLength < 1) 102 throw new IllegalArgumentException("The byte length must be a positive integer"); 103 104 byte[] n = new byte[byteLength]; 105 106 SECURE_RANDOM.nextBytes(n); 107 108 value = Base64URL.encode(n).toString().getBytes(Charset.forName("UTF-8")); 109 110 this.expDate = expDate; 111 } 112 113 114 /** 115 * Creates a new secret with a randomly generated 256-bit (32-byte) 116 * value, Base64URL-encoded. 117 */ 118 public Secret() { 119 120 this(DEFAULT_BYTE_LENGTH); 121 } 122 123 124 /** 125 * Gets the value of this secret. 126 * 127 * @return The value as a UTF-8 encoded string, {@code null} if it has 128 * been erased. 129 */ 130 public String getValue() { 131 132 if (ArrayUtils.isEmpty(value)) 133 return null; 134 135 return new String(value, Charset.forName("utf-8")); 136 } 137 138 139 /** 140 * Gets the value of this secret. 141 * 142 * @return The value as a byte array, {@code null} if it has 143 * been erased. 144 */ 145 public byte[] getValueBytes() { 146 147 return value; 148 } 149 150 151 /** 152 * Erases of the value of this secret. 153 */ 154 public void erase() { 155 156 if (ArrayUtils.isEmpty(value)) 157 return; 158 159 for (int i=0; i < value.length; i++) 160 value[i] = 0; 161 162 value = null; 163 } 164 165 166 /** 167 * Gets the expiration date of this secret. 168 * 169 * @return The expiration date, {@code null} if not specified. 170 */ 171 public Date getExpirationDate() { 172 173 return expDate; 174 } 175 176 177 /** 178 * Checks is this secret has expired. 179 * 180 * @return {@code true} if the secret has an associated expiration date 181 * which is in the past (according to the current system time), 182 * else returns {@code false}. 183 */ 184 public boolean expired() { 185 186 if (expDate == null) 187 return false; 188 189 final Date now = new Date(); 190 191 return expDate.before(now); 192 } 193 194 195 196 /** 197 * Overrides {@code Object.equals()}. 198 * 199 * @param object The object to compare to. 200 * 201 * @return {@code true} if the objects are secrets the same value, 202 * otherwise {@code false}. 203 */ 204 @Override 205 public boolean equals(final Object object) { 206 207 return object instanceof Secret && 208 this.getValue().equals(((Secret)object).getValue()); 209 } 210}