View Javadoc

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       // Concatenate nonce, timestamp and password.
147       // This has to be done with byte arrays since the nonce
148       // is not character data.
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       // Hash the assembled byte array.
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       // Encode the bytes and return them as a string.
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 }