/** * 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.wss4j.dom.processor; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.security.spec.MGF1ParameterSpec; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.OAEPParameterSpec; import javax.crypto.spec.PSource; import javax.xml.crypto.dsig.XMLSignatureFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.apache.wss4j.common.bsp.BSPEnforcer; import org.apache.wss4j.common.bsp.BSPRule; import org.apache.wss4j.common.crypto.AlgorithmSuite; import org.apache.wss4j.common.crypto.AlgorithmSuiteValidator; import org.apache.wss4j.common.crypto.CryptoType; import org.apache.wss4j.common.ext.WSSecurityException; import org.apache.wss4j.common.token.DOMX509IssuerSerial; import org.apache.wss4j.common.token.SecurityTokenReference; import org.apache.wss4j.common.util.KeyUtils; import org.apache.wss4j.common.util.XMLUtils; import org.apache.wss4j.dom.WSConstants; import org.apache.wss4j.dom.WSDataRef; import org.apache.wss4j.dom.WSDocInfo; import org.apache.wss4j.dom.engine.WSSecurityEngineResult; import org.apache.wss4j.dom.handler.RequestData; import org.apache.wss4j.dom.str.EncryptedKeySTRParser; import org.apache.wss4j.dom.str.STRParser; import org.apache.wss4j.dom.str.STRParserParameters; import org.apache.wss4j.dom.str.STRParserResult; import org.apache.wss4j.dom.util.EncryptionUtils; import org.apache.wss4j.dom.util.SignatureUtils; import org.apache.wss4j.dom.util.WSSecurityUtil; import org.apache.wss4j.dom.util.X509Util; import org.apache.xml.security.algorithms.JCEMapper; import org.apache.xml.security.encryption.XMLCipher; public class EncryptedKeyProcessor implements Processor { private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(EncryptedKeyProcessor.class); private Provider provider; public EncryptedKeyProcessor() { // complete } public EncryptedKeyProcessor(Provider provider) { this.provider = provider; } public List<WSSecurityEngineResult> handleToken( Element elem, RequestData data ) throws WSSecurityException { return handleToken(elem, data, data.getAlgorithmSuite()); } public List<WSSecurityEngineResult> handleToken( Element elem, RequestData data, AlgorithmSuite algorithmSuite ) throws WSSecurityException { LOG.debug("Found encrypted key element"); // See if this key has already been processed. If so then just return the result String id = elem.getAttributeNS(null, "Id"); if (!"".equals(id)) { WSSecurityEngineResult result = data.getWsDocInfo().getResult(id); if (result != null && WSConstants.ENCR == (Integer)result.get(WSSecurityEngineResult.TAG_ACTION) ) { return Collections.singletonList(result); } } if (data.getCallbackHandler() == null) { throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "noCallback"); } // // lookup xenc:EncryptionMethod, get the Algorithm attribute to determine // how the key was encrypted. Then check if we support the algorithm // String encryptedKeyTransportMethod = X509Util.getEncAlgo(elem); if (encryptedKeyTransportMethod == null) { throw new WSSecurityException( WSSecurityException.ErrorCode.UNSUPPORTED_ALGORITHM, "noEncAlgo" ); } if (WSConstants.KEYTRANSPORT_RSA15.equals(encryptedKeyTransportMethod) && !data.isAllowRSA15KeyTransportAlgorithm() && (algorithmSuite == null || !algorithmSuite.getKeyWrapAlgorithms().contains(WSConstants.KEYTRANSPORT_RSA15))) { LOG.debug( "The Key transport method does not match the requirement" ); throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY); } // Check BSP Compliance checkBSPCompliance(elem, encryptedKeyTransportMethod, data.getBSPEnforcer()); // // Now lookup CipherValue. // Element xencCipherValue = EncryptionUtils.getCipherValueFromEncryptedData(elem); if (xencCipherValue == null) { throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY, "noCipher"); } Element keyInfoChildElement = getKeyInfoChildElement(elem, data); X509Certificate[] certs = null; STRParser.REFERENCE_TYPE referenceType = null; PublicKey publicKey = null; boolean symmetricKeyWrap = isSymmetricKeyWrap(encryptedKeyTransportMethod); if (!symmetricKeyWrap) { if (SecurityTokenReference.SECURITY_TOKEN_REFERENCE.equals(keyInfoChildElement.getLocalName()) && WSConstants.WSSE_NS.equals(keyInfoChildElement.getNamespaceURI())) { STRParserParameters parameters = new STRParserParameters(); parameters.setData(data); parameters.setStrElement(keyInfoChildElement); STRParser strParser = new EncryptedKeySTRParser(); STRParserResult parserResult = strParser.parseSecurityTokenReference(parameters); certs = parserResult.getCertificates(); publicKey = parserResult.getPublicKey(); referenceType = parserResult.getCertificatesReferenceType(); } else { certs = getCertificatesFromX509Data(keyInfoChildElement, data); if (certs == null) { XMLSignatureFactory signatureFactory; if (provider == null) { // Try to install the Santuario Provider - fall back to the JDK provider if this does // not work try { signatureFactory = XMLSignatureFactory.getInstance("DOM", "ApacheXMLDSig"); } catch (NoSuchProviderException ex) { signatureFactory = XMLSignatureFactory.getInstance("DOM"); } } else { signatureFactory = XMLSignatureFactory.getInstance("DOM", provider); } publicKey = X509Util.parseKeyValue((Element)keyInfoChildElement.getParentNode(), signatureFactory); } } if (publicKey == null && (certs == null || certs.length < 1 || certs[0] == null)) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "noCertsFound", new Object[] {"decryption (KeyId)"}); } if (certs != null && certs.length > 0) { publicKey = certs[0].getPublicKey(); } } // Check for compliance against the defined AlgorithmSuite if (algorithmSuite != null) { AlgorithmSuiteValidator algorithmSuiteValidator = new AlgorithmSuiteValidator(algorithmSuite); if (!symmetricKeyWrap) { algorithmSuiteValidator.checkAsymmetricKeyLength(publicKey); } algorithmSuiteValidator.checkEncryptionKeyWrapAlgorithm( encryptedKeyTransportMethod ); } byte[] encryptedEphemeralKey = null; byte[] decryptedBytes = null; Element refList = XMLUtils.getDirectChildElement(elem, "ReferenceList", WSConstants.ENC_NS); // Get the key bytes from CipherValue directly or via an attachment String xopUri = EncryptionUtils.getXOPURIFromCipherValue(xencCipherValue); if (xopUri != null && xopUri.startsWith("cid:")) { encryptedEphemeralKey = WSSecurityUtil.getBytesFromAttachment(xopUri, data); } else { encryptedEphemeralKey = EncryptionUtils.getDecodedBase64EncodedData(xencCipherValue); } if (symmetricKeyWrap) { decryptedBytes = getSymmetricDecryptedBytes(data, data.getWsDocInfo(), keyInfoChildElement, refList, encryptedEphemeralKey); } else { PrivateKey privateKey = getPrivateKey(data, certs, publicKey); decryptedBytes = getAsymmetricDecryptedBytes(data, data.getWsDocInfo(), encryptedKeyTransportMethod, encryptedEphemeralKey, refList, elem, privateKey); } List<WSDataRef> dataRefs = decryptDataRefs(refList, data.getWsDocInfo(), decryptedBytes, data); WSSecurityEngineResult result = new WSSecurityEngineResult( WSConstants.ENCR, decryptedBytes, encryptedEphemeralKey, dataRefs, certs ); result.put( WSSecurityEngineResult.TAG_ENCRYPTED_KEY_TRANSPORT_METHOD, encryptedKeyTransportMethod ); result.put(WSSecurityEngineResult.TAG_TOKEN_ELEMENT, elem); String tokenId = elem.getAttributeNS(null, "Id"); if (!"".equals(tokenId)) { result.put(WSSecurityEngineResult.TAG_ID, tokenId); } if (referenceType != null) { result.put(WSSecurityEngineResult.TAG_X509_REFERENCE_TYPE, referenceType); } if (publicKey != null) { result.put(WSSecurityEngineResult.TAG_PUBLIC_KEY, publicKey); } data.getWsDocInfo().addResult(result); data.getWsDocInfo().addTokenElement(elem); return Collections.singletonList(result); } private PrivateKey getPrivateKey( RequestData data, X509Certificate[] certs, PublicKey publicKey ) throws WSSecurityException { try { if (certs != null) { return data.getDecCrypto().getPrivateKey(certs[0], data.getCallbackHandler()); } return data.getDecCrypto().getPrivateKey(publicKey, data.getCallbackHandler()); } catch (WSSecurityException ex) { throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_CHECK, ex); } } private static byte[] getSymmetricDecryptedBytes( RequestData data, WSDocInfo wsDocInfo, Element keyInfoChildElement, Element refList, byte[] encryptedEphemeralKey ) throws WSSecurityException { // Get the (first) encryption algorithm String uri = getFirstDataRefURI(refList); String algorithmURI = null; if (uri != null) { Element ee = EncryptionUtils.findEncryptedDataElement(refList.getOwnerDocument(), wsDocInfo, uri); algorithmURI = X509Util.getEncAlgo(ee); } return X509Util.getSecretKey(keyInfoChildElement, algorithmURI, data.getCallbackHandler(), encryptedEphemeralKey); } private static byte[] getAsymmetricDecryptedBytes( RequestData data, WSDocInfo wsDocInfo, String encryptedKeyTransportMethod, byte[] encryptedEphemeralKey, Element refList, Element encryptedKeyElement, PrivateKey privateKey ) throws WSSecurityException { if (data.getDecCrypto() == null) { throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "noDecCryptoFile"); } String cryptoProvider = data.getDecCrypto().getCryptoProvider(); Cipher cipher = KeyUtils.getCipherInstance(encryptedKeyTransportMethod, cryptoProvider); try { OAEPParameterSpec oaepParameterSpec = null; if (WSConstants.KEYTRANSPORT_RSAOAEP.equals(encryptedKeyTransportMethod) || WSConstants.KEYTRANSPORT_RSAOAEP_XENC11.equals(encryptedKeyTransportMethod)) { // Get the DigestMethod if it exists String digestAlgorithm = EncryptionUtils.getDigestAlgorithm(encryptedKeyElement); String jceDigestAlgorithm = "SHA-1"; if (digestAlgorithm != null && !"".equals(digestAlgorithm)) { jceDigestAlgorithm = JCEMapper.translateURItoJCEID(digestAlgorithm); } MGF1ParameterSpec mgfParameterSpec = new MGF1ParameterSpec("SHA-1"); if (WSConstants.KEYTRANSPORT_RSAOAEP_XENC11.equals(encryptedKeyTransportMethod)) { String mgfAlgorithm = EncryptionUtils.getMGFAlgorithm(encryptedKeyElement); if (WSConstants.MGF_SHA224.equals(mgfAlgorithm)) { mgfParameterSpec = new MGF1ParameterSpec("SHA-224"); } else if (WSConstants.MGF_SHA256.equals(mgfAlgorithm)) { mgfParameterSpec = new MGF1ParameterSpec("SHA-256"); } else if (WSConstants.MGF_SHA384.equals(mgfAlgorithm)) { mgfParameterSpec = new MGF1ParameterSpec("SHA-384"); } else if (WSConstants.MGF_SHA512.equals(mgfAlgorithm)) { mgfParameterSpec = new MGF1ParameterSpec("SHA-512"); } } PSource.PSpecified pSource = PSource.PSpecified.DEFAULT; byte[] pSourceBytes = EncryptionUtils.getPSource(encryptedKeyElement); if (pSourceBytes != null) { pSource = new PSource.PSpecified(pSourceBytes); } oaepParameterSpec = new OAEPParameterSpec( jceDigestAlgorithm, "MGF1", mgfParameterSpec, pSource ); } if (oaepParameterSpec == null) { cipher.init(Cipher.UNWRAP_MODE, privateKey); } else { cipher.init(Cipher.UNWRAP_MODE, privateKey, oaepParameterSpec); } } catch (Exception ex) { throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_CHECK, ex); } try { String keyAlgorithm = JCEMapper.translateURItoJCEID(encryptedKeyTransportMethod); return cipher.unwrap(encryptedEphemeralKey, keyAlgorithm, Cipher.SECRET_KEY).getEncoded(); } catch (IllegalStateException ex) { throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_CHECK, ex); } catch (Exception ex) { return getRandomKey(refList, wsDocInfo); } } private static boolean isSymmetricKeyWrap(String transportAlgorithm) { return XMLCipher.AES_128_KeyWrap.equals(transportAlgorithm) || XMLCipher.AES_192_KeyWrap.equals(transportAlgorithm) || XMLCipher.AES_256_KeyWrap.equals(transportAlgorithm) || XMLCipher.TRIPLEDES_KeyWrap.equals(transportAlgorithm) || XMLCipher.CAMELLIA_128_KeyWrap.equals(transportAlgorithm) || XMLCipher.CAMELLIA_192_KeyWrap.equals(transportAlgorithm) || XMLCipher.CAMELLIA_256_KeyWrap.equals(transportAlgorithm) || XMLCipher.SEED_128_KeyWrap.equals(transportAlgorithm); } /** * Generates a random secret key using the algorithm specified in the * first DataReference URI */ private static byte[] getRandomKey(Element refList, WSDocInfo wsDocInfo) throws WSSecurityException { try { String alg = "AES"; int size = 16; String uri = getFirstDataRefURI(refList); if (uri != null) { Element ee = EncryptionUtils.findEncryptedDataElement(refList.getOwnerDocument(), wsDocInfo, uri); String algorithmURI = X509Util.getEncAlgo(ee); alg = JCEMapper.getJCEKeyAlgorithmFromURI(algorithmURI); size = KeyUtils.getKeyLength(algorithmURI); } KeyGenerator kgen = KeyGenerator.getInstance(alg); kgen.init(size * 8); SecretKey k = kgen.generateKey(); return k.getEncoded(); } catch (Throwable ex) { // Fallback to just using AES to avoid attacks on EncryptedData algorithms try { KeyGenerator kgen = KeyGenerator.getInstance("AES"); kgen.init(128); SecretKey k = kgen.generateKey(); return k.getEncoded(); } catch (NoSuchAlgorithmException e) { throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_CHECK, e); } } } private static String getFirstDataRefURI(Element refList) { // Lookup the references that are encrypted with this key if (refList != null) { for (Node node = refList.getFirstChild(); node != null; node = node.getNextSibling()) { if (Node.ELEMENT_NODE == node.getNodeType() && WSConstants.ENC_NS.equals(node.getNamespaceURI()) && "DataReference".equals(node.getLocalName())) { String dataRefURI = ((Element) node).getAttributeNS(null, "URI"); return XMLUtils.getIDFromReference(dataRefURI); } } } return null; } private Element getKeyInfoChildElement( Element xencEncryptedKey, RequestData data ) throws WSSecurityException { Element keyInfo = XMLUtils.getDirectChildElement(xencEncryptedKey, "KeyInfo", WSConstants.SIG_NS); if (keyInfo != null) { Element strElement = null; int result = 0; Node node = keyInfo.getFirstChild(); while (node != null) { if (Node.ELEMENT_NODE == node.getNodeType()) { result++; strElement = (Element)node; } node = node.getNextSibling(); } if (result != 1) { data.getBSPEnforcer().handleBSPRule(BSPRule.R5424); } if (strElement == null) { throw new WSSecurityException( WSSecurityException.ErrorCode.INVALID_SECURITY, "noSecTokRef" ); } return strElement; } else { throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY, "noKeyinfo"); } } private X509Certificate[] getCertificatesFromX509Data( Element keyInfoChildElement, RequestData data ) throws WSSecurityException { if (WSConstants.SIG_NS.equals(keyInfoChildElement.getNamespaceURI()) && WSConstants.X509_DATA_LN.equals(keyInfoChildElement.getLocalName())) { data.getBSPEnforcer().handleBSPRule(BSPRule.R5426); Element x509Child = getFirstElement(keyInfoChildElement); if (x509Child != null && WSConstants.SIG_NS.equals(x509Child.getNamespaceURI())) { if (WSConstants.X509_ISSUER_SERIAL_LN.equals(x509Child.getLocalName())) { DOMX509IssuerSerial issuerSerial = new DOMX509IssuerSerial(x509Child); CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ISSUER_SERIAL); cryptoType.setIssuerSerial(issuerSerial.getIssuer(), issuerSerial.getSerialNumber()); return data.getDecCrypto().getX509Certificates(cryptoType); } else if (WSConstants.X509_CERT_LN.equals(x509Child.getLocalName())) { byte[] token = EncryptionUtils.getDecodedBase64EncodedData(x509Child); if (token == null) { throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidCertData", new Object[] {"0"}); } try (InputStream in = new ByteArrayInputStream(token)) { X509Certificate cert = data.getDecCrypto().loadCertificate(in); if (cert != null) { return new X509Certificate[]{cert}; } } catch (IOException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.SECURITY_TOKEN_UNAVAILABLE, e, "parseError" ); } } } } return null; } private Element getFirstElement(Element element) { for (Node currentChild = element.getFirstChild(); currentChild != null; currentChild = currentChild.getNextSibling() ) { if (Node.ELEMENT_NODE == currentChild.getNodeType()) { return (Element) currentChild; } } return null; } /** * Decrypt all data references */ private List<WSDataRef> decryptDataRefs(Element refList, WSDocInfo docInfo, byte[] decryptedBytes, RequestData data ) throws WSSecurityException { // // At this point we have the decrypted session (symmetric) key. According // to W3C XML-Enc this key is used to decrypt _any_ references contained in // the reference list if (refList == null) { return null; } List<WSDataRef> dataRefs = new ArrayList<>(); for (Node node = refList.getFirstChild(); node != null; node = node.getNextSibling()) { if (Node.ELEMENT_NODE == node.getNodeType() && WSConstants.ENC_NS.equals(node.getNamespaceURI()) && "DataReference".equals(node.getLocalName())) { String dataRefURI = ((Element) node).getAttributeNS(null, "URI"); dataRefURI = XMLUtils.getIDFromReference(dataRefURI); WSDataRef dataRef = decryptDataRef(refList.getOwnerDocument(), dataRefURI, docInfo, decryptedBytes, data); dataRefs.add(dataRef); } } return dataRefs; } /** * Decrypt an EncryptedData element referenced by dataRefURI */ private WSDataRef decryptDataRef( Document doc, String dataRefURI, WSDocInfo docInfo, byte[] decryptedData, RequestData data ) throws WSSecurityException { LOG.debug("found data reference: {}", dataRefURI); // // Find the encrypted data element referenced by dataRefURI // Element encryptedDataElement = EncryptionUtils.findEncryptedDataElement(doc, docInfo, dataRefURI); if (encryptedDataElement != null && data.isRequireSignedEncryptedDataElements()) { List<WSSecurityEngineResult> signedResults = docInfo.getResultsByTag(WSConstants.SIGN); SignatureUtils.verifySignedElement(encryptedDataElement, signedResults); } // // Prepare the SecretKey object to decrypt EncryptedData // String symEncAlgo = X509Util.getEncAlgo(encryptedDataElement); // EncryptionAlgorithm cannot be null if (symEncAlgo == null) { LOG.debug("No encryption algorithm was specified in the request"); throw new WSSecurityException(WSSecurityException.ErrorCode.UNSUPPORTED_ALGORITHM, "badEncAlgo", new Object[] {null}); } // EncryptionAlgorithm must be 3DES, or AES128, or AES256 if (!WSConstants.TRIPLE_DES.equals(symEncAlgo) && !WSConstants.AES_128.equals(symEncAlgo) && !WSConstants.AES_128_GCM.equals(symEncAlgo) && !WSConstants.AES_256.equals(symEncAlgo) && !WSConstants.AES_256_GCM.equals(symEncAlgo)) { data.getBSPEnforcer().handleBSPRule(BSPRule.R5620); } SecretKey symmetricKey = null; try { symmetricKey = KeyUtils.prepareSecretKey(symEncAlgo, decryptedData); } catch (IllegalArgumentException ex) { throw new WSSecurityException( WSSecurityException.ErrorCode.UNSUPPORTED_ALGORITHM, ex, "badEncAlgo", new Object[] {symEncAlgo}); } // Check for compliance against the defined AlgorithmSuite AlgorithmSuite algorithmSuite = data.getAlgorithmSuite(); if (algorithmSuite != null) { AlgorithmSuiteValidator algorithmSuiteValidator = new AlgorithmSuiteValidator(algorithmSuite); algorithmSuiteValidator.checkSymmetricKeyLength(symmetricKey.getEncoded().length); algorithmSuiteValidator.checkSymmetricEncryptionAlgorithm(symEncAlgo); } return EncryptionUtils.decryptEncryptedData( doc, dataRefURI, encryptedDataElement, symmetricKey, symEncAlgo, data.getAttachmentCallbackHandler(), data.getEncryptionSerializer() ); } /** * A method to check that the EncryptedKey is compliant with the BSP spec. * @throws WSSecurityException */ private void checkBSPCompliance( Element elem, String encAlgo, BSPEnforcer bspEnforcer ) throws WSSecurityException { String attribute = elem.getAttributeNS(null, "Type"); if (attribute != null && !"".equals(attribute)) { bspEnforcer.handleBSPRule(BSPRule.R3209); } attribute = elem.getAttributeNS(null, "MimeType"); if (attribute != null && !"".equals(attribute)) { bspEnforcer.handleBSPRule(BSPRule.R5622); } attribute = elem.getAttributeNS(null, "Encoding"); if (attribute != null && !"".equals(attribute)) { bspEnforcer.handleBSPRule(BSPRule.R5623); } attribute = elem.getAttributeNS(null, "Recipient"); if (attribute != null && !"".equals(attribute)) { bspEnforcer.handleBSPRule(BSPRule.R5602); } // EncryptionAlgorithm must be RSA15, or RSAOEP. if (!(WSConstants.KEYTRANSPORT_RSA15.equals(encAlgo) || WSConstants.KEYTRANSPORT_RSAOAEP.equals(encAlgo) || WSConstants.KEYTRANSPORT_RSAOAEP_XENC11.equals(encAlgo))) { bspEnforcer.handleBSPRule(BSPRule.R5621); } } }