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