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