diff --git a/apps/sam/java/src/net/i2p/sam/SAMBridge.java b/apps/sam/java/src/net/i2p/sam/SAMBridge.java index 77ec2cffe..4c67d1412 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMBridge.java +++ b/apps/sam/java/src/net/i2p/sam/SAMBridge.java @@ -85,6 +85,9 @@ public class SAMBridge implements Runnable, ClientApp { private static final String PROP_SAM_SSL = "sam.useSSL"; public static final String PROP_TCP_HOST = "sam.tcp.host"; public static final String PROP_TCP_PORT = "sam.tcp.port"; + public static final String PROP_AUTH = "sam.auth"; + public static final String PROP_PW_PREFIX = "sam.auth."; + public static final String PROP_PW_SUFFIX = ".shash"; protected static final String DEFAULT_TCP_HOST = "127.0.0.1"; protected static final String DEFAULT_TCP_PORT = "7656"; diff --git a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java index dc4c8f24f..49bb72bd9 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java +++ b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java @@ -18,6 +18,7 @@ import java.util.StringTokenizer; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.util.Log; +import net.i2p.util.PasswordManager; import net.i2p.util.VersionComparator; /** @@ -93,6 +94,20 @@ class SAMHandlerFactory { SAMHandler.writeString("HELLO REPLY RESULT=NOVERSION\n", s); return null; } + + if (Boolean.valueOf(i2cpProps.getProperty(SAMBridge.PROP_AUTH))) { + String user = props.getProperty("USER"); + String pw = props.getProperty("PASSWORD"); + if (user == null || pw == null) + throw new SAMException("USER and PASSWORD required"); + String savedPW = i2cpProps.getProperty(SAMBridge.PROP_PW_PREFIX + user + SAMBridge.PROP_PW_SUFFIX); + if (savedPW == null) + throw new SAMException("Authorization failed"); + PasswordManager pm = new PasswordManager(I2PAppContext.getGlobalContext()); + if (!pm.checkHash(savedPW, pw)) + throw new SAMException("Authorization failed"); + } + // Let's answer positively if (!SAMHandler.writeString("HELLO REPLY RESULT=OK VERSION=" + ver + "\n", s)) throw new SAMException("Error writing to socket"); diff --git a/core/java/src/net/i2p/util/PasswordManager.java b/core/java/src/net/i2p/util/PasswordManager.java index 5e51dcb7d..e264254fc 100644 --- a/core/java/src/net/i2p/util/PasswordManager.java +++ b/core/java/src/net/i2p/util/PasswordManager.java @@ -99,6 +99,18 @@ public class PasswordManager { String shash = _context.getProperty(pfx + PROP_SHASH); if (shash == null) return false; + return checkHash(shash, pw); + } + + /** + * Check pw against b64 salt+hash, as generated by createHash() + * + * @param shash b64 string + * @param pw plain text non-null, already trimmed + * @return if pw verified + * @since 0.9.22 + */ + public boolean checkHash(String shash, String pw) { byte[] shashBytes = Base64.decode(shash); if (shashBytes == null || shashBytes.length != SHASH_LENGTH) return false; @@ -110,6 +122,23 @@ public class PasswordManager { return DataHelper.eq(hash, pwHash); } + /** + * Create a salt+hash, to be saved and verified later by verifyHash(). + * + * @param pw plain text non-null, already trimmed + * @return salted+hash b64 string + * @since 0.9.22 + */ + public String createHash(String pw) { + byte[] salt = new byte[SALT_LENGTH]; + _context.random().nextBytes(salt); + byte[] pwHash = _context.keyGenerator().generateSessionKey(salt, DataHelper.getUTF8(pw)).getData(); + byte[] shashBytes = new byte[SHASH_LENGTH]; + System.arraycopy(salt, 0, shashBytes, 0, SALT_LENGTH); + System.arraycopy(pwHash, 0, shashBytes, SALT_LENGTH, SessionKey.KEYSIZE_BYTES); + return Base64.encode(shashBytes); + } + /** * Either plain or b64 * diff --git a/router/java/src/net/i2p/router/util/RouterPasswordManager.java b/router/java/src/net/i2p/router/util/RouterPasswordManager.java index 97ef2468d..3c2fb9b7d 100644 --- a/router/java/src/net/i2p/router/util/RouterPasswordManager.java +++ b/router/java/src/net/i2p/router/util/RouterPasswordManager.java @@ -158,13 +158,7 @@ public class RouterPasswordManager extends PasswordManager { String pfx = realm; if (user != null && user.length() > 0) pfx += '.' + user; - byte[] salt = new byte[SALT_LENGTH]; - _context.random().nextBytes(salt); - byte[] pwHash = _context.keyGenerator().generateSessionKey(salt, DataHelper.getUTF8(pw)).getData(); - byte[] shashBytes = new byte[SHASH_LENGTH]; - System.arraycopy(salt, 0, shashBytes, 0, SALT_LENGTH); - System.arraycopy(pwHash, 0, shashBytes, SALT_LENGTH, SessionKey.KEYSIZE_BYTES); - String shash = Base64.encode(shashBytes); + String shash = createHash(pw); Map toAdd = Collections.singletonMap(pfx + PROP_SHASH, shash); List toDel = new ArrayList(4); toDel.add(pfx + PROP_PW);