Skip to content

Commit d8afa6f

Browse files
committed
strip all trailing dots in name-constraint host canonicalization
1 parent d8dca60 commit d8afa6f

3 files changed

Lines changed: 30 additions & 5 deletions

File tree

‎core/src/main/java/org/bouncycastle/asn1/x509/PKIXNameConstraintValidator.java‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,13 +1240,21 @@ private static boolean isDNSConstrained(String constraint, String dns)
12401240

12411241
private static String stripTrailingDot(String s)
12421242
{
1243-
// length > 1 so a single bare "." (theoretically the empty-label
1244-
// root) is preserved rather than reduced to "".
1245-
if (s != null && s.length() > 1 && s.charAt(s.length() - 1) == '.')
1243+
if (s == null)
12461244
{
1247-
return s.substring(0, s.length() - 1);
1245+
return s;
12481246
}
1249-
return s;
1247+
// Strip *every* trailing dot, not just one: a dNSName/rfc822Name/URI host such as
1248+
// "example.com.." (legal IA5String, unvalidated on parse) otherwise keeps a phantom
1249+
// empty label after a single strip and slips past the exact-match check. The lower bound
1250+
// of 1 preserves a bare "." (theoretically the empty-label root) rather than reducing it
1251+
// to "".
1252+
int end = s.length();
1253+
while (end > 1 && s.charAt(end - 1) == '.')
1254+
{
1255+
end--;
1256+
}
1257+
return end == s.length() ? s : s.substring(0, end);
12501258
}
12511259

12521260
/**

‎docs/releasenotes.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ Date: 2026, TBD
208208

209209
- The PKCS12-PBMAC1 keystore asked PBKDF2 for a 256-octet MAC key against an HMAC-SHA-512 message-authentication scheme, four times the size of the key that scheme uses. RFC 9579 sec. 5 says the derived key "SHOULD be the same size as the HMAC function output size", giving the worked example of a 32-octet key for HMAC-SHA-256, so the keystore now asks for 64 - and so does the default on PKCS12StoreParameter.pbmac1WithPBKDF2Builder(), the public builder that configures the same field. Nothing was weaker for the extra length; the shorter derivation is the conformant one and is marginally cheaper. Reading is unaffected either way, since the length is taken from the file: a PFX written by an earlier release, asking for 256, still verifies, and that is why the keyLength upper bound is well above the HMAC output size rather than at it. The builder's setKeySize() parameter was named keySizeinBits but has always been used as octets, being passed straight into PBKDF2Params, whose keyLength RFC 8018 app. A.2 defines as "the length in octets of the derived key"; it is renamed keySizeInOctets, which changes no behaviour and no signature - only what the parameter is called. PBMAC1WithPBKDF2Builder had no javadoc at all, so the units were undiscoverable from the API docs; it and its six methods are now documented, including the RFC 9579 sec. 5 rule that the key length match the output size of the message-authentication scheme.
210210

211+
- The name-constraint trailing-dot canonicalisation in PKIXNameConstraintValidator stripped only a single RFC 1034 root-label dot, so a dNSName, rfc822Name host or uniformResourceIdentifier host carrying two or more trailing dots (e.g. "example.com..", legal IA5String and unvalidated on parse) kept a phantom empty label and slipped past the exact-match check, escaping an excluded subtree it should have been caught by (RFC 5280 sec. 4.2.1.10). stripTrailingDot now removes every trailing dot, closing the gap uniformly across the dNSName, rfc822Name and URI paths that share it; a bare "." is still preserved and a sibling domain is still not caught. This completes the single-dot canonicalisation added earlier for the same class of bypass.
212+
211213
### 2.1.3 Additional Features and Functionality
212214

213215
- BIP340Signer can now be initialised from an AsymmetricCipherKeyPair, through the new init(boolean, AsymmetricCipherKeyPair) and init(boolean, AsymmetricCipherKeyPair, SecureRandom) methods. BIP-340 signing needs the public point twice, for the parity of d and for bytes(P) in the nonce and challenge hashes (sec. 3.3 steps 3-4), and derived it with a d'*G multiplication every time; a caller that already holds the public key can now supply it and skip that. Since the two fixed-base multiplications - that one and R = k'*G - are very nearly the whole cost of a signature, producing one measures about twice as fast this way (github #2420). The public key is not checked against the private key, because checking it means performing the multiplication being avoided; a mismatched pair simply produces a signature that does not verify, and the javadoc says so. Independently, the public point is now derived once per init() rather than on every generateSignature() call, so a signer initialised once and used for several signatures gets most of the same saving with no change on the caller's part. Signatures are unchanged in every case, and the existing CipherParameters init is untouched.

‎prov/src/test/java/org/bouncycastle/jce/provider/test/PKIXNameConstraintsTest.java‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,21 @@ private void testTrailingDotBypass() throws Exception
295295
// uniformResourceIdentifier: the host trailing dot is stripped like the dNSName path.
296296
isTrue("trailing-dot URI host must be caught by the excluded competitor.example subtree",
297297
isExcluded(uriName("competitor.example"), uriName("https://competitor.example./")));
298+
299+
// More than one trailing dot must be caught too - stripping only a single dot left a phantom
300+
// empty label that slipped the exact-match check past an excluded subtree.
301+
isTrue("exact host with two trailing dots must be caught",
302+
isExcluded(dnsName("example.com"), dnsName("example.com..")));
303+
isTrue("exact host with three trailing dots must be caught",
304+
isExcluded(dnsName("example.com"), dnsName("example.com...")));
305+
isTrue("two-trailing-dot email host must be caught",
306+
isExcluded(emailName("bank.com"), emailName("ceo@bank.com..")));
307+
isTrue("two-trailing-dot email mailbox must be caught",
308+
isExcluded(emailName("ceo@bank.com"), emailName("ceo@bank.com..")));
309+
isTrue("two-trailing-dot URI host must be caught",
310+
isExcluded(uriName("competitor.example"), uriName("https://competitor.example../")));
311+
isTrue("a sibling domain with trailing dots must still not be caught",
312+
!isExcluded(dnsName("example.com"), dnsName("notexample.com..")));
298313
}
299314

300315
/**

0 commit comments

Comments
 (0)