diff --git a/jetty-core/jetty-siwe/src/main/java/org/eclipse/jetty/security/siwe/EthereumAuthenticator.java b/jetty-core/jetty-siwe/src/main/java/org/eclipse/jetty/security/siwe/EthereumAuthenticator.java index 6c70f00b240..e13cbebf1e0 100644 --- a/jetty-core/jetty-siwe/src/main/java/org/eclipse/jetty/security/siwe/EthereumAuthenticator.java +++ b/jetty-core/jetty-siwe/src/main/java/org/eclipse/jetty/security/siwe/EthereumAuthenticator.java @@ -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; diff --git a/jetty-core/jetty-siwe/src/main/java/org/eclipse/jetty/security/siwe/internal/SignInWithEthereumParser.java b/jetty-core/jetty-siwe/src/main/java/org/eclipse/jetty/security/siwe/internal/SignInWithEthereumParser.java deleted file mode 100644 index 5db01554590..00000000000 --- a/jetty-core/jetty-siwe/src/main/java/org/eclipse/jetty/security/siwe/internal/SignInWithEthereumParser.java +++ /dev/null @@ -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 EIP-4361. - */ -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_PATTERN + ")://)?(?" + DOMAIN_PATTERN + ") wants you to sign in with your Ethereum account:\n" + - "(?
" + ADDRESS_PATTERN + ")\n\n" + - "(?" + STATEMENT_PATTERN + ")?\n\n" + - "URI: (?" + URI_PATTERN + ")\n" + - "Version: (?" + VERSION_PATTERN + ")\n" + - "Chain ID: (?" + CHAIN_ID_PATTERN + ")\n" + - "Nonce: (?" + NONCE_PATTERN + ")\n" + - "Issued At: (?" + DATE_TIME_PATTERN + ")" + - "(?:\nExpiration Time: (?" + DATE_TIME_PATTERN + "))?" + - "(?:\nNot Before: (?" + DATE_TIME_PATTERN + "))?" + - "(?:\nRequest ID: (?" + REQUEST_ID_PATTERN + "))?" + - "(?:\nResources:(?" + 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")); - } -} diff --git a/jetty-core/jetty-siwe/src/main/java/org/eclipse/jetty/security/siwe/internal/SignInWithEthereumToken.java b/jetty-core/jetty-siwe/src/main/java/org/eclipse/jetty/security/siwe/internal/SignInWithEthereumToken.java index ea8ecdfe6e5..a4e2a2780e1 100644 --- a/jetty-core/jetty-siwe/src/main/java/org/eclipse/jetty/security/siwe/internal/SignInWithEthereumToken.java +++ b/jetty-core/jetty-siwe/src/main/java/org/eclipse/jetty/security/siwe/internal/SignInWithEthereumToken.java @@ -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_PATTERN + ")://)?(?" + DOMAIN_PATTERN + ") wants you to sign in with your Ethereum account:\n" + + "(?
" + ADDRESS_PATTERN + ")\n\n" + + "(?" + STATEMENT_PATTERN + ")?\n\n" + + "URI: (?" + URI_PATTERN + ")\n" + + "Version: (?" + VERSION_PATTERN + ")\n" + + "Chain ID: (?" + CHAIN_ID_PATTERN + ")\n" + + "Nonce: (?" + NONCE_PATTERN + ")\n" + + "Issued At: (?" + DATE_TIME_PATTERN + ")" + + "(?:\nExpiration Time: (?" + DATE_TIME_PATTERN + "))?" + + "(?:\nNot Before: (?" + DATE_TIME_PATTERN + "))?" + + "(?:\nRequest ID: (?" + REQUEST_ID_PATTERN + "))?" + + "(?:\nResources:(?" + RESOURCES_PATTERN + "))?$", + Pattern.DOTALL + ); + + /** + * Parses a SIWE Message into a {@link SignInWithEthereumToken}, + * based off the ABNF Message Format from EIP-4361. + * @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}. diff --git a/jetty-core/jetty-siwe/src/test/java/org/eclipse/jetty/security/siwe/SignInWithEthereumParserTest.java b/jetty-core/jetty-siwe/src/test/java/org/eclipse/jetty/security/siwe/SignInWithEthereumParserTest.java index 63fb1152d1b..1b24651f642 100644 --- a/jetty-core/jetty-siwe/src/test/java/org/eclipse/jetty/security/siwe/SignInWithEthereumParserTest.java +++ b/jetty-core/jetty-siwe/src/test/java/org/eclipse/jetty/security/siwe/SignInWithEthereumParserTest.java @@ -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)); diff --git a/jetty-core/jetty-siwe/src/test/java/org/eclipse/jetty/security/siwe/SignInWithEthereumTokenTest.java b/jetty-core/jetty-siwe/src/test/java/org/eclipse/jetty/security/siwe/SignInWithEthereumTokenTest.java index e7b7511466d..a04a6d5b3c6 100644 --- a/jetty-core/jetty-siwe/src/test/java/org/eclipse/jetty/security/siwe/SignInWithEthereumTokenTest.java +++ b/jetty-core/jetty-siwe/src/test/java/org/eclipse/jetty/security/siwe/SignInWithEthereumTokenTest.java @@ -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 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 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 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 nonceValidation = nonce -> true;