blob: 7c48f13628bba0c7f552e267c419b1bd225dd9c6 [file] [log] [blame]
/*
* Copyright 2003-2004 The Apache Software Foundation.
*
* Licensed 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;
import java.security.InvalidKeyException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
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.components.crypto.Crypto;
import org.apache.ws.security.message.token.BinarySecurity;
import org.apache.ws.security.message.token.Reference;
import org.apache.ws.security.message.token.SecurityTokenReference;
import org.apache.ws.security.message.token.X509Security;
import org.apache.ws.security.util.UUIDGenerator;
import org.apache.ws.security.util.WSSecurityUtil;
import org.apache.xml.security.keys.KeyInfo;
import org.apache.xml.security.keys.content.X509Data;
import org.apache.xml.security.keys.content.x509.XMLX509IssuerSerial;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
/**
* Builder class to build an EncryptedKey.
*
* This is especially useful in the case where the same
* <code>EncryptedKey</code> has to be used to sign and encrypt the message In
* such a situation this builder will add the <code>EncryptedKey</code> to the
* security header and we can use the information form the builder to provide to
* other builders to reference to the token
*/
public class WSSecEncryptedKey extends WSSecBase {
private static Log log = LogFactory.getLog(WSSecEncryptedKey.class
.getName());
protected Document document;
/**
* soap:Envelope element
*/
protected Element envelope = null;
/**
* Session key used as the secret in key derivation
*/
protected byte[] ephemeralKey;
/**
* Encrypted bytes of the ephemeral key
*/
protected byte[] encryptedEphemeralKey;
/**
* Remote user's alias to obtain the cert to encrypt the ephemeral key
*/
protected String encrUser = null;
/**
* Algorithm used to encrypt the ephemeral key
*/
protected String keyEncAlgo = WSConstants.KEYTRANSPORT_RSA15;
/**
* xenc:EncryptedKey element
*/
protected Element encryptedKeyElement = null;
/**
* The Token identifier of the token that the <code>DerivedKeyToken</code>
* is (or to be) derived from.
*/
protected String encKeyId = null;
/**
* BinarySecurityToken to be included in the case where BST_DIRECT_REFERENCE
* is used to refer to the asymm encryption cert
*/
protected BinarySecurity bstToken = null;
protected X509Certificate useThisCert = null;
/**
* Key size in bits
* Defaults to 128
*/
protected int keySize = 128;
/**
* Set the user name to get the encryption certificate.
*
* The public key of this certificate is used, thus no password necessary.
* The user name is a keystore alias usually.
*
* @param user
*/
public void setUserInfo(String user) {
this.user = user;
}
/**
* Get the id generated during <code>prepare()</code>.
*
* Returns the the value of wsu:Id attribute of the EncryptedKey element.
*
* @return Return the wsu:Id of this token or null if <code>prepare()</code>
* was not called before.
*/
public String getId() {
return encKeyId;
}
/**
* Prepare the ephemeralKey and the tokens required to be added to the
* security header
*
* @param doc
* The SOAP envelope as <code>Document</code>
* @param crypto
* An instance of the Crypto API to handle keystore and
* certificates
* @throws WSSecurityException
*/
public void prepare(Document doc, Crypto crypto) throws WSSecurityException {
document = doc;
/*
* Set up the ephemeral key
*/
if (this.ephemeralKey == null) {
this.ephemeralKey = generateEphemeralKey();
}
/*
* Get the certificate that contains the public key for the public key
* algorithm that will encrypt the generated symmetric (session) key.
*/
X509Certificate remoteCert = null;
if (useThisCert != null) {
remoteCert = useThisCert;
} else {
X509Certificate[] certs = crypto.getCertificates(user);
if (certs == null || certs.length <= 0) {
throw new WSSecurityException(
WSSecurityException.FAILURE,
"noUserCertsFound",
new Object[] { user, "encryption" }
);
}
remoteCert = certs[0];
}
prepareInternal(ephemeralKey, remoteCert, crypto);
}
/**
* Encrypt the symmetric key data and prepare the EncryptedKey element
*
* This method does the most work for to prepare the EncryptedKey element.
* It is also used by the WSSecEncrypt sub-class.
*
* @param keyBytes
* The bytes that represent the symmetric key
* @param remoteCert
* The certificate that contains the public key to encrypt the
* symmetric key data
* @param crypto
* An instance of the Crypto API to handle keystore and
* certificates
* @throws WSSecurityException
*/
protected void prepareInternal(byte[] keyBytes, X509Certificate remoteCert,
Crypto crypto) throws WSSecurityException {
String certUri = UUIDGenerator.getUUID();
Cipher cipher = WSSecurityUtil.getCipherInstance(keyEncAlgo);
try {
cipher.init(Cipher.ENCRYPT_MODE, remoteCert.getPublicKey());
} catch (InvalidKeyException e) {
throw new WSSecurityException(WSSecurityException.FAILED_ENCRYPTION,
null, null, e);
}
if (doDebug) {
log.debug("cipher blksize: " + cipher.getBlockSize()
+ ", symm key length: " + keyBytes.length);
}
int blockSize = cipher.getBlockSize();
if (blockSize > 0 && blockSize < keyBytes.length) {
throw new WSSecurityException(WSSecurityException.FAILURE,
"unsupportedKeyTransp",
new Object[] { "public key algorithm too weak to encrypt "
+ "symmetric key" });
}
try {
this.encryptedEphemeralKey = cipher.doFinal(keyBytes);
} catch (IllegalStateException e1) {
throw new WSSecurityException(WSSecurityException.FAILED_ENCRYPTION,
null, null, e1);
} catch (IllegalBlockSizeException e1) {
throw new WSSecurityException(WSSecurityException.FAILED_ENCRYPTION,
null, null, e1);
} catch (BadPaddingException e1) {
throw new WSSecurityException(WSSecurityException.FAILED_ENCRYPTION,
null, null, e1);
}
Text keyText = WSSecurityUtil.createBase64EncodedTextNode(document,
this.encryptedEphemeralKey);
/*
* Now we need to setup the EncryptedKey header block 1) create a
* EncryptedKey element and set a wsu:Id for it 2) Generate ds:KeyInfo
* element, this wraps the wsse:SecurityTokenReference 3) Create and set
* up the SecurityTokenReference according to the keyIdentifier parameter
* 4) Create the CipherValue element structure and insert the encrypted
* session key
*/
encryptedKeyElement = createEncryptedKey(document, keyEncAlgo);
if(this.encKeyId == null || "".equals(this.encKeyId)) {
this.encKeyId = "EncKeyId-" + UUIDGenerator.getUUID();
}
encryptedKeyElement.setAttributeNS(null, "Id", this.encKeyId);
KeyInfo keyInfo = new KeyInfo(document);
SecurityTokenReference secToken = new SecurityTokenReference(document);
switch (keyIdentifierType) {
case WSConstants.X509_KEY_IDENTIFIER:
secToken.setKeyIdentifier(remoteCert);
break;
case WSConstants.SKI_KEY_IDENTIFIER:
secToken.setKeyIdentifierSKI(remoteCert, crypto);
break;
case WSConstants.THUMBPRINT_IDENTIFIER:
secToken.setKeyIdentifierThumb(remoteCert);
break;
case WSConstants.ENCRYPTED_KEY_SHA1_IDENTIFIER:
//
// This identifier is not applicable for this case, so fall back to
// ThumbprintRSA.
//
secToken.setKeyIdentifierThumb(remoteCert);
break;
case WSConstants.ISSUER_SERIAL:
XMLX509IssuerSerial data = new XMLX509IssuerSerial(document,
remoteCert);
X509Data x509Data = new X509Data(document);
x509Data.add(data);
secToken.setX509IssuerSerial(x509Data);
break;
case WSConstants.BST_DIRECT_REFERENCE:
Reference ref = new Reference(document);
ref.setURI("#" + certUri);
bstToken = new X509Security(document);
((X509Security) bstToken).setX509Certificate(remoteCert);
bstToken.setID(certUri);
ref.setValueType(bstToken.getValueType());
secToken.setReference(ref);
break;
default:
throw new WSSecurityException(WSSecurityException.FAILURE,
"unsupportedKeyId");
}
keyInfo.addUnknownElement(secToken.getElement());
Element keyInfoElement = keyInfo.getElement();
keyInfoElement.setAttributeNS(WSConstants.XMLNS_NS, "xmlns:"
+ WSConstants.SIG_PREFIX, WSConstants.SIG_NS);
WSSecurityUtil.appendChildElement(document, encryptedKeyElement,
keyInfoElement);
Element xencCipherValue = createCipherValue(document,
encryptedKeyElement);
xencCipherValue.appendChild(keyText);
envelope = document.getDocumentElement();
envelope.setAttributeNS(WSConstants.XMLNS_NS, "xmlns:"
+ WSConstants.ENC_PREFIX, WSConstants.ENC_NS);
}
/**
* Create an ephemeral key
*
* @return an ephemeral key
* @throws WSSecurityException
*/
protected byte[] generateEphemeralKey() throws WSSecurityException {
try {
final SecureRandom r = WSSecurityUtil.resolveSecureRandom();
if (r == null) {
throw new WSSecurityException("Random generator is not initialzed.");
}
byte[] temp = new byte[this.keySize / 8];
r.nextBytes(temp);
return temp;
} catch (Exception e) {
throw new WSSecurityException(
"Error in creating the ephemeral key", e);
}
}
/**
* Create DOM subtree for <code>xenc:EncryptedKey</code>
*
* @param doc
* the SOAP envelope parent document
* @param keyTransportAlgo
* specifies which algorithm to use to encrypt the symmetric key
* @return an <code>xenc:EncryptedKey</code> element
*/
protected Element createEncryptedKey(Document doc, String keyTransportAlgo) {
Element encryptedKey = doc.createElementNS(WSConstants.ENC_NS,
WSConstants.ENC_PREFIX + ":EncryptedKey");
WSSecurityUtil.setNamespace(encryptedKey, WSConstants.ENC_NS,
WSConstants.ENC_PREFIX);
Element encryptionMethod = doc.createElementNS(WSConstants.ENC_NS,
WSConstants.ENC_PREFIX + ":EncryptionMethod");
encryptionMethod.setAttributeNS(null, "Algorithm", keyTransportAlgo);
WSSecurityUtil.appendChildElement(doc, encryptedKey, encryptionMethod);
return encryptedKey;
}
/**
* Create DOM subtree for <code>xenc:EncryptedKey</code>
*
* @param doc
* the SOAP envelope parent document
* @param keyTransportAlgo
* specifies which algorithm to use to encrypt the symmetric key
* @return an <code>xenc:EncryptedKey</code> element
* @deprecated use createEncryptedKey(Document doc, String keyTransportAlgo) instead
*/
protected Element createEnrcyptedKey(Document doc, String keyTransportAlgo) {
return createEncryptedKey(doc, keyTransportAlgo);
}
protected Element createCipherValue(Document doc, Element encryptedKey) {
Element cipherData = doc.createElementNS(WSConstants.ENC_NS,
WSConstants.ENC_PREFIX + ":CipherData");
Element cipherValue = doc.createElementNS(WSConstants.ENC_NS,
WSConstants.ENC_PREFIX + ":CipherValue");
cipherData.appendChild(cipherValue);
WSSecurityUtil.appendChildElement(doc, encryptedKey, cipherData);
return cipherValue;
}
/**
* Prepend the EncryptedKey element to the elements already in the Security
* header.
*
* The method can be called any time after <code>prepare()</code>. This
* allows to insert the EncryptedKey element at any position in the Security
* header.
*
* @param secHeader
* The security header that holds the Signature element.
*/
public void prependToHeader(WSSecHeader secHeader) {
WSSecurityUtil.prependChildElement(document, secHeader
.getSecurityHeader(), encryptedKeyElement, false);
}
/**
* Append the EncryptedKey element to the elements already in the Security
* header.
*
* The method can be called any time after <code>prepare()</code>. This
* allows to insert the EncryptedKey element at any position in the Security
* header.
*
* @param secHeader
* The security header that holds the Signature element.
*/
public void appendToHeader(WSSecHeader secHeader) {
WSSecurityUtil.appendChildElement(document, secHeader
.getSecurityHeader(), encryptedKeyElement);
}
/**
* Prepend the BinarySecurityToken to the elements already in the Security
* header.
*
* The method can be called any time after <code>prepare()</code>. This
* allows to insert the BST element at any position in the Security header.
*
* @param secHeader
* The security header that holds the BST element.
*/
public void prependBSTElementToHeader(WSSecHeader secHeader) {
if (bstToken != null) {
WSSecurityUtil.prependChildElement(document, secHeader
.getSecurityHeader(), bstToken.getElement(), false);
}
bstToken = null;
}
/**
* Append the BinarySecurityToken to the elements already in the Security
* header.
*
* The method can be called any time after <code>prepare()</code>. This
* allows to insert the BST element at any position in the Security header.
*
* @param secHeader
* The security header that holds the BST element.
*/
public void appendBSTElementToHeader(WSSecHeader secHeader) {
if (bstToken != null) {
WSSecurityUtil.appendChildElement(document, secHeader
.getSecurityHeader(), bstToken.getElement());
}
bstToken = null;
}
/**
* @return Returns the ephemeralKey.
*/
public byte[] getEphemeralKey() {
return ephemeralKey;
}
/**
* Set the X509 Certificate to use for encryption.
*
* If this is set <b>and</b> the key identifier is set to
* <code>DirectReference</code> then use this certificate to get the
* public key for encryption.
*
* @param cert
* is the X509 certificate to use for encryption
*/
public void setUseThisCert(X509Certificate cert) {
useThisCert = cert;
}
/**
* @return Returns the encryptedKeyElement.
*/
public Element getEncryptedKeyElement() {
return encryptedKeyElement;
}
/**
* Set the encrypted key element when a pre prepared encrypted key is used
* @param encryptedKeyElement EncryptedKey element of the encrypted key used
*/
public void setEncryptedKeyElement(Element encryptedKeyElement) {
this.encryptedKeyElement = encryptedKeyElement;
}
/**
* @return Returns the BinarySecurityToken element.
*/
public Element getBinarySecurityTokenElement() {
if(this.bstToken != null) {
return this.bstToken.getElement();
} else {
return null;
}
}
public void setKeySize(int keySize) throws WSSecurityException {
if(keySize < 64) {
//Minimum size has to be 64 bits - E.g. A DES key
throw new WSSecurityException("invalidKeySize");
}
this.keySize = keySize;
}
public void setKeyEncAlgo(String keyEncAlgo) {
this.keyEncAlgo = keyEncAlgo;
}
/**
* @param ephemeralKey The ephemeralKey to set.
*/
public void setEphemeralKey(byte[] ephemeralKey) {
this.ephemeralKey = ephemeralKey;
}
/**
* Get the id of the BSt generated during <code>prepare()</code>.
*
* @return Returns the the value of wsu:Id attribute of the
* BinaruSecurityToken element.
*/
public String getBSTTokenId() {
if(this.bstToken == null) {
return null;
}
return this.bstToken.getID();
}
/**
* @param document The document to set.
*/
public void setDocument(Document document) {
this.document = document;
}
/**
* @param encKeyId The encKeyId to set.
*/
public void setEncKeyId(String encKeyId) {
this.encKeyId = encKeyId;
}
public boolean isCertSet() {
return (useThisCert == null ? true : false) ;
}
public byte[] getEncryptedEphemeralKey() {
return encryptedEphemeralKey;
}
}