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