diff options
Diffstat (limited to 'src/main/java/ganarchy/friendcode/sam')
3 files changed, 94 insertions, 23 deletions
diff --git a/src/main/java/ganarchy/friendcode/sam/I2PSamAuthUtil.java b/src/main/java/ganarchy/friendcode/sam/I2PSamAuthUtil.java index fad222c..ff84551 100644 --- a/src/main/java/ganarchy/friendcode/sam/I2PSamAuthUtil.java +++ b/src/main/java/ganarchy/friendcode/sam/I2PSamAuthUtil.java @@ -1,32 +1,87 @@ package ganarchy.friendcode.sam; +import ganarchy.friendcode.util.ConfigUtil; +import org.apache.commons.codec.binary.Base32; + +import java.security.SecureRandom; +import java.util.Properties; + /** -* Helper for I2P SAM authentication. -*/ + * Helper for I2P SAM authentication. + */ public class I2PSamAuthUtil { /** - * Returns the currently active SAM auth pair. - */ + * The default username. It is used by default. + */ + private static final String DEFAULT_USERNAME = "minecraft_friendcode"; + + /** + * Fallback authentication password, used on first install. + */ + private static final AuthenticationPair INSECURE_FALLBACK = + new AuthenticationPair(DEFAULT_USERNAME, "friendcode"); + + /** + * Returns the currently active SAM auth pair. + */ public static AuthenticationPair getAuthPair() { - return new AuthenticationPair("minecraft_friendcode", "friendcode"); + AuthenticationPair strongAuthPair = getStrongAuthPair(); + if (strongAuthPair != null) { + return strongAuthPair; + } + return INSECURE_FALLBACK; } /** - * Generates and stores a modern auth pair. - * - * @return The generated auth pair. - */ + * Generates and stores a modern auth pair. + * + * @return The generated auth pair. + */ public static AuthenticationPair upgradeAuthPair() { - return new AuthenticationPair("minecraft_friendcode", "friendcode"); + var rand = new SecureRandom(); + var bytes = new byte[16]; + rand.nextBytes(bytes); + var b32 = new Base32().encodeToString(bytes); + Properties auth = new Properties(); + auth.setProperty("i2p.sam.username", DEFAULT_USERNAME); + auth.setProperty("i2p.sam.password", b32); + if (ConfigUtil.updateSettings(auth)) { + return new AuthenticationPair(DEFAULT_USERNAME, b32); + } else { + return INSECURE_FALLBACK; + } } /** - * Returns whether strong auth is enabled. - */ + * Returns whether strong auth is enabled. + */ public static boolean isStrongAuth() { - return false; + return getStrongAuthPair() != null; } + + /** + * Returns the currently active strong SAM auth pair, or null if using the + * weak fallback. + */ + private static AuthenticationPair getStrongAuthPair() { + Properties auth = new Properties(); + if (ConfigUtil.getSettings(auth)) { + String username = auth.getProperty("i2p.sam.username"); + String password = auth.getProperty("i2p.sam.password"); + if (username != null && password != null) { + return new AuthenticationPair(username, password); + } + } + return null; + } + + /** + * An authentication pair. + * + * @param user The username. + * @param password The password. + */ public record AuthenticationPair(String user, String password) { } } diff --git a/src/main/java/ganarchy/friendcode/sam/I2PSamControl.java b/src/main/java/ganarchy/friendcode/sam/I2PSamControl.java index 693cba3..50748e6 100644 --- a/src/main/java/ganarchy/friendcode/sam/I2PSamControl.java +++ b/src/main/java/ganarchy/friendcode/sam/I2PSamControl.java @@ -12,7 +12,7 @@ public class I2PSamControl extends I2PSamStateMachine { private final boolean zeroHop; /** * The session's private key. - * + * <p> * The threat model assumes the local machine (including RAM) to be trusted. */ private String privateKey; @@ -25,7 +25,7 @@ public class I2PSamControl extends I2PSamStateMachine { /** * Creates a new SAM control socket. * - * @param zeroHop Whether to use zero-hop tunnels. + * @param zeroHop Whether to use zero-hop tunnels. * @param privateKey The (optional) identity private key, for persistent friend codes. */ public I2PSamControl(boolean zeroHop, String privateKey) { @@ -39,12 +39,12 @@ public class I2PSamControl extends I2PSamStateMachine { // try IPv6 first // there's no Inet6Address.getLoopbackAddress() which is unfortunate. final Socket samSocket = new Socket(); - samSocket.connect(new InetSocketAddress(Inet6Address.getByAddress("localhost", new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, 0), 7656), 3000); + samSocket.connect(new InetSocketAddress(Inet6Address.getByAddress("localhost", new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, 0), 7656), 3000); return this.connect(samSocket); } catch (IOException e) { try { final Socket samSocket = new Socket(); - samSocket.connect(new InetSocketAddress(Inet4Address.getByAddress("localhost", new byte[] {127,0,0,1}), 7656), 3000); + samSocket.connect(new InetSocketAddress(Inet4Address.getByAddress("localhost", new byte[] {127, 0, 0, 1}), 7656), 3000); return this.connect(samSocket); } catch (IOException ex) { return false; @@ -63,8 +63,8 @@ public class I2PSamControl extends I2PSamStateMachine { if (this.privateKey == null) { // generate our keys this.sendCommand(new I2PSamCommand( - "DEST", "GENERATE", - ImmutableMap.of("SIGNATURE_TYPE", "EdDSA_SHA512_Ed25519") + "DEST", "GENERATE", + ImmutableMap.of("SIGNATURE_TYPE", "EdDSA_SHA512_Ed25519") )); var dest = this.getCommand("DEST", "REPLY"); this.publicKey = dest.parameters().get("PUB"); @@ -125,6 +125,7 @@ public class I2PSamControl extends I2PSamStateMachine { /** * Creates a stream forwarder. + * * @return A stream forwarder. */ public I2PSamStreamForwarder forwardStream(String port) { @@ -133,6 +134,7 @@ public class I2PSamControl extends I2PSamStateMachine { /** * Creates a stream forwarder. + * * @return A stream connector. */ public I2PSamStreamConnector connectStream(String b32) { diff --git a/src/main/java/ganarchy/friendcode/sam/I2PSamStateMachine.java b/src/main/java/ganarchy/friendcode/sam/I2PSamStateMachine.java index 57424dd..37deda3 100644 --- a/src/main/java/ganarchy/friendcode/sam/I2PSamStateMachine.java +++ b/src/main/java/ganarchy/friendcode/sam/I2PSamStateMachine.java @@ -2,6 +2,7 @@ package ganarchy.friendcode.sam; import com.google.common.collect.ImmutableMap; import ganarchy.friendcode.FriendCode; +import ganarchy.friendcode.util.ConfigUtil; import net.minecraft.util.Util; import java.io.*; @@ -68,7 +69,18 @@ public abstract class I2PSamStateMachine implements Closeable { "PASSWORD", auth.password() ) )); - return "OK".equals(this.getCommand("HELLO", "REPLY").parameters().get("RESULT")); + var replyParams = this.getCommand("HELLO", "REPLY").parameters(); + if ("I2P_ERROR".equals(replyParams.get("RESULT"))) { + var msg = replyParams.getOrDefault("MESSAGE", ""); + FriendCode.LOGGER.error("Couldn't connect to I2P: {}", msg); + FriendCode.LOGGER.error( + "If the above error is about authorization," + + " please create the relevant file at {} and provide" + + " i2p.sam.username and i2p.sam.password.", + ConfigUtil.getGlobalConfigFilePath() + ); + } + return "OK".equals(replyParams.get("RESULT")); } catch (IOException e) { return false; } @@ -76,7 +88,7 @@ public abstract class I2PSamStateMachine implements Closeable { /** * Attempts to step the SAM session. - * + * <p> * This will generally not block, unless something went wrong. */ public void tryStep() { @@ -143,7 +155,7 @@ public abstract class I2PSamStateMachine implements Closeable { /** * Reads a name from the SAM session. * - * @param name The command name. + * @param name The command name. * @param opcode The subcommand name. * @return The name line, or null if something went wrong. * @throws IOException If an I/O error occurs. @@ -190,6 +202,7 @@ public abstract class I2PSamStateMachine implements Closeable { /** * Returns the SAM bridge address. + * * @return The SAM bridge address. */ protected SocketAddress getSamBridgeAddress() { @@ -201,12 +214,13 @@ public abstract class I2PSamStateMachine implements Closeable { /** * Closes this connection. + * * @throws IOException As per {@link Socket#close()}. */ public void close() throws IOException { if (this.connected) { this.samSocket.close(); - } + } } protected Socket unwrap() { |