Merge Fix Flaky Crypto Tests

This commit is contained in:
Robert Winch 2026-03-03 15:45:08 -06:00
commit 7e4a926527
No known key found for this signature in database
6 changed files with 162 additions and 4 deletions

View File

@ -1,6 +1,7 @@
plugins {
id 'security-nullability'
id 'javadoc-warnings-error'
id 'java-test-fixtures'
}
apply plugin: 'io.spring.convention.spring-module'
@ -11,6 +12,8 @@ dependencies {
optional 'org.bouncycastle:bcpkix-jdk18on'
optional libs.com.password4j.password4j
testFixturesImplementation "org.assertj:assertj-core"
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-params"

View File

@ -0,0 +1,47 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.security.crypto.assertions;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link CryptoAssertions} and {@link CryptoStringAssert}.
*/
class CryptoAssertionsTests {
@Test
void doesNotDecryptToPassesWhenSupplierThrows() {
CryptoAssertions.assertThat((() -> {
throw new RuntimeException("decrypt failed");
})).doesNotDecryptTo("any");
}
@Test
void doesNotDecryptToPassesWhenResultDiffersFromExpected() {
CryptoAssertions.assertThat(() -> "other").doesNotDecryptTo("plaintext");
}
@Test
void doesNotDecryptToFailsWhenResultEqualsExpected() {
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> CryptoAssertions.assertThat(() -> "plaintext").doesNotDecryptTo("plaintext"))
.withMessageContaining("Expected supplier not to return <plaintext> but it did");
}
}

View File

@ -22,8 +22,9 @@ import java.security.interfaces.RSAPublicKey;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.security.crypto.assertions.CryptoAssertions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* @author Dave Syer
@ -86,13 +87,15 @@ public class RsaSecretEncryptorTests {
@Test
public void roundTripWithMixedAlgorithm() {
RsaSecretEncryptor oaep = new RsaSecretEncryptor(RsaAlgorithm.OAEP);
assertThatIllegalStateException().isThrownBy(() -> oaep.decrypt(this.encryptor.encrypt("encryptor")));
CryptoAssertions.assertThat(() -> oaep.decrypt(this.encryptor.encrypt("encryptor")))
.doesNotDecryptTo("encryptor");
}
@Test
public void roundTripWithMixedSalt() {
RsaSecretEncryptor other = new RsaSecretEncryptor(this.encryptor.getPublicKey(), RsaAlgorithm.DEFAULT, "salt");
assertThatIllegalStateException().isThrownBy(() -> this.encryptor.decrypt(other.encrypt("encryptor")));
CryptoAssertions.assertThat(() -> this.encryptor.decrypt(other.encrypt("encryptor")))
.doesNotDecryptTo("encryptor");
}
@Test
@ -106,7 +109,8 @@ public class RsaSecretEncryptorTests {
public void publicKeyCannotDecrypt() {
RsaSecretEncryptor encryptor = new RsaSecretEncryptor(this.encryptor.getPublicKey());
assertThat(encryptor.canDecrypt()).as("Encryptor schould not be able to decrypt").isFalse();
assertThatIllegalStateException().isThrownBy(() -> encryptor.decrypt(encryptor.encrypt("encryptor")));
CryptoAssertions.assertThat(() -> encryptor.decrypt(encryptor.encrypt("encryptor")))
.doesNotDecryptTo("encryptor");
}
@Test

View File

@ -0,0 +1,44 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.security.crypto.assertions;
import java.util.function.Supplier;
/**
* AssertJ entry point for crypto-related assertions. Use {@link #assertThat(Supplier)} to
* assert on a {@link Supplier}&lt;{@link String}&gt; (e.g. a decryption lambda).
* <p>
* Example: <pre>
* assertThat(() -&gt; encryptor.decrypt(ciphertext)).doesNotDecryptTo("plaintext");
* </pre>
*/
public final class CryptoAssertions {
private CryptoAssertions() {
}
/**
* Create assertions for the given supplier (e.g. a decryption expression).
* @param actual the supplier to assert on
* @return assertion object with methods like
* {@link CryptoStringAssert#doesNotDecryptTo(String)}
*/
public static CryptoStringAssert assertThat(Supplier<String> actual) {
return new CryptoStringAssert(actual);
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed 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
*
* https://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.springframework.security.crypto.assertions;
import java.util.Objects;
import java.util.function.Supplier;
import org.assertj.core.api.AbstractObjectAssert;
/**
* AssertJ assertion for {@link Supplier}&lt;{@link String}&gt;, supporting
* decryption-related checks such as {@link #doesNotDecryptTo(String)}.
*/
public final class CryptoStringAssert extends AbstractObjectAssert<CryptoStringAssert, Supplier<String>> {
CryptoStringAssert(Supplier<String> actual) {
super(actual, CryptoStringAssert.class);
}
/**
* Asserts that either the supplier throws an exception when invoked, or the value it
* returns is not equal to the given string. Use this to assert that a decryption
* attempt does not yield a specific plaintext (e.g. wrong key or tampered
* ciphertext).
* @param expected the value that the supplier must not return
* @return this assertion for chaining
*/
public CryptoStringAssert doesNotDecryptTo(String expected) {
isNotNull();
try {
String result = this.actual.get();
if (Objects.equals(result, expected)) {
failWithMessage("Expected supplier not to return <%s> but it did", expected);
}
}
catch (Exception ex) {
// Exception thrown: supplier does not "decrypt to" the expected value
}
return this;
}
}

View File

@ -60,6 +60,10 @@
<suppress files="[\\/]src[\\/]test[\\/]" checks="JavadocPackage"/>
<suppress files="[\\/]src[\\/]test[\\/].*package-info\.java$" checks="RegexpMultiline" id="requireNullMarkedInPackageInfo"/>
<!-- Suppress package-info.java and @NullMarked checks for test fixture sources -->
<suppress files="[\\/]src[\\/]testFixtures[\\/]" checks="JavadocPackage"/>
<suppress files="[\\/]src[\\/]testFixtures[\\/].*package-info\.java$" checks="RegexpMultiline" id="requireNullMarkedInPackageInfo"/>
<!-- Suppress nullability checks for modules that don't have JSpecify nullability applied yet -->
<suppress files="access[\\/]" checks="IllegalImport" id="bannedNullabilityImports"/>
<suppress files="access[\\/]" checks="JavadocPackage"/>