This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch 3_0_x-fixes in repository https://gitbox.apache.org/repos/asf/ws-wss4j.git
commit 57a4a1dd6d51c5b86c75d127dfcf2480c8a46c4b Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Tue Sep 22 10:01:43 2026 +0100 Finishing DER work (#727) --- src/site/asciidoc/wss4j40.adoc | 38 +++++++++--- .../wss4j/common/crypto/BouncyCastleUtils.java | 68 ++++++++++++++++++++++ ...ierTest.java => X509KeyIdentifierUtilTest.java} | 31 ++++++++-- 3 files changed, 125 insertions(+), 12 deletions(-) diff --git a/src/site/asciidoc/wss4j40.adoc b/src/site/asciidoc/wss4j40.adoc index 9d25ca610..db0f3eaea 100644 --- a/src/site/asciidoc/wss4j40.adoc +++ b/src/site/asciidoc/wss4j40.adoc @@ -17,10 +17,34 @@ // under the License. // -=== Apache WSS4J 4.0.0 Migration Guide +=== Apache WSS4J 4.0.x Migration Guide -This section is a migration guide for helping Apache WSS4J 3.0.x users to migrate -to the 4.0.x releases. +This section covers changes within the 4.0.x releases that may affect existing +code. Everything described below was introduced in 4.0.2. + +==== BouncyCastleUtils renamed to X509KeyIdentifierUtil + +`org.apache.wss4j.common.crypto.BouncyCastleUtils` is now +`org.apache.wss4j.common.crypto.X509KeyIdentifierUtil`. The class no longer uses +Bouncy Castle, so the old name was misleading. Its two methods, +`getSubjectKeyIdentifierBytes` and `getAuthorityKeyIdentifierBytes`, keep their +names and signatures. + +`BouncyCastleUtils` is retained as a deprecated class that delegates to the new +one, so code written against 4.0.1 and earlier keeps working. It is marked for +removal and will be dropped in a later release, so move to +`X509KeyIdentifierUtil` when convenient. + +==== DOMX509SKI signals a malformed SubjectKeyIdentifier + +`org.apache.wss4j.common.token.DOMX509SKI`'s +`DOMX509SKI(Document, X509Certificate)` constructor now declares +`throws WSSecurityException`, raised when the certificate's SubjectKeyIdentifier +extension is present but will not decode. Previously it threw an unchecked +exception from a constructor with no throws clause. + +Code constructing this class directly must handle or propagate the exception. +This is a source compatibility change only; existing compiled callers still link. ==== Stricter DER decoding of X.509 key identifiers @@ -45,10 +69,10 @@ The encodings that change behaviour are: the `[0]` field. Such a certificate now causes an `IllegalArgumentException` from -`BouncyCastleUtils.getSubjectKeyIdentifierBytes` and -`BouncyCastleUtils.getAuthorityKeyIdentifierBytes`, and a `WSSecurityException` -from `Crypto.getSKIBytesFromCert`. This surfaces when signing with an SKI key -identifier (`DOMX509SKI`) and when establishing trust with `MerlinAKI`. +`X509KeyIdentifierUtil.getSubjectKeyIdentifierBytes` and +`X509KeyIdentifierUtil.getAuthorityKeyIdentifierBytes`, and a +`WSSecurityException` from `Crypto.getSKIBytesFromCert`, from `DOMX509SKI` when +signing with an SKI key identifier, and from `MerlinAKI` when establishing trust. If you hit this, the certificate is malformed and should be reissued. There is no configuration option to restore the previous tolerance. diff --git a/ws-security-common/src/main/java/org/apache/wss4j/common/crypto/BouncyCastleUtils.java b/ws-security-common/src/main/java/org/apache/wss4j/common/crypto/BouncyCastleUtils.java new file mode 100644 index 000000000..f7966328d --- /dev/null +++ b/ws-security-common/src/main/java/org/apache/wss4j/common/crypto/BouncyCastleUtils.java @@ -0,0 +1,68 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.wss4j.common.crypto; + +import java.security.cert.X509Certificate; + +/** + * Decodes the X.509 key identifier extensions. + * + * @deprecated the decoding no longer uses Bouncy Castle, so this class was renamed to + * {@link X509KeyIdentifierUtil}. This name is retained for callers compiled against + * 4.0.1 and earlier, and delegates to the new class. Use + * {@link X509KeyIdentifierUtil} instead. + */ +@Deprecated(since = "4.0.2", forRemoval = true) +public final class BouncyCastleUtils { + + private BouncyCastleUtils() { + // complete + } + + /** + * Read the keyIdentifier of the AuthorityKeyIdentifier extension (2.5.29.35) of the + * given certificate. + * + * @param cert the certificate to read the AuthorityKeyIdentifier from. + * @return an empty array if the certificate has no AuthorityKeyIdentifier extension, null if + * the extension is present but carries no keyIdentifier, otherwise the keyIdentifier. + * @throws IllegalArgumentException if the extension is present but is not valid DER. + * @deprecated use {@link X509KeyIdentifierUtil#getAuthorityKeyIdentifierBytes(X509Certificate)} + */ + @Deprecated(since = "4.0.2", forRemoval = true) + public static byte[] getAuthorityKeyIdentifierBytes(X509Certificate cert) { + return X509KeyIdentifierUtil.getAuthorityKeyIdentifierBytes(cert); + } + + /** + * Read the SubjectKeyIdentifier extension (2.5.29.14) of the given certificate. + * + * @param cert the certificate to read the SubjectKeyIdentifier from. + * @return an empty array if the certificate has no SubjectKeyIdentifier extension, otherwise + * the key identifier. + * @throws IllegalArgumentException if the extension is present but is not valid DER. + * @deprecated use {@link X509KeyIdentifierUtil#getSubjectKeyIdentifierBytes(X509Certificate)} + */ + @Deprecated(since = "4.0.2", forRemoval = true) + public static byte[] getSubjectKeyIdentifierBytes(X509Certificate cert) { + return X509KeyIdentifierUtil.getSubjectKeyIdentifierBytes(cert); + } + +} diff --git a/ws-security-common/src/test/java/org/apache/wss4j/common/crypto/AuthorityKeyIdentifierTest.java b/ws-security-common/src/test/java/org/apache/wss4j/common/crypto/X509KeyIdentifierUtilTest.java similarity index 84% rename from ws-security-common/src/test/java/org/apache/wss4j/common/crypto/AuthorityKeyIdentifierTest.java rename to ws-security-common/src/test/java/org/apache/wss4j/common/crypto/X509KeyIdentifierUtilTest.java index 95c6e1a37..f63bd31d0 100644 --- a/ws-security-common/src/test/java/org/apache/wss4j/common/crypto/AuthorityKeyIdentifierTest.java +++ b/ws-security-common/src/test/java/org/apache/wss4j/common/crypto/X509KeyIdentifierUtilTest.java @@ -36,12 +36,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; /** - * This is a test for extracting AuthorityKeyIdentifier/SubjectKeyIdentifier information from - * certificates. + * This is a test for X509KeyIdentifierUtil, which extracts AuthorityKeyIdentifier and + * SubjectKeyIdentifier information from certificates. */ -public class AuthorityKeyIdentifierTest { +public class X509KeyIdentifierUtilTest { - public AuthorityKeyIdentifierTest() { + public X509KeyIdentifierUtilTest() { WSProviderConfig.init(); } @@ -120,6 +120,27 @@ public class AuthorityKeyIdentifierTest { ); } + /** + * BouncyCastleUtils is the pre-4.0.2 name of this class, kept as a deprecated delegate so + * that code written against 4.0.1 and earlier still compiles and behaves the same. + */ + @Test + @SuppressWarnings("removal") + public void testDeprecatedBouncyCastleUtilsDelegates() throws Exception { + KeyStore keyStore = loadKeyStore("keys/wss40.jks", "security"); + X509Certificate cert = (X509Certificate)keyStore.getCertificate("wss40"); + assertNotNull(cert); + + assertArrayEquals( + X509KeyIdentifierUtil.getAuthorityKeyIdentifierBytes(cert), + BouncyCastleUtils.getAuthorityKeyIdentifierBytes(cert) + ); + assertArrayEquals( + X509KeyIdentifierUtil.getSubjectKeyIdentifierBytes(cert), + BouncyCastleUtils.getSubjectKeyIdentifierBytes(cert) + ); + } + @Test public void testMerlinAKI() throws Exception { // Load the keystore @@ -153,7 +174,7 @@ public class AuthorityKeyIdentifierTest { private KeyStore loadKeyStore(String path, String password) throws Exception { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - ClassLoader loader = Loader.getClassLoader(AuthorityKeyIdentifierTest.class); + ClassLoader loader = Loader.getClassLoader(X509KeyIdentifierUtilTest.class); InputStream input = Merlin.loadInputStream(loader, path); keyStore.load(input, password.toCharArray()); input.close();
