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 * @author Guy Rixon
14 */
15 public class PasswordEncoding {
16
17 /***
18 * The password, before hashing.
19 */
20 private String plainPassword;
21
22 /***
23 * The password, after hashing and base-64 encoding.
24 */
25 private String encodedPassword;
26
27 /***
28 * The nonce, unencoded and unhashed.
29 */
30 protected byte[] nonce;
31
32 /***
33 * The timestamp as an ISO8601 string.
34 */
35 protected String timestamp;
36
37 /***
38 * A formatter for using ISO8601 timestamps.
39 */
40 private static Iso8601DateFormat iso8601 = new Iso8601DateFormat();
41
42
43 /***
44 * Constructs a Password. The associated nonce is generated
45 * and stored internally. The timestamp is taken and stored
46 * internally.
47 *
48 * @param password the plain-text password
49 */
50 public PasswordEncoding (String password) throws SOAPException {
51 try {
52 this.plainPassword = password;
53 SecureRandom prng = SecureRandom.getInstance("SHA1PRNG");
54 this.nonce = new byte[16];
55 prng.nextBytes(nonce);
56 this.timestamp = iso8601.format(new Date());
57 }
58 catch (Exception e) {
59 throw new SOAPException("Failed to construct a Password", e);
60 }
61 }
62
63
64 /***
65 * Constructs a password. The given nonce, timestamp and
66 * encoded password are stored internally; no new nonces
67 * or timestamps are generated.
68 */
69 public PasswordEncoding (String password,
70 String nonce,
71 String timestamp) throws SOAPException {
72 try {
73 this.nonce = Base64.decode(nonce);
74 this.timestamp = timestamp;
75 this.encodedPassword = password;
76 }
77 catch (Exception e) {
78 throw new SOAPException("Failed to construct a password", e);
79 }
80 }
81
82
83 /***
84 * Returns the nonce in a form ready to serialize as XML.
85 *
86 * @return the nonce, in base-64 encoding
87 */
88 public String getNonce () {
89 return Base64.encodeBytes(this.nonce);
90 }
91
92
93 /***
94 * Returns the timestamp, in a form ready to serialize as XML.
95 *
96 * @return the timestamp in ISO8601 format with no encoding.
97 */
98 public String getTimestamp () {
99 return this.timestamp;
100 }
101
102
103 /***
104 * Hash, encode and return the password. The algorithm combines the
105 * nonce, timestamp and clear-text password as prescribed by
106 * WS-Security. See the class comments for details.
107 *
108 * @return the encoded password
109 * @throws SOAPException if anything goes wrong
110 */
111 public String getEncodedPassword () throws SOAPException {
112 try {
113 return this.encodePassword(this.plainPassword,
114 this.nonce,
115 this.timestamp);
116 }
117 catch (Exception e) {
118 throw new SOAPException("Failed to encoded a password", e);
119 }
120 }
121
122
123 /***
124 * Tests a plain-text password against the cached password.
125 * The given password is hashed and encoded and then tested
126 * against the encoded password set at construction.
127 */
128 public boolean match (String plainPassword) throws SOAPException {
129 try {
130 String candidate = this.encodePassword(plainPassword,
131 this.nonce,
132 this.timestamp);
133 return (candidate.equals(this.encodedPassword));
134 }
135 catch (Exception e) {
136 throw new SOAPException("Failed to encode a password", e);
137 }
138 }
139
140
141 /***
142 * Applies the hashing and encoding algorithms.
143 */
144 private String encodePassword(String plainPassword,
145 byte[] nonce,
146 String timestamp) throws Exception {
147
148
149
150
151 byte[] timestampBytes = timestamp.getBytes("UTF8");
152 byte[] passwordBytes = plainPassword.getBytes("UTF8");
153 int totalLength = nonce.length
154 + timestampBytes.length
155 + passwordBytes.length;
156 byte[] ensemble = new byte[totalLength];
157 int i = 0;
158 for (int j = 0; j < nonce.length; j++) {
159 ensemble[i] = this.nonce[j];
160 i++;
161 }
162 for (int k = 0; k < timestampBytes.length; k++) {
163 ensemble[i] = timestampBytes[k];
164 i++;
165 }
166 for (int l = 0; l < passwordBytes.length; l++) {
167 ensemble[i] = passwordBytes[l];
168 i++;
169 }
170 System.out.println("timestampBytes: " + new String(timestampBytes));
171 System.out.println("passwordBytes: " + new String(passwordBytes));
172 System.out.println("nonce: " + new String(nonce));
173 System.out.println("ensemble: " + new String(ensemble));
174
175
176 MessageDigest md = MessageDigest.getInstance("SHA");
177 md.update(ensemble);
178 byte[] digest = md.digest();
179 System.out.println("digest: " + new String(digest));
180
181
182 return Base64.encodeBytes(digest);
183 }
184
185 }