Skip to content

Commit dc6822a

Browse files
committed
CRMF: reject the publication information RFC 4211 sec. 6.3 forbids - pubInfos present with the dontPublish action, or present and empty - on parsing and on construction.
1 parent 684b690 commit dc6822a

3 files changed

Lines changed: 76 additions & 2 deletions

File tree

‎docs/releasenotes.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Date: 2026, TBD
3232
- 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 (github PR #2446).
3333
- A CMS message whose EncryptedContentInfo named the RFC 9709 key derivation but carried no readable content-encryption AlgorithmIdentifier in its parameters was reported as a NullPointerException, or as an IllegalArgumentException from the ASN.1 decoder, out of methods declared to throw CMSException, RecipientInformation.getContent() among them. The four places that resolve the wrapper - the CEK derivation, the content cipher selection, the key-size check, and the recipient's allowed-algorithm and tag-size checks - now share one resolver, which reports an absent or unreadable inner algorithm as a CMSException.
3434
- The two YubiKey OpenPGP smart-card decryptor factories zeroized the user PIN array the KeyPassphraseProvider handed them rather than a copy of it, in a finally block after each private-key operation. Both providers BC ships return the application's own array by reference - DefaultKeyPassphraseProvider hands back the char[] it has cached for the key, and the provider inside OpenPGPApi.editKey returns its argument - so the first card operation destroyed the caller's PIN and the next private-key operation presented an all-zero PIN to the card, which the card refuses at the cost of a PIN retry. The PIN is now fetched as a clone the card operation owns - in OpenPGPSmartCard.requireUserPin, where the smart-card restructuring of this release put the fetch the two factories used to make - and KeyPassphraseProvider.getKeyPassword records that the array it returns stays owned by the provider (github PR #2444).
35+
- The CRMF PKIPublicationInfo structure accepted, and could be built with, publication information RFC 4211 sec. 6.3 forbids: pubInfos MUST NOT be present if the action is dontPublish, and the field is SEQUENCE SIZE (1..MAX), so a present one is never empty. Both contradictions are now rejected with an IllegalArgumentException, on parsing and on construction from an array of SinglePubInfo. An absent pubInfos with the pleasePublish action, which is how the RFC spells "don't care", is unaffected, and no constructor in the library could produce either rejected form.
3536

3637
### 2.1.3 Additional Features and Functionality
3738

‎util/src/main/java/org/bouncycastle/asn1/crmf/PKIPublicationInfo.java‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,30 @@ private PKIPublicationInfo(ASN1Sequence seq)
3535
action = ASN1Integer.getInstance(seq.getObjectAt(0));
3636
if (seq.size() > 1)
3737
{
38-
pubInfos = ASN1Sequence.getInstance(seq.getObjectAt(1));
38+
pubInfos = checkPubInfos(action, ASN1Sequence.getInstance(seq.getObjectAt(1)));
3939
}
4040
}
4141

42+
/**
43+
* RFC 4211 sec. 6.3: pubInfos MUST NOT be present if action is dontPublish, and the field is
44+
* SEQUENCE SIZE (1..MAX), so a present one carries at least one SinglePubInfo. "Don't care" is
45+
* an absent pubInfos with the pleasePublish action, not an empty or contradictory one.
46+
*/
47+
private static ASN1Sequence checkPubInfos(ASN1Integer action, ASN1Sequence pubInfos)
48+
{
49+
if (dontPublish.equals(action))
50+
{
51+
throw new IllegalArgumentException("pubInfos must be absent if action is dontPublish");
52+
}
53+
54+
if (pubInfos.size() == 0)
55+
{
56+
throw new IllegalArgumentException("pubInfos must contain at least one SinglePubInfo");
57+
}
58+
59+
return pubInfos;
60+
}
61+
4262
public static PKIPublicationInfo getInstance(Object o)
4363
{
4464
if (o instanceof PKIPublicationInfo)
@@ -85,7 +105,7 @@ public PKIPublicationInfo(SinglePubInfo[] pubInfos)
85105

86106
if (pubInfos != null)
87107
{
88-
this.pubInfos = new DERSequence(pubInfos);
108+
this.pubInfos = checkPubInfos(this.action, new DERSequence(pubInfos));
89109
}
90110
else
91111
{

‎util/src/test/java/org/bouncycastle/asn1/crmf/test/PKIPublicationInfoTest.java‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import java.io.IOException;
44

5+
import org.bouncycastle.asn1.ASN1EncodableVector;
6+
import org.bouncycastle.asn1.DERSequence;
57
import org.bouncycastle.asn1.crmf.PKIPublicationInfo;
68
import org.bouncycastle.asn1.crmf.SinglePubInfo;
79
import org.bouncycastle.asn1.x500.X500Name;
@@ -70,6 +72,57 @@ public void performTest()
7072
isTrue(null == pkiPubInfo.getPubInfos());
7173

7274
encEqualTest(pkiPubInfo);
75+
76+
rejectionTests();
77+
}
78+
79+
/**
80+
* RFC 4211 sec. 6.3: pubInfos MUST NOT be present if the action is dontPublish, and the field
81+
* is SEQUENCE SIZE (1..MAX), so a present one is never empty. Both are rejected on parsing and
82+
* on construction.
83+
*/
84+
private void rejectionTests()
85+
throws IOException
86+
{
87+
ASN1EncodableVector v = new ASN1EncodableVector();
88+
89+
v.add(PKIPublicationInfo.dontPublish);
90+
v.add(new DERSequence(new SinglePubInfo(SinglePubInfo.dontCare, null)));
91+
92+
try
93+
{
94+
PKIPublicationInfo.getInstance(new DERSequence(v).getEncoded());
95+
fail("no exception on dontPublish with pubInfos");
96+
}
97+
catch (IllegalArgumentException e)
98+
{
99+
isEquals("pubInfos must be absent if action is dontPublish", e.getMessage());
100+
}
101+
102+
v = new ASN1EncodableVector();
103+
104+
v.add(PKIPublicationInfo.pleasePublish);
105+
v.add(new DERSequence());
106+
107+
try
108+
{
109+
PKIPublicationInfo.getInstance(new DERSequence(v).getEncoded());
110+
fail("no exception on an empty pubInfos");
111+
}
112+
catch (IllegalArgumentException e)
113+
{
114+
isEquals("pubInfos must contain at least one SinglePubInfo", e.getMessage());
115+
}
116+
117+
try
118+
{
119+
new PKIPublicationInfo(new SinglePubInfo[0]);
120+
fail("no exception on an empty pubInfo array");
121+
}
122+
catch (IllegalArgumentException e)
123+
{
124+
isEquals("pubInfos must contain at least one SinglePubInfo", e.getMessage());
125+
}
73126
}
74127

75128
private void encEqualTest(PKIPublicationInfo pubInfo)

0 commit comments

Comments
 (0)