PR #11883 - changes from review

Signed-off-by: Lachlan Roberts <lachlan.p.roberts@gmail.com>
This commit is contained in:
Lachlan Roberts 2024-07-25 10:08:09 +10:00
parent 32b7043932
commit 2f668353ac
No known key found for this signature in database
GPG Key ID: 5663FB7A8FF7E348
5 changed files with 58 additions and 86 deletions

View File

@ -44,7 +44,6 @@ import org.eclipse.jetty.security.authentication.LoginAuthenticator;
import org.eclipse.jetty.security.authentication.SessionAuthentication;
import org.eclipse.jetty.security.siwe.internal.AnyUserLoginService;
import org.eclipse.jetty.security.siwe.internal.EthereumUtil;
import org.eclipse.jetty.security.siwe.internal.SignInWithEthereumParser;
import org.eclipse.jetty.security.siwe.internal.SignInWithEthereumToken;
import org.eclipse.jetty.server.FormFields;
import org.eclipse.jetty.server.Request;
@ -573,7 +572,7 @@ public class EthereumAuthenticator extends LoginAuthenticator implements Dumpabl
SignedMessage signedMessage = parseMessage(request, response, callback);
if (signedMessage == null)
return AuthenticationState.SEND_FAILURE;
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(signedMessage.message());
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(signedMessage.message());
if (siwe == null || !validateSignInWithEthereumToken(siwe, signedMessage, request, response, callback))
return AuthenticationState.SEND_FAILURE;

View File

@ -1,73 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.security.siwe.internal;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Parses a SIWE Message, based off the ABNF Message Format from <a href="https://eips.ethereum.org/EIPS/eip-4361">EIP-4361</a>.
*/
public class SignInWithEthereumParser
{
private static final String SCHEME_PATTERN = "[a-zA-Z][a-zA-Z0-9+\\-.]*";
private static final String DOMAIN_PATTERN = "(?:[a-zA-Z0-9\\-._~%]+@)?[a-zA-Z0-9\\-._~%]+(?:\\:[0-9]+)?";
private static final String ADDRESS_PATTERN = "0x[0-9a-fA-F]{40}";
private static final String STATEMENT_PATTERN = "[^\\n]*";
private static final String URI_PATTERN = "[^\\n]+";
private static final String VERSION_PATTERN = "[0-9]+";
private static final String CHAIN_ID_PATTERN = "[0-9]+";
private static final String NONCE_PATTERN = "[a-zA-Z0-9]{8}";
private static final String DATE_TIME_PATTERN = "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})?";
private static final String REQUEST_ID_PATTERN = "[^\\n]*";
private static final String RESOURCE_PATTERN = "- " + URI_PATTERN;
private static final String RESOURCES_PATTERN = "(?:\n" + RESOURCE_PATTERN + ")*";
private static final Pattern SIGN_IN_WITH_ETHEREUM_PATTERN = Pattern.compile(
"^(?:(?<scheme>" + SCHEME_PATTERN + ")://)?(?<domain>" + DOMAIN_PATTERN + ") wants you to sign in with your Ethereum account:\n" +
"(?<address>" + ADDRESS_PATTERN + ")\n\n" +
"(?<statement>" + STATEMENT_PATTERN + ")?\n\n" +
"URI: (?<uri>" + URI_PATTERN + ")\n" +
"Version: (?<version>" + VERSION_PATTERN + ")\n" +
"Chain ID: (?<chainId>" + CHAIN_ID_PATTERN + ")\n" +
"Nonce: (?<nonce>" + NONCE_PATTERN + ")\n" +
"Issued At: (?<issuedAt>" + DATE_TIME_PATTERN + ")" +
"(?:\nExpiration Time: (?<expirationTime>" + DATE_TIME_PATTERN + "))?" +
"(?:\nNot Before: (?<notBefore>" + DATE_TIME_PATTERN + "))?" +
"(?:\nRequest ID: (?<requestId>" + REQUEST_ID_PATTERN + "))?" +
"(?:\nResources:(?<resources>" + RESOURCES_PATTERN + "))?$",
Pattern.DOTALL
);
private SignInWithEthereumParser()
{
}
/**
* Parse a {@link SignInWithEthereumToken} from a {@link String}.
* @param message the SIWE message to parse.
* @return the {@link SignInWithEthereumToken} or null if it was not a valid SIWE message.
*/
public static SignInWithEthereumToken parse(String message)
{
Matcher matcher = SIGN_IN_WITH_ETHEREUM_PATTERN.matcher(message);
if (!matcher.matches())
return null;
return new SignInWithEthereumToken(matcher.group("scheme"), matcher.group("domain"),
matcher.group("address"), matcher.group("statement"), matcher.group("uri"),
matcher.group("version"), matcher.group("chainId"), matcher.group("nonce"),
matcher.group("issuedAt"), matcher.group("expirationTime"), matcher.group("notBefore"),
matcher.group("requestId"), matcher.group("resources"));
}
}

