All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.ws.security.message.token.UsernameToken Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 1.6.19
Show newest version
/**
 * 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy