/** * 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.cxf.rs.security.xml; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.Base64; import java.util.logging.Logger; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.common.util.StringUtils; import org.apache.cxf.helpers.DOMUtils; import org.apache.cxf.message.Message; import org.apache.cxf.message.MessageUtils; import org.apache.cxf.rs.security.common.CryptoLoader; import org.apache.cxf.rs.security.common.RSSecurityUtils; import org.apache.cxf.rt.security.SecurityConstants; import org.apache.cxf.rt.security.utils.SecurityUtils; import org.apache.wss4j.common.crypto.Crypto; import org.apache.wss4j.common.ext.WSSecurityException; import org.apache.wss4j.common.token.DOMX509Data; import org.apache.wss4j.common.token.DOMX509IssuerSerial; import org.apache.wss4j.common.util.KeyUtils; import org.apache.wss4j.common.util.XMLUtils; import org.apache.xml.security.encryption.XMLCipher; import org.apache.xml.security.stax.impl.util.IDGenerator; import org.apache.xml.security.utils.EncryptionConstants; public class XmlEncOutInterceptor extends AbstractXmlSecOutInterceptor { private static final Logger LOG = LogUtils.getL7dLogger(XmlEncOutInterceptor.class); private static final String DEFAULT_RETRIEVAL_METHOD_TYPE = "http://www.w3.org/2001/04/xmlenc#EncryptedKey"; private boolean encryptSymmetricKey = true; private SecretKey symmetricKey; private EncryptionProperties encProps = new EncryptionProperties(); public XmlEncOutInterceptor() { addAfter(XmlSigOutInterceptor.class.getName()); } public void setEncryptionProperties(EncryptionProperties props) { this.encProps = props; } public void setKeyIdentifierType(String type) { encProps.setEncryptionKeyIdType(type); } public void setSymmetricEncAlgorithm(String algo) { if (!(algo.startsWith(EncryptionConstants.EncryptionSpecNS) || algo.startsWith(EncryptionConstants.EncryptionSpec11NS))) { algo = EncryptionConstants.EncryptionSpecNS + algo; } encProps.setEncryptionSymmetricKeyAlgo(algo); } public void setKeyEncAlgorithm(String algo) { encProps.setEncryptionKeyTransportAlgo(algo); } public void setDigestAlgorithm(String algo) { encProps.setEncryptionDigestAlgo(algo); } protected Document processDocument(Message message, Document payloadDoc) throws Exception { return encryptDocument(message, payloadDoc); } protected Document encryptDocument(Message message, Document payloadDoc) throws Exception { String symEncAlgo = encProps.getEncryptionSymmetricKeyAlgo() == null ? XMLCipher.AES_256 : encProps.getEncryptionSymmetricKeyAlgo(); byte[] secretKey = getSymmetricKey(symEncAlgo); Document encryptedDataDoc = DOMUtils.createDocument(); Element encryptedDataElement = createEncryptedDataElement(encryptedDataDoc, symEncAlgo); if (encryptSymmetricKey) { X509Certificate receiverCert = null; String userName = (String)SecurityUtils.getSecurityPropertyValue(SecurityConstants.ENCRYPT_USERNAME, message); if (RSSecurityUtils.USE_REQUEST_SIGNATURE_CERT.equals(userName) && !MessageUtils.isRequestor(message)) { receiverCert = (X509Certificate)message.getExchange().getInMessage().get( AbstractXmlSecInHandler.SIGNING_CERT); if (receiverCert == null) { receiverCert = (X509Certificate)message.getExchange().getInMessage().get( SecurityConstants.ENCRYPT_CERT); } } else { CryptoLoader loader = new CryptoLoader(); Crypto crypto = loader.getCrypto(message, SecurityConstants.ENCRYPT_CRYPTO, SecurityConstants.ENCRYPT_PROPERTIES); userName = RSSecurityUtils.getUserName(crypto, userName); if (StringUtils.isEmpty(userName)) { throw new Exception("User name is not available"); } receiverCert = getReceiverCertificateFromCrypto(crypto, userName); } if (receiverCert == null) { throw new Exception("Receiver certificate is not available"); } String keyEncAlgo = encProps.getEncryptionKeyTransportAlgo() == null ? XMLCipher.RSA_OAEP : encProps.getEncryptionKeyTransportAlgo(); String digestAlgo = encProps.getEncryptionDigestAlgo(); byte[] encryptedSecretKey = encryptSymmetricKey(secretKey, receiverCert, keyEncAlgo, digestAlgo); addEncryptedKeyElement(encryptedDataElement, receiverCert, encryptedSecretKey, keyEncAlgo, digestAlgo); } // encrypt payloadDoc XMLCipher xmlCipher = EncryptionUtils.initXMLCipher(symEncAlgo, XMLCipher.ENCRYPT_MODE, symmetricKey); Document result = xmlCipher.doFinal(payloadDoc, payloadDoc.getDocumentElement(), false); NodeList list = result.getElementsByTagNameNS(ENC_NS, "CipherValue"); if (list.getLength() != 1) { throw new Exception("Payload CipherData is missing"); } String cipherText = ((Element)list.item(0)).getTextContent().trim(); Element cipherValue = createCipherValue(encryptedDataDoc, encryptedDataDoc.getDocumentElement()); cipherValue.appendChild(encryptedDataDoc.createTextNode(cipherText)); //StaxUtils.copy(new DOMSource(encryptedDataDoc), System.out); return encryptedDataDoc; } private byte[] getSymmetricKey(String symEncAlgo) throws Exception { synchronized (this) { if (symmetricKey == null) { KeyGenerator keyGen = KeyUtils.getKeyGenerator(symEncAlgo); symmetricKey = keyGen.generateKey(); } return symmetricKey.getEncoded(); } } private X509Certificate getReceiverCertificateFromCrypto(Crypto crypto, String user) throws Exception { X509Certificate[] certs = RSSecurityUtils.getCertificates(crypto, user); return certs[0]; } // Apache Security XMLCipher does not support // Certificates for encrypting the keys protected byte[] encryptSymmetricKey(byte[] keyBytes, X509Certificate remoteCert, String keyEncAlgo, String digestAlgo) throws WSSecurityException { Cipher cipher = EncryptionUtils.initCipherWithCert( keyEncAlgo, digestAlgo, Cipher.ENCRYPT_MODE, remoteCert ); int blockSize = cipher.getBlockSize(); if (blockSize > 0 && blockSize < keyBytes.length) { String message = "Public key algorithm too weak to encrypt symmetric key"; LOG.severe(message); throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "unsupportedKeyTransp", new Object[] {message} ); } byte[] encryptedEphemeralKey = null; try { encryptedEphemeralKey = cipher.doFinal(keyBytes); } catch (IllegalStateException | IllegalBlockSizeException | BadPaddingException ex) { throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_ENCRYPTION, ex ); } return encryptedEphemeralKey; } private void addEncryptedKeyElement(Element encryptedDataElement, X509Certificate cert, byte[] encryptedKey, String keyEncAlgo, String digestAlgo) throws Exception { Document doc = encryptedDataElement.getOwnerDocument(); String encodedKey = Base64.getMimeEncoder().encodeToString(encryptedKey); Element encryptedKeyElement = createEncryptedKeyElement(doc, keyEncAlgo, digestAlgo); String encKeyId = IDGenerator.generateID("EK-"); encryptedKeyElement.setAttributeNS(null, "Id", encKeyId); Element keyInfoElement = createKeyInfoElement(doc, cert); encryptedKeyElement.appendChild(keyInfoElement); Element xencCipherValue = createCipherValue(doc, encryptedKeyElement); xencCipherValue.appendChild(doc.createTextNode(encodedKey)); Element topKeyInfoElement = doc.createElementNS(SIG_NS, SIG_PREFIX + ":KeyInfo"); Element retrievalMethodElement = doc.createElementNS(SIG_NS, SIG_PREFIX + ":RetrievalMethod"); retrievalMethodElement.setAttribute("Type", DEFAULT_RETRIEVAL_METHOD_TYPE); topKeyInfoElement.appendChild(retrievalMethodElement); topKeyInfoElement.appendChild(encryptedKeyElement); encryptedDataElement.appendChild(topKeyInfoElement); } protected Element createCipherValue(Document doc, Element encryptedKey) { Element cipherData = doc.createElementNS(ENC_NS, ENC_PREFIX + ":CipherData"); Element cipherValue = doc.createElementNS(ENC_NS, ENC_PREFIX + ":CipherValue"); cipherData.appendChild(cipherValue); encryptedKey.appendChild(cipherData); return cipherValue; } private Element createKeyInfoElement(Document encryptedDataDoc, X509Certificate remoteCert) throws Exception { Element keyInfoElement = encryptedDataDoc.createElementNS(SIG_NS, SIG_PREFIX + ":KeyInfo"); String keyIdType = encProps.getEncryptionKeyIdType() == null ? RSSecurityUtils.X509_CERT : encProps.getEncryptionKeyIdType(); Node keyIdentifierNode = null; if (keyIdType.equals(RSSecurityUtils.X509_CERT)) { byte data[] = null; try { data = remoteCert.getEncoded(); } catch (CertificateEncodingException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.SECURITY_TOKEN_UNAVAILABLE, e, "encodeError" ); } Text text = encryptedDataDoc.createTextNode(Base64.getMimeEncoder().encodeToString(data)); Element cert = encryptedDataDoc.createElementNS(SIG_NS, SIG_PREFIX + ":X509Certificate"); cert.appendChild(text); Element x509Data = encryptedDataDoc.createElementNS(SIG_NS, SIG_PREFIX + ":X509Data"); x509Data.appendChild(cert); keyIdentifierNode = x509Data; } else if (keyIdType.equals(RSSecurityUtils.X509_ISSUER_SERIAL)) { String issuer = remoteCert.getIssuerDN().getName(); java.math.BigInteger serialNumber = remoteCert.getSerialNumber(); DOMX509IssuerSerial domIssuerSerial = new DOMX509IssuerSerial( encryptedDataDoc, issuer, serialNumber ); DOMX509Data domX509Data = new DOMX509Data(encryptedDataDoc, domIssuerSerial); keyIdentifierNode = domX509Data.getElement(); } else { throw new Exception("Unsupported key identifier:" + keyIdType); } keyInfoElement.appendChild(keyIdentifierNode); return keyInfoElement; } protected Element createEncryptedKeyElement(Document encryptedDataDoc, String keyEncAlgo, String digestAlgo) { Element encryptedKey = encryptedDataDoc.createElementNS(ENC_NS, ENC_PREFIX + ":EncryptedKey"); Element encryptionMethod = encryptedDataDoc.createElementNS(ENC_NS, ENC_PREFIX + ":EncryptionMethod"); encryptionMethod.setAttributeNS(null, "Algorithm", keyEncAlgo); if (digestAlgo != null) { Element digestMethod = encryptedDataDoc.createElementNS(SIG_NS, SIG_PREFIX + ":DigestMethod"); digestMethod.setAttributeNS(null, "Algorithm", digestAlgo); encryptionMethod.appendChild(digestMethod); } encryptedKey.appendChild(encryptionMethod); return encryptedKey; } protected Element createEncryptedDataElement(Document encryptedDataDoc, String symEncAlgo) { Element encryptedData = encryptedDataDoc.createElementNS(ENC_NS, ENC_PREFIX + ":EncryptedData"); XMLUtils.setNamespace(encryptedData, ENC_NS, ENC_PREFIX); Element encryptionMethod = encryptedDataDoc.createElementNS(ENC_NS, ENC_PREFIX + ":EncryptionMethod"); encryptionMethod.setAttributeNS(null, "Algorithm", symEncAlgo); encryptedData.appendChild(encryptionMethod); encryptedDataDoc.appendChild(encryptedData); return encryptedData; } }