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