Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/releasenotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Date: 2026, TBD
- The lightweight SubjectPublicKeyInfoFactory and PrivateKeyInfoFactory encoded a GOST R 34.10-2012 key on one of the legacy CryptoPro curves under id-GostR3410-2001, although RFC 9215 sec. 4.2 permits those curves for 2012 keys. The digestParamSet now decides: a GOST R 34.11-94 parameter set means 2001 (RFC 4491 sec. 2.3.2), a GOST R 34.11-2012 digest or none means 2012 with 256/512 taken from the curve field size, and any other value is rejected. GOST3410PublicKeyAlgParameters treats digestParamSet as OPTIONAL on both read and write per RFC 9215, and PrivateKeyInfoFactory now passes attributes through for ECGOST3410 keys (bc-csharp github #707).

- The name-constraint host canonicalisation removed a single RFC 1034 root-label dot, the only empty label a name may legally carry, but nothing refused the ones that are not legal: a dNSName, rfc822Name host or uniformResourceIdentifier host such as "example.com.." kept a phantom empty label after the strip and so matched no constraint at all, escaping an excluded subtree naming the host it appears to carry. A tested name whose host carries an empty label - a second trailing dot, a doubled dot or a leading dot - is now refused outright wherever a constraint of that type is in force, rather than canonicalised into a name it is not: removing the extra dots would decide on the caller's behalf that "example.com.." names example.com, which is not how a consumer resolving or comparing the name reads it, and refusing fails closed in both directions where canonicalising would newly admit such a name under a permitted subtree. The single trailing dot is canonicalised as before, a bare "." remains the root label rather than an empty one, and the guard is scoped to the host, so the doubled dot a quoted local part may legally carry is unaffected. Constraints are untouched - one may still begin with a dot, which is how this implementation spells "subdomains only" (github PR #2436).
- The RFC 9709 content-encryption AlgorithmIdentifier, which carries the real algorithm inside the parameters of an outer id-alg-cek-hkdf-sha256, was unwrapped at only one of the points where a CMS recipient makes a decision about it. Key-size validation was corrected for plain key transport in 1.86, but the same call in the KEK, RSA-KTS and KEM recipients, and in the key-transport recipient's own ORI-KEM branch, still compared the recovered key against the outer identifier, which registers no key size, so setKeySizeValidation(true) silently checked nothing there; the setAllowedContentAlgorithms allow-list and the setMinimumTagSize floor were applied to the outer identifier on every recipient family, including the one already corrected, so neither constrained an RFC 9709 message. The unwrap now happens once for the key-size check and once for the two policy checks, and every recipient polices and validates the content-encryption algorithm the message actually carries. A recipient with no allow-list, no tag floor and no key-size validation configured behaves exactly as before; a caller who listed id-alg-cek-hkdf-sha256 in an allow-list in order to admit RFC 9709 messages must now list the content-encryption algorithms themselves.

### 2.1.3 Additional Features and Functionality

Expand Down
35 changes: 35 additions & 0 deletions pkix/src/main/java/org/bouncycastle/cms/AbstractRecipient.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.Set;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;

/**
Expand Down Expand Up @@ -52,4 +53,38 @@ protected final void checkTagSize(AlgorithmIdentifier contentAlgorithm)
}
}
}

/**
* Apply both of the recipient's restrictions to the content-encryption algorithm the message
* names, resolving an RFC 9709 key-derivation wrapper to the content-encryption algorithm it
* carries first, so that the restrictions are applied to the algorithm the content is actually
* encrypted under.
*
* @param contentAlgorithm the content-encryption AlgorithmIdentifier taken from the message.
* @throws CMSAlgorithmNotAllowedException if the content algorithm is not in the allowed set.
* @throws CMSTagLengthException if the tag size is below the configured minimum.
*/
protected final void checkContentAlgorithm(AlgorithmIdentifier contentAlgorithm)
throws CMSException
{
AlgorithmIdentifier encAlgId;

// RFC 9709: the EncryptedContentInfo carries an outer id-alg-cek-hkdf-sha256 wrapping the
// real inner content-encryption AlgorithmIdentifier - it is the inner one these checks apply to.
if (contentAlgorithm.getAlgorithm().equals(CMSObjectIdentifiers.id_alg_cek_hkdf_sha256))
{
encAlgId = AlgorithmIdentifier.getInstance(contentAlgorithm.getParameters());
}
else
{
encAlgId = contentAlgorithm;
}

if (!isContentAlgorithmAllowed(encAlgId.getAlgorithm()))
{
throw new CMSAlgorithmNotAllowedException("content-encryption algorithm not in recipient's allowed set: " + encAlgId.getAlgorithm());
}

checkTagSize(encAlgId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.cms.AbstractRecipient;
import org.bouncycastle.cms.CMSAlgorithmNotAllowedException;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.KEKRecipient;
import org.bouncycastle.crypto.CipherParameters;
Expand Down Expand Up @@ -42,12 +41,7 @@ public BcKEKRecipient setAllowedContentAlgorithms(Set<ASN1ObjectIdentifier> allo
protected CipherParameters extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier contentEncryptionAlgorithm, byte[] encryptedContentEncryptionKey)
throws CMSException
{
if (!isContentAlgorithmAllowed(contentEncryptionAlgorithm.getAlgorithm()))
{
throw new CMSAlgorithmNotAllowedException("content-encryption algorithm not in recipient's allowed set: " + contentEncryptionAlgorithm.getAlgorithm());
}

checkTagSize(contentEncryptionAlgorithm);
checkContentAlgorithm(contentEncryptionAlgorithm);

try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.cms.AbstractRecipient;
import org.bouncycastle.cms.CMSAlgorithmNotAllowedException;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.KeyTransRecipient;
import org.bouncycastle.crypto.CipherParameters;
Expand Down Expand Up @@ -43,12 +42,7 @@ public BcKeyTransRecipient setAllowedContentAlgorithms(Set<ASN1ObjectIdentifier>
protected CipherParameters extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedEncryptionKey)
throws CMSException
{
if (!isContentAlgorithmAllowed(encryptedKeyAlgorithm.getAlgorithm()))
{
throw new CMSAlgorithmNotAllowedException("content-encryption algorithm not in recipient's allowed set: " + encryptedKeyAlgorithm.getAlgorithm());
}

checkTagSize(encryptedKeyAlgorithm);
checkContentAlgorithm(encryptedKeyAlgorithm);

AsymmetricKeyUnwrapper unwrapper = new BcRSAAsymmetricKeyUnwrapper(keyEncryptionAlgorithm, recipientKey);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import org.bouncycastle.asn1.pkcs.PBKDF2Params;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.cms.AbstractRecipient;
import org.bouncycastle.cms.CMSAlgorithmNotAllowedException;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.PasswordRecipient;
import org.bouncycastle.crypto.InvalidCipherTextException;
Expand Down Expand Up @@ -62,12 +61,7 @@ public BcPasswordRecipient setAllowedContentAlgorithms(Set<ASN1ObjectIdentifier>
protected KeyParameter extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier contentEncryptionAlgorithm, byte[] derivedKey, byte[] encryptedContentEncryptionKey)
throws CMSException
{
if (!isContentAlgorithmAllowed(contentEncryptionAlgorithm.getAlgorithm()))
{
throw new CMSAlgorithmNotAllowedException("content-encryption algorithm not in recipient's allowed set: " + contentEncryptionAlgorithm.getAlgorithm());
}

checkTagSize(contentEncryptionAlgorithm);
checkContentAlgorithm(contentEncryptionAlgorithm);

Wrapper keyEncryptionCipher = EnvelopedDataHelper.createRFC3211Wrapper(keyEncryptionAlgorithm.getAlgorithm());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,21 @@ public Key getJceKey(AlgorithmIdentifier algId, GenericKey key)
public void keySizeCheck(AlgorithmIdentifier keyAlgorithm, Key key)
throws CMSException
{
int expectedKeySize = EnvelopedDataHelper.KEY_SIZE_PROVIDER.getKeySize(keyAlgorithm);
AlgorithmIdentifier encAlgId;

// RFC 9709: the EncryptedContentInfo carries an outer id-alg-cek-hkdf-sha256 wrapping the
// real inner content-encryption AlgorithmIdentifier - it is the inner one that fixes the
// key size the recovered CEK is expected to have.
if (keyAlgorithm.getAlgorithm().equals(CMSObjectIdentifiers.id_alg_cek_hkdf_sha256))
{
encAlgId = AlgorithmIdentifier.getInstance(keyAlgorithm.getParameters());
}
else
{
encAlgId = keyAlgorithm;
}

int expectedKeySize = EnvelopedDataHelper.KEY_SIZE_PROVIDER.getKeySize(encAlgId);
if (expectedKeySize > 0)
{
byte[] keyEnc = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.cms.AbstractRecipient;
import org.bouncycastle.cms.CMSAlgorithmNotAllowedException;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.KEKRecipient;
import org.bouncycastle.operator.OperatorException;
Expand Down Expand Up @@ -133,12 +132,7 @@ public JceKEKRecipient setMinimumTagSize(int tagSizeInBits)
protected Key extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedContentEncryptionKey)
throws CMSException
{
if (!isContentAlgorithmAllowed(encryptedKeyAlgorithm.getAlgorithm()))
{
throw new CMSAlgorithmNotAllowedException("content-encryption algorithm not in recipient's allowed set: " + encryptedKeyAlgorithm.getAlgorithm());
}

checkTagSize(encryptedKeyAlgorithm);
checkContentAlgorithm(encryptedKeyAlgorithm);

SymmetricKeyUnwrapper unwrapper = helper.createSymmetricUnwrapper(keyEncryptionAlgorithm, recipientKey);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import org.bouncycastle.asn1.cms.KEMRecipientInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.cms.AbstractRecipient;
import org.bouncycastle.cms.CMSAlgorithmNotAllowedException;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.KEMRecipient;
import org.bouncycastle.operator.OperatorException;
Expand Down Expand Up @@ -176,12 +175,7 @@ public JceKEMRecipient setMinimumTagSize(int tagSizeInBits)
protected Key extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedEncryptionKey)
throws CMSException
{
if (!isContentAlgorithmAllowed(encryptedKeyAlgorithm.getAlgorithm()))
{
throw new CMSAlgorithmNotAllowedException("content-encryption algorithm not in recipient's allowed set: " + encryptedKeyAlgorithm.getAlgorithm());
}

checkTagSize(encryptedKeyAlgorithm);
checkContentAlgorithm(encryptedKeyAlgorithm);

// TODO: note there is a move to change the type for KEMs from KeyTrans, expect this to change
KEMRecipientInfo gktParams = KEMRecipientInfo.getInstance(keyEncryptionAlgorithm.getParameters());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.cms.AbstractRecipient;
import org.bouncycastle.cms.CMSAlgorithmNotAllowedException;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.KeyTransRecipient;
import org.bouncycastle.cms.KeyTransRecipientId;
Expand Down Expand Up @@ -168,12 +167,7 @@ public JceKTSKeyTransRecipient setMinimumTagSize(int tagSizeInBits)
protected Key extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedEncryptionKey)
throws CMSException
{
if (!isContentAlgorithmAllowed(encryptedKeyAlgorithm.getAlgorithm()))
{
throw new CMSAlgorithmNotAllowedException("content-encryption algorithm not in recipient's allowed set: " + encryptedKeyAlgorithm.getAlgorithm());
}

checkTagSize(encryptedKeyAlgorithm);
checkContentAlgorithm(encryptedKeyAlgorithm);

JceKTSKeyUnwrapper unwrapper = helper.createAsymmetricUnwrapper(keyEncryptionAlgorithm, recipientKey, ANONYMOUS_SENDER, partyVInfo);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.cms.AbstractKeyAgreeRecipient;
import org.bouncycastle.cms.CMSAlgorithmNotAllowedException;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.jcajce.spec.GOST28147WrapParameterSpec;
import org.bouncycastle.jcajce.spec.MQVParameterSpec;
Expand Down Expand Up @@ -283,12 +282,7 @@ protected Key unwrapSessionKey(ASN1ObjectIdentifier wrapAlg, SecretKey agreedKey
protected Key extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier contentEncryptionAlgorithm, SubjectPublicKeyInfo senderKey, ASN1OctetString userKeyingMaterial, byte[] encryptedContentEncryptionKey)
throws CMSException
{
if (!isContentAlgorithmAllowed(contentEncryptionAlgorithm.getAlgorithm()))
{
throw new CMSAlgorithmNotAllowedException("content-encryption algorithm not in recipient's allowed set: " + contentEncryptionAlgorithm.getAlgorithm());
}

checkTagSize(contentEncryptionAlgorithm);
checkContentAlgorithm(contentEncryptionAlgorithm);

try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import org.bouncycastle.asn1.cryptopro.GostR3410TransportParameters;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.cms.AbstractKeyTransRecipient;
import org.bouncycastle.cms.CMSAlgorithmNotAllowedException;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.jcajce.spec.GOST28147WrapParameterSpec;
import org.bouncycastle.jcajce.spec.UserKeyingMaterialSpec;
Expand Down Expand Up @@ -190,12 +189,7 @@ public JceKeyTransRecipient setMinimumTagSize(int tagSizeInBits)
protected Key extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier encryptedKeyAlgorithm, byte[] encryptedEncryptionKey)
throws CMSException
{
if (!isContentAlgorithmAllowed(encryptedKeyAlgorithm.getAlgorithm()))
{
throw new CMSAlgorithmNotAllowedException("content-encryption algorithm not in recipient's allowed set: " + encryptedKeyAlgorithm.getAlgorithm());
}

checkTagSize(encryptedKeyAlgorithm);
checkContentAlgorithm(encryptedKeyAlgorithm);

if (isGOST(keyEncryptionAlgorithm.getAlgorithm()))
{
Expand Down Expand Up @@ -282,15 +276,7 @@ else if (CMSObjectIdentifiers.id_ori_kem.equals(keyEncryptionAlgorithm.getAlgori

if (validateKeySize)
{
if (encryptedKeyAlgorithm.getAlgorithm().equals(CMSObjectIdentifiers.id_alg_cek_hkdf_sha256))
{
helper.keySizeCheck(
AlgorithmIdentifier.getInstance(encryptedKeyAlgorithm.getParameters()), key);
}
else
{
helper.keySizeCheck(encryptedKeyAlgorithm, key);
}
helper.keySizeCheck(encryptedKeyAlgorithm, key);
}

return key;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.cms.AbstractRecipient;
import org.bouncycastle.cms.CMSAlgorithmNotAllowedException;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.PasswordRecipient;

Expand Down Expand Up @@ -89,12 +88,7 @@ public JcePasswordRecipient setMinimumTagSize(int tagSizeInBits)
protected Key extractSecretKey(AlgorithmIdentifier keyEncryptionAlgorithm, AlgorithmIdentifier contentEncryptionAlgorithm, byte[] derivedKey, byte[] encryptedContentEncryptionKey)
throws CMSException
{
if (!isContentAlgorithmAllowed(contentEncryptionAlgorithm.getAlgorithm()))
{
throw new CMSAlgorithmNotAllowedException("content-encryption algorithm not in recipient's allowed set: " + contentEncryptionAlgorithm.getAlgorithm());
}

checkTagSize(contentEncryptionAlgorithm);
checkContentAlgorithm(contentEncryptionAlgorithm);

Cipher keyEncryptionCipher = helper.createRFC3211Wrapper(keyEncryptionAlgorithm.getAlgorithm());

Expand Down
Loading