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.Charset; 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(Charset.forName("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(Charset.forName("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, Charset.forName("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 for (int i=0; i < value.length; i++) { 198 value[i] = 0; 199 } 200 201 value = null; 202 } 203 204 205 /** 206 * Gets the expiration date of this secret. 207 * 208 * @return The expiration date, {@code null} if not specified. 209 */ 210 public Date getExpirationDate() { 211 212 return expDate; 213 } 214 215 216 /** 217 * Checks is this secret has expired. 218 * 219 * @return {@code true} if the secret has an associated expiration date 220 * which is in the past (according to the current system time), 221 * else returns {@code false}. 222 */ 223 public boolean expired() { 224 225 if (expDate == null) { 226 return false; // never expires 227 } 228 229 final Date now = new Date(); 230 231 return expDate.before(now); 232 } 233 234 235 /** 236 * Constant time comparison of the SHA-256 hashes of this and another 237 * secret. 238 * 239 * @param other The other secret. May be {@code null}. 240 * 241 * @return {@code true} if the SHA-256 hashes of the two secrets are 242 * equal, else {@code false}. 243 */ 244 public boolean equalsSHA256Based(final Secret other) { 245 246 if (other == null) { 247 return false; 248 } 249 250 byte[] thisHash = getSHA256(); 251 byte[] otherHash = other.getSHA256(); 252 253 if (thisHash == null || otherHash == null) { 254 return false; 255 } 256 257 return ConstantTimeUtils.areEqual(thisHash, otherHash); 258 } 259 260 261 /** 262 * Comparison with another secret is constant time. 263 * 264 * @param o The other object. May be {@code null}. 265 * 266 * @return {@code true} if both objects are equal, else {@code false}. 267 */ 268 @Override 269 public boolean equals(final Object o) { 270 if (this == o) return true; 271 if (value == null) return false; 272 if (!(o instanceof Secret)) return false; 273 Secret secret = (Secret) o; 274 return ConstantTimeUtils.areEqual(value, secret.value); 275 } 276 277 278 @Override 279 public int hashCode() { 280 return Arrays.hashCode(value); 281 } 282}