Skip to content

Commit

Permalink
[SANTUARIO-616] enable manual resolving of the encrypted key with the…
Browse files Browse the repository at this point in the history
… key agreement (#305)

Co-authored-by: RIHTARSIC Joze <[email protected]>
  • Loading branch information
jrihtarsic and RIHTARSIC Joze authored May 3, 2024
1 parent 98df98f commit bbc0b2b
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 10 deletions.
46 changes: 36 additions & 10 deletions src/main/java/org/apache/xml/security/encryption/XMLCipher.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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 <A HREF="https://issues.apache.org/jira/browse/SANTUARIO-616">SANTUARIO-616</A>
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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*
* <p/>
* @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
Expand Down

0 comments on commit bbc0b2b

Please sign in to comment.