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