mirror of https://github.com/shred/acme4j
parent
aae98d7ce8
commit
db8eb4d012
|
@ -21,14 +21,12 @@ import java.security.KeyStore;
|
||||||
import java.security.KeyStoreException;
|
import java.security.KeyStoreException;
|
||||||
import java.security.cert.PKIXParameters;
|
import java.security.cert.PKIXParameters;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
|
||||||
import edu.umd.cs.findbugs.annotations.Nullable;
|
import edu.umd.cs.findbugs.annotations.Nullable;
|
||||||
import jakarta.mail.Message;
|
import jakarta.mail.Message;
|
||||||
import jakarta.mail.MessagingException;
|
import jakarta.mail.MessagingException;
|
||||||
|
@ -353,85 +351,6 @@ public final class EmailProcessor {
|
||||||
return new ResponseGenerator(this);
|
return new ResponseGenerator(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an optional property from the message.
|
|
||||||
* <p>
|
|
||||||
* Optional property means: If there is a signed message, try to fetch the property
|
|
||||||
* from there. If the property is not present, fetch it from the unsigned message
|
|
||||||
* instead. If it's also not there, return {@code null}.
|
|
||||||
*
|
|
||||||
* @param getter
|
|
||||||
* The getter method of {@link Message} to be invoked
|
|
||||||
* @param message
|
|
||||||
* The outer (unsigned) {@link Message} that serves as fallback
|
|
||||||
* @param signedMessage
|
|
||||||
* The signed (inner) {@link Message} where the property is looked up first
|
|
||||||
* @param <T>
|
|
||||||
* The expected result type
|
|
||||||
* @return The mail property, or {@code null} if not found
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
private <T> T getOptional(MessageFunction<Message, T> getter, Message message, @Nullable Message signedMessage)
|
|
||||||
throws MessagingException {
|
|
||||||
if (signedMessage != null) {
|
|
||||||
T result = getter.apply(signedMessage);
|
|
||||||
if (result != null) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return getter.apply(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a mandatory property from the message.
|
|
||||||
* <p>
|
|
||||||
* Mandatory means: If there is a signed message, the property <em>must</em> be
|
|
||||||
* present there. The unsigned message is only queried as fallback if there is no
|
|
||||||
* signed message at all.
|
|
||||||
*
|
|
||||||
* @param getter
|
|
||||||
* The getter method of {@link Message} to be invoked
|
|
||||||
* @param message
|
|
||||||
* The outer (unsigned) {@link Message} that serves as fallback if there is
|
|
||||||
* no signed message.
|
|
||||||
* @param signedMessage
|
|
||||||
* The signed (inner) {@link Message} where the property is expected, or
|
|
||||||
* {@code null} if there is no signed message.
|
|
||||||
* @param header
|
|
||||||
* Name of the expected header
|
|
||||||
* @param <T>
|
|
||||||
* The expected result type
|
|
||||||
* @return The mail property, or {@code null} if not found
|
|
||||||
*/
|
|
||||||
@CheckForNull
|
|
||||||
private <T> T getMandatory(MessageFunction<Message, T> getter, Message message, @Nullable Message signedMessage, String header)
|
|
||||||
throws MessagingException, AcmeInvalidMessageException {
|
|
||||||
if (signedMessage != null) {
|
|
||||||
T value = getter.apply(signedMessage);
|
|
||||||
if (value == null) {
|
|
||||||
throw new AcmeInvalidMessageException("Protected header '" + header + "' expected, but missing.");
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
return getter.apply(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if this message is "auto-generated".
|
|
||||||
*
|
|
||||||
* @param autoSubmitted
|
|
||||||
* Auto-Submitted header content
|
|
||||||
* @return {@code true} if the mail was auto-generated.
|
|
||||||
*/
|
|
||||||
private boolean isAutoGenerated(@Nullable String[] autoSubmitted) throws MessagingException {
|
|
||||||
if (autoSubmitted == null || autoSubmitted.length == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return Arrays.stream(autoSubmitted)
|
|
||||||
.map(String::trim)
|
|
||||||
.anyMatch(h -> h.startsWith("auto-generated"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a challenge has been set. Throws an exception if not.
|
* Checks if a challenge has been set. Throws an exception if not.
|
||||||
*/
|
*/
|
||||||
|
@ -441,12 +360,6 @@ public final class EmailProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
private interface MessageFunction<M extends Message, R> {
|
|
||||||
@CheckForNull
|
|
||||||
R apply(M message) throws MessagingException;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A builder for {@link EmailProcessor}.
|
* A builder for {@link EmailProcessor}.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -456,7 +369,7 @@ public final class EmailProcessor {
|
||||||
*/
|
*/
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
private boolean unsigned = false;
|
private boolean unsigned = false;
|
||||||
private SignedMailBuilder builder = new SignedMailBuilder();
|
private final SignedMailBuilder builder = new SignedMailBuilder();
|
||||||
|
|
||||||
private Builder() {
|
private Builder() {
|
||||||
// Private constructor
|
// Private constructor
|
||||||
|
|
|
@ -93,7 +93,7 @@ public class ResponseGenerator {
|
||||||
* Sets a {@link ResponseBodyGenerator} that is used for generating a response body.
|
* Sets a {@link ResponseBodyGenerator} that is used for generating a response body.
|
||||||
* <p>
|
* <p>
|
||||||
* Use this generator to individually style the email body, for example to use a
|
* Use this generator to individually style the email body, for example to use a
|
||||||
* multipart body. However be aware that the response mail is evaluated by a machine,
|
* multipart body. However, be aware that the response mail is evaluated by a machine,
|
||||||
* and usually not read by humans, so the body should be designed as simple as
|
* and usually not read by humans, so the body should be designed as simple as
|
||||||
* possible.
|
* possible.
|
||||||
* <p>
|
* <p>
|
||||||
|
|
|
@ -30,9 +30,9 @@ import jakarta.mail.internet.AddressException;
|
||||||
import jakarta.mail.internet.InternetAddress;
|
import jakarta.mail.internet.InternetAddress;
|
||||||
import org.assertj.core.api.AutoCloseableSoftAssertions;
|
import org.assertj.core.api.AutoCloseableSoftAssertions;
|
||||||
import org.bouncycastle.asn1.ASN1Encodable;
|
import org.bouncycastle.asn1.ASN1Encodable;
|
||||||
|
import org.bouncycastle.asn1.ASN1IA5String;
|
||||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||||
import org.bouncycastle.asn1.DERBitString;
|
import org.bouncycastle.asn1.DERBitString;
|
||||||
import org.bouncycastle.asn1.DERIA5String;
|
|
||||||
import org.bouncycastle.asn1.pkcs.Attribute;
|
import org.bouncycastle.asn1.pkcs.Attribute;
|
||||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
|
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
|
||||||
import org.bouncycastle.asn1.x500.X500Name;
|
import org.bouncycastle.asn1.x500.X500Name;
|
||||||
|
@ -55,14 +55,12 @@ import org.shredzone.acme4j.util.KeyPairUtils;
|
||||||
public class SMIMECSRBuilderTest {
|
public class SMIMECSRBuilderTest {
|
||||||
|
|
||||||
private static KeyPair testKey;
|
private static KeyPair testKey;
|
||||||
private static KeyPair testEcKey;
|
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
public static void setup() {
|
public static void setup() {
|
||||||
Security.addProvider(new BouncyCastleProvider());
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
|
|
||||||
testKey = KeyPairUtils.createKeyPair(512);
|
testKey = KeyPairUtils.createKeyPair(512);
|
||||||
testEcKey = KeyPairUtils.createECKeyPair("secp256r1");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -160,21 +158,21 @@ public class SMIMECSRBuilderTest {
|
||||||
assertThat(builder.toString()).isEqualTo(",TYPE=SIGNING_AND_ENCRYPTION");
|
assertThat(builder.toString()).isEqualTo(",TYPE=SIGNING_AND_ENCRYPTION");
|
||||||
|
|
||||||
assertThatExceptionOfType(NullPointerException.class)
|
assertThatExceptionOfType(NullPointerException.class)
|
||||||
.isThrownBy(() -> new SMIMECSRBuilder().addValue((String) null, "value"))
|
.as("addValue(String, String)")
|
||||||
.as("addValue(String, String)");
|
.isThrownBy(() -> new SMIMECSRBuilder().addValue((String) null, "value"));
|
||||||
assertThatExceptionOfType(NullPointerException.class)
|
assertThatExceptionOfType(NullPointerException.class)
|
||||||
.isThrownBy(() -> new SMIMECSRBuilder().addValue((ASN1ObjectIdentifier) null, "value"))
|
.as("addValue(ASN1ObjectIdentifier, String)")
|
||||||
.as("addValue(ASN1ObjectIdentifier, String)");
|
.isThrownBy(() -> new SMIMECSRBuilder().addValue((ASN1ObjectIdentifier) null, "value"));
|
||||||
assertThatExceptionOfType(NullPointerException.class)
|
assertThatExceptionOfType(NullPointerException.class)
|
||||||
.isThrownBy(() -> new SMIMECSRBuilder().addValue("C", null))
|
|
||||||
.as("addValue(String, null)");
|
|
||||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
|
||||||
.isThrownBy(() -> new SMIMECSRBuilder().addValue("UNKNOWNATT", "val"))
|
|
||||||
.as("addValue(String, null)")
|
.as("addValue(String, null)")
|
||||||
|
.isThrownBy(() -> new SMIMECSRBuilder().addValue("C", null));
|
||||||
|
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||||
|
.as("addValue(String, null)")
|
||||||
|
.isThrownBy(() -> new SMIMECSRBuilder().addValue("UNKNOWNATT", "val"))
|
||||||
.withMessage(invAttNameExMessage);
|
.withMessage(invAttNameExMessage);
|
||||||
assertThatExceptionOfType(AddressException.class)
|
assertThatExceptionOfType(AddressException.class)
|
||||||
.isThrownBy(() -> new SMIMECSRBuilder().addValue("CN", "invalid@example..com"))
|
.as("addValue(String, invalid String)")
|
||||||
.as("addValue(String, invalid String)");
|
.isThrownBy(() -> new SMIMECSRBuilder().addValue("CN", "invalid@example..com"));
|
||||||
|
|
||||||
assertThat(builder.toString()).isEqualTo(",TYPE=SIGNING_AND_ENCRYPTION");
|
assertThat(builder.toString()).isEqualTo(",TYPE=SIGNING_AND_ENCRYPTION");
|
||||||
|
|
||||||
|
@ -238,7 +236,7 @@ public class SMIMECSRBuilderTest {
|
||||||
GeneralNames names = GeneralNames.fromExtensions((Extensions) extensions[0], Extension.subjectAlternativeName);
|
GeneralNames names = GeneralNames.fromExtensions((Extensions) extensions[0], Extension.subjectAlternativeName);
|
||||||
assertThat(names.getNames())
|
assertThat(names.getNames())
|
||||||
.filteredOn(gn -> gn.getTagNo() == GeneralName.rfc822Name)
|
.filteredOn(gn -> gn.getTagNo() == GeneralName.rfc822Name)
|
||||||
.extracting(gn -> DERIA5String.getInstance(gn.getName()).getString())
|
.extracting(gn -> ASN1IA5String.getInstance(gn.getName()).getString())
|
||||||
.containsExactlyInAnyOrder("mail@example.com", "info@example.com",
|
.containsExactlyInAnyOrder("mail@example.com", "info@example.com",
|
||||||
"sales@example.com", "shop@example.com", "support@example.com",
|
"sales@example.com", "shop@example.com", "support@example.com",
|
||||||
"help@example.com");
|
"help@example.com");
|
||||||
|
@ -307,7 +305,7 @@ public class SMIMECSRBuilderTest {
|
||||||
* Make sure an exception is thrown when nothing is set.
|
* Make sure an exception is thrown when nothing is set.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testNoEmail() throws IOException {
|
public void testNoEmail() {
|
||||||
assertThrows(IllegalStateException.class, () -> {
|
assertThrows(IllegalStateException.class, () -> {
|
||||||
SMIMECSRBuilder builder = new SMIMECSRBuilder();
|
SMIMECSRBuilder builder = new SMIMECSRBuilder();
|
||||||
builder.sign(testKey);
|
builder.sign(testKey);
|
||||||
|
@ -318,7 +316,7 @@ public class SMIMECSRBuilderTest {
|
||||||
* Make sure all getters will fail if the CSR is not signed.
|
* Make sure all getters will fail if the CSR is not signed.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testNoSign() throws IOException {
|
public void testNoSign() {
|
||||||
SMIMECSRBuilder builder = new SMIMECSRBuilder();
|
SMIMECSRBuilder builder = new SMIMECSRBuilder();
|
||||||
|
|
||||||
assertThrows(IllegalStateException.class, builder::getCSR, "getCSR");
|
assertThrows(IllegalStateException.class, builder::getCSR, "getCSR");
|
||||||
|
|
Loading…
Reference in New Issue