mirror of https://github.com/shred/acme4j
Validate challenge tokens before use
parent
3abf56e325
commit
9d3ab4972c
|
@ -23,6 +23,7 @@ import org.jose4j.jwk.PublicJsonWebKey;
|
|||
import org.jose4j.lang.JoseException;
|
||||
import org.shredzone.acme4j.Login;
|
||||
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
||||
import org.shredzone.acme4j.toolbox.AcmeUtils;
|
||||
import org.shredzone.acme4j.toolbox.JSON;
|
||||
|
||||
/**
|
||||
|
@ -51,7 +52,11 @@ public class TokenChallenge extends Challenge {
|
|||
* Gets the token.
|
||||
*/
|
||||
protected String getToken() {
|
||||
return getJSON().get(KEY_TOKEN).asString();
|
||||
String token = getJSON().get(KEY_TOKEN).asString();
|
||||
if (!AcmeUtils.isValidBase64Url(token)) {
|
||||
throw new AcmeProtocolException("Invalid token: " + token);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -82,8 +82,6 @@ public class DefaultConnection implements Connection {
|
|||
private static final String MIME_JSON_PROBLEM = "application/problem+json";
|
||||
private static final String MIME_CERTIFICATE_CHAIN = "application/pem-certificate-chain";
|
||||
|
||||
private static final Pattern BASE64URL_PATTERN = Pattern.compile("[0-9A-Za-z_-]+");
|
||||
|
||||
private static final URI BAD_NONCE_ERROR = URI.create("urn:ietf:params:acme:error:badNonce");
|
||||
private static final int MAX_ATTEMPTS = 10;
|
||||
|
||||
|
@ -235,7 +233,7 @@ public class DefaultConnection implements Connection {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (!BASE64URL_PATTERN.matcher(nonceHeader).matches()) {
|
||||
if (!AcmeUtils.isValidBase64Url(nonceHeader)) {
|
||||
throw new AcmeProtocolException("Invalid replay nonce: " + nonceHeader);
|
||||
}
|
||||
|
||||
|
|
|
@ -69,6 +69,8 @@ public final class AcmeUtils {
|
|||
|
||||
private static final Pattern MAIL_PATTERN = Pattern.compile("\\?|@.*,");
|
||||
|
||||
private static final Pattern BASE64URL_PATTERN = Pattern.compile("[0-9A-Za-z_-]*");
|
||||
|
||||
private static final Base64.Encoder PEM_ENCODER = Base64.getMimeEncoder(64,
|
||||
"\n".getBytes(StandardCharsets.US_ASCII));
|
||||
|
||||
|
@ -156,6 +158,20 @@ public final class AcmeUtils {
|
|||
return Base64Url.decode(base64);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the given {@link String} is a valid base64url encoded value.
|
||||
*
|
||||
* @param base64
|
||||
* {@link String} to validate
|
||||
* @return {@code true}: String contains a valid base64url encoded value.
|
||||
* {@code false} if the {@link String} was {@code null} or contained illegal
|
||||
* characters.
|
||||
* @since 2.6
|
||||
*/
|
||||
public static boolean isValidBase64Url(@Nullable String base64) {
|
||||
return base64 != null && BASE64URL_PATTERN.matcher(base64).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* ASCII encodes a domain name.
|
||||
* <p>
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* acme4j - Java ACME client
|
||||
*
|
||||
* Copyright (C) 2019 Richard "Shred" Körber
|
||||
* http://acme4j.shredzone.org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
package org.shredzone.acme4j.challenge;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.shredzone.acme4j.Login;
|
||||
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
||||
import org.shredzone.acme4j.provider.TestableConnectionProvider;
|
||||
import org.shredzone.acme4j.toolbox.JSONBuilder;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link TokenChallenge}.
|
||||
*/
|
||||
public class TokenChallengeTest {
|
||||
|
||||
/**
|
||||
* Test that invalid tokens are detected.
|
||||
*/
|
||||
@Test
|
||||
public void testInvalidToken() throws IOException {
|
||||
TestableConnectionProvider provider = new TestableConnectionProvider();
|
||||
Login login = provider.createLogin();
|
||||
|
||||
JSONBuilder jb = new JSONBuilder();
|
||||
jb.put("url", "https://example.com/acme/1234");
|
||||
jb.put("type", "generic");
|
||||
jb.put("token", "<script>someMaliciousCode()</script>");
|
||||
|
||||
TokenChallenge challenge = new TokenChallenge(login, jb.toJSON());
|
||||
|
||||
try {
|
||||
challenge.getToken();
|
||||
fail("Invalid token was accepted");
|
||||
} catch (AcmeProtocolException ex) {
|
||||
// expected
|
||||
}
|
||||
|
||||
provider.close();
|
||||
}
|
||||
|
||||
}
|
|
@ -92,6 +92,22 @@ public class AcmeUtilsTest {
|
|||
assertThat(base64UrlDecode, is(sha256hash("foobar")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test base64 URL validation.
|
||||
*/
|
||||
@Test
|
||||
public void testBase64UrlValidate() {
|
||||
assertThat(isValidBase64Url(null), is(false));
|
||||
assertThat(isValidBase64Url(""), is(true));
|
||||
assertThat(isValidBase64Url(" "), is(false));
|
||||
assertThat(isValidBase64Url("Zg"), is(true));
|
||||
assertThat(isValidBase64Url("Zg="), is(false));
|
||||
assertThat(isValidBase64Url("Zg=="), is(false));
|
||||
assertThat(isValidBase64Url("Zm9v"), is(true));
|
||||
assertThat(isValidBase64Url(" Zm9v "), is(false));
|
||||
assertThat(isValidBase64Url("<some>.illegal#Text"), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test ACE conversion.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue