001: /*
002: * $Id: UsernameToken.java,v 1.5 2007/01/04 09:29:55 venu Exp $
003: */
004:
005: /*
006: * The contents of this file are subject to the terms
007: * of the Common Development and Distribution License
008: * (the License). You may not use this file except in
009: * compliance with the License.
010: *
011: * You can obtain a copy of the license at
012: * https://glassfish.dev.java.net/public/CDDLv1.0.html.
013: * See the License for the specific language governing
014: * permissions and limitations under the License.
015: *
016: * When distributing Covered Code, include this CDDL
017: * Header Notice in each file and include the License file
018: * at https://glassfish.dev.java.net/public/CDDLv1.0.html.
019: * If applicable, add the following below the CDDL Header,
020: * with the fields enclosed by brackets [] replaced by
021: * you own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Copyright 2006 Sun Microsystems Inc. All Rights Reserved
025: */
026:
027: package com.sun.xml.wss.core;
028:
029: import java.io.UnsupportedEncodingException;
030: import java.security.MessageDigest;
031: import java.security.NoSuchAlgorithmException;
032: import java.security.SecureRandom;
033: import java.util.Iterator;
034: import java.util.logging.Level;
035: import java.util.logging.Logger;
036:
037: import javax.xml.soap.SOAPElement;
038: import javax.xml.soap.SOAPException;
039:
040: import org.w3c.dom.Document;
041: import org.w3c.dom.Node;
042:
043: import com.sun.org.apache.xml.internal.security.exceptions.Base64DecodingException;
044: import com.sun.xml.wss.impl.misc.Base64;
045: import com.sun.xml.wss.logging.LogDomainConstants;
046: import com.sun.xml.wss.impl.MessageConstants;
047: import com.sun.xml.wss.impl.SecurityTokenException;
048: import com.sun.xml.wss.impl.XMLUtil;
049: import com.sun.xml.wss.XWSSecurityException;
050: import com.sun.xml.wss.impl.misc.SecurityHeaderBlockImpl;
051:
052: import com.sun.xml.ws.security.Token;
053:
054: /*
055: <xsd:complexType name="UsernameTokenType">
056: - <xsd:annotation>
057: - <xsd:documentation>
058: This type represents a username token per Section 4.1
059: </xsd:documentation>
060: </xsd:annotation>
061: - <xsd:sequence>
062: <xsd:element name="Username" type="wsse:AttributedString"/>
063: <xsd:any processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
064: </xsd:sequence>
065: <xsd:attribute ref="wsu:Id"/>
066: <xsd:anyAttribute namespace="##other" processContents="lax"/>
067: </xsd:complexType>
068: */
069:
070: /**
071: *
072: * Support for a WSS:Username Token Profile.
073: *
074: * Represents a wsse:UsernameToken.
075: *
076: * @author Manveen Kaur
077: * @author Edwin Goei
078: */
079: public class UsernameToken extends SecurityHeaderBlockImpl implements
080: SecurityToken, Token {
081:
082: public static final long MAX_NONCE_AGE = 900000; //milliseconds
083:
084: private String username;
085:
086: private String password = null;
087:
088: // password type
089: private String passwordType = MessageConstants.PASSWORD_TEXT_NS;
090:
091: // password Digest value
092: private String passwordDigest = null;
093:
094: private byte[] decodedNonce = null;
095:
096: // specifies a cryptographically random sequence
097: private String nonce = null;
098:
099: // default nonce encoding
100: private String nonceEncodingType = MessageConstants.BASE64_ENCODING_NS;
101:
102: // time stamp to indicate creation time
103: private String created = null;
104:
105: // flag to indicate whether BSP checks should be made or not.
106: private boolean bsp = false;
107:
108: private Document soapDoc;
109:
110: private static Logger log = Logger.getLogger(
111: LogDomainConstants.WSS_API_DOMAIN,
112: LogDomainConstants.WSS_API_DOMAIN_BUNDLE);
113:
114: public static SecurityHeaderBlock fromSoapElement(
115: SOAPElement element) throws XWSSecurityException {
116: return SecurityHeaderBlockImpl.fromSoapElement(element,
117: UsernameToken.class);
118: }
119:
120: public UsernameToken(Document document, String username)
121: throws SecurityTokenException {
122: this .soapDoc = document;
123: this .username = username;
124: // set default password type
125: setPasswordType(MessageConstants.PASSWORD_TEXT_NS);
126: }
127:
128: public UsernameToken(Document document, String username,
129: String password, boolean digestPassword)
130: throws SecurityTokenException {
131: this (document, username);
132: this .password = password;
133: if (digestPassword) {
134: setPasswordType(MessageConstants.PASSWORD_DIGEST_NS);
135: }
136: }
137:
138: public UsernameToken(Document document, String username,
139: String password) throws SecurityTokenException {
140: this (document, username, password, false);
141: }
142:
143: /**
144: * C'tor that creates the optional element nonce, created is not set.
145: */
146: public UsernameToken(Document document, String username,
147: String password, boolean setNonce, boolean digestPassword)
148: throws SecurityTokenException {
149:
150: this (document, username, password, digestPassword);
151: if (setNonce) {
152: createNonce();
153: }
154: }
155:
156: /**
157: * C'tor that creates the optional elements of nonce and created.
158: */
159: public UsernameToken(Document document, String username,
160: String password, boolean setNonce,
161: boolean setCreatedTimestamp, boolean digestPassword)
162: throws SecurityTokenException {
163:
164: this (document, username, password, setNonce, digestPassword);
165: if (setCreatedTimestamp) {
166: try {
167: this .created = getCreatedFromTimestamp();
168: } catch (Exception e) {
169: log.log(Level.SEVERE,
170: "WSS0280.failed.create.UsernameToken", e);
171: throw new SecurityTokenException(e);
172: }
173: }
174: }
175:
176: public UsernameToken(SOAPElement usernameTokenSoapElement,
177: boolean bspFlag) throws XWSSecurityException {
178: this (usernameTokenSoapElement);
179: isBSP(bspFlag);
180: }
181:
182: /**
183: * Extracts info from SOAPElement representation
184: *
185: * @param usernameTokenSoapElement
186: */
187: public UsernameToken(SOAPElement usernameTokenSoapElement)
188: throws XWSSecurityException {
189:
190: setSOAPElement(usernameTokenSoapElement);
191: this .soapDoc = getOwnerDocument();
192:
193: if (!("UsernameToken".equals(getLocalName()) && XMLUtil
194: .inWsseNS(this ))) {
195: log.log(Level.SEVERE, "WSS0329.usernametoken.expected",
196: new Object[] { getLocalName() });
197:
198: throw new SecurityTokenException(
199: "Expected UsernameToken Element, but Found "
200: + getLocalName());
201: }
202:
203: boolean invalidToken = false;
204:
205: Iterator children = getChildElements();
206:
207: // Check that the first child element is a Username
208:
209: Node object = null;
210: while (children.hasNext() && !(object instanceof SOAPElement)) {
211: object = (Node) children.next();
212: }
213:
214: if ((object != null)
215: && (object.getNodeType() == Node.ELEMENT_NODE)) {
216: SOAPElement element = (SOAPElement) object;
217: if ("Username".equals(element.getLocalName())
218: && XMLUtil.inWsseNS(element)) {
219: username = element.getValue();
220: } else {
221: log
222: .log(Level.SEVERE,
223: "WSS0330.usernametoken.firstchild.mustbe.username");
224: throw new SecurityTokenException(
225: "The first child of a UsernameToken Element, should be"
226: + " a Username ");
227: }
228: } else {
229: invalidToken = true;
230: }
231:
232: while (children.hasNext()) {
233:
234: object = (Node) children.next();
235:
236: if (object.getNodeType() == Node.ELEMENT_NODE) {
237:
238: SOAPElement element = (SOAPElement) object;
239: if ("Password".equals(element.getLocalName())
240: && XMLUtil.inWsseNS(element)) {
241: String passwordType = element.getAttribute("Type");
242:
243: if (isBSP() && passwordType.length() < 1) {
244: // Type should be specified
245: log.log(Level.SEVERE,
246: "BSP4201.PasswordType.Username");
247: throw new XWSSecurityException(
248: " A wsse:UsernameToken/wsse:Password element in a SECURITY_HEADER MUST specify a Type attribute.");
249: }
250:
251: if (!"".equals(passwordType))
252: setPasswordType(passwordType);
253:
254: if (MessageConstants.PASSWORD_TEXT_NS == this .passwordType)
255: password = element.getValue();
256: else
257: passwordDigest = element.getValue();
258: } else if ("Nonce".equals(element.getLocalName())
259: && XMLUtil.inWsseNS(element)) {
260: nonce = element.getValue();
261: String encodingType = element
262: .getAttribute("EncodingType");
263: if (!"".equals(encodingType))
264: setNonceEncodingType(encodingType);
265: try {
266: decodedNonce = Base64.decode(nonce);
267: } catch (Base64DecodingException bde) {
268: log.log(Level.SEVERE,
269: "WSS0309.couldnot.decode.base64.nonce",
270: bde);
271: throw new XWSSecurityException(bde);
272: }
273: } else if ("Created".equals(element.getLocalName())
274: && XMLUtil.inWsuNS(element)) {
275: created = element.getValue();
276: } else {
277: invalidToken = true;
278: }
279: }
280: }
281:
282: if (invalidToken) {
283: log.log(Level.SEVERE, "WSS0331.invalid.usernametoken");
284: throw new SecurityTokenException(
285: "Element passed was not a SOAPElement or"
286: + " is not a proper UsernameToken");
287: }
288:
289: if (null == username) {
290: log
291: .log(Level.SEVERE,
292: "WSS0332.usernametoken.null.username");
293: throw new SecurityTokenException(
294: "Username token does not contain the username");
295: }
296: }
297:
298: /**
299: * @return Returns the username.
300: */
301: public String getUsername() {
302: return username;
303: }
304:
305: public void setUsername(String username) {
306: this .username = username;
307: }
308:
309: /**
310: * @return Returns the password which may be null meaning no password.
311: */
312: public String getPassword() {
313: return password;
314: }
315:
316: /**
317: * @return Returns the passwordType.
318: */
319: public String getPasswordType() {
320: return passwordType;
321: }
322:
323: private void setPasswordType(String passwordType)
324: throws SecurityTokenException {
325:
326: if (MessageConstants.PASSWORD_TEXT_NS.equals(passwordType)) {
327: this .passwordType = MessageConstants.PASSWORD_TEXT_NS;
328: } else if (MessageConstants.PASSWORD_DIGEST_NS
329: .equals(passwordType)) {
330: this .passwordType = MessageConstants.PASSWORD_DIGEST_NS;
331: } else {
332: log.log(Level.SEVERE, "WSS0306.invalid.passwd.type",
333: new Object[] { MessageConstants.PASSWORD_TEXT_NS,
334: MessageConstants.PASSWORD_DIGEST_NS });
335: throw new SecurityTokenException(
336: "Invalid password type. Must be one of "
337: + MessageConstants.PASSWORD_TEXT_NS
338: + " or "
339: + MessageConstants.PASSWORD_DIGEST_NS);
340: }
341: }
342:
343: /**
344: * @return Returns the Nonce Encoding type.
345: */
346: public String getNonceEncodingType() {
347: return this .nonceEncodingType;
348: }
349:
350: /**
351: * Sets the nonce encoding type.
352: * As per WSS:UserNameToken profile, for valid values, refer to
353: * wsse:BinarySecurityToken schema.
354: */
355: private void setNonceEncodingType(String nonceEncodingType) {
356:
357: if (!MessageConstants.BASE64_ENCODING_NS
358: .equals(nonceEncodingType)) {
359: log.log(Level.SEVERE, "WSS0307.nonce.enctype.invalid");
360: throw new RuntimeException("Nonce encoding type invalid");
361: }
362: this .nonceEncodingType = MessageConstants.BASE64_ENCODING_NS;
363: }
364:
365: /**
366: * @return Returns the encoded nonce. Null indicates no nonce was set.
367: */
368: public String getNonce() throws SecurityTokenException {
369: return nonce;
370: }
371:
372: /**
373: * Returns the created which may be null meaning no time of creation.
374: */
375: public String getCreated() {
376: return created;
377: }
378:
379: public String getPasswordDigest() {
380: return this .passwordDigest;
381: }
382:
383: /**
384: * Sets the password.
385: * @param passwd
386: */
387: public void setPassword(String passwd) {
388: this .password = passwd;
389: }
390:
391: /**
392: * set the nonce value.If nonce value is null then it will create one.
393: * @param nonceValue
394: */
395: public void setNonce(String nonceValue) {
396: if (nonceValue == null
397: || MessageConstants.EMPTY_STRING.equals(nonceValue)) {
398: createNonce();
399: } else {
400: this .nonce = nonceValue;
401: }
402: }
403:
404: /**
405: * set the creation time.
406: * @param time If null or empty then this method would create one.
407: */
408: public void setCreationTime(String time)
409: throws XWSSecurityException {
410: if (time == null || MessageConstants.EMPTY_STRING.equals(time)) {
411: this .created = getCreatedFromTimestamp();
412: } else {
413: this .created = time;
414: }
415: }
416:
417: public void setDigestOn() throws SecurityTokenException {
418: setPasswordType(MessageConstants.PASSWORD_DIGEST_NS);
419: }
420:
421: public SOAPElement getAsSoapElement() throws SecurityTokenException {
422:
423: if (null != delegateElement)
424: return delegateElement;
425: try {
426: setSOAPElement((SOAPElement) soapDoc.createElementNS(
427: MessageConstants.WSSE_NS,
428: MessageConstants.WSSE_PREFIX + ":UsernameToken"));
429:
430: addNamespaceDeclaration(MessageConstants.WSSE_PREFIX,
431: MessageConstants.WSSE_NS);
432:
433: if (null == username
434: || MessageConstants._EMPTY.equals(username)) {
435: log.log(Level.SEVERE,
436: "WSS0387.error.creating.usernametoken");
437: throw new SecurityTokenException("username was not set");
438: } else {
439: addChildElement("Username",
440: MessageConstants.WSSE_PREFIX).addTextNode(
441: username);
442: }
443:
444: if (password != null
445: && !MessageConstants._EMPTY.equals(password)) {
446: SOAPElement wssePassword = addChildElement("Password",
447: MessageConstants.WSSE_PREFIX);
448:
449: if (MessageConstants.PASSWORD_DIGEST_NS == passwordType) {
450: createDigest();
451: wssePassword.addTextNode(passwordDigest);
452: } else {
453: wssePassword.addTextNode(password);
454: }
455: wssePassword.setAttribute("Type", passwordType);
456: }
457:
458: if (nonce != null) {
459: SOAPElement wsseNonce = addChildElement("Nonce",
460: MessageConstants.WSSE_PREFIX);
461: wsseNonce.addTextNode(nonce);
462:
463: if (nonceEncodingType != null) {
464: wsseNonce.setAttribute("EncodingType",
465: nonceEncodingType);
466: }
467: }
468:
469: if (created != null) {
470: SOAPElement wsuCreated = addChildElement("Created",
471: MessageConstants.WSU_PREFIX,
472: MessageConstants.WSU_NS);
473: wsuCreated.addTextNode(created);
474: }
475:
476: } catch (SOAPException se) {
477: log.log(Level.SEVERE,
478: "WSS0388.error.creating.usernametoken", se
479: .getMessage());
480: throw new SecurityTokenException(
481: "There was an error creating Username Token "
482: + se.getMessage());
483: }
484: return delegateElement;
485: }
486:
487: /*
488: * Create a unique nonce. Default encoded with base64.
489: * A nonce is a random value that the sender creates
490: * to include in the username token that it sends.
491: * Nonce is an effective counter measure against replay attacks.
492: */
493: private void createNonce() {
494:
495: this .decodedNonce = new byte[18];
496: try {
497: SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
498: random.nextBytes(decodedNonce);
499: } catch (NoSuchAlgorithmException e) {
500: log.log(Level.SEVERE, "WSS0310.no.such.algorithm",
501: new Object[] { e.getMessage() });
502: throw new RuntimeException("No such algorithm found"
503: + e.getMessage());
504: }
505: if (MessageConstants.BASE64_ENCODING_NS == nonceEncodingType)
506: this .nonce = Base64.encode(decodedNonce);
507: else {
508: log.log(Level.SEVERE,
509: "WSS0389.unrecognized.nonce.encoding",
510: nonceEncodingType);
511: throw new RuntimeException("Unrecognized encoding: "
512: + nonceEncodingType);
513: }
514: }
515:
516: /*
517: * Password Digest creation.
518: * As per WSS-UsernameToken spec, if either or both of <wsse:Nonce>
519: * and <wsu:Created> are present, then they must be included in the
520: * digest as follows:
521: *
522: * Password_digest = Base64( SHA_1 (nonce + created + password) )
523: *
524: */
525: private void createDigest() throws SecurityTokenException {
526:
527: String utf8String = "";
528: if (created != null) {
529: utf8String = utf8String + created;
530: }
531:
532: // password is also optional
533: if (password != null) {
534: utf8String = utf8String + password;
535: }
536:
537: byte[] utf8Bytes;
538: try {
539: utf8Bytes = utf8String.getBytes("utf-8");
540: } catch (UnsupportedEncodingException uee) {
541: log.log(Level.SEVERE,
542: "WSS0390.unsupported.charset.exception");
543: throw new SecurityTokenException(uee);
544: }
545:
546: byte[] bytesToHash;
547: if (decodedNonce != null) {
548: bytesToHash = new byte[utf8Bytes.length + 18];
549: for (int i = 0; i < 18; i++)
550: bytesToHash[i] = decodedNonce[i];
551: for (int i = 18; i < utf8Bytes.length + 18; i++)
552: bytesToHash[i] = utf8Bytes[i - 18];
553: } else {
554: bytesToHash = utf8Bytes;
555: }
556:
557: byte[] hash;
558: try {
559: MessageDigest sha = MessageDigest.getInstance("SHA-1");
560: hash = sha.digest(bytesToHash);
561: } catch (Exception e) {
562: log.log(Level.SEVERE,
563: "WSS0311.passwd.digest.couldnot.be.created",
564: new Object[] { e.getMessage() });
565: throw new SecurityTokenException(
566: "Password Digest could not be created. "
567: + e.getMessage());
568: }
569: this .passwordDigest = Base64.encode(hash);
570: }
571:
572: private String getCreatedFromTimestamp()
573: throws XWSSecurityException {
574: Timestamp ts = new Timestamp();
575: ts.createDateTime();
576: return ts.getCreated();
577: }
578:
579: public void isBSP(boolean flag) {
580: bsp = flag;
581: }
582:
583: public boolean isBSP() {
584: return bsp;
585: }
586:
587: public String getType() {
588: return MessageConstants.USERNAME_TOKEN_NS;
589: }
590:
591: public Object getTokenValue() {
592: log.log(Level.SEVERE, "WSS0281.unsupported.operation");
593: throw new UnsupportedOperationException("Not yet implemented");
594: }
595: }
|