001package com.nimbusds.oauth2.sdk.auth;
002
003
004import java.nio.charset.Charset;
005import java.security.SecureRandom;
006import java.util.Date;
007
008import com.nimbusds.jose.util.Base64URL;
009import net.jcip.annotations.Immutable;
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@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. Must not be {@code null} or empty 
050         *              string.
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. Must be UTF-8 encoded, not 
062         *                {@code null} or empty string.
063         * @param expDate The expiration date, {@code null} if not specified.
064         */
065        public Secret(final String value, final Date expDate) {
066
067                if (StringUtils.isBlank(value))
068                        throw new IllegalArgumentException("The value must not be null or empty string");
069
070                this.value = value.getBytes(Charset.forName("utf-8"));
071
072                this.expDate = expDate;
073        }
074        
075        
076        /**
077         * Creates a new secret with a randomly generated value of the 
078         * specified byte length, Base64URL-encoded.
079         *
080         * @param byteLength The byte length of the secret value to generate. 
081         *                   Must be greater than one.
082         */
083        public Secret(final int byteLength) {
084
085                this(byteLength, null);
086        }
087
088
089        /**
090         * Creates a new secret with a randomly generated value of the 
091         * specified byte length, Base64URL-encoded, and the specified 
092         * expiration date.
093         *
094         * @param byteLength The byte length of the secret value to generate. 
095         *                   Must be greater than one.
096         * @param expDate    The expiration date, {@code null} if not 
097         *                   specified.
098         */
099        public Secret(final int byteLength, final Date expDate) {
100        
101                if (byteLength < 1)
102                        throw new IllegalArgumentException("The byte length must be a positive integer");
103                
104                byte[] n = new byte[byteLength];
105                
106                SECURE_RANDOM.nextBytes(n);
107
108                value = Base64URL.encode(n).toString().getBytes(Charset.forName("UTF-8"));
109                
110                this.expDate = expDate;
111        }
112        
113        
114        /**
115         * Creates a new secret with a randomly generated 256-bit (32-byte) 
116         * value, Base64URL-encoded.
117         */
118        public Secret() {
119
120                this(DEFAULT_BYTE_LENGTH);
121        }
122
123
124        /**
125         * Gets the value of this secret.
126         *
127         * @return The value as a UTF-8 encoded string, {@code null} if it has 
128         *         been erased.
129         */
130        public String getValue() {
131
132                if (ArrayUtils.isEmpty(value))
133                        return null;
134                
135                return new String(value, Charset.forName("utf-8"));
136        }
137        
138        
139        /**
140         * Gets the value of this secret.
141         *
142         * @return The value as a byte array, {@code null} if it has 
143         *         been erased.
144         */
145        public byte[] getValueBytes() {
146
147                return value;
148        }
149
150
151        /**
152         * Erases of the value of this secret.
153         */
154        public void erase() {
155
156                if (ArrayUtils.isEmpty(value))
157                        return;
158                
159                for (int i=0; i < value.length; i++)
160                        value[i] = 0;
161                
162                value = null;
163        }
164
165
166        /**
167         * Gets the expiration date of this secret.
168         *
169         * @return The expiration date, {@code null} if not specified.
170         */
171        public Date getExpirationDate() {
172
173                return expDate;
174        }
175
176
177        /**
178         * Checks is this secret has expired.
179         *
180         * @return {@code true} if the secret has an associated expiration date
181         *         which is in the past (according to the current system time), 
182         *         else returns {@code false}.
183         */
184        public boolean expired() {
185
186                if (expDate == null)
187                        return false;
188
189                final Date now = new Date();
190
191                return expDate.before(now);
192        }
193
194        
195        
196        /**
197         * Overrides {@code Object.equals()}.
198         *
199         * @param object The object to compare to.
200         *
201         * @return {@code true} if the objects are secrets the same value, 
202         *         otherwise {@code false}.
203         */
204        @Override
205        public boolean equals(final Object object) {
206        
207                return object instanceof Secret &&
208                       this.getValue().equals(((Secret)object).getValue());
209        }
210}