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