1 package org.astrogrid.security;
2
3 import java.security.MessageDigest;
4 import java.security.SecureRandom;
5 import java.util.Date;
6 import javax.xml.soap.SOAPException;
7
8 /***
9 * A password with associated nonce and timestamp, as specified in
10 * WS-Security. This class supports the password hashing and encoding
11 * algorithms of WS-Security.
12 *
13 * @deprecated This interface needs a re-think. Treat it as unstable.
14 *
15 * @author Guy Rixon
16 */
17 public class Password {
18
19 /***
20 * The password, before hashing.
21 */
22 private String plainPassword;
23
24 /***
25 * The nonce, unencoded and unhashed.
26 */
27 protected byte[] nonce;
28
29 /***
30 * The timestamp as an ISO8601 string.
31 */
32 protected String timestamp;
33
34 /***
35 * True if the password should be encoded for transmission.
36 */
37 private boolean encodable;
38
39 /***
40 * A formatter for using ISO8601 timestamps.
41 */
42 private static Iso8601DateFormat iso8601 = new Iso8601DateFormat();
43
44
45 /***
46 * Constructs a Password. The associated nonce is generated
47 * and stored internally. The timestamp is taken and stored
48 * internally.
49 *
50 * @param password the plain-text password
51 * @param encodable true if the password should be encoded
52 * for transmission
53 */
54 public Password (String password,
55 boolean encodable) throws SOAPException {
56 this.plainPassword = password;
57 this.nonce = Password.generateNonce();
58 this.timestamp = Password.generateTimestamp();
59 this.encodable = encodable;
60 }
61
62
63 /***
64 * Constructs a password. The given nonce, timestamp and
65 * encoded password are stored internally; no new nonces
66 * or timestamps are generated.
67 *
68 * @param password the plain-text password
69 * @param nonce the nonce, in base-64 encoding
70 * @param timestamp the timestamp, in plain text
71 * @boolean encodable true if the password should be
72 * encoded for transmission
73 */
74 public Password (String password,
75 String nonce,
76 String timestamp,
77 boolean encodable) throws SOAPException {
78 try {
79 this.nonce = Base64.decode(nonce);
80 this.timestamp = timestamp;
81 this.plainPassword = password;
82 this.encodable = encodable;
83 }
84 catch (Exception e) {
85 throw new SOAPException("Failed to construct a password", e);
86 }
87 }
88
89
90 /***
91 * Renew the nonce and timestamp fields of the object.
92 */
93 public void renewNonceAndTimestamp () throws SOAPException {
94 this.nonce = Password.generateNonce();
95 this.timestamp = Password.generateTimestamp();
96 }
97
98
99 /***
100 * Returns the nonce in a form ready to serialize as XML.
101 *
102 * @return the nonce, in base-64 encoding
103 */
104 public String getNonce () {
105 return Base64.encodeBytes(this.nonce);
106 }
107
108
109 /***
110 * Returns the timestamp, in a form ready to serialize as XML.
111 *
112 * @return the timestamp in ISO8601 format with no encoding.
113 */
114 public String getTimestamp () {
115 return this.timestamp;
116 }
117
118
119 /***
120 * Tests a plain-text password against the cached password.
121 * The given password is hashed and encoded and then tested
122 * against the encoded password set at construction.
123 */
124 public boolean match (String candidate) throws SOAPException {
125 return (candidate.equals(this.getEncodedPassword()));
126 }
127
128 /***
129 * Reveals the plain-text password.
130 *
131 * @return the password
132 */
133 public String getPlainPassword () {
134 return this.plainPassword;
135 }
136
137
138 /***
139 * Applies the hashing and encoding algorithms.
140 *
141 * @return the encoded password
142 */
143 public String getEncodedPassword() throws SOAPException {
144 try {
145
146
147
148
149 byte[] timestampBytes = this.timestamp.getBytes("UTF8");
150 byte[] passwordBytes = this.plainPassword.getBytes("UTF8");
151 int totalLength = this.nonce.length
152 + timestampBytes.length
153 + passwordBytes.length;
154 byte[] ensemble = new byte[totalLength];
155 int i = 0;
156 for (int j = 0; j < nonce.length; j++) {
157 ensemble[i] = this.nonce[j];
158 i++;
159 }
160 for (int k = 0; k < timestampBytes.length; k++) {
161 ensemble[i] = timestampBytes[k];
162 i++;
163 }
164 for (int l = 0; l < passwordBytes.length; l++) {
165 ensemble[i] = passwordBytes[l];
166 i++;
167 }
168 System.out.println("timestampBytes: " + new String(timestampBytes));
169 System.out.println("passwordBytes: " + new String(passwordBytes));
170 System.out.println("nonce: " + new String(nonce));
171 System.out.println("ensemble: " + new String(ensemble));
172
173
174 MessageDigest md = MessageDigest.getInstance("SHA");
175 md.update(ensemble);
176 byte[] digest = md.digest();
177 System.out.println("digest: " + new String(digest));
178
179
180 return Base64.encodeBytes(digest);
181 }
182 catch (Exception e) {
183 throw new SOAPException("Failed to encode a password");
184 }
185 }
186
187
188 /***
189 * Indicates whether the password should be encoded for transmission.
190 */
191 public boolean isEncodable () {
192 return this.encodable;
193 }
194
195
196 /***
197 * Regenerates a nonce.
198 *
199 * @return the nonce, with no encoding
200 */
201 private static byte[] generateNonce () throws SOAPException {
202 try {
203 SecureRandom prng = SecureRandom.getInstance("SHA1PRNG");
204 byte[] nonce = new byte[16];
205 prng.nextBytes(nonce);
206 return nonce;
207 }
208 catch (Exception e) {
209 throw new SOAPException("Failed to construct a nonce", e);
210 }
211 }
212
213 /***
214 * Regenerates a timestamp.
215 *
216 * @return the stampstamp in ISO8601 format
217 */
218 private static String generateTimestamp () throws SOAPException {
219 try {
220 return Password.iso8601.format(new Date());
221 }
222 catch (Exception e) {
223 throw new SOAPException("Failed to construct a timestamp", e);
224 }
225 }
226
227 }