org.apache.ws.security.message.token.UsernameToken Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of wss4j Show documentation
Show all versions of wss4j Show documentation
The Apache WSS4J project provides a Java implementation of the primary security standards
for Web Services, namely the OASIS Web Services Security (WS-Security) specifications
from the OASIS Web Services Security TC.
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.ws.security.message.token;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.WSSecurityException;
import org.apache.ws.security.util.DOM2Writer;
import org.apache.ws.security.util.WSSecurityUtil;
import org.apache.ws.security.util.XmlSchemaDateFormat;
import org.apache.ws.security.util.Base64;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.namespace.QName;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.TimeZone;
/**
* UsernameToken according to WS Security specifications, UsernameToken profile.
*
* Enhanced to support digest password type for username token signature
* Enhanced to support passwordless usernametokens as allowed by spec.
*
* @author Davanum Srinivas ([email protected])
* @author Werner Dittmann ([email protected])
*/
public class UsernameToken {
public static final String BASE64_ENCODING = WSConstants.SOAPMESSAGE_NS + "#Base64Binary";
public static final String PASSWORD_TYPE = "passwordType";
public static final int DEFAULT_ITERATION = 1000;
public static final QName TOKEN =
new QName(WSConstants.WSSE_NS, WSConstants.USERNAME_TOKEN_LN);
private static final Log LOG = LogFactory.getLog(UsernameToken.class.getName());
private static final boolean DO_DEBUG = LOG.isDebugEnabled();
private static SecureRandom random;
protected Element element = null;
protected Element elementUsername = null;
protected Element elementPassword = null;
protected Element elementNonce = null;
protected Element elementCreated = null;
protected Element elementSalt = null;
protected Element elementIteration = null;
protected String passwordType = null;
protected boolean hashed = true;
private String rawPassword; // enhancement by Alberto Coletti
private boolean passwordsAreEncoded = false;
static {
try {
random = WSSecurityUtil.resolveSecureRandom();
} catch (NoSuchAlgorithmException e) {
if (DO_DEBUG) {
LOG.debug(e.getMessage(), e);
}
}
}
/**
* Constructs a UsernameToken
object and parses the
* wsse:UsernameToken
element to initialize it.
*
* @param elem the wsse:UsernameToken
element that contains
* the UsernameToken data
* @throws WSSecurityException
*/
public UsernameToken(Element elem) throws WSSecurityException {
this (elem, false);
}
/**
* Constructs a UsernameToken
object and parses the
* wsse:UsernameToken
element to initialize it.
*
* @param elem the wsse:UsernameToken
element that contains
* the UsernameToken data
* @param allowNamespaceQualifiedPasswordTypes whether to allow (wsse)
* namespace qualified password types or not (for interop with WCF)
* @throws WSSecurityException
*/
public UsernameToken(
Element elem,
boolean allowNamespaceQualifiedPasswordTypes
) throws WSSecurityException {
element = elem;
QName el = new QName(element.getNamespaceURI(), element.getLocalName());
if (!el.equals(TOKEN)) {
throw new WSSecurityException(
WSSecurityException.INVALID_SECURITY_TOKEN,
"badTokenType00",
new Object[] {el}
);
}
elementUsername =
(Element) WSSecurityUtil.getDirectChild(
element, WSConstants.USERNAME_LN, WSConstants.WSSE_NS
);
elementPassword =
(Element) WSSecurityUtil.getDirectChild(
element, WSConstants.PASSWORD_LN, WSConstants.WSSE_NS
);
elementNonce =
(Element) WSSecurityUtil.getDirectChild(
element, WSConstants.NONCE_LN, WSConstants.WSSE_NS
);
elementCreated =
(Element) WSSecurityUtil.getDirectChild(
element, WSConstants.CREATED_LN, WSConstants.WSU_NS
);
elementSalt =
(Element) WSSecurityUtil.getDirectChild(
element, WSConstants.SALT_LN, WSConstants.WSSE11_NS
);
elementIteration =
(Element) WSSecurityUtil.getDirectChild(
element, WSConstants.ITERATION_LN, WSConstants.WSSE11_NS
);
if (elementUsername == null) {
throw new WSSecurityException(
WSSecurityException.INVALID_SECURITY_TOKEN,
"badTokenType01",
new Object[] {el}
);
}
hashed = false;
if (elementSalt != null) {
//
// If the UsernameToken is to be used for key derivation, the (1.1)
// spec says that it cannot contain a password, and it must contain
// an Iteration element
//
if (elementPassword != null || elementIteration == null) {
throw new WSSecurityException(
WSSecurityException.INVALID_SECURITY_TOKEN,
"badTokenType01",
new Object[] {el}
);
}
return;
}
if (elementPassword != null) {
if (elementPassword.hasAttribute(WSConstants.PASSWORD_TYPE_ATTR)) {
passwordType = elementPassword.getAttribute(WSConstants.PASSWORD_TYPE_ATTR);
} else if (elementPassword.hasAttributeNS(
WSConstants.WSSE_NS, WSConstants.PASSWORD_TYPE_ATTR)
) {
if (allowNamespaceQualifiedPasswordTypes) {
passwordType =
elementPassword.getAttributeNS(
WSConstants.WSSE_NS, WSConstants.PASSWORD_TYPE_ATTR
);
} else {
throw new WSSecurityException(
WSSecurityException.INVALID_SECURITY_TOKEN,
"badTokenType01",
new Object[] {el}
);
}
}
}
if (passwordType != null
&& passwordType.equals(WSConstants.PASSWORD_DIGEST)) {
hashed = true;
if (elementNonce == null || elementCreated == null) {
throw new WSSecurityException(
WSSecurityException.INVALID_SECURITY_TOKEN,
"badTokenType01",
new Object[] {el}
);
}
}
}
/**
* Constructs a UsernameToken
object according to the defined
* parameters. This constructs set the password encoding to
* {@link WSConstants#PASSWORD_DIGEST}
*
* @param doc the SOAP envelope as Document
*/
public UsernameToken(boolean milliseconds, Document doc) {
this(milliseconds, doc, WSConstants.PASSWORD_DIGEST);
}
/**
* Constructs a UsernameToken
object according to the defined
* parameters.
*
* @param doc the SOAP envelope as Document
* @param pwType the required password encoding, either
* {@link WSConstants#PASSWORD_DIGEST} or
* {@link WSConstants#PASSWORD_TEXT} or
* {@link WSConstants#PW_NONE} null
if no
* password required
*/
public UsernameToken(boolean milliseconds, Document doc, String pwType) {
element =
doc.createElementNS(WSConstants.WSSE_NS, "wsse:" + WSConstants.USERNAME_TOKEN_LN);
WSSecurityUtil.setNamespace(element, WSConstants.WSSE_NS, WSConstants.WSSE_PREFIX);
elementUsername =
doc.createElementNS(WSConstants.WSSE_NS, "wsse:" + WSConstants.USERNAME_LN);
elementUsername.appendChild(doc.createTextNode(""));
element.appendChild(elementUsername);
if (pwType != null) {
elementPassword =
doc.createElementNS(WSConstants.WSSE_NS, "wsse:" + WSConstants.PASSWORD_LN);
elementPassword.appendChild(doc.createTextNode(""));
element.appendChild(elementPassword);
hashed = false;
passwordType = pwType;
if (passwordType.equals(WSConstants.PASSWORD_DIGEST)) {
hashed = true;
addNonce(doc);
addCreated(milliseconds, doc);
}
}
}
/**
* Creates and adds a Nonce element to this UsernameToken
*/
public void addNonce(Document doc) {
if (elementNonce != null) {
return;
}
byte[] nonceValue = new byte[16];
random.nextBytes(nonceValue);
elementNonce = doc.createElementNS(WSConstants.WSSE_NS, "wsse:" + WSConstants.NONCE_LN);
elementNonce.appendChild(doc.createTextNode(Base64.encode(nonceValue)));
elementNonce.setAttributeNS(null, "EncodingType", BASE64_ENCODING);
element.appendChild(elementNonce);
}
/**
* Creates and adds a Created element to this UsernameToken
*/
public void addCreated(boolean milliseconds, Document doc) {
if (elementCreated != null) {
return;
}
DateFormat zulu = null;
if (milliseconds) {
zulu = new XmlSchemaDateFormat();
} else {
zulu = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
zulu.setTimeZone(TimeZone.getTimeZone("UTC"));
}
Calendar rightNow = Calendar.getInstance();
elementCreated =
doc.createElementNS(
WSConstants.WSU_NS,WSConstants.WSU_PREFIX + ":" + WSConstants.CREATED_LN
);
WSSecurityUtil.setNamespace(element, WSConstants.WSU_NS, WSConstants.WSU_PREFIX);
elementCreated.appendChild(doc.createTextNode(zulu.format(rightNow.getTime())));
element.appendChild(elementCreated);
}
/**
* Adds and optionally creates a Salt element to this UsernameToken.
*
* If the saltValue
is null
the the method
* generates a new salt. Otherwise it uses the the given value.
*
* @param doc The Document for the UsernameToken
* @param saltValue The salt to add, if null generate a new salt value
* @param mac If true
then an optionally generated value is
* usable for a MAC
* @return Returns the added salt
*/
public byte[] addSalt(Document doc, byte[] saltValue, boolean mac) {
if (saltValue == null) {
saltValue = generateSalt(mac);
}
elementSalt =
doc.createElementNS(
WSConstants.WSSE11_NS, WSConstants.WSSE11_PREFIX + ":" + WSConstants.SALT_LN
);
WSSecurityUtil.setNamespace(this.element, WSConstants.WSSE11_NS, WSConstants.WSSE11_PREFIX);
elementSalt.appendChild(doc.createTextNode(Base64.encode(saltValue)));
element.appendChild(elementSalt);
return saltValue;
}
/**
* Creates and adds a Iteration element to this UsernameToken
*/
public void addIteration(Document doc, int iteration) {
String text = "" + iteration;
elementIteration =
doc.createElementNS(
WSConstants.WSSE11_NS, WSConstants.WSSE11_PREFIX + ":" + WSConstants.ITERATION_LN
);
WSSecurityUtil.setNamespace(element, WSConstants.WSSE11_NS, WSConstants.WSSE11_PREFIX);
this.elementIteration.appendChild(doc.createTextNode(text));
element.appendChild(elementIteration);
}
/**
* Get the user name.
*
* @return the data from the user name element.
*/
public String getName() {
return nodeString(elementUsername);
}
/**
* Set the user name.
*
* @param name sets a text node containing the use name into the user name
* element.
*/
public void setName(String name) {
Text node = getFirstNode(elementUsername);
node.setData(name);
}
/**
* Get the nonce.
*
* @return the data from the nonce element.
*/
public String getNonce() {
return nodeString(elementNonce);
}
/**
* Get the created timestamp.
*
* @return the data from the created time element.
*/
public String getCreated() {
return nodeString(elementCreated);
}
/**
* Gets the password string. This is the password as it is in the password
* element of a username token. Thus it can be either plain text or the
* password digest value.
*
* @return the password string or null
if no such node exists.
*/
public String getPassword() {
String password = nodeString(elementPassword);
// See WSS-219
if (password == null && elementPassword != null) {
return "";
}
return password;
}
/**
* Get the Salt value of this UsernameToken.
*
* @return Returns the binary Salt value or null
if no Salt
* value is available in the username token.
* @throws WSSecurityException
*/
public byte[] getSalt() throws WSSecurityException {
String salt = nodeString(elementSalt);
if (salt != null) {
return Base64.decode(nodeString(elementSalt));
}
return null;
}
/**
* Get the Iteration value of this UsernameToken.
*
* @return Returns the Iteration value. If no Iteration was specified in the
* username token the default value according to the specification
* is returned.
*/
public int getIteration() {
String iter = nodeString(elementIteration);
if (iter != null) {
return Integer.parseInt(iter);
}
return DEFAULT_ITERATION;
}
/**
* Get the hashed indicator. If the indicator is true> the password of the
* UsernameToken
was encoded using {@link WSConstants#PASSWORD_DIGEST}
*
* @return the hashed indicator.
*/
public boolean isHashed() {
return hashed;
}
/**
* @return Returns the passwordType.
*/
public String getPasswordType() {
return passwordType;
}
/**
* Sets the password string. This function sets the password in the
* UsernameToken
either as plain text or encodes the password
* according to the WS Security specifications, UsernameToken profile, into
* a password digest.
*
* @param pwd the password to use
*/
public void setPassword(String pwd) {
if (pwd == null) {
if (passwordType != null) {
throw new IllegalArgumentException("pwd == null but a password is needed");
} else {
// Ignore setting the password.
return;
}
}
rawPassword = pwd; // enhancement by Alberto coletti
Text node = getFirstNode(elementPassword);
try {
if (!hashed) {
node.setData(pwd);
elementPassword.setAttributeNS(null, "Type", WSConstants.PASSWORD_TEXT);
} else {
if (passwordsAreEncoded) {
node.setData(doPasswordDigest(getNonce(), getCreated(), Base64.decode(pwd)));
} else {
node.setData(doPasswordDigest(getNonce(), getCreated(), pwd));
}
elementPassword.setAttributeNS(null, "Type", WSConstants.PASSWORD_DIGEST);
}
} catch (Exception e) {
if (DO_DEBUG) {
LOG.debug(e.getMessage(), e);
}
}
}
/**
* Set the raw (plain text) password used to compute secret key.
*
* @param newRawPassword the raw password to set
*/
public void setRawPassword(String newRawPassword) {
rawPassword = newRawPassword;
}
/**
* Get the raw (plain text) password used to compute secret key.
*/
public String getRawPassword() {
return rawPassword;
}
/**
* @param passwordsAreEncoded whether passwords are encoded
*/
public void setPasswordsAreEncoded(boolean passwordsAreEncoded) {
this.passwordsAreEncoded = passwordsAreEncoded;
}
/**
* @return whether passwords are encoded
*/
public boolean getPasswordsAreEncoded() {
return passwordsAreEncoded;
}
public static String doPasswordDigest(String nonce, String created, byte[] password) {
String passwdDigest = null;
try {
byte[] b1 = nonce != null ? Base64.decode(nonce) : new byte[0];
byte[] b2 = created != null ? created.getBytes("UTF-8") : new byte[0];
byte[] b3 = password;
byte[] b4 = new byte[b1.length + b2.length + b3.length];
int offset = 0;
System.arraycopy(b1, 0, b4, offset, b1.length);
offset += b1.length;
System.arraycopy(b2, 0, b4, offset, b2.length);
offset += b2.length;
System.arraycopy(b3, 0, b4, offset, b3.length);
MessageDigest sha = MessageDigest.getInstance("SHA-1");
sha.reset();
sha.update(b4);
passwdDigest = Base64.encode(sha.digest());
} catch (Exception e) {
if (DO_DEBUG) {
LOG.debug(e.getMessage(), e);
}
}
return passwdDigest;
}
public static String doPasswordDigest(String nonce, String created, String password) {
String passwdDigest = null;
try {
passwdDigest = doPasswordDigest(nonce, created, password.getBytes("UTF-8"));
} catch (Exception e) {
if (DO_DEBUG) {
LOG.debug(e.getMessage(), e);
}
}
return passwdDigest;
}
/**
* Returns the first text node of an element.
*
* @param e the element to get the node from
* @return the first text node or null
if node is null or is
* not a text node
*/
private Text getFirstNode(Element e) {
Node node = e.getFirstChild();
return ((node != null) && node instanceof Text) ? (Text) node : null;
}
/**
* Returns the data of an element as String or null if either the the element
* does not contain a Text node or the node is empty.
*
* @param e DOM element
* @return Element text node data as String
*/
private String nodeString(Element e) {
if (e != null) {
Text node = getFirstNode(e);
if (node != null) {
return node.getData();
}
}
return null;
}
/**
* Returns the dom element of this UsernameToken
object.
*
* @return the wsse:UsernameToken
element
*/
public Element getElement() {
return element;
}
/**
* Returns the string representation of the token.
*
* @return a XML string representation
*/
public String toString() {
return DOM2Writer.nodeToString((Node) this.element);
}
/**
* Gets the id.
*
* @return the value of the wsu:Id
attribute of this username
* token
*/
public String getID() {
return element.getAttributeNS(WSConstants.WSU_NS, "Id");
}
/**
* Set the id of this username token.
*
* @param id
* the value for the wsu:Id
attribute of this
* username token
*/
public void setID(String id) {
String prefix =
WSSecurityUtil.setNamespace(element, WSConstants.WSU_NS, WSConstants.WSU_PREFIX);
element.setAttributeNS(WSConstants.WSU_NS, prefix + ":Id", id);
}
/**
* Gets the secret key as per WS-Trust spec. This method uses default setting
* to generate the secret key. These default values are suitable for .NET
* WSE.
*
* @return a secret key constructed from information contained in this
* username token
*/
public byte[] getSecretKey() {
return getSecretKey(WSConstants.WSE_DERIVED_KEY_LEN, WSConstants.LABEL_FOR_DERIVED_KEY);
}
/**
* Gets the secret key as per WS-Trust spec. This method uses default setting
* to generate the secret key. These default values are suitable for .NET
* WSE.
*
* @return a secret key constructed from information contained in this
* username token
*/
public byte[] getSecretKey(int keylen) {
return getSecretKey(keylen, WSConstants.LABEL_FOR_DERIVED_KEY);
}
/**
* Gets the secret key as per WS-Trust spec.
*
* @param keylen How many bytes to generate for the key
* @param labelString the label used to generate the seed
* @return a secret key constructed from information contained in this
* username token
*/
public byte[] getSecretKey(int keylen, String labelString) {
byte[] key = null;
try {
Mac mac = Mac.getInstance("HMACSHA1");
byte[] password;
if (passwordsAreEncoded) {
password = Base64.decode(rawPassword);
} else {
password = rawPassword.getBytes("UTF-8"); // enhancement by Alberto Coletti
}
byte[] label = labelString.getBytes("UTF-8");
byte[] nonce = Base64.decode(getNonce());
byte[] created = getCreated().getBytes("UTF-8");
byte[] seed = new byte[label.length + nonce.length + created.length];
int offset = 0;
System.arraycopy(label, 0, seed, offset, label.length);
offset += label.length;
System.arraycopy(nonce, 0, seed, offset, nonce.length);
offset += nonce.length;
System.arraycopy(created, 0, seed, offset, created.length);
key = P_hash(password, seed, mac, keylen);
if (LOG.isDebugEnabled()) {
LOG.debug("password :" + Base64.encode(password));
LOG.debug("label :" + Base64.encode(label));
LOG.debug("nonce :" + Base64.encode(nonce));
LOG.debug("created :" + Base64.encode(created));
LOG.debug("seed :" + Base64.encode(seed));
LOG.debug("Key :" + Base64.encode(key));
}
} catch (Exception e) {
if (DO_DEBUG) {
LOG.debug(e.getMessage(), e);
}
return null;
}
return key;
}
/**
* This static method generates a derived key as defined in WSS Username
* Token Profile.
*
* @param password The password to include in the key generation
* @param salt The Salt value
* @param iteration The Iteration value. If zero (0) is given the method uses the
* default value
* @return Returns the derived key a byte array
* @throws WSSecurityException
*/
public static byte[] generateDerivedKey(
byte[] password,
byte[] salt,
int iteration
) throws WSSecurityException {
if (iteration == 0) {
iteration = DEFAULT_ITERATION;
}
byte[] pwSalt = new byte[salt.length + password.length];
System.arraycopy(password, 0, pwSalt, 0, password.length);
System.arraycopy(salt, 0, pwSalt, password.length, salt.length);
MessageDigest sha = null;
try {
sha = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
if (DO_DEBUG) {
LOG.debug(e.getMessage(), e);
}
throw new WSSecurityException(
WSSecurityException.FAILURE, "noSHA1availabe", null, e
);
}
sha.reset();
//
// Make the first hash round with start value
//
byte[] K = sha.digest(pwSalt);
//
// Perform the 1st up to iteration-1 hash rounds
//
for (int i = 1; i < iteration; i++) {
K = sha.digest(K);
}
return K;
}
/**
* This static method generates a derived key as defined in WSS Username
* Token Profile.
*
* @param password The password to include in the key generation
* @param salt The Salt value
* @param iteration The Iteration value. If zero (0) is given the method uses the
* default value
* @return Returns the derived key a byte array
* @throws WSSecurityException
*/
public static byte[] generateDerivedKey(
String password,
byte[] salt,
int iteration
) throws WSSecurityException {
try {
return generateDerivedKey(password.getBytes("UTF-8"), salt, iteration);
} catch (final java.io.UnsupportedEncodingException e) {
if (DO_DEBUG) {
LOG.debug(e.getMessage(), e);
}
throw new WSSecurityException("Unable to convert password to UTF-8", e);
}
}
/**
* This method gets a derived key as defined in WSS Username Token Profile.
*
* @return Returns the derived key as a byte array
* @throws WSSecurityException
*/
public byte[] getDerivedKey() throws WSSecurityException {
int iteration = getIteration();
byte[] salt = getSalt();
if (passwordsAreEncoded) {
return generateDerivedKey(Base64.decode(rawPassword), salt, iteration);
} else {
return generateDerivedKey(rawPassword, salt, iteration);
}
}
/**
* Return whether the UsernameToken represented by this class is to be used
* for key derivation as per the UsernameToken Profile 1.1. It does this by
* checking that the username token has salt and iteration values.
*
* @throws WSSecurityException
*/
public boolean isDerivedKey() throws WSSecurityException {
if (elementSalt != null && elementIteration != null) {
return true;
}
return false;
}
/**
* This static method generates a 128 bit salt value as defined in WSS
* Username Token Profile.
*
* @param useForMac If true
define the Salt for use in a MAC
* @return Returns the 128 bit salt value as byte array
*/
public static byte[] generateSalt(boolean useForMac) {
byte[] saltValue = new byte[16];
random.nextBytes(saltValue);
if (useForMac) {
saltValue[0] = 0x01;
} else {
saltValue[0] = 0x02;
}
return saltValue;
}
/**
* P_hash as defined in RFC 2246 for TLS.
*
* @param secret is the key for the HMAC
* @param seed the seed value to start the generation - A(0)
* @param mac the HMAC algorithm
* @param required number of bytes to generate
* @return a byte array that contains a secret key
* @throws Exception
*/
private static byte[] P_hash(
byte[] secret,
byte[] seed,
Mac mac,
int required
) throws Exception {
byte[] out = new byte[required];
int offset = 0, tocpy;
byte[] A, tmp;
//
// A(0) is the seed
//
A = seed;
SecretKeySpec key = new SecretKeySpec(secret, "HMACSHA1");
mac.init(key);
while (required > 0) {
mac.update(A);
A = mac.doFinal();
mac.update(A);
mac.update(seed);
tmp = mac.doFinal();
tocpy = min(required, tmp.length);
System.arraycopy(tmp, 0, out, offset, tocpy);
offset += tocpy;
required -= tocpy;
}
return out;
}
/**
* helper method.
*
* @param a
* @param b
* @return
*/
private static int min(int a, int b) {
return (a > b) ? b : a;
}
}