001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.oauth2.sdk.auth; 019 020 021import java.io.Serializable; 022import java.nio.charset.StandardCharsets; 023import java.security.MessageDigest; 024import java.security.NoSuchAlgorithmException; 025import java.security.SecureRandom; 026import java.util.Arrays; 027import java.util.Date; 028 029import com.nimbusds.jose.crypto.utils.ConstantTimeUtils; 030import com.nimbusds.jose.util.Base64URL; 031import net.jcip.annotations.Immutable; 032 033 034/** 035 * Secret. The secret value should be {@link #erase erased} when no longer in 036 * use. 037 */ 038@Immutable 039public class Secret implements Serializable { 040 041 042 private static final long serialVersionUID = 1L; 043 044 045 /** 046 * The default byte length of generated secrets. 047 */ 048 public static final int DEFAULT_BYTE_LENGTH = 32; 049 050 051 /** 052 * The secure random generator. 053 */ 054 private static final SecureRandom SECURE_RANDOM = new SecureRandom(); 055 056 057 /** 058 * The secret value. 059 */ 060 private byte[] value; 061 062 063 /** 064 * Optional expiration date. 065 */ 066 private final Date expDate; 067 068 069 /** 070 * Creates a new secret with the specified value. 071 * 072 * @param value The secret value. May be an empty string. Must be 073 * UTF-8 encoded and not {@code null}. 074 */ 075 public Secret(final String value) { 076 077 this(value, null); 078 } 079 080 081 /** 082 * Creates a new secret with the specified value and expiration date. 083 * 084 * @param value The secret value. May be an empty string. Must be 085 * UTF-8 encoded and not {@code null}. 086 * @param expDate The expiration date, {@code null} if not specified. 087 */ 088 public Secret(final String value, final Date expDate) { 089 090 this.value = value.getBytes(StandardCharsets.UTF_8); 091 this.expDate = expDate; 092 } 093 094 095 /** 096 * Generates a new secret with a cryptographic random value of the 097 * specified byte length, Base64URL-encoded. 098 * 099 * @param byteLength The byte length of the secret value to generate. 100 * Must be greater than one. 101 */ 102 public Secret(final int byteLength) { 103 104 this(byteLength, null); 105 } 106 107 108 /** 109 * Generates a new secret with a cryptographic random value of the 110 * specified byte length, Base64URL-encoded, and the specified 111 * expiration date. 112 * 113 * @param byteLength The byte length of the secret value to generate. 114 * Must be greater than one. 115 * @param expDate The expiration date, {@code null} if not 116 * specified. 117 */ 118 public Secret(final int byteLength, final Date expDate) { 119 120 if (byteLength < 1) 121 throw new IllegalArgumentException("The byte length must be a positive integer"); 122 123 byte[] n = new byte[byteLength]; 124 125 SECURE_RANDOM.nextBytes(n); 126 127 value = Base64URL.encode(n).toString().getBytes(StandardCharsets.UTF_8); 128 129 this.expDate = expDate; 130 } 131 132 133 /** 134 * Generates a new secret with a cryptographic 256-bit (32-byte) random 135 * value, Base64URL-encoded. 136 */ 137 public Secret() { 138 139 this(DEFAULT_BYTE_LENGTH); 140 } 141 142 143 /** 144 * Gets the value of this secret. 145 * 146 * @return The value as a UTF-8 encoded string, {@code null} if it has 147 * been erased. 148 */ 149 public String getValue() { 150 151 if (value == null) { 152 return null; // value has been erased 153 } 154 155 return new String(value, StandardCharsets.UTF_8); 156 } 157 158 159 /** 160 * Gets the value of this secret. 161 * 162 * @return The value as a byte array, {@code null} if it has 163 * been erased. 164 */ 165 public byte[] getValueBytes() { 166 167 return value; 168 } 169 170 171 /** 172 * Gets the SHA-256 hash of this secret. 173 * 174 * @return The SHA-256 hash, {@code null} if the secret value has been 175 * erased. 176 */ 177 public byte[] getSHA256() { 178 179 if (value == null) { 180 return null; 181 } 182 183 try { 184 MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); 185 return sha256.digest(value); 186 } catch (NoSuchAlgorithmException e) { 187 throw new RuntimeException(e); 188 } 189 } 190 191 192 /** 193 * Erases of the value of this secret. 194 */ 195 public void erase() { 196 197 if (value == null) { 198 return; // Already erased 199 } 200 201 Arrays.fill(value, (byte) 0); 202 203 value = null; 204 } 205 206 207 /** 208 * Gets the expiration date of this secret. 209 * 210 * @return The expiration date, {@code null} if not specified. 211 */ 212 public Date getExpirationDate() { 213 214 return expDate; 215 } 216 217 218 /** 219 * Checks is this secret has expired. 220 * 221 * @return {@code true} if the secret has an associated expiration date 222 * which is in the past (according to the current system time), 223 * else returns {@code false}. 224 */ 225 public boolean expired() { 226 227 if (expDate == null) { 228 return false; // never expires 229 } 230 231 final Date now = new Date(); 232 233 return expDate.before(now); 234 } 235 236 237 /** 238 * Constant time comparison of the SHA-256 hashes of this and another 239 * secret. 240 * 241 * @param other The other secret. May be {@code null}. 242 * 243 * @return {@code true} if the SHA-256 hashes of the two secrets are 244 * equal, else {@code false}. 245 */ 246 public boolean equalsSHA256Based(final Secret other) { 247 248 if (other == null) { 249 return false; 250 } 251 252 byte[] thisHash = getSHA256(); 253 byte[] otherHash = other.getSHA256(); 254 255 if (thisHash == null || otherHash == null) { 256 return false; 257 } 258 259 return ConstantTimeUtils.areEqual(thisHash, otherHash); 260 } 261 262 263 /** 264 * Comparison with another secret is constant time. 265 * 266 * @param o The other object. May be {@code null}. 267 * 268 * @return {@code true} if both objects are equal, else {@code false}. 269 */ 270 @Override 271 public boolean equals(final Object o) { 272 if (this == o) return true; 273 if (value == null) return false; 274 if (!(o instanceof Secret)) return false; 275 Secret secret = (Secret) o; 276 return ConstantTimeUtils.areEqual(value, secret.value); 277 } 278 279 280 @Override 281 public int hashCode() { 282 return Arrays.hashCode(value); 283 } 284}