Repository navigation
Description
Summary
CMSAuthenticatedDataParser.getUnauthAttrs() never returns the unauthenticated attributes of an AuthenticatedData. It always returns null. Behind that is a second defect: once the first is fixed, the call throws ClassCastException instead.
Both are present in 1.86 and on main (d401e82d3f). The non-streaming CMSAuthenticatedData.getUnauthAttrs() reads the same message correctly.
Defect 1: unauthAttrNotRead is never set
CMSAuthenticatedDataParser.getUnauthAttrs() only reads the attributes if (unauthAttrs == null && unauthAttrNotRead). The constructor sets authAttrNotRead = true but never sets unauthAttrNotRead, so the guard is always false and the method returns null.
CMSAuthEnvelopedDataParser sets both flags in its constructor:
authAttrNotRead = true;
unauthAttrNotRead = true;Defect 2: ClassCastException in AuthenticatedDataParser.getUnauthAttrs()
ASN1TaggedObject o = (ASN1TaggedObject)nextObject;nextObject comes from an ASN1SequenceParser, so it is a DLTaggedObjectParser or BERTaggedObjectParser, never an ASN1TaggedObject. The sibling AuthEnvelopedDataParser.getUnauthAttrs() casts to ASN1TaggedObjectParser, which is correct.
Defect 1 hides defect 2 in the cms API. Calling org.bouncycastle.asn1.cms.AuthenticatedDataParser directly hits defect 2 on any message that has unauthAttrs.
Reproducer
The message is generated by BC itself:
Security.addProvider(new BouncyCastleProvider());
CMSAuthenticatedDataGenerator gen = new CMSAuthenticatedDataGenerator();
gen.addRecipientInfoGenerator(new JcePasswordRecipientInfoGenerator(
CMSAlgorithm.AES128_CBC, "password".toCharArray()).setProvider("BC")
.setPasswordConversionScheme(0).setSaltAndIterationCount(new byte[20], 1000));
Attribute attr = new Attribute(new ASN1ObjectIdentifier("1.2.3.4"),
new DERSet(new DERUTF8String("x")));
gen.setUnauthenticatedAttributeGenerator(
new SimpleAttributeTableGenerator(new AttributeTable(attr)));
byte[] encoded = gen.generate(new CMSProcessableByteArray("hello".getBytes("UTF-8")),
new JceCMSMacCalculatorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider("BC").build())
.getEncoded();
// non-streaming: 1 attribute
System.out.println(new CMSAuthenticatedData(encoded).getUnauthAttrs().size());
// streaming, read in the documented order: content, mac, attributes
CMSAuthenticatedDataParser parser = new CMSAuthenticatedDataParser(encoded);
RecipientInformation recipient =
(RecipientInformation)parser.getRecipientInfos().getRecipients().iterator().next();
Streams.drain(recipient.getContentStream(
new JcePasswordAuthenticatedRecipient("password".toCharArray()).setProvider("BC")
.setPasswordConversionScheme(0)).getContentStream());
System.out.println(Arrays.areEqual(parser.getMac(), recipient.getMac())); // true
System.out.println(parser.getUnauthAttrs()); // nullIf unauthAttrNotRead is set to true (via reflection, to simulate fixing defect 1), the last call throws:
java.lang.ClassCastException: class org.bouncycastle.asn1.DLTaggedObjectParser cannot be cast to class org.bouncycastle.asn1.ASN1TaggedObject
at org.bouncycastle.asn1.cms.AuthenticatedDataParser.getUnauthAttrs
at org.bouncycastle.cms.CMSAuthenticatedDataParser.getUnauthAttrs
Suggested fix
- In the
CMSAuthenticatedDataParserconstructor, setthis.unauthAttrNotRead = true;next toauthAttrNotRead. - In
AuthenticatedDataParser.getUnauthAttrs(), cast toASN1TaggedObjectParser, asAuthEnvelopedDataParserdoes. - Add a test that reads unauthenticated attributes through
CMSAuthenticatedDataParser. No existing test does:NewAuthenticatedDataStreamTestnever callsgetUnauthAttrs(), which is how both defects went unnoticed.
Activity
CMSAuthenticatedDataParser now marks the unauthenticated attributes a…
dghgit commentedon Oct 31, 2026
Thanks for the report. Now fixed and up on https://www.bouncycastle.org/betas