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