diff --git a/src/main/java/org/apache/xml/security/encryption/XMLCipher.java b/src/main/java/org/apache/xml/security/encryption/XMLCipher.java index 4c1309729..ed3778124 100644 --- a/src/main/java/org/apache/xml/security/encryption/XMLCipher.java +++ b/src/main/java/org/apache/xml/security/encryption/XMLCipher.java @@ -1477,8 +1477,7 @@ public Key decryptKey(EncryptedKey encryptedKey, String algorithm) try { String keyWrapAlg = encryptedKey.getEncryptionMethod().getAlgorithm(); String keyType = JCEMapper.getJCEKeyAlgorithmFromURI(keyWrapAlg); - if ( !(ki instanceof KeyInfoEnc && ((KeyInfoEnc)ki).containsAgreementMethod()) - && ("RSA".equals(keyType) || "EC".equals(keyType))) { + if ( "RSA".equals(keyType) || "EC".equals(keyType)) { key = ki.getPrivateKey(); } else { key = ki.getSecretKey(); @@ -1608,14 +1607,41 @@ private AlgorithmParameterSpec getAlgorithmParameters(EncryptedKey encryptedKey) } KeyInfoEnc keyInfo = encryptedKey.getKeyInfo() instanceof KeyInfoEnc ? (KeyInfoEnc) encryptedKey.getKeyInfo(): null; - if (keyInfo != null && keyInfo.containsAgreementMethod()) { - // resolve the agreement method - LOG.log(Level.DEBUG,"EncryptedKey key is using Key agreement data"); - AgreementMethod agreementMethod = keyInfo.itemAgreementMethod(0); - return XMLCipherUtil.constructRecipientKeyAgreementParameters(encryptionAlgorithm, - agreementMethod, (PrivateKey) this.key); - } - return null; + return constructKeyAgreementParameters(keyInfo, encryptionAlgorithm); + } + + /** + * The method validates whether key agreement data is present and checks if + * the provided key is of type PrivateKey. If both conditions are met, it + * proceeds to extract the KeyAgreementParameters for key derivation; otherwise, it returns null + * + * @param keyInfo the KeyInfoEnc object containing the key agreement data + * @param encryptionAlgorithm the encryption algorithm + * + * @return KeyAgreementParameters object containing the key agreement data + * or null if the key agreement data is not present or the provided key is not a PrivateKey + */ + private KeyAgreementParameters constructKeyAgreementParameters(KeyInfoEnc keyInfo, + String encryptionAlgorithm) throws XMLSecurityException { + + if (keyInfo == null || !keyInfo.containsAgreementMethod() ) { + LOG.log(Level.DEBUG,"EncryptedKey key does not contain AgreementMethod data"); + return null; + } + + if (!(this.key instanceof PrivateKey)) { + LOG.log(Level.INFO,"The EncryptedKey key is using Key agreement data, " + + "but provided key is not a PrivateKey. Skipping Key Agreement data processing."); + // do not throw error because of the legacy reasons to allow users to resolve + // the encryption Key manually and provide the derived key to XMLCipher + // @see SANTUARIO-616 + return null; + } + // resolve the agreement method + LOG.log(Level.DEBUG,"EncryptedKey key is using Key agreement data"); + AgreementMethod agreementMethod = keyInfo.itemAgreementMethod(0); + return XMLCipherUtil.constructRecipientKeyAgreementParameters(encryptionAlgorithm, + agreementMethod, (PrivateKey) this.key); } /** diff --git a/src/test/java/org/apache/xml/security/test/dom/encryption/XMLCipherTest.java b/src/test/java/org/apache/xml/security/test/dom/encryption/XMLCipherTest.java index cec743e9f..d0fb87e08 100644 --- a/src/test/java/org/apache/xml/security/test/dom/encryption/XMLCipherTest.java +++ b/src/test/java/org/apache/xml/security/test/dom/encryption/XMLCipherTest.java @@ -51,6 +51,7 @@ import org.apache.xml.security.encryption.EncryptionProperties; import org.apache.xml.security.encryption.EncryptionProperty; import org.apache.xml.security.encryption.XMLCipher; +import org.apache.xml.security.encryption.XMLCipherUtil; import org.apache.xml.security.encryption.keys.KeyInfoEnc; import org.apache.xml.security.encryption.params.ConcatKDFParams; import org.apache.xml.security.encryption.params.KeyAgreementParameters; @@ -427,6 +428,74 @@ void testAES128ElementRsaOaepKWCipher(String keyWrapAlgorithmURI, String mgf1URI } } + /** + * Test decryption using key agreement method processing and manual key derivation + * where KeyAgreementMethod is present in EncryptedKey, but it is not used for decryption + * because decryption key is provided manually. The test ensures legacy behavior is preserved + * where some implementations implemented it own key agreement method processing + * and XMLCipher is used just for key unwrapping. + * + *

+ * @throws Exception Thrown when there is any problem in signing or verification + */ + @Test + void testDecryptionSkipKeyAgreementMethodProcessing() throws Exception { + + // init parameters encrypted key object + String keyWrapAlgorithm = XMLCipher.AES_128_KeyWrap; + int transportKeyBitLength = KeyUtils.getAESKeyBitSizeForWrapAlgorithm(keyWrapAlgorithm); + + // Generate test recipient key pair + KeyPair recipientKeyPair = KeyTestUtils.generateKeyPair(KeyUtils.KeyType.SECP256R1); + PrivateKey privRecipientKey = recipientKeyPair.getPrivate(); + PublicKey pubRecipientKey = recipientKeyPair.getPublic(); + + // Generate a traffic key + KeyGenerator keygen = KeyGenerator.getInstance("AES"); + keygen.init(transportKeyBitLength); + Key ephemeralSymmetricKey = keygen.generateKey(); + + XMLCipher cipherEncKey = XMLCipher.getInstance(keyWrapAlgorithm); + cipherEncKey.init(XMLCipher.WRAP_MODE, pubRecipientKey); + cipherEncKey.setSecureValidation(true); + // create key agreement parameters + KeyDerivationParameters keyDerivationParameter = new ConcatKDFParams(transportKeyBitLength, + MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA256); + KeyAgreementParameters parameterSpec = new KeyAgreementParameters( + KeyAgreementParameters.ActorType.ORIGINATOR, + EncryptionConstants.ALGO_ID_KEYAGREEMENT_ECDH_ES, + keyDerivationParameter); + + // Generate EncryptedKey with KeyAgreementMethod + Document doc = TestUtils.newDocument(); + EncryptedKey encryptedKey = cipherEncKey.encryptKey(doc, ephemeralSymmetricKey, parameterSpec, null); + // assert that KeyAgreementMethod is present + assertEquals(1, ((KeyInfoEnc) encryptedKey.getKeyInfo()).lengthAgreementMethod()); + + // decrypt EncryptedKey key handled by xmlsec. + XMLCipher kwCipherWithKeyAgreement = XMLCipher.getInstance(); + kwCipherWithKeyAgreement.init(XMLCipher.UNWRAP_MODE, privRecipientKey); + Key symmetricKeyWithKeyAgreement = kwCipherWithKeyAgreement.decryptKey( + encryptedKey, encryptedKey.getEncryptionMethod().getAlgorithm() + ); + assertEquals(ephemeralSymmetricKey, symmetricKeyWithKeyAgreement); + + // decrypt EncryptedKey key manually (skip KeyAgreementMethod processing) + // derive encrypted key manually + KeyAgreementParameters params = XMLCipherUtil.constructRecipientKeyAgreementParameters(keyWrapAlgorithm, + ((KeyInfoEnc) encryptedKey.getKeyInfo()).itemAgreementMethod(0), privRecipientKey); + Key keyWrappingKey = KeyUtils.aesWrapKeyWithDHGeneratedKey(params); + + // use manually derived key to decrypt EncryptedKey + XMLCipher kwCipherManually = XMLCipher.getInstance(); + kwCipherManually.init(XMLCipher.UNWRAP_MODE, keyWrappingKey); + + Key symmetricKeyManualDecryption = kwCipherManually.decryptKey( + encryptedKey, encryptedKey.getEncryptionMethod().getAlgorithm() + ); + assertEquals(ephemeralSymmetricKey, symmetricKeyManualDecryption); + } + /** * Test encryption using a generated AES 192 bit key that is * encrypted using a 3DES key. Then reverse by decrypting