diff --git a/drongo b/drongo index e96aa0d3..7d0699fa 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit e96aa0d3f836b2d035cf5449da010147e56c6354 +Subproject commit 7d0699faa0d89a04c37425d018f143a4e26b49b9 diff --git a/src/main/java/com/sparrowwallet/sparrow/net/Auth47.java b/src/main/java/com/sparrowwallet/sparrow/net/Auth47.java index 83d6e17f..5baf6104 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/Auth47.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/Auth47.java @@ -4,6 +4,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.sparrowwallet.drongo.ExtendedKey; import com.sparrowwallet.drongo.KeyPurpose; +import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.crypto.ChildNumber; import com.sparrowwallet.drongo.crypto.ECKey; import com.sparrowwallet.drongo.protocol.ScriptType; @@ -69,19 +70,17 @@ public class Auth47 { this.srbnName = srbnUrl.getUserInfo(); this.callback = new URI(HTTPS_PROTOCOL + srbnUrl.getHost()).toURL(); } else { - this.callback = new URI(strCallback).toURL(); + URI callbackUri = new URI(strCallback); + if(!Utils.isSecureUrl(callbackUri)) { + throw new IllegalArgumentException("Invalid callback parameter (not https, http .onion or srbn): " + strCallback); + } + this.callback = callbackUri.toURL(); } this.expiry = parameterMap.get("e"); this.resource = parameterMap.get("r"); if(resource == null) { - if(srbn) { - this.resource = "srbn"; - } else if(strCallback.startsWith("http")) { - this.resource = strCallback; - } else { - throw new IllegalArgumentException("Invalid callback parameter (not http/s or srbn): " + strCallback); - } + this.resource = srbn ? "srbn" : strCallback; } } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/LnurlAuth.java b/src/main/java/com/sparrowwallet/sparrow/net/LnurlAuth.java index 3311e0bc..1566caa7 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/LnurlAuth.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/LnurlAuth.java @@ -41,9 +41,17 @@ public class LnurlAuth { public LnurlAuth(URI uri) throws MalformedURLException, URISyntaxException { String lnurl = uri.getSchemeSpecificPart(); Bech32.Bech32Data bech32 = Bech32.decode(lnurl, 2000); + if(!"lnurl".equals(bech32.hrp)) { + throw new IllegalArgumentException("LNURL-auth bech32 prefix must be lnurl"); + } + byte[] urlBytes = Bech32.convertBits(bech32.data, 0, bech32.data.length, 5, 8, false); String strUrl = new String(urlBytes, StandardCharsets.UTF_8); - this.url = new URI(strUrl).toURL(); + URI decodedUri = new URI(strUrl); + if(!Utils.isSecureUrl(decodedUri)) { + throw new IllegalArgumentException("LNURL-auth URL must be https or http .onion"); + } + this.url = decodedUri.toURL(); Map parameterMap = new LinkedHashMap<>(); String query = url.getQuery(); diff --git a/src/test/java/com/sparrowwallet/sparrow/net/Auth47Test.java b/src/test/java/com/sparrowwallet/sparrow/net/Auth47Test.java new file mode 100644 index 00000000..c6e59851 --- /dev/null +++ b/src/test/java/com/sparrowwallet/sparrow/net/Auth47Test.java @@ -0,0 +1,38 @@ +package com.sparrowwallet.sparrow.net; + +import org.junit.jupiter.api.Test; + +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class Auth47Test { + @Test + public void acceptsHttpsCallbacksWithResource() throws Exception { + Auth47 auth47 = new Auth47(new URI("auth47://nonce?c=https://example.com/auth&r=example")); + + assertEquals("https", auth47.getCallback().getProtocol()); + assertEquals("example.com", auth47.getCallback().getHost()); + } + + @Test + public void acceptsSrbnCallbacks() throws Exception { + Auth47 auth47 = new Auth47(new URI("auth47://nonce?c=srbn://alice@relay.example.com")); + + assertEquals("https", auth47.getCallback().getProtocol()); + assertEquals("relay.example.com", auth47.getCallback().getHost()); + } + + @Test + public void rejectsHttpClearnetCallbacks() { + assertThrows(IllegalArgumentException.class, () -> + new Auth47(new URI("auth47://nonce?c=http://example.com/auth&r=example"))); + } + + @Test + public void rejectsNonHttpCallbacksWithResource() { + assertThrows(IllegalArgumentException.class, () -> + new Auth47(new URI("auth47://nonce?c=file:///tmp/auth47&r=example"))); + } +} diff --git a/src/test/java/com/sparrowwallet/sparrow/net/LnurlAuthTest.java b/src/test/java/com/sparrowwallet/sparrow/net/LnurlAuthTest.java new file mode 100644 index 00000000..66ec71bd --- /dev/null +++ b/src/test/java/com/sparrowwallet/sparrow/net/LnurlAuthTest.java @@ -0,0 +1,57 @@ +package com.sparrowwallet.sparrow.net; + +import com.sparrowwallet.drongo.protocol.Bech32; +import org.junit.jupiter.api.Test; + +import java.net.URI; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class LnurlAuthTest { + private static final String K1 = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; + + @Test + public void acceptsHttpsCallbacks() throws Exception { + LnurlAuth lnurlAuth = new LnurlAuth(lightningUri("https://example.com/lnurl-auth?tag=login&k1=" + K1)); + + assertEquals("example.com", lnurlAuth.getDomain()); + assertEquals("login to example.com", lnurlAuth.getLoginMessage()); + } + + @Test + public void acceptsHttpOnionCallbacks() throws Exception { + LnurlAuth lnurlAuth = new LnurlAuth(lightningUri("http://abcdefghijklmnopqrstuvwxyzabcdefghijklmnop.onion/lnurl-auth?tag=login&k1=" + K1)); + + assertEquals("abcdefghijklmnopqrstuvwxyzabcdefghijklmnop.onion", lnurlAuth.getDomain()); + } + + @Test + public void rejectsHttpClearnetCallbacks() { + assertThrows(IllegalArgumentException.class, () -> + new LnurlAuth(lightningUri("http://example.com/lnurl-auth?tag=login&k1=" + K1))); + } + + @Test + public void rejectsNonHttpCallbacks() { + assertThrows(IllegalArgumentException.class, () -> + new LnurlAuth(lightningUri("ftp://example.com/lnurl-auth?tag=login&k1=" + K1))); + } + + @Test + public void rejectsNonLnurlBech32Prefix() { + assertThrows(IllegalArgumentException.class, () -> + new LnurlAuth(lightningUri("lnurlx", "https://example.com/lnurl-auth?tag=login&k1=" + K1))); + } + + private static URI lightningUri(String url) throws Exception { + return lightningUri("lnurl", url); + } + + private static URI lightningUri(String prefix, String url) throws Exception { + byte[] urlBytes = url.getBytes(StandardCharsets.UTF_8); + byte[] lnurlData = Bech32.convertBits(urlBytes, 0, urlBytes.length, 8, 5, true); + return new URI("lightning:" + Bech32.encode(prefix, Bech32.Encoding.BECH32, lnurlData)); + } +}