View File

@ -16,6 +16,8 @@ package org.eclipse.jetty.security.siwe.internal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jetty.security.ServerAuthException;
import org.eclipse.jetty.security.siwe.EthereumAuthenticator;
@ -52,6 +54,52 @@ public record SignInWithEthereumToken(String scheme,
String requestId,
String resources)
{
private static final String SCHEME_PATTERN = "[a-zA-Z][a-zA-Z0-9+\\-.]*";
private static final String DOMAIN_PATTERN = "(?:[a-zA-Z0-9\\-._~%]+@)?[a-zA-Z0-9\\-._~%]+(?:\\:[0-9]+)?";
private static final String ADDRESS_PATTERN = "0x[0-9a-fA-F]{40}";
private static final String STATEMENT_PATTERN = "[^\\n]*";
private static final String URI_PATTERN = "[^\\n]+";
private static final String VERSION_PATTERN = "[0-9]+";
private static final String CHAIN_ID_PATTERN = "[0-9]+";
private static final String NONCE_PATTERN = "[a-zA-Z0-9]{8}";
private static final String DATE_TIME_PATTERN = "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})?";
private static final String REQUEST_ID_PATTERN = "[^\\n]*";
private static final String RESOURCE_PATTERN = "- " + URI_PATTERN;
private static final String RESOURCES_PATTERN = "(?:\n" + RESOURCE_PATTERN + ")*";
private static final Pattern SIGN_IN_WITH_ETHEREUM_PATTERN = Pattern.compile(
"^(?:(?<scheme>" + SCHEME_PATTERN + ")://)?(?<domain>" + DOMAIN_PATTERN + ") wants you to sign in with your Ethereum account:\n" +
"(?<address>" + ADDRESS_PATTERN + ")\n\n" +
"(?<statement>" + STATEMENT_PATTERN + ")?\n\n" +
"URI: (?<uri>" + URI_PATTERN + ")\n" +
"Version: (?<version>" + VERSION_PATTERN + ")\n" +
"Chain ID: (?<chainId>" + CHAIN_ID_PATTERN + ")\n" +
"Nonce: (?<nonce>" + NONCE_PATTERN + ")\n" +
"Issued At: (?<issuedAt>" + DATE_TIME_PATTERN + ")" +
"(?:\nExpiration Time: (?<expirationTime>" + DATE_TIME_PATTERN + "))?" +
"(?:\nNot Before: (?<notBefore>" + DATE_TIME_PATTERN + "))?" +
"(?:\nRequest ID: (?<requestId>" + REQUEST_ID_PATTERN + "))?" +
"(?:\nResources:(?<resources>" + RESOURCES_PATTERN + "))?$",
Pattern.DOTALL
);
/**
* Parses a SIWE Message into a {@link SignInWithEthereumToken},
* based off the ABNF Message Format from <a href="https://eips.ethereum.org/EIPS/eip-4361">EIP-4361</a>.
* @param message the SIWE message to parse.
* @return the {@link SignInWithEthereumToken} or null if it was not a valid SIWE message.
*/
public static SignInWithEthereumToken from(String message)
{
Matcher matcher = SIGN_IN_WITH_ETHEREUM_PATTERN.matcher(message);
if (!matcher.matches())
return null;
return new SignInWithEthereumToken(matcher.group("scheme"), matcher.group("domain"),
matcher.group("address"), matcher.group("statement"), matcher.group("uri"),
matcher.group("version"), matcher.group("chainId"), matcher.group("nonce"),
matcher.group("issuedAt"), matcher.group("expirationTime"), matcher.group("notBefore"),
matcher.group("requestId"), matcher.group("resources"));
}
/**
* @param signedMessage the {@link EthereumAuthenticator.SignedMessage}.

View File

@ -20,7 +20,6 @@ import java.util.List;
import java.util.stream.Stream;
import org.eclipse.jetty.security.siwe.internal.EthereumUtil;
import org.eclipse.jetty.security.siwe.internal.SignInWithEthereumParser;
import org.eclipse.jetty.security.siwe.internal.SignInWithEthereumToken;
import org.eclipse.jetty.security.siwe.util.SignInWithEthereumGenerator;
import org.junit.jupiter.api.Test;
@ -97,7 +96,7 @@ public class SignInWithEthereumParserTest
@MethodSource("specExamples")
public void testSpecExamples(String message, String scheme, String domain)
{
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(message);
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(message);
assertNotNull(siwe);
assertThat(siwe.address(), equalTo("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"));
assertThat(siwe.issuedAt(), equalTo("2021-09-30T16:25:24Z"));
@ -139,7 +138,7 @@ public class SignInWithEthereumParserTest
String message = SignInWithEthereumGenerator.generateMessage(scheme, domain, address, statement, uri, version, chainId, nonce, issuedAt,
expirationTime, notBefore, requestId, resources);
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(message);
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(message);
assertNotNull(siwe);
assertThat(siwe.scheme(), equalTo(scheme));
assertThat(siwe.domain(), equalTo(domain));

View File

@ -17,7 +17,6 @@ import java.time.LocalDateTime;
import java.util.function.Predicate;
import org.eclipse.jetty.security.siwe.internal.EthereumUtil;
import org.eclipse.jetty.security.siwe.internal.SignInWithEthereumParser;
import org.eclipse.jetty.security.siwe.internal.SignInWithEthereumToken;
import org.eclipse.jetty.security.siwe.util.EthereumCredentials;
import org.eclipse.jetty.security.siwe.util.SignInWithEthereumGenerator;
@ -51,7 +50,7 @@ public class SignInWithEthereumTokenTest
);
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(message);
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(message);
assertNotNull(siwe);
Throwable error = assertThrows(Throwable.class, () ->
@ -80,7 +79,7 @@ public class SignInWithEthereumTokenTest
);
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(message);
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(message);
assertNotNull(siwe);
Throwable error = assertThrows(Throwable.class, () ->
@ -110,7 +109,7 @@ public class SignInWithEthereumTokenTest
);
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(message);
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(message);
assertNotNull(siwe);
Throwable error = assertThrows(Throwable.class, () ->
@ -137,7 +136,7 @@ public class SignInWithEthereumTokenTest
);
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(message);
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(message);
assertNotNull(siwe);
IncludeExcludeSet<String, String> domains = new IncludeExcludeSet<>();
@ -167,7 +166,7 @@ public class SignInWithEthereumTokenTest
);
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(message);
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(message);
assertNotNull(siwe);
IncludeExcludeSet<String, String> chainIds = new IncludeExcludeSet<>();
@ -197,7 +196,7 @@ public class SignInWithEthereumTokenTest
);
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(message);
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(message);
assertNotNull(siwe);
Predicate<String> nonceValidation = nonce -> false;
@ -225,7 +224,7 @@ public class SignInWithEthereumTokenTest
);
EthereumAuthenticator.SignedMessage signedMessage = credentials.signMessage(message);
SignInWithEthereumToken siwe = SignInWithEthereumParser.parse(message);
SignInWithEthereumToken siwe = SignInWithEthereumToken.from(message);
assertNotNull(siwe);
Predicate<String> nonceValidation = nonce -> true;