MINOR: Bump org.bouncycastle:bcpkix-jdk18on from 1.84 to 1.85 (#1286)

Bumps
[org.bouncycastle:bcpkix-jdk18on](https://github.com/bcgit/bc-java) from
1.84 to 1.85.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.md">org.bouncycastle:bcpkix-jdk18on's
changelog</a>.</em></p>
<blockquote>
<h1>Bouncy Castle Crypto Package - Release Notes</h1>
<h2>1.0 Introduction</h2>
<p>The Bouncy Castle Crypto package is a Java implementation of
cryptographic algorithms. The package is organised so that it contains a
light-weight API suitable for use in any environment (including the
J2ME) with the additional infrastructure to conform the algorithms to
the JCE framework.</p>
<h2>2.0 Release History</h2>
<p><!-- raw HTML omitted --><!-- raw HTML omitted --></p>
<h3>2.1.1 Version</h3>
<p>Release: 1.86<br />
Date: 2026, TBD</p>
<h3>2.1.2 Defects Fixed</h3>
<ul>
<li>The high-level OpenPGP API (org.bouncycastle.openpgp.api) let a
subkey inherit the primary key's Key Flags when its own Subkey Binding
signature carried no Key Flags subpacket, which made the two capability
decisions taken for one subkey disagree.
OpenPGPCertificate.OpenPGPComponentKey.isSigningKey() reads the
effective flags, which fell back to the primary key's direct-key or
primary user ID self-signature, so a subkey bound with no flags of its
own counted as signing-capable; verifyEmbeddedPrimaryKeyBinding reads
the binding signature's own flags, found no signing capability there,
and so skipped the embedded Primary Key Binding (cross-certification)
signature that RFC 9580 sec. 5.2.1.8 and sec. 10.1.3 require of a subkey
that can issue signatures. A data signature made by such a subkey was
therefore attributed to the certificate and reported valid by
OpenPGPSignature.OpenPGPDocumentSignature.isValid() with the
cross-certification requirement never applied, where GnuPG refuses the
same certificate and message as not cross-certified. An attacker holding
a third party's public signing subkey - which is public material - could
bind it to their own primary key with a Subkey Binding signature they
are able to make, carrying no Key Flags and no embedded Primary Key
Binding signature, which they cannot make without the subkey's private
key, and have that party's genuine signatures verify as valid under the
attacker's own identity: misattribution of a real signature rather than
a forgery of a new one, since the signature still has to be one the
subkey actually made. Key Flags are a statement about the key the
carrying signature refers to (RFC 9580 sec. 5.2.3.29), so a subkey no
longer inherits them from the certificate-wide signatures of the primary
key: a Subkey Binding signature that omits the subpacket now leaves the
subkey with no capabilities rather than the primary's, which makes the
flags the cross-certification check consults the same flags every other
decision consults. Preferences and the other subpackets a direct-key
signature carries are inherited as before, and the primary key itself -
whose flags legitimately come from its direct-key or user ID
self-signature - is unaffected. The low-level PGPSignature /
PGPPublicKeyRing API performs no binding checks by design and is
unchanged.</li>
<li>The high-level OpenPGP API (org.bouncycastle.openpgp.api) used a
version 6 key that carried no valid Direct Key signature, falling back
to the primary user ID binding as it correctly does for a version 4 key.
RFC 9580 sec. 5.2.3.10 requires the opposite: &quot;An implementation
MUST ensure that a valid Direct Key signature is present before using a
version 6 key. This prevents certain attacks where an adversary strips a
self-signature specifying a Key Expiration Time or certain
preferences.&quot; The certificate grammar says the same structurally,
the Direct Key signature being mandatory in the version 6 structure of
sec. 10.1.1 and optional in the version 4 one of sec. 10.1.3. Because a
version 6 certificate carries its key expiration, features and algorithm
preferences on the Direct Key signature - the convention the RFC
recommends and the one OpenPGPKeyGenerator follows, its user ID
certification carrying no expiration at all - removing that single
signature packet from a published certificate silently dropped the
expiration along with the preferences and features:
OpenPGPCertificate.getSignatureChainFor fell back to the user ID
binding, the primary key was still reported bound, and
getEncryptionKeys() and getSigningKeys() went on returning the subkeys
of a key whose owner had set it to expire. The primary key fingerprint
is unchanged by the removal, so a relying party pinning the key by
fingerprint still treats it as the same key, and no private key or hash
collision is involved; the natural moment for the strip is the key
refresh that RFC 9580 names as the reason to refetch a key at all - to
learn about changes in expiration, features, preferences and revocation
- which is exactly the update it defeats. This is a downgrade rather
than a forgery, nothing being attributed to a key that did not authorise
it, and the concerning direction is encryption, to a key meant to have
been retired. OpenPGPCertificate.isBoundBy now requires a valid Direct
Key self-signature on a version 6 primary key before any component of
the certificate - the primary key, its subkeys or its identities - is
treated as bound, so a version 6 certificate stripped of it offers no
keys at all rather than an unexpiring set. Version 4 certificates are
unaffected: there the key expiration legitimately lives on the user ID
self-signature and the fallback is correct, so it stays. The
revocation-only version 6 certificate of sec. 10.1.2, which legitimately
carries no Direct Key signature, is unaffected as well - its key was
already refused as revoked, and reading the revocation does not go
through the binding check.</li>
<li>The lightweight LMSSigner and HSSSigner refused a key wrapped in
ParametersWithRandom, which is how BcContentSignerBuilder passes a key
whenever setSecureRandom() has been called - so
BcHssLmsContentSignerBuilder built a working signer until a random was
set and then failed with &quot;Incorrect Key Parameters&quot;, and the
two signers themselves raised ClassCastException on the same input. All
three now unwrap it, as the promoted ML-DSA and SLH-DSA signers already
did. The random is accepted and not used: LMS derives its message
randomiser C from the key's seed and the one-time index, so it is
deterministic and cannot repeat while q does not. Note SP 800-208 sec.
6.1 asks for C to come from an approved random bit generator, which this
implementation does not do; that is unchanged here, and a supplied
random is now ignored rather than refused.</li>
<li>LMS signature verification did not apply two of the checks RFC 8554
sec. 5.4.2 requires before a signature is processed. Step 2g refuses a
signature whose LMS typecode is not the one from the public key, and
without it the path computation took its height and tree digest from the
parameter set the signature named rather than the key's, so a signature
claiming a height-25 parameter set drove a 25-level computation against
a height-5 key. Step 2i refuses a leaf number q outside the tree, and
without it an out-of-range q flowed into the node arithmetic and was
left for the candidate-root comparison to catch. Neither was a forgery -
the domain separation between D_LEAF and D_INTR and the final comparison
saw to that - but both are attacker-chosen work the specification says
to refuse up front. Both are now checked, and a signature failing either
is still reported as not verifying rather than thrown out of
Signature.verify(). The catch around the signature decode in LMSSigner
and HSSSigner has also been narrowed to the decode itself, as the
corresponding SPI was corrected to do for github <a
href="https://redirect.github.com/bcgit/bc-java/issues/2408">#2408</a>:
past the parse the engine reports an inconsistent signature by returning
false rather than by throwing, so the wider catch caught nothing while
standing ready to turn a future internal error into a quiet false.</li>
<li>The LMS and HSS key parameter classes now apply at construction the
checks their decoders apply, so a key built directly cannot be one the
decoder would refuse. LMSPrivateKeyParameters accepted an identifier of
any length although the decoder reads exactly 16 bytes - such a key
encoded but could not be read back - and left q, maxQ and the seed
length unchecked; the seed is now required to be at least m bytes at
decode as well, where a one-byte seed had been decoding silently and
then deriving every one-time key from it. HSSPrivateKeyParameters
checked neither its level count nor that it had been given a component
key per level and a chaining signature per level below the root, and
then indexed both lists, so a mismatch surfaced as
IndexOutOfBoundsException - or, where a level happened to match, as a
null chaining signature that only failed at signing time; the level is
now checked after the reset that fills it in, since a null is legitimate
on the way in. LMSPrivateKeyParameters.getInstance(byte[], byte[])
adopted the public key supplied beside the private one without comparing
them, so a mismatched public key was simply reported by getPublicKey();
it now cross-checks the identifier, both parameter sets and, where the
tree cache already holds it, the root, as the HSS entry point does. The
decoders also now report a bad version or seed length as IOException
rather than IllegalStateException, so a caller can catch one type for a
malformed key, and the package-private LM-OTS public key decoder no
longer declares throws Exception or dereferences an unrecognised
typecode. The deprecated org.bouncycastle.pqc.crypto.lms copies carry
the decoder corrections.</li>
<li>In the LMS JCE layer, LMSKeyGenParameterSpec.fromNames knew all
twenty LMS parameter-set names but only four of the sixteen LM-OTS ones,
so none of the SP 800-208 n24 or SHAKE sets could be named; all sixteen
are now present. KeyPairGenerator.initialize(int, SecureRandom) reports
InvalidParameterException, which is what the JCA specifies and which
extends the IllegalArgumentException it raised before, so existing
catches still match. BCLMSPrivateKey.getIndex now takes the exhaustion
check and the index read under the key's own monitor rather than as two
separate calls, and two unused fields have gone from LMSSignatureSpi.
Note that the LMS Signature claims its one-time key at the first
update() rather than at sign(), so a Signature that is initialised and
updated and then abandoned spends an index without producing a signature
- the safe direction for a one-time scheme, and now documented on the
SPI.</li>
<li>Every other key pair generator that refuses a key size did the same
thing the LMS one did above. KeyPairGenerator.initialize(int,
SecureRandom) is documented to raise InvalidParameterException when the
key size is not one the generator supports, and thirty of them raised a
bare IllegalArgumentException instead, so a caller following the JCA and
catching the documented type saw an exception escaping rather than a
refusal. The ML-DSA, ML-KEM, SLH-DSA, Classic McEliece, FrodoKEM, NTRU
and composite signature and KEM generators in the BC provider, and every
generator in BCPQC, now raise InvalidParameterException - NewHope
included, where the value is a key size it will not take rather than a
mode of initialisation it does not offer. InvalidParameterException
extends IllegalArgumentException, so callers written against the old
behaviour go on catching it unchanged. The two RSA generators are
corrected as well, and differently: theirs is a key size below a floor
rather than a mode of initialisation they do not offer, and the refusal
is raised by the lightweight RSAKeyGenerationParameters, which cannot
name a java.security exception at all, so the RSA KeyPairGeneratorSpi
now translates it - keeping the message verbatim and the original
exception as the cause, through a new
SecurityExceptions.invalidParameterException factory, since
InvalidParameterException has no constructor that takes one. Of the 304
KeyPairGenerator services the two providers register, 299 now refuse a
nonsense key size the way the JCA defines it and five accept it as a
strength they can work with, with none left raising a bare
IllegalArgumentException; that is asserted as a sweep over both
providers rather than per algorithm, so a generator added later is
covered without the test being touched. The jdk1.3 provider overlays of
the four generators that have one carry the same change, and the jdk1.3
SecurityExceptions overlay gains the new factory along with the
invalidAlgorithmParameterException one it had been missing.</li>
<li>KeyPairGenerator.initialize(AlgorithmParameterSpec, SecureRandom)
had the same shape of problem as the int overload above, and only 44 of
the 304 services the two providers register reported an unusable spec as
the InvalidAlgorithmParameterException that method declares. The
twenty-three PQC generators that resolve a parameter set by name - cmce,
frodokem, mldsa, mlkem, slhdsa, aimer, bike, faest, falcon, haetae, hqc,
mayo, mqom, ntru, ntruplus, both ntruprime variants, qruov, saber,
sdith, smaugt, snova and sqisign, 230 services between them -
case-folded the name they got back from the spec without checking it, so
a spec with no getName() method, and a null spec, produced a
NullPointerException from inside the fold; their own &quot;is this name
one I know&quot; branch, which does report the declared exception, was
unreachable. The two composite generators, whose parameter set is fixed
by the algorithm name so that null is the only spec they accept, refused
every other one with IllegalArgumentException. RSA answered correctly
for a spec of the wrong type but not for one of the right type carrying
values its lightweight parameters will not take - an even public
exponent, or a key size below the floor - which is now translated the
same way the int overload's is. All 304 services now report the declared
exception, with the composites still taking the null spec that is right
for them; note that InvalidAlgorithmParameterException is a checked
exception and not an IllegalArgumentException, so a caller that was
catching what these threw before has to catch the declared type
instead.</li>
<li>An HSS private key claimed the two records of its position under two
different monitors. The top-level index and the bottom component key's
one-time index q are independent records of the same position - the
decoder requires them to agree, see the entry below - but
generateLMSContext incremented the index under the HSS key's own
monitor, released it, and only then claimed q under the component key's.
A getEncoded() issued in between saw the index advanced and q not, and
produced an encoding this implementation's own decoder rejects; and two
threads meeting at a bottom-tree boundary could both pass the exhaustion
test, take consecutive top-level indices and claim the same q, after
which one of them was refused with &quot;ots private key exhausted&quot;
by a key still reporting usages remaining, a top-level index had been
spent with no signature made, and the two records stayed one apart for
the rest of the key's life in that process - so it could no longer be
encoded, cloned or sharded, and getIndex() and getUsagesRemaining()
misreported by one. No one-time key was reused: the component key's
claim is itself atomic, and the divergence runs index ahead of leaves,
so the effect was on the key's usability rather than on the signatures
it had made. Both records are now claimed under the one monitor, and the
component key is claimed before the index is incremented so that an
exhausted one leaves both untouched. The deprecated
org.bouncycastle.pqc.crypto.lms copy carries the same correction.</li>
<li>TimeStampToken parsed the attacker-controlled TSTInfo content of a
time-stamp token (org.bouncycastle.tsp.TimeStampToken, reached from
TimeStampResponse(byte[]) and (InputStream)) inside a try that caught
only CMSException, so a well-formed RFC 3161 TimeStampResp whose
embedded token carried a malformed TSTInfo - a SEQUENCE with fewer
elements than the five mandatory fields, a non-SEQUENCE, truncated DER,
or an unknown context tag - let an IllegalArgumentException or a
NoSuchElementException escape the constructor's declared throws
TSPException, IOException contract. A token signed by no signers or by
more than one raised a bare IllegalArgumentException from the same
constructor, before that try, for the same reason. Both are now reported
as TSPException, matching the package-private
TimeStampResponse(DLSequence) constructor and the existing
getSignedAttributes guard in the same method; the signer-count refusal
is a TSPValidationException, as the neighbouring check on the content
type already was. Related, and the cause of the NoSuchElementException:
asn1.tsp.TSTInfo read its five mandatory fields off the sequence with no
bound on how many elements were actually there, so a short SEQUENCE left
the enumeration to run out rather than being refused, and an oversize
one was accepted with its extra elements absorbed into the optional
slots. Decode now requires the five to ten elements RFC 3161 sec. 2.4.2
gives the type, which is what the sibling Accuracy and ArchiveTimeStamp
decoders already did, so the failure is the IllegalArgumentException
that getInstance is expected to raise (github <a
href="https://redirect.github.com/bcgit/bc-java/issues/2415">#2415</a>).</li>
<li>Neither the HSS nor the XMSS^MT private key decoder checked its
declared index against the traversal state stored beside it, although
the two are independent records of the same position in the key and so
can be compared. For HSS the records are the top-level index and the
component keys' one-time indices q; for XMSS^MT they are the global
index and the per-layer BDS states. A stored key whose index had been
rolled back while its state stayed advanced - a partial write, a restore
from backup, a buggy storage layer - was therefore accepted, and it then
signed a second message under a one-time key the key had already used,
producing a signature that verified, so nothing anywhere surfaced the
reuse. RFC 8554 sec. 1 and RFC 8391 sec. 1.1 both require each one-time
key to be used exactly once, and this is the failure those requirements
exist to prevent; the single-tree XMSS decoder has tied its BDS state to
its index since that state was first validated, and this brings the two
multi-tree schemes into line. HSS decode now requires the declared index
to equal the position the component q values imply - a level above the
last contributes (q - 1) leaves of the levels beneath it, since its q
has already advanced past the subtree it signed - and XMSS^MT decode now
requires each present layer's BDS index to equal the leaf index that
layer derives from the global index, allowing the one position where a
layer has moved into a new subtree and its state legitimately still
carries the previous subtree's final index. A layer with no state yet is
unaffected, since those are built lazily at signing time. Related, and
the same shape of omission: an XMSS or XMSS^MT private key encoding
carries the tree root twice - the key's own root field and the root node
of the BDS state stored beside it, which for XMSS^MT is the top layer's
- and the two were never compared either. A corrupted root was accepted
and then poisoned every signature the key made, because the root is
hashed into the message digest: the signature did not verify and nothing
indicated why. Decode now requires the two copies to agree. The BDS node
values themselves are not checkable the way the LMS tree cache above is
- a BDS authentication path, stack, retain or keep node does not have
its children stored alongside it, so recomputing one means building a
subtree, which is the work the state exists to avoid. Both checks are
integer comparisons over the levels of the key, too small to measure
against the surrounding decode, and both were verified not to reject any
legitimate key by walking every index a key can reach: the full key
space of the two-level HSS and the h=4/d=2, h=6/d=2, h=6/d=3, h=9/d=3
and h=8/d=4 XMSS^MT parameter sets, plus a three-level HSS key across a
subtree boundary and an HSS shard. Since those node values cannot be
recomputed, the encoded state now carries a checksum over itself
instead, with the owning key's public seed hashed in front of it. Any
corruption of the stored state is refused at decode rather than being
loaded and then producing signatures that silently do not verify, and
because the public seed is bound in, a state transplanted between two
keys of the same parameter set is refused too, even though it is
internally consistent and arrives with its own matching root. The public
seed is bound rather than the secret seed or the PRF key deliberately:
the state's own root and index are inside the encoding and so are
already covered, hashing secret material would make the stored checksum
a commitment to it for no gain in detection, and the PRF key does not
influence the state at all. <strong>This is an error-detecting code and
not integrity protection</strong> - anyone able to rewrite the stored
key recomputes it, so it establishes that the state is unchanged since
it was written, never that it was correct when written, and the
allocation bounds on the encoding remain the guard against a crafted
one. It costs one SHA-256 over the state, measured at 5 to 8
microseconds each way for the h=10 and h=16 parameter sets, and 32 bytes
of encoding. The state encoding was added earlier in this same cycle and
has not been released, so the checksum is simply part of it rather than
a new version: a state written by a 1.86 beta is rejected, which is
recovered from by re-exporting the key. The deprecated
org.bouncycastle.pqc.crypto.lms copy carries the HSS check as well
(github <a
href="https://redirect.github.com/bcgit/bc-java/issues/2414">#2414</a>).</li>
<li>The S/MIME example smoke test in the misc module
(org.bouncycastle.mail.smime.examples.test.AllTests) drove
SendSignedAndEncryptedMail against smtp.gmail.com, and that example
finishes with Transport.send() under JavaMail's default settings, which
have no connect timeout. Where outbound port 25 is refused the failure
was swallowed and the test passed; where it is silently dropped, as on
many home networks, the connect blocked and ./gradlew build hung in
:misc:test indefinitely with &quot;0 tests completed&quot;. The test now
delivers to an SMTP stub on a loopback port, with connect / read / write
timeouts as a backstop, and asserts the message arrived (github <a
href="https://redirect.github.com/bcgit/bc-java/issues/2407">#2407</a>).</li>
<li>Composite ML-KEM encapsulation took the traditional component public
key bytes it feeds the KEM combiner from the recipient key's own
encoding, while decapsulation recomputes the point from the private key
and so always produced an uncompressed one. Section 4 of
draft-ietf-lamps-pq-composite-kem requires an EC component to be carried
as an uncompressed point, but a component key that encodes itself
compressed - a BC EC key whose point format has been set through
org.bouncycastle.jce.interfaces.ECPointEncoder, or a key from a provider
that preserves a compressed encoding - was passed through as it came.
Both sides then combined a different tradPK and derived different shared
secrets, with no error reported on either: encapsulation and
decapsulation both succeeded and the recipient simply could not decrypt.
The EC component is now normalised to an uncompressed point wherever the
engine serialises one, which covers the ephemeral key that forms the
ciphertext as well. X25519 and X448 components have a single encoding
and were unaffected, as were EC keys left in their default
(uncompressed) format, whose shared secrets are unchanged.
CompositePublicKey.getEncoded() took its component bytes the same way,
so such a key also encoded to a composite key other implementations
reject and whose bytes changed across an encode / decode / encode round
trip - 1238 bytes rather than 1270 for MLKEM768-ECDH-P256, and for the
composite ML-DSA keys sharing that method, 2006 rather than 2038 for
MLDSA65-ECDSA-P256. It now normalises the component the same way. This
is a write-side change only: a composite key carrying a compressed EC
component is still decoded, since the component key factories accept
either form, and continues to verify signatures as before - it simply
re-encodes in the normalised form. The shared normalisation is
org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil.getUncompressedSubjectPublicKeyBytes.</li>
<li>Composite ML-KEM encapsulation threw a NullPointerException, wrapped
in an IllegalStateException out of KeyGenerator.generateKey(), when the
SecureRandom it was given was null - which
javax.crypto.KEM.newEncapsulator() documents as a request for the
provider's default, and which KeyGenerator.init(spec, null) passes
straight through. The three RSA-OAEP composites draw the traditional
shared secret from that random directly, so they were the ones affected;
the ECDH and X25519 / X448 composites escaped only because their
component KeyPairGenerators default a random of their own.
CompositeMLKEMEngine now defaults one through
CryptoServicesRegistrar.getSecureRandom() on first use, as the composite
KEM Cipher's wrap path already did, and as the KEM generators corrected
earlier in this cycle now do. Related, the engine now also clears the
ML-KEM component's shared secret alongside the traditional one on both
the encapsulate and decapsulate paths - the copy handed back by
getEncoded() was left in the heap - as section 3.5 of
draft-ietf-lamps-pq-composite-kem requires.</li>
<li>CompositePublicKey.getAlgorithm() and
CompositePrivateKey.getAlgorithm() returned null for all twelve
Composite ML-KEM (draft-ietf-lamps-pq-composite-kem) parameter sets.
Both classes resolved the name through the composite signature index
only, which holds the composite ML-DSA OIDs, so a composite KEM key pair
- generated, parsed from a certificate, or read from PKCS#8 - reported
no algorithm at all, and the standard JCA idiom of reconstructing a key
with KeyFactory.getInstance(key.getAlgorithm()) raised a
NullPointerException. The lookup now falls back to the composite KEM
index, so the name returned is the one the provider registers the
algorithm under (e.g. MLKEM768-X25519-SHA3-256), matching the composite
ML-DSA behaviour. The same single-index assumption made the
CompositePublicKey(SubjectPublicKeyInfo) and
CompositePrivateKey(PrivateKeyInfo) constructors reject a composite KEM
key with &quot;unable to create CompositePublicKey from
SubjectPublicKeyInfo&quot;; they now dispatch to the composite KEM key
factory for those OIDs. Keys obtained through KeyFactory or through
BouncyCastleProvider.getPublicKey / getPrivateKey were unaffected and
are unchanged (github <a
href="https://redirect.github.com/bcgit/bc-java/issues/2404">#2404</a>).</li>
<li>The org.bouncycastle.jcajce.spec.KEMKDFSpec constructor stored a
null otherInfo as given, so getOtherInfo() returned null, and three of
the KDF branches KdfUtil.makeKeyBytes dispatches to - KMAC-128, KMAC-256
and SHAKE-256 - read the otherInfo length without a guard and threw
NullPointerException out of the KEM operation, where the KDF2, KDF3 and
HKDF branches tolerate a null through KDFParameters / HKDFParameters.
The Builder of every spec in the package already mapped null to empty,
so no provider path reached it, but the constructor is protected on a
public class and KdfUtil is documented for callers building their own
KEM integration; the deprecated KEMParameterSpec passes a null itself
and escaped only because it also pins the KDF to null. The constructor
now stores empty for a null, so getOtherInfo() never returns null, and a
null and an explicitly empty otherInfo derive the same key.</li>
<li>QR-UOV signature verification accepted a signature encoding that was
not canonical, so the encoding of a signature was not unique even after
the trailing-byte fix of github <a
href="https://redirect.github.com/bcgit/bc-java/issues/2403">#2403</a>.
Each F_q element of the signature is stored in ceil(log2 q) bits, one
more bit pattern than the field has elements: q itself is representable
and is arithmetically congruent to zero, so an element written as q
verified exactly as the same element written as zero would, and the bits
padding the last element out to the byte boundary were never read at
all. Every zero element of a signature therefore carried a second
encoding, and for the q = 7 parameter sets roughly one element in seven
is zero - a single qruov_5_q7_L10 signature measured 256 spare bits, so
on the order of 2^256 distinct byte strings verified for the one message
and key. Verification now rejects any element outside [0, q) and any set
padding bit; a signature produced by this or by the reference
implementation is unaffected, as the KAT vectors of every parameter set
confirm. (github <a
href="https://redirect.github.com/bcgit/bc-java/issues/2403">#2403</a>)</li>
<li>SNOVA signature verification ignored four bits inside the signature
for any parameter set whose solution is an odd number of GF(16) nibbles
- the SNOVA_24_5_5, SNOVA_25_8_3, SNOVA_29_6_5 and SNOVA_66_15_3
families, sixteen of the forty-four parameter sets. The last byte of the
encoded solution carries a single nibble and the signer leaves the top
four bits zero, but the decoder did not read them, so sixteen distinct
byte strings verified for one signature. This is the same non-unique
encoding github <a
href="https://redirect.github.com/bcgit/bc-java/issues/2403">#2403</a>
closed for bytes following the signature, applied inside it; the
verifier now requires those bits to be zero. (github <a
href="https://redirect.github.com/bcgit/bc-java/issues/2403">#2403</a>)</li>
<li>SnovaPrivateKeyParameters did not validate the length of the private
key encoding handed to it - the only one of the five schemes of github
<a
href="https://redirect.github.com/bcgit/bc-java/issues/2403">#2403</a>
that did not - and SnovaParameters.getPrivateKeyLength() reported the
expanded (&quot;ESK&quot;) length even for a parameter set whose private
key is the seed pair. A private key encoding reaches this constructor
straight from a PKCS#8 blob, so a wrong length went undetected: a
seed-form key with extra bytes appended was accepted and signed under a
different derived key, and a short expanded-form key sized the signer's
decode buffer negatively, throwing NegativeArraySizeException out of
generateSignature() rather than being reported at construction. Related,
the signing retry loop could not terminate: the vinegar values are
derived from a single-byte counter, so only 256 distinct linear systems
can be tried, and an expanded-form private key that is not a real
central map is singular for all of them - generateSignature() then span
forever rather than failing. The length is now checked at construction,
getPrivateKeyLength() reports the length that parameter set's private
key actually has, and the retry loop gives up after its 256 attempts as
MAYO's does.</li>
<li>MayoSigner and MayoKeyPairGenerator did not clear several buffers
holding secret key material that the MAYO reference implementation
explicitly clears. Signing left the secret oil space O, the expanded L =
(P1 + P1^t) * O + P2, and the M / VPV / Ox intermediates of the central
map in place, having gone to the trouble of clearing eleven other
buffers; key generation left the expanded seed, whose tail is the
encoded oil space, and the P1 * O + P2 half of P; and the row-echelon
step left the packed echelon form of the secret linear system and its
pivot rows. Separately, if all 256 attempts at solving for the signature
had given a rank-deficient system, signing emitted a signature built
from the failed attempt's state instead of reporting the failure the
reference returns, and AIMerSigner.generateSignature returned an empty
array on failure, which a caller would hand on as though it were a
signature. Both now throw.</li>
<li>Five PQC signature schemes - MAYO, SNOVA, QR-UOV, SQIsign and AIMer
- returned the NIST crypto_sign &quot;sm&quot; signed-message envelope
from generateSignature() rather than the signature. That envelope is an
artefact of the reference KAT harness, which records the message
alongside the signature so a vector file can be self-contained; it is
not part of any of the five specifications, and no other BC signer emits
it (Falcon's KAT test rebuilds the equivalent envelope in the test,
which is where it belongs). Two consequences followed, both reaching the
JCA Signature services of every parameter set of the five schemes in
BouncyCastlePQCProvider. First, since the message was appended to the
signature, verification had to skip whatever followed the signature
proper, and it did so by checking only that the buffer was long enough -
so any number of trailing bytes could be added to a valid signature, or
the appended message replaced with unrelated data, and it still
verified. A signature encoding was therefore not unique: anyone holding
one valid signature could produce unlimited distinct byte strings that
all verified for the same message and key, which breaks any use that
treats the signature bytes as an identifier, deduplicates on them, or
records them as evidence. Second, the envelope propagated into
everything built on the operator layer: because ContentSigner hands the
signature straight into the structure being signed, every X.509
certificate, CRL, CMS SignedData and TLS CertificateVerify BC produced
with one of these algorithms carried a verbatim copy of the signed data
inside its own signature field - a self-signed MAYO-1 certificate came
to 3567 bytes where the same certificate is now 2020 - which no other
implementation can parse as a signature, and which in a detached CMS
signature meant the &quot;detached&quot; signature carried the content.
generateSignature() now returns the bare signature, and
verifySignature() requires exactly the parameter set's signature length,
so appended or truncated data is rejected rather than ignored.
<strong>This is a behavioural change for signatures produced by an
earlier release</strong> - MAYO and SNOVA from 1.84, QR-UOV, SQIsign and
AIMer from 1.85 - which are no longer accepted in the envelope form they
were emitted in; the signature bytes themselves are unchanged, so a
stored value can be recovered by taking the leading signature-length
bytes, or for AIMer, whose envelope was message || signature rather than
signature || message, the trailing ones. The KAT tests now rebuild the
envelope before comparing against the vector files, which continue to
record it. Note that AIMer's verification had already been made
length-exact during this cycle (see the entry below relating to github
<a
href="https://redirect.github.com/bcgit/bc-java/issues/2401">#2401</a>),
so of the five only its envelope remained (github <a
href="https://redirect.github.com/bcgit/bc-java/issues/2403">#2403</a>).</li>
<li>The MLS implementation did not bind an X.509 credential to the
LeafNode's signature_key. LeafNode.verify() checked a leaf's signature
against the signature_key declared in the leaf itself, while the X.509
credential's certificate chain was stored but never parsed or checked,
so the certificate's public key was never required to match
signature_key (RFC 9420 sec. 5.3). A leaf could therefore carry one
party's certificate while being signed by an unrelated key and still be
accepted under that party's identity through KeyPackage.verify() and the
Group leaf-validation path. LeafNode.verify() now requires the
end-entity certificate's subject public key, in the cipher suite's
signature encoding, to equal signature_key for an X.509 credential, and
rejects the leaf otherwise - including an empty chain or a certificate
whose key type does not match the cipher suite; certificate-chain and
identity validation to a trust anchor remain the application's
responsibility per RFC 9420 sec. 5.3.1. A public
org.bouncycastle.mls.codec.Certificate(byte[]) constructor and a
Credential.getCertificates() accessor are added so callers can build and
inspect X.509 credentials. Basic credentials are unaffected.</li>
<li>The SecureRandom supplied to
org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder.setSecureRandom()
did not drive the content IV / nonce for any algorithm other than RC2.
EnvelopedDataHelper.generateParameters passed the caller's SecureRandom
to the AlgorithmParameterGenerator only in the RC2_CBC branch; every
other content-encryption algorithm - AES-CBC, AES-GCM, AES-CCM,
Camellia, ARIA, SEED and the rest - reached pGen.generateParameters() on
an uninitialised generator, so the IV / nonce was drawn from a default
SecureRandom and setSecureRandom() was silently ignored (the builder's
javadoc states that random is used for IV/nonce generation). The
generator is now initialised with the supplied random on the general
path as well, so a caller who provides a specific randomness source -
for a controlled or FIPS-approved DRBG, say - has it honoured for the
content IV / nonce. The session-key generation path was unaffected and
already used the supplied random. Because the content IV / nonce now
comes from the supplied SecureRandom, the org.bouncycastle.crypto.util
JournalingSecureRandom / JournaledAlgorithm reproducible-encryption
support records it in the transcript: a resumed session reproduces the
IV / nonce by regenerating it from the replayed randomness - build the
resuming encryptor from the content-algorithm OID - rather than by
reusing the AlgorithmIdentifier captured from the first encryption,
which no longer keeps the transcript aligned.</li>
<li>The NTRU LPRime, NTRU+ and SMAUG-T KEM generators threw a
NullPointerException when constructed with a null SecureRandom, where
every other KEM generator - including NTRU LPRime's own SNTRU Prime
counterpart in the same package - defaults one through
CryptoServicesRegistrar.getSecureRandom(). This is reachable from the
lightweight API directly, and from javax.crypto.KEM, whose
newEncapsulator() documents a null random as a request for the
provider's default.</li>
<li>FrodoKEMEngine kept a single SHAKE instance in a field, so an engine
reached concurrently produced wrong results. It is reached that way
through org.bouncycastle.crypto.kems.FrodoKEMExtractor, which holds one
engine for its lifetime: two threads extracting through one extractor
interleaved the digest's absorb and squeeze phases, yielding shared
secrets that silently did not match the sender's, or an
IllegalStateException of &quot;attempt to absorb while squeezing&quot;
from inside extractSecret. The digest is now built per call, as
CMCEEngine's already was, which makes an extractor safe to share.
Encapsulation was unaffected, since FrodoKEMGenerator builds an engine
per call. Results for any single-threaded use are unchanged - the
reference KAT vectors are byte-identical.</li>
<li>The BCJSSE provider carried the TLS 1.2 coupling between the
supported_groups extension and ECDSA over into TLS 1.3: an ECDSA
signature scheme was treated as usable - offered in the
signature_algorithms and signature_algorithms_cert extensions, and
eligible when selecting the local credentials - only while the
corresponding curve was among the named groups enabled for key exchange,
both per context (a group unavailable for key agreement disabled the
scheme outright) and per connection (the curve had to be in the
supported_groups list about to be sent). RFC 8446 sec. 4.2.7 scopes
supported_groups to key exchange only, with signature algorithms
negotiated independently (sec. 4.2.3), so this incorrect restriction in
TLS 1.3 has been removed. Ed25519, Ed448 and the RSA schemes were
unaffected (as well as typical deployments using a default configuration
for named groups).</li>
<li>The bcmail module descriptor did not declare its
javax.mail/javax.activation dependences, so a modular (module-path)
consumer of the jar hit IllegalAccessError/module-resolution failures
when the S/MIME classes touched the mail API. The descriptor now
requires them optionally (requires static) under all four module names
those libraries are known by - the automatic names mail and activation
carried by the javax.mail:mail / javax.activation:activation artifacts,
and the explicit names java.mail and java.activation carried by the
newer com.sun.mail / com.sun.activation ones - a hard requires on any
one name would break users of the others (github <a
href="https://redirect.github.com/bcgit/bc-java/issues/2389">#2389</a>).</li>
<li>Four type-coercion helpers in the OER / IEEE 1609.2 (ITS) decoder
tested the wrong type in the identity fast path that lets a
getInstance() factory return an argument that is already of the target
type. org.bouncycastle.oer.its.ieee1609dot2.basetypes.UINT32.getInstance
and org.bouncycastle.oer.its.etsi102941.basetypes.Version.getInstance
guarded on UINT8 - a sibling of UINT32 under UintBase, and unrelated to
Version - so passing a UINT8 threw ClassCastException, while passing an
actual UINT32 or Version missed the fast path and fell through to
ASN1Integer.getInstance, which rejects them: neither factory accepted
its own type.
org.bouncycastle.oer.its.etsi103097.EtsiTs103097DataEncryptedUnicast.getInstance
guarded on its sibling EtsiTs103097DataEncrypted and then cast to the
unicast type, so an EtsiTs103097DataEncrypted threw ClassCastException.
org.bouncycastle.oer.OEROptional.getObject(Class) called
value.getClass().isInstance(type) with the arguments transposed, which
is always false because the argument is a java.lang.Class, so the cast
path was dead and every optional field was resolved reflectively,
failing with IllegalStateException for a target type with no static
getInstance. Each guard now names the type it returns, matching the
sibling UINT8 / UINT16 / UINT64 and EtsiTs103097DataEncrypted factories
(github <a
href="https://redirect.github.com/bcgit/bc-java/issues/2373">#2373</a>).</li>
<li>DefaultAlgorithmNameFinder and DefaultSignatureNameFinder had no
entries at all for the ShangMi algorithms, so an SM2 signature
AlgorithmIdentifier that DefaultSignatureAlgorithmIdentifierFinder
itself produces came back named only by its OID string -
getAlgorithmName(GMObjectIdentifiers.sm2sign_with_sm3) returned
&quot;1.2.156.10197.1.501&quot; and hasAlgorithmName returned false.
Both finders now name sm2sign_with_sm3 as SM3WITHSM2 and
sm2sign_with_sha256 as SHA256WITHSM2, and DefaultAlgorithmNameFinder
additionally names the sm3 digest. All three resolve through the BC
provider, as Signature and MessageDigest respectively. The remaining GM
arc - the SM4 cipher modes, the sm2encrypt variants, and the SM1 / SM6 /
SSF33 ciphers BC does not implement - is still unnamed (github <a
href="https://redirect.github.com/bcgit/bc-java/issues/2377">#2377</a>).</li>
<li>The RFC 4998 evidence-record classes compared the digest
AlgorithmIdentifier named by a time-stamp authority with the one their
own DigestCalculator uses, and did so with AlgorithmIdentifier.equals(),
which compares the encodings. A TSA that names SHA-256 with an explicit
NULL parameters field - DigiCert among them - therefore failed against
BC's own calculator, which names it with the parameters absent, and
ERSArchiveTimeStampGenerator.generateArchiveTimeStamp rejected the
response with &quot;time stamp imprint for wrong algorithm&quot;. Both
spellings name the same digest and RFC 5754 sec. 2 requires a receiver
to accept either, while requiring that identifiers be generated with the
parameters absent, which BC already does. The three affected comparisons
- the two in ERSArchiveTimeStampGenerator and the digest check in
ERSEvidenceRecord.renew - now use the new
AlgorithmIdentifier.areEquivalent, which matches on the algorithm and
treats an absent parameters field and NULL as the same, and the
consistency check across an evidence record's archive time stamp chain
uses it too. An identifier carrying an actual parameter structure is
never equivalent to one carrying none (github <a
href="https://redirect.github.com/bcgit/bc-java/issues/2379">#2379</a>).</li>
<li>EDIPartyName.toASN1Primitive emitted the nameAssigner and partyName
DirectoryStrings without their context tags, so an EDIPartyName built
through its public constructor could not be parsed back by
EDIPartyName.getInstance, which correctly requires them. RFC 5280 sec.
4.2.1.6 tags both members [0] and [1], and those tags are explicit
despite the module's IMPLICIT TAGS because DirectoryString is a CHOICE,
which X.680 does not allow to be tagged implicitly - the decoder already
had this right. The encoder now matches it. Note the type was added
during the 1.85 cycle and GeneralName validates its ediPartyName
alternative through it, so a GeneralName carrying an untagged
ediPartyName - including one BC itself produced - is rejected where 1.84
passed it through unexamined; the untagged form is not read leniently
(github <a
href="https://redirect.github.com/bcgit/bc-java/issues/2380">#2380</a>).</li>
<li>RSASSA-PSS could not be used with a RIPEMD digest through the JCA
API. Nothing registered the RIPEMD PSS signatures, so
Signature.getInstance(&quot;RIPEMD160WITHRSAANDMGF1&quot;) raised
NoSuchAlgorithmException, and the generic RSASSA-PSS route with an
explicit PSSParameterSpec failed too:
org.bouncycastle.jcajce.provider.util.DigestFactory.getDigest returned
null for a RIPEMD name, and isSameDigest - an allow-list of the SHA
families and MD5 - reported two identical RIPEMD names as different
digests, so the spec was rejected with &quot;digest algorithm for MGF
should be the same as for PSS parameters&quot;. isSameDigest now answers
true for equal names whatever the digest, which also covers Whirlpool,
SM3, GOST3411 and anything else outside that allow-list; DigestFactory
recognises RIPEMD128, RIPEMD160 and RIPEMD256 by name and OID; the three
PSS signatures are registered with MGF1 over the same digest and a salt
of the digest length; and DefaultSignatureAlgorithmIdentifierFinder
gains the matching RIPEMD*WITHRSAANDMGF1 entries with their
RSASSA-PSS-params, so the operator/JcaContentSignerBuilder path works as
well. Note BC continues to require the PSS hash and the MGF1 hash to be
the same, which RFC 8017 does not itself demand (github <a
href="https://redirect.github.com/bcgit/bc-java/issues/2381">#2381</a>).</li>
<li>The opt-in key-size validation on CMS key-transport recipients
(org.bouncycastle.cms.jcajce.JceKeyTransRecipient.setKeySizeValidation(true))
never ran for a message using RFC 9709 CEK derivation
(id-alg-cek-hkdf-sha256): the branch that should have selected the
actual content-encryption algorithm carried in the KDF
AlgorithmIdentifier's parameters compared the encrypted-key byte array
against the id-alg-cek-hkdf-sha256 object identifier - a comparison that
is always false - so the check fell through to a key-size lookup on the
outer KDF OID, which has no registered key size, and silently checked
nothing. A key-transport EnvelopedData/AuthEnvelopedData whose
transported (and HKDF-derived) content-encryption key did not match the
key size of the advertised content-encryption algorithm was therefore
accepted even with validation enabled. The recipient now dispatches on
the content-encryption AlgorithmIdentifier's algorithm OID, so key-size
validation of RFC 9709 messages checks the recovered key against the
inner content-encryption algorithm. Messages with a matching key size,
non-HKDF messages, and recipients that do not enable validation are
unaffected.</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/bcgit/bc-java/commits">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.bouncycastle:bcpkix-jdk18on&package-manager=maven&previous-version=1.84&new-version=1.85)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 file changed
tree: fdad40a331d6c85436aefda50ad30c62a32e74f7
  1. .github/
  2. .mvn/
  3. adapter/
  4. algorithm/
  5. arrow-format/
  6. arrow-variant/
  7. bom/
  8. c/
  9. ci/
  10. compression/
  11. dataset/
  12. dev/
  13. docs/
  14. flight/
  15. format/
  16. gandiva/
  17. memory/
  18. performance/
  19. tools/
  20. vector/
  21. .asf.yaml
  22. .cmake-format.py
  23. .editorconfig
  24. .env
  25. .gitattributes
  26. .gitignore
  27. .gitmodules
  28. .pre-commit-config.yaml
  29. api-changes.md
  30. Brewfile
  31. CMakeLists.txt
  32. CODE_OF_CONDUCT.md
  33. compose.yaml
  34. CONTRIBUTING.md
  35. LICENSE.txt
  36. NOTICE.txt
  37. pom.xml
  38. README.md
README.md

Arrow Java

Getting Started

The following guides explain the fundamental data structures used in the Java implementation of Apache Arrow.

  • ValueVector is an abstraction that is used to store a sequence of values having the same type in an individual column.
  • VectorSchemaRoot is a container that can hold multiple vectors based on a schema.
  • The Reading/Writing IPC formats guide explains how to stream record batches as well as serializing record batches to files.

Generated javadoc documentation is available here.

Building from source

Refer to Building Apache Arrow for documentation of environment setup and build instructions.

Flatbuffers dependency

Arrow uses Google's Flatbuffers to transport metadata. The java version of the library requires the generated flatbuffer classes can only be used with the same version that generated them. Arrow packages a version of the arrow-vector module that shades flatbuffers and arrow-format into a single JAR. Using the classifier “shade-format-flatbuffers” in your pom.xml will make use of this JAR, you can then exclude/resolve the original dependency to a version of your choosing.

Updating the flatbuffers generated code

  1. Verify that your version of flatc matches the declared dependency:
$ flatc --version
flatc version 25.1.24

$ grep "dep.fbs.version" pom.xml
    <dep.fbs.version>25.1.24</dep.fbs.version>
  1. Generate the flatbuffer java files by performing the following:
cd $ARROW_HOME

# remove the existing files
rm -rf format/src

# regenerate from the .fbs files
flatc --java -o format/src/main/java arrow-format/*.fbs

# prepend license header
mvn spotless:apply -pl :arrow-format

Performance Tuning

There are several system/environmental variables that users can configure. These trade off safety (they turn off checking) for speed. Typically they are only used in production settings after the code has been thoroughly tested without using them.

  • Bounds Checking for memory accesses: Bounds checking is on by default. You can disable it by setting either the system property(arrow.enable_unsafe_memory_access) or the environmental variable (ARROW_ENABLE_UNSAFE_MEMORY_ACCESS) to true. When both the system property and the environmental variable are set, the system property takes precedence.

  • null checking for gets: ValueVector get methods (not getObject) methods by default verify the slot is not null. You can disable it by setting either the system property(arrow.enable_null_check_for_get) or the environmental variable (ARROW_ENABLE_NULL_CHECK_FOR_GET) to false. When both the system property and the environmental variable are set, the system property takes precedence.

Java Properties

  • -Dio.netty.tryReflectionSetAccessible=true should be set. This fixes java.lang.UnsupportedOperationException: sun.misc.Unsafe or java.nio.DirectByteBuffer.(long, int) not available. thrown by Netty.
  • To support duplicate fields in a StructVector enable -Darrow.struct.conflict.policy=CONFLICT_APPEND. Duplicate fields are ignored (CONFLICT_REPLACE) by default and overwritten. To support different policies for conflicting or duplicate fields set this JVM flag or use the correct static constructor methods for StructVectors.

Java Code Style Guide

Arrow Java follows the Google Java Style Guide with the following differences:

  • Imports are grouped, from top to bottom, in this order: static imports, standard Java, org.*, com.*
  • Line length can be up to 120 characters
  • Operators for line wrapping are at end-of-line
  • Naming rules for methods, parameters, etc. have been relaxed
  • Disabled NoFinalizer, OverloadMethodsDeclarationOrder, and VariableDeclarationUsageDistance due to the existing code base. These rules should be followed when possible.

Refer to checkstyle.xml for rule specifics.

Test Logging Configuration

When running tests, Arrow Java uses the Logback logger with SLF4J. By default, it uses the logback.xml present in the corresponding module's src/test/resources directory, which has the default log level set to INFO. Arrow Java can be built with an alternate logback configuration file using the following command run in the project root directory:

mvn -Dlogback.configurationFile=file:<path-of-logback-file>

See Logback Configuration for more details.

Integration Tests

Integration tests which require more time or more memory can be run by activating the integration-tests profile. This activates the Maven Failsafe plugin and any class prefixed with IT will be run during the testing phase. The integration tests currently require a larger amount of memory (>4GB) and time to complete. To activate the profile:

mvn -Pintegration-tests <rest of mvn arguments>