From nextgens at freenetproject.org Sat Jun 10 16:45:19 2006 From: nextgens at freenetproject.org (Florent =?iso-8859-1?Q?Daigni=E8re_=28NextGen$=29?=) Date: Sat, 10 Jun 2006 18:45:19 +0200 Subject: [Freemail] Testing Message-ID: <20060610164518.GB5620@freenetproject.org> Testing the mailing list. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 189 bytes Desc: Digital signature Url : http://emu.freenetproject.org/pipermail/freemail/attachments/20060610/b0ca9ba6/attachment.pgp From dbkr at freenetproject.org Tue Jun 13 18:58:53 2006 From: dbkr at freenetproject.org (dbkr at freenetproject.org) Date: Tue, 13 Jun 2006 18:58:53 +0000 (UTC) Subject: [Freemail] r9185 - in trunk/apps/Freemail: . src/freemail src/freemail/utils Message-ID: <20060613185853.EE3419C91D@emu.freenetproject.org> Author: dbkr Date: 2006-06-13 18:58:30 +0000 (Tue, 13 Jun 2006) New Revision: 9185 Modified: trunk/apps/Freemail/README trunk/apps/Freemail/build.xml trunk/apps/Freemail/src/freemail/AccountManager.java trunk/apps/Freemail/src/freemail/utils/PropsFile.java Log: Import the bouncycastle code (fetch and compile it in the any buildfile) and generate a keypair. Also nomenclature corrections to the README. Modified: trunk/apps/Freemail/README =================================================================== --- trunk/apps/Freemail/README 2006-06-13 18:57:44 UTC (rev 9184) +++ trunk/apps/Freemail/README 2006-06-13 18:58:30 UTC (rev 9185) @@ -11,7 +11,7 @@ Proper, secure implemenations of the Freemail protocol will come later. All the data, including your passwd file, will most likley end up world -readable. Under unix, you could try running fnmail with a modified umask +readable. Under unix, you could try running Freemail with a modified umask if this bothers you, but I don't believe there is a portable way of doing this in Java (or I haven't found it). @@ -23,28 +23,28 @@ compile: (however you compile Java, an ant buildfile is supplied) run with --newaccount to create an account, eg: -java -cp build/ fnmail.FNMail --newaccount fred +java -cp build/ freemail.Freemail --newaccount fred Use --passwd to set your password -java -cp build/ fnmail.FNMail --passwd fred fredspassword +java -cp build/ freemail.Freemail --passwd fred fredspassword Run: -java -cp build/ fnmail.FNMail +java -cp build/ Freemail.FNMail Set up your email client to point at IMAP port 3143 and SMTP port 3025. -Your address is @nim.fnmail +Your address is @nim.Freemail And yes, in case you were wondering, no - there's nothing to stop someone else using the same address. I did say it was insecure ;) Send me a message if you like, I promise to reply if it works :) -dbkr at nim.fnmail +dbkr at nim.Freemail -(and since anyone can read fnmail message right now, my fnmail public key +(and since anyone can read Freemail messages right now, my Freemail public key can be found at USK at vjETpEgDH-6EzlngZoO8KgOZm-B8AAlvZ-6oP6aQmow,DZYYfhpOxIrtdCNJiflIPjd0Qy8nA1d3Dwy86dcdhu0,AQABAAE/dbkr/10/contact/pubkey.fnmail.asc, or failing that, http://accidentalegg.co.uk/contact/pubkey.fnmail) If it doesn't, dbkr at freenetproject.org! Modified: trunk/apps/Freemail/build.xml =================================================================== --- trunk/apps/Freemail/build.xml 2006-06-13 18:57:44 UTC (rev 9184) +++ trunk/apps/Freemail/build.xml 2006-06-13 18:58:30 UTC (rev 9185) @@ -1,49 +1,67 @@ - - - + + + - + + + + - - - + + + + + + + + - - + + + + + + + + + + - - - - - - - + + - - - - -
- - - -
-
-
+ + + + + +
+ + + +
+
+
- + + +
Modified: trunk/apps/Freemail/src/freemail/AccountManager.java =================================================================== --- trunk/apps/Freemail/src/freemail/AccountManager.java 2006-06-13 18:57:44 UTC (rev 9184) +++ trunk/apps/Freemail/src/freemail/AccountManager.java 2006-06-13 18:58:30 UTC (rev 9185) @@ -9,7 +9,14 @@ import java.util.Random; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.math.BigInteger; +import org.bouncycastle.crypto.generators.RSAKeyPairGenerator; +import org.bouncycastle.crypto.params.RSAKeyGenerationParameters; +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import java.security.SecureRandom; + import freemail.fcp.HighLevelFCPClient; import freemail.fcp.SSKKeyPair; import freemail.util.PropsFile; @@ -22,6 +29,10 @@ private static final String ACCOUNT_FILE = "accprops"; private static final int RTS_KEY_LENGTH = 32; + private static final int ASYM_KEY_MODULUS_LENGTH = 4096; + private static final BigInteger ASYM_KEY_EXPONENT = new BigInteger("17", 10); + private static final int ASYM_KEY_CERTAINTY = 80; + public static void Create(String username) throws IOException { File datadir = new File(DATADIR); @@ -141,6 +152,26 @@ } catch (IOException ioe) { System.out.println("Couldn't create mailsite key file! "+ioe.getMessage()); } + + // generate an RSA keypair + System.out.println("Generating cryptographic keypair (this could take a few minutes)..."); + + SecureRandom rand = new SecureRandom(); + + RSAKeyGenerationParameters kparams = new RSAKeyGenerationParameters(ASYM_KEY_EXPONENT, rand, ASYM_KEY_MODULUS_LENGTH, ASYM_KEY_CERTAINTY); + + RSAKeyPairGenerator kpg = new RSAKeyPairGenerator(); + kpg.init(kparams); + + AsymmetricCipherKeyPair keypair = kpg.generateKeyPair(); + RSAKeyParameters pub = (RSAKeyParameters) keypair.getPublic(); + RSAKeyParameters priv = (RSAKeyParameters) keypair.getPrivate(); + + accfile.put("asymkey.modulus=", pub.getModulus().toString()); + accfile.put("asymkey.pubexponent=", pub.getExponent().toString()); + accfile.put("asymkey.privexponent=", priv.getExponent().toString()); + + System.out.println("Account creation completed."); } public static boolean authenticate(String username, String password) { Modified: trunk/apps/Freemail/src/freemail/utils/PropsFile.java =================================================================== --- trunk/apps/Freemail/src/freemail/utils/PropsFile.java 2006-06-13 18:57:44 UTC (rev 9184) +++ trunk/apps/Freemail/src/freemail/utils/PropsFile.java 2006-06-13 18:58:30 UTC (rev 9185) @@ -57,6 +57,8 @@ } public String get(String key) { + if (this.data == null) return null; + return (String)this.data.get(key); } From dbkr at freenetproject.org Wed Jun 14 17:26:45 2006 From: dbkr at freenetproject.org (dbkr at freenetproject.org) Date: Wed, 14 Jun 2006 17:26:45 +0000 (UTC) Subject: [Freemail] r9198 - in trunk/apps/Freemail/src/freemail: . fcp Message-ID: <20060614172645.3686E9BE82@emu.freenetproject.org> Author: dbkr Date: 2006-06-14 17:26:40 +0000 (Wed, 14 Jun 2006) New Revision: 9198 Added: trunk/apps/Freemail/src/freemail/MailSite.java Modified: trunk/apps/Freemail/src/freemail/AccountManager.java trunk/apps/Freemail/src/freemail/MailFetcher.java trunk/apps/Freemail/src/freemail/MessageSender.java trunk/apps/Freemail/src/freemail/SingleAccountWatcher.java trunk/apps/Freemail/src/freemail/fcp/HighLevelFCPClient.java Log: Upload a mailsite Modified: trunk/apps/Freemail/src/freemail/AccountManager.java =================================================================== --- trunk/apps/Freemail/src/freemail/AccountManager.java 2006-06-14 16:50:41 UTC (rev 9197) +++ trunk/apps/Freemail/src/freemail/AccountManager.java 2006-06-14 17:26:40 UTC (rev 9198) @@ -86,52 +86,30 @@ accfile.put("md5passwd", strmd5); } - public static String getMailsitePubkey(File accdir) { - PropsFile accfile = getAccountFile(accdir); + public static PropsFile getAccountFile(File accdir) { + PropsFile accfile = new PropsFile(new File(accdir, ACCOUNT_FILE)); - String retval = accfile.get("mailsite.pubkey"); - - if (retval == null) { + if (!accfile.exists()) { initAccFile(accfile); - retval = accfile.get("mailsite.pubkey"); } - return retval; - } - - public static String getMailsitePrivkey(File accdir) { - PropsFile accfile = getAccountFile(accdir); - - String retval = accfile.get("mailsite.pubkey"); - - if (retval == null) { - initAccFile(accfile); - retval = accfile.get("mailsite.privkey"); - } - - return retval; - } - - private static PropsFile getAccountFile(File accdir) { - PropsFile accfile = new PropsFile(new File(accdir, ACCOUNT_FILE)); - return accfile; } private static void initAccFile(PropsFile accfile) { try { System.out.println("Generating mailsite keys..."); - HighLevelFCPClient fcpcli = new HighLevelFCPClient(Freemail.getFCPConnection()); + HighLevelFCPClient fcpcli = new HighLevelFCPClient(); SSKKeyPair keypair = fcpcli.makeSSK(); // write private key - if (!accfile.put("mailsite.privkey", keypair.privkey)) { + if (!accfile.put("mailsite.privkey", keypair.privkey+"mailsite")) { throw new IOException("Unable to write account file"); } // write public key - if (!accfile.put("mailsite.pubkey", keypair.pubkey)) { + if (!accfile.put("mailsite.pubkey", keypair.pubkey+"mailsite")) { throw new IOException("Unable to write account file"); } @@ -167,9 +145,9 @@ RSAKeyParameters pub = (RSAKeyParameters) keypair.getPublic(); RSAKeyParameters priv = (RSAKeyParameters) keypair.getPrivate(); - accfile.put("asymkey.modulus=", pub.getModulus().toString()); - accfile.put("asymkey.pubexponent=", pub.getExponent().toString()); - accfile.put("asymkey.privexponent=", priv.getExponent().toString()); + accfile.put("asymkey.modulus", pub.getModulus().toString()); + accfile.put("asymkey.pubexponent", pub.getExponent().toString()); + accfile.put("asymkey.privexponent", priv.getExponent().toString()); System.out.println("Account creation completed."); } Modified: trunk/apps/Freemail/src/freemail/MailFetcher.java =================================================================== --- trunk/apps/Freemail/src/freemail/MailFetcher.java 2006-06-14 16:50:41 UTC (rev 9197) +++ trunk/apps/Freemail/src/freemail/MailFetcher.java 2006-06-14 17:26:40 UTC (rev 9198) @@ -65,7 +65,7 @@ public void fetch_day(Contact contact, MailLog log, String date) { HighLevelFCPClient fcpcli; - fcpcli = new HighLevelFCPClient(this.fcpconn); + fcpcli = new HighLevelFCPClient(); String keybase; try { Added: trunk/apps/Freemail/src/freemail/MailSite.java =================================================================== --- trunk/apps/Freemail/src/freemail/MailSite.java 2006-06-14 16:50:41 UTC (rev 9197) +++ trunk/apps/Freemail/src/freemail/MailSite.java 2006-06-14 17:26:40 UTC (rev 9198) @@ -0,0 +1,76 @@ +package freemail; + +import java.io.ByteArrayInputStream; +import java.io.UnsupportedEncodingException; + +import freemail.util.PropsFile; +import freemail.fcp.HighLevelFCPClient; + +public class MailSite { + private final PropsFile accprops; + + MailSite(PropsFile a) { + this.accprops = a; + } + + private String getMailPage() { + StringBuffer buf = new StringBuffer(); + + String rtsksk = this.accprops.get("rtskey"); + if (rtsksk == null) { + System.out.println("Can't insert mailsite - missing RTS KSK"); + return null; + } + buf.append("rtsksk=").append(rtsksk).append("\r\n"); + + String keymodulus = this.accprops.get("asymkey.modulus"); + if (keymodulus == null) { + System.out.println("Can't insert mailsite - missing asymmetic crypto key modulus"); + return null; + } + buf.append("asymkey.modulus=").append(keymodulus).append("\r\n"); + + String key_pubexponent = this.accprops.get("asymkey.pubexponent"); + if (key_pubexponent == null) { + System.out.println("Can't insert mailsite - missing asymmetic crypto key public exponent"); + return null; + } + buf.append("asymkey.pubexponent=").append(key_pubexponent).append("\r\n"); + + return buf.toString(); + } + + public int Publish() { + byte[] mailpage; + String mailsite_s = this.getMailPage(); + if (mailsite_s == null) { + return -1; + } + try { + mailpage = mailsite_s.getBytes("UTF-8"); + } catch (UnsupportedEncodingException use) { + mailpage = mailsite_s.getBytes(); + } + + ByteArrayInputStream bis = new ByteArrayInputStream(mailpage); + + String key = this.accprops.get("mailsite.privkey"); + if (key == null) return -1; + + HighLevelFCPClient cli = new HighLevelFCPClient(); + + String minslot_s = this.accprops.get("mailsite.slot"); + int minslot; + if (minslot_s != null) { + minslot = Integer.parseInt(minslot_s); + } else { + minslot = 1; + } + + int actualslot = cli.SlotInsert(bis, key, 1, "/mailpage"); + + this.accprops.put("mailsite.slot", new Integer(actualslot).toString()); + + return actualslot; + } +} Modified: trunk/apps/Freemail/src/freemail/MessageSender.java =================================================================== --- trunk/apps/Freemail/src/freemail/MessageSender.java 2006-06-14 16:50:41 UTC (rev 9197) +++ trunk/apps/Freemail/src/freemail/MessageSender.java 2006-06-14 17:26:40 UTC (rev 9198) @@ -117,44 +117,18 @@ } if (addr.domain.equalsIgnoreCase("nim.freemail")) { - if (this.slotinsert(msg, NIM_KEY_PREFIX+addr.user+"-"+DateStringFactory.getKeyString())) { - msg.delete(); - } - } - } - - private boolean slotinsert(File data, String basekey) { - HighLevelFCPClient cli = new HighLevelFCPClient(Freemail.getFCPConnection()); - - int slot = 1; - boolean carryon = true; - while (carryon) { - System.out.println("trying slotinsert to "+basekey+"-"+slot); + HighLevelFCPClient cli = new HighLevelFCPClient(); + FileInputStream fis; try { - fis = new FileInputStream(data); + fis = new FileInputStream(msg); } catch (FileNotFoundException fnfe) { - // riiiiiight... - return false; + return; } - FCPInsertErrorMessage emsg; - try { - emsg = cli.put(fis, basekey+"-"+slot); - } catch (FCPBadFileException bfe) { - return false; + + if (cli.SlotInsert(fis, NIM_KEY_PREFIX+addr.user+"-"+DateStringFactory.getKeyString(), 1, "") > -1) { + msg.delete(); } - if (emsg == null) { - System.out.println("insert successful"); - return true; - } else if (emsg.errorcode == FCPInsertErrorMessage.COLLISION) { - slot++; - System.out.println("collision"); - } else { - System.out.println("nope - error code is "+emsg.errorcode); - // try again later - return false; - } } - return false; } } Modified: trunk/apps/Freemail/src/freemail/SingleAccountWatcher.java =================================================================== --- trunk/apps/Freemail/src/freemail/SingleAccountWatcher.java 2006-06-14 16:50:41 UTC (rev 9197) +++ trunk/apps/Freemail/src/freemail/SingleAccountWatcher.java 2006-06-14 17:26:40 UTC (rev 9198) @@ -3,6 +3,8 @@ import java.io.File; import java.lang.InterruptedException; +import freemail.util.PropsFile; + public class SingleAccountWatcher implements Runnable { public static final String CONTACTS_DIR = "contacts"; private static final int MIN_POLL_DURATION = 60000; // in milliseconds @@ -13,8 +15,9 @@ SingleAccountWatcher(File accdir) { File contacts_dir = new File(accdir, CONTACTS_DIR); - ///////////////// - AccountManager.getMailsitePrivkey(accdir); + PropsFile accprops = AccountManager.getAccountFile(accdir); + MailSite ms = new MailSite(accprops); + ms.Publish(); this.mb = new MessageBank(accdir.getName()); this.mf = new MailFetcher(this.mb, contacts_dir, Freemail.getFCPConnection()); Modified: trunk/apps/Freemail/src/freemail/fcp/HighLevelFCPClient.java =================================================================== --- trunk/apps/Freemail/src/freemail/fcp/HighLevelFCPClient.java 2006-06-14 16:50:41 UTC (rev 9197) +++ trunk/apps/Freemail/src/freemail/fcp/HighLevelFCPClient.java 2006-06-14 17:26:40 UTC (rev 9198) @@ -3,12 +3,14 @@ import java.io.File; import java.io.InputStream; +import freemail.Freemail; + public class HighLevelFCPClient implements FCPClient { private FCPConnection conn; private FCPMessage donemsg; - public HighLevelFCPClient(FCPConnection c) { - this.conn = c; + public HighLevelFCPClient() { + this.conn = Freemail.getFCPConnection(); } // It's up to the client to delete this File once they're @@ -120,6 +122,33 @@ } } + public int SlotInsert(InputStream data, String basekey, int minslot, String suffix) { + int slot = minslot; + boolean carryon = true; + while (carryon) { + System.out.println("trying slotinsert to "+basekey+"-"+slot+suffix); + + FCPInsertErrorMessage emsg; + try { + emsg = this.put(data, basekey+"-"+slot+suffix); + } catch (FCPBadFileException bfe) { + return -1; + } + if (emsg == null) { + System.out.println("insert successful"); + return slot; + } else if (emsg.errorcode == FCPInsertErrorMessage.COLLISION) { + slot++; + System.out.println("collision"); + } else { + System.out.println("nope - error code is "+emsg.errorcode); + // try again later + return -1; + } + } + return -1; + } + public void requestStatus(FCPMessage msg) { } From dbkr at freenetproject.org Thu Jun 22 18:23:12 2006 From: dbkr at freenetproject.org (Dave Baker) Date: Thu, 22 Jun 2006 19:23:12 +0100 Subject: [Freemail] Freemail addresses Message-ID: <200606221923.12607.dbkr@freenetproject.org> Hi, So a Freemail address needs to express an SSK public key at which a 'mailsite' can be fetched containing all the data necessary to set up some communication. The problem is that Freenet keys are case sensitive, and email addresses aren't. My mail client mangles the address to lowercase, meaning you can't just put the SSK into the address. Can anyone think of a (ideally standard) way of encoding an SSK to case insensitive ASCII? I'm currently using Hex encoding, but that makes Freemail addresses like: dbkr at 667265656e65743a53534b40317574304a6b7a57614c6b672d556b4d44704a684a5a65646332346672306445362d74394d7a7e43517e552c54475668325272505564614c505a775858454d6f51737177376f4e36634964704c73794f61355974584e382c415141424141452f.freemail Not great. I don't really want to write another encoding scheme. Dave From dbkr at freenetproject.org Thu Jun 22 19:11:38 2006 From: dbkr at freenetproject.org (dbkr at freenetproject.org) Date: Thu, 22 Jun 2006 19:11:38 +0000 (UTC) Subject: [Freemail] r9354 - in trunk/apps/Freemail: . src/freemail src/freemail/utils Message-ID: <20060622191138.9C7829C91C@emu.freenetproject.org> Author: dbkr Date: 2006-06-22 19:11:33 +0000 (Thu, 22 Jun 2006) New Revision: 9354 Added: trunk/apps/Freemail/src/freemail/BadFreemailAddressException.java trunk/apps/Freemail/src/freemail/OutboundContact.java trunk/apps/Freemail/src/freemail/OutboundContactFatalException.java Modified: trunk/apps/Freemail/build.xml trunk/apps/Freemail/src/freemail/AccountManager.java trunk/apps/Freemail/src/freemail/Freemail.java trunk/apps/Freemail/src/freemail/MailFetcher.java trunk/apps/Freemail/src/freemail/MailSite.java trunk/apps/Freemail/src/freemail/MessageSender.java trunk/apps/Freemail/src/freemail/SingleAccountWatcher.java trunk/apps/Freemail/src/freemail/utils/EmailAddress.java trunk/apps/Freemail/src/freemail/utils/PropsFile.java Log: Work towards sending the RTS message. Doesn't work just yet as the message is bigger than an RSA block. Modified: trunk/apps/Freemail/build.xml =================================================================== --- trunk/apps/Freemail/build.xml 2006-06-22 18:39:47 UTC (rev 9353) +++ trunk/apps/Freemail/build.xml 2006-06-22 19:11:33 UTC (rev 9354) @@ -23,8 +23,8 @@ - - + + Modified: trunk/apps/Freemail/src/freemail/AccountManager.java =================================================================== --- trunk/apps/Freemail/src/freemail/AccountManager.java 2006-06-22 18:39:47 UTC (rev 9353) +++ trunk/apps/Freemail/src/freemail/AccountManager.java 2006-06-22 19:11:33 UTC (rev 9354) @@ -9,17 +9,18 @@ import java.util.Random; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.math.BigInteger; import org.bouncycastle.crypto.generators.RSAKeyPairGenerator; import org.bouncycastle.crypto.params.RSAKeyGenerationParameters; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.params.RSAKeyParameters; -import java.security.SecureRandom; +import org.bouncycastle.util.encoders.Hex; import freemail.fcp.HighLevelFCPClient; import freemail.fcp.SSKKeyPair; -import freemail.util.PropsFile; +import freemail.utils.PropsFile; public class AccountManager { public static final String DATADIR = "data"; @@ -33,6 +34,8 @@ private static final BigInteger ASYM_KEY_EXPONENT = new BigInteger("17", 10); private static final int ASYM_KEY_CERTAINTY = 80; + public static final String MAILSITE_SUFFIX = "mailsite"; + public static void Create(String username) throws IOException { File datadir = new File(DATADIR); @@ -42,6 +45,7 @@ File accountdir = new File(DATADIR, username); if (!accountdir.mkdir()) throw new IOException("Failed to create directory "+username+" in "+DATADIR); + getAccountFile(accountdir); } public static void setupNIM(String username) throws IOException { @@ -52,7 +56,12 @@ if (!contacts_dir.mkdir()) throw new IOException("Failed to create contacts directory"); } - File nimdir = new File(contacts_dir, NIMDIR); + File inbound_dir = new File(contacts_dir, SingleAccountWatcher.INBOUND_DIR); + if (!inbound_dir.exists()) { + if (!inbound_dir.mkdir()) throw new IOException("Failed to create inbound contacts directory"); + } + + File nimdir = new File(inbound_dir, NIMDIR); if (!nimdir.exists()) { if (!nimdir.mkdir()) throw new IOException("Failed to create nim directory"); } @@ -81,7 +90,7 @@ PropsFile accfile = getAccountFile(accountdir); byte[] md5passwd = md.digest(newpassword.getBytes()); - String strmd5 = bytestoHex(md5passwd); + String strmd5 = new String(Hex.encode(md5passwd)); accfile.put("md5passwd", strmd5); } @@ -96,6 +105,20 @@ return accfile; } + public static RSAKeyParameters getPrivateKey(File accdir) { + PropsFile props = getAccountFile(accdir); + + String mod_str = props.get("asymkey.modulus"); + String privexp_str = props.get("asymkey.privexponent"); + + if (mod_str == null || privexp_str == null) { + System.out.println("Couldn't get private key - account file corrupt?"); + return null; + } + + return new RSAKeyParameters(true, new BigInteger(mod_str, 10), new BigInteger(privexp_str, 10)); + } + private static void initAccFile(PropsFile accfile) { try { System.out.println("Generating mailsite keys..."); @@ -103,13 +126,18 @@ SSKKeyPair keypair = fcpcli.makeSSK(); + if (keypair == null) { + System.out.println("Unable to connect to the Freenet nodenode"); + return; + } + // write private key - if (!accfile.put("mailsite.privkey", keypair.privkey+"mailsite")) { + if (!accfile.put("mailsite.privkey", keypair.privkey+MAILSITE_SUFFIX)) { throw new IOException("Unable to write account file"); } // write public key - if (!accfile.put("mailsite.pubkey", keypair.pubkey+"mailsite")) { + if (!accfile.put("mailsite.pubkey", keypair.pubkey+MAILSITE_SUFFIX)) { throw new IOException("Unable to write account file"); } @@ -175,7 +203,7 @@ } byte[] givenmd5 = md.digest(password.getBytes()); - String givenmd5str = bytestoHex(givenmd5); + String givenmd5str = new String(Hex.encode(givenmd5)); if (realmd5str.equals(givenmd5str)) { return true; @@ -187,17 +215,4 @@ if (username.matches("[\\w_]*")) return true; return false; } - - public static String bytestoHex(byte[] bytes) { - String retval = new String(""); - - for (int i = 0; i < bytes.length; i++) { - String b = Integer.toHexString((int)(bytes[i] & 0xFF)); - if (b.length() < 2) { - b = "0" + b; - } - retval += b; - } - return retval; - } } Added: trunk/apps/Freemail/src/freemail/BadFreemailAddressException.java =================================================================== --- trunk/apps/Freemail/src/freemail/BadFreemailAddressException.java 2006-06-22 18:39:47 UTC (rev 9353) +++ trunk/apps/Freemail/src/freemail/BadFreemailAddressException.java 2006-06-22 19:11:33 UTC (rev 9354) @@ -0,0 +1,5 @@ +package freemail; + +public class BadFreemailAddressException extends Exception { + +} Modified: trunk/apps/Freemail/src/freemail/Freemail.java =================================================================== --- trunk/apps/Freemail/src/freemail/Freemail.java 2006-06-22 18:39:47 UTC (rev 9353) +++ trunk/apps/Freemail/src/freemail/Freemail.java 2006-06-22 19:11:33 UTC (rev 9354) @@ -26,36 +26,29 @@ String fcphost = "localhost"; int fcpport = 9481; + String action = ""; + String account = null; + String newpasswd = null; + for (int i = 0; i < args.length; i++) { if (args[i].equals("--newaccount")) { + action = args[i]; i++; if (args.length - 1 < i) { System.out.println("Usage: --newaccount "); return; } - try { - AccountManager.Create(args[i]); - // for now - AccountManager.setupNIM(args[i]); - System.out.println("Account created for "+args[i]+". You may now set a password with --passwd "); - System.out.println("For the time being, you address is "+args[i]+"@nim.freemail"); - } catch (IOException ioe) { - System.out.println("Couldn't create account. Please check write access to Freemail's working directory. Error: "+ioe.getMessage()); - } - return; + + account = args[i]; } else if (args[i].equals("--passwd")) { + action = args[i]; i = i + 2; if (args.length - 1 < i) { System.out.println("Usage: --passwd "); return; } - try { - AccountManager.ChangePassword(args[i - 1], args[i]); - System.out.println("Password changed."); - } catch (Exception e) { - System.out.println("Couldn't change password for "+args[i - 1]+". "+e.getMessage()); - } - return; + account = args[i - 1]; + newpasswd = args[i]; } else if (args[i].equals("-h")) { i++; if (args.length - 1 < i) { @@ -84,6 +77,28 @@ fcpthread.setDaemon(true); fcpthread.start(); + if (action.equals("--newaccount")) { + try { + AccountManager.Create(account); + // for now + AccountManager.setupNIM(account); + System.out.println("Account created for "+account+". You may now set a password with --passwd "); + System.out.println("For the time being, you address is "+account+"@nim.freemail"); + } catch (IOException ioe) { + System.out.println("Couldn't create account. Please check write access to Freemail's working directory. Error: "+ioe.getMessage()); + } + return; + } else if (action.equals("--passwd")) { + try { + AccountManager.ChangePassword(account, newpasswd); + System.out.println("Password changed."); + } catch (Exception e) { + System.out.println("Couldn't change password for "+account+". "+e.getMessage()); + e.printStackTrace(); + } + return; + } + // start a SingleAccountWatcher for each account Freemail.datadir = new File("data"); if (!Freemail.datadir.exists()) { Modified: trunk/apps/Freemail/src/freemail/MailFetcher.java =================================================================== --- trunk/apps/Freemail/src/freemail/MailFetcher.java 2006-06-22 18:39:47 UTC (rev 9353) +++ trunk/apps/Freemail/src/freemail/MailFetcher.java 2006-06-22 19:11:33 UTC (rev 9354) @@ -16,6 +16,8 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import org.bouncycastle.util.encoders.Hex; + public class MailFetcher { private final MessageBank mb; private File contact_dir; @@ -127,6 +129,6 @@ file.delete(); byte[] checksum = md.digest(); - return AccountManager.bytestoHex(checksum); + return new String(Hex.encode(checksum)); } } Modified: trunk/apps/Freemail/src/freemail/MailSite.java =================================================================== --- trunk/apps/Freemail/src/freemail/MailSite.java 2006-06-22 18:39:47 UTC (rev 9353) +++ trunk/apps/Freemail/src/freemail/MailSite.java 2006-06-22 19:11:33 UTC (rev 9354) @@ -3,11 +3,12 @@ import java.io.ByteArrayInputStream; import java.io.UnsupportedEncodingException; -import freemail.util.PropsFile; +import freemail.utils.PropsFile; import freemail.fcp.HighLevelFCPClient; public class MailSite { private final PropsFile accprops; + public static final String MAILPAGE = "mailpage"; MailSite(PropsFile a) { this.accprops = a; @@ -67,7 +68,7 @@ minslot = 1; } - int actualslot = cli.SlotInsert(bis, key, 1, "/mailpage"); + int actualslot = cli.SlotInsert(bis, key, 1, "/"+MAILPAGE); this.accprops.put("mailsite.slot", new Integer(actualslot).toString()); Modified: trunk/apps/Freemail/src/freemail/MessageSender.java =================================================================== --- trunk/apps/Freemail/src/freemail/MessageSender.java 2006-06-22 18:39:47 UTC (rev 9353) +++ trunk/apps/Freemail/src/freemail/MessageSender.java 2006-06-22 19:11:33 UTC (rev 9354) @@ -16,7 +16,7 @@ public class MessageSender implements Runnable { public static final String OUTBOX_DIR = "outbox"; - public static final int MIN_RUN_TIME = 60000; + private static final int MIN_RUN_TIME = 60000; public static final String NIM_KEY_PREFIX = "KSK at freemail-nim-"; private final File datadir; private Thread senderthread; @@ -77,7 +77,7 @@ if (!outbox.exists()) outbox.mkdir(); - this.sendDir(outbox); + this.sendDir(files[i], outbox); } // don't spin around the loop if nothing's // going on @@ -92,17 +92,17 @@ } } - private void sendDir(File dir) { + private void sendDir(File accdir, File dir) { File[] files = dir.listFiles(); for (int i = 0; i < files.length; i++) { if (files[i].getName().startsWith(".")) continue; - this.sendSingle(files[i]); + this.sendSingle(accdir, files[i]); } } - private void sendSingle(File msg) { + private void sendSingle(File accdir, File msg) { String parts[] = msg.getName().split(":", 2); EmailAddress addr; if (parts.length < 2) { @@ -129,6 +129,39 @@ if (cli.SlotInsert(fis, NIM_KEY_PREFIX+addr.user+"-"+DateStringFactory.getKeyString(), 1, "") > -1) { msg.delete(); } + } else { + if (this.sendSecure(accdir, addr, msg)) { + msg.delete(); + } } } + + private boolean sendSecure(File accdir, EmailAddress addr, File msg) { + System.out.println("sending secure"); + OutboundContact ct; + try { + ct = new OutboundContact(accdir, addr); + } catch (BadFreemailAddressException bfae) { + // TODO: bounce + return true; + } + boolean ready; + if (!ct.exists()) { + try { + System.out.println("initing outbound contact"); + ready = ct.init(); + } catch (OutboundContactFatalException fe) { + // will never succeed, so report success to delete the message + // TODO: send a bounce message or something + return true; + } + } else { + System.out.println("ready"); + ready = true; + } + + if (!ready) return false; + + return false; + } } Added: trunk/apps/Freemail/src/freemail/OutboundContact.java =================================================================== --- trunk/apps/Freemail/src/freemail/OutboundContact.java 2006-06-22 18:39:47 UTC (rev 9353) +++ trunk/apps/Freemail/src/freemail/OutboundContact.java 2006-06-22 19:11:33 UTC (rev 9354) @@ -0,0 +1,176 @@ +package freemail; + +import java.io.File; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import freemail.utils.EmailAddress; +import freemail.utils.PropsFile; +import freemail.utils.DateStringFactory; +import freemail.fcp.HighLevelFCPClient; +import freemail.fcp.SSKKeyPair; + +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.engines.RSAEngine; +import org.bouncycastle.crypto.InvalidCipherTextException; + +public class OutboundContact { + private final PropsFile contactfile; + private final File accdir; + private final EmailAddress address; + private static final String OUTBOUND_DIR = "outbound"; + + public OutboundContact(File accdir, EmailAddress a) throws BadFreemailAddressException { + this.address = a; + + this.accdir = accdir; + + if (this.address.getMailsiteKey() == null) { + this.contactfile = null; + throw new BadFreemailAddressException(); + } else { + File contactsdir = new File(accdir, SingleAccountWatcher.CONTACTS_DIR); + if (!contactsdir.exists()) + contactsdir.mkdir(); + File outbounddir = new File(contactsdir, OUTBOUND_DIR); + + if (!outbounddir.exists()) + outbounddir.mkdir(); + + this.contactfile = new PropsFile(new File(outbounddir, this.address.getMailsiteKey())); + } + } + + public boolean exists() { + return this.contactfile.exists(); + } + + /** + * Set up an outbound contact. Fetch the mailsite, generate a new SSK keypair and post an RTS message to the appropriate KSK. + * Will block for mailsite retrieval and RTS insertion + * + * @return true for success + */ + public boolean init() throws OutboundContactFatalException { + HighLevelFCPClient cli = new HighLevelFCPClient(); + + System.out.println("Attempting to fetch "+this.getMailpageKey()); + File mailsite_file = cli.fetch(this.getMailpageKey()); + + if (mailsite_file == null) { + // TODO: Give up for now, try later, count number of and limit attempts + System.out.println("Failed to retrieve mailsite for "+this.address); + return false; + } + + System.out.println("got mailsite"); + + PropsFile mailsite = new PropsFile(mailsite_file); + + String rtskey = mailsite.get("rtsksk"); + String keymod_str = mailsite.get("asymkey.modulus"); + String keyexp_str = mailsite.get("asymkey.pubexponent"); + + if (rtskey == null || keymod_str == null || keyexp_str == null) { + // TODO: More failure mechanisms - this is fatal. + System.out.println("Mailsite for "+this.address+" does not contain all necessary iformation!"); + throw new OutboundContactFatalException("Mailsite for "+this.address+" does not contain all necessary iformation!"); + } + mailsite_file.delete(); + + SSKKeyPair ssk = cli.makeSSK(); + + StringBuffer rtsmessage = new StringBuffer(); + + rtsmessage.append("messagetype=rts\r\n"); + + // must include who this RTS is to, otherwise we're vulnerable to surruptitious forwarding + rtsmessage.append("to="+this.address.getMailsiteKey()+"\r\n"); + + // get our mailsite URI + String our_mailsite_uri = AccountManager.getAccountFile(this.accdir).get("mailsite.pubkey"); + + rtsmessage.append("mailsite="+our_mailsite_uri+"\r\n"); + + // the public part of the SSK keypair we generated + rtsmessage.append("commssk="+ssk.pubkey+"\r\n"); + + rtsmessage.append("\r\n"); + + // sign the message + + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException alge) { + System.out.println("No SHA 256 implementation available - no mail can be sent!"); + return false; + } + + byte[] hash = md.digest(rtsmessage.toString().getBytes()); + + RSAKeyParameters our_priv_key = AccountManager.getPrivateKey(this.accdir); + + AsymmetricBlockCipher sigcipher = new RSAEngine(); + sigcipher.init(true, our_priv_key); + byte[] sig = null; + try { + sig = sigcipher.processBlock(hash, 0, hash.length); + } catch (InvalidCipherTextException e) { + e.printStackTrace(); + return false; + } + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + try { + bos.write(rtsmessage.toString().getBytes()); + bos.write(sig); + } catch(IOException ioe) { + ioe.printStackTrace(); + return false; + } + + // now encrypt it + + BigInteger keymodulus = new BigInteger(keymod_str, 10); + BigInteger keyexponent = new BigInteger(keyexp_str, 10); + + // is not private + RSAKeyParameters their_pub_key = new RSAKeyParameters(false, keymodulus, keyexponent); + + AsymmetricBlockCipher enccipher = new RSAEngine(); + enccipher.init(true, their_pub_key); + byte[] encmsg = null; + try { + encmsg = sigcipher.processBlock(bos.toByteArray(), 0, bos.toByteArray().length); + } catch (InvalidCipherTextException e) { + e.printStackTrace(); + return false; + } + + // insert it! + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + + if (cli.SlotInsert(bis, "KSK@"+rtskey+"-"+DateStringFactory.getKeyString(), 1, "") < 0) { + return false; + } + + // now we can create a new outbound contact file + this.contactfile.put("rtskey", rtskey); + this.contactfile.put("asymkey.modulus", keymod_str); + this.contactfile.put("asymnkey.exponent", keyexp_str); + this.contactfile.put("commssk", ssk.privkey); + + return true; + } + + private String getMailpageKey() { + return "USK@"+this.address.getMailsiteKey()+"/"+AccountManager.MAILSITE_SUFFIX+"/1/"+MailSite.MAILPAGE; + } +} Added: trunk/apps/Freemail/src/freemail/OutboundContactFatalException.java =================================================================== --- trunk/apps/Freemail/src/freemail/OutboundContactFatalException.java 2006-06-22 18:39:47 UTC (rev 9353) +++ trunk/apps/Freemail/src/freemail/OutboundContactFatalException.java 2006-06-22 19:11:33 UTC (rev 9354) @@ -0,0 +1,7 @@ +package freemail; + +public class OutboundContactFatalException extends Exception { + public OutboundContactFatalException(String msg) { + super(msg); + } +} Modified: trunk/apps/Freemail/src/freemail/SingleAccountWatcher.java =================================================================== --- trunk/apps/Freemail/src/freemail/SingleAccountWatcher.java 2006-06-22 18:39:47 UTC (rev 9353) +++ trunk/apps/Freemail/src/freemail/SingleAccountWatcher.java 2006-06-22 19:11:33 UTC (rev 9354) @@ -3,10 +3,11 @@ import java.io.File; import java.lang.InterruptedException; -import freemail.util.PropsFile; +import freemail.utils.PropsFile; public class SingleAccountWatcher implements Runnable { public static final String CONTACTS_DIR = "contacts"; + public static final String INBOUND_DIR = "inbound"; private static final int MIN_POLL_DURATION = 60000; // in milliseconds private static final int MAILSITE_UPLOAD_INTERVAL = 60 * 60 * 1000; private final MessageBank mb; @@ -14,13 +15,15 @@ SingleAccountWatcher(File accdir) { File contacts_dir = new File(accdir, CONTACTS_DIR); + File inbound_dir = new File(contacts_dir, INBOUND_DIR); + // TODO: do this in the loop, periodically PropsFile accprops = AccountManager.getAccountFile(accdir); MailSite ms = new MailSite(accprops); ms.Publish(); this.mb = new MessageBank(accdir.getName()); - this.mf = new MailFetcher(this.mb, contacts_dir, Freemail.getFCPConnection()); + this.mf = new MailFetcher(this.mb, inbound_dir, Freemail.getFCPConnection()); } public void run() { Modified: trunk/apps/Freemail/src/freemail/utils/EmailAddress.java =================================================================== --- trunk/apps/Freemail/src/freemail/utils/EmailAddress.java 2006-06-22 18:39:47 UTC (rev 9353) +++ trunk/apps/Freemail/src/freemail/utils/EmailAddress.java 2006-06-22 19:11:33 UTC (rev 9354) @@ -1,5 +1,7 @@ package freemail.utils; +import org.bouncycastle.util.encoders.Hex; + public class EmailAddress { public String realname; public String user; @@ -47,6 +49,26 @@ public boolean is_freemail_address() { if (this.domain == null) return false; - return this.domain.equalsIgnoreCase("nim.freemail"); + if (!this.domain.endsWith(".freemail")) return false; + if (this.getMailsiteKey() == null) return false; + return true; } + + public String getMailsiteKey() { + String[] domparts = this.domain.split("\\.", 2); + + if (domparts.length < 2) return null; + + try { + return new String (Hex.decode(domparts[0].getBytes())); + } catch (ArrayIndexOutOfBoundsException aiobe) { + // the Hex decoder just generates this exception if the input is not hex + // (since it looks up a non-hex charecter in the decoding table) + return null; + } + } + + public String toString() { + return this.user+"@"+this.domain; + } } Modified: trunk/apps/Freemail/src/freemail/utils/PropsFile.java =================================================================== --- trunk/apps/Freemail/src/freemail/utils/PropsFile.java 2006-06-22 18:39:47 UTC (rev 9353) +++ trunk/apps/Freemail/src/freemail/utils/PropsFile.java 2006-06-22 19:11:33 UTC (rev 9354) @@ -1,4 +1,4 @@ -package freemail.util; +package freemail.utils; import java.io.File; import java.io.FileReader; From dbkr at freenetproject.org Fri Jun 23 11:16:22 2006 From: dbkr at freenetproject.org (dbkr at freenetproject.org) Date: Fri, 23 Jun 2006 11:16:22 +0000 (UTC) Subject: [Freemail] r9357 - in trunk/apps/Freemail/src: . freemail freemail/utils org org/archive org/archive/util Message-ID: <20060623111622.271F29BD15@emu.freenetproject.org> Author: dbkr Date: 2006-06-23 11:16:15 +0000 (Fri, 23 Jun 2006) New Revision: 9357 Added: trunk/apps/Freemail/src/freemail/FreenetURI.java trunk/apps/Freemail/src/org/ trunk/apps/Freemail/src/org/archive/ trunk/apps/Freemail/src/org/archive/util/ trunk/apps/Freemail/src/org/archive/util/Base32.java Modified: trunk/apps/Freemail/src/freemail/AccountManager.java trunk/apps/Freemail/src/freemail/utils/EmailAddress.java Log: Change addresses to be base 32 encoded instead of base 16. Makes them a bit shorter. Modified: trunk/apps/Freemail/src/freemail/AccountManager.java =================================================================== --- trunk/apps/Freemail/src/freemail/AccountManager.java 2006-06-23 03:24:18 UTC (rev 9356) +++ trunk/apps/Freemail/src/freemail/AccountManager.java 2006-06-23 11:16:15 UTC (rev 9357) @@ -18,6 +18,8 @@ import org.bouncycastle.crypto.params.RSAKeyParameters; import org.bouncycastle.util.encoders.Hex; +import org.archive.util.Base32; + import freemail.fcp.HighLevelFCPClient; import freemail.fcp.SSKKeyPair; import freemail.utils.PropsFile; @@ -155,6 +157,12 @@ } System.out.println("Mailsite keys generated."); + + FreenetURI puburi = new FreenetURI(keypair.pubkey); + + String base32body = Base32.encode(puburi.getKeyBody().getBytes()); + + System.out.println("Your Freemail address is: @"+base32body+".freemail"); } catch (IOException ioe) { System.out.println("Couldn't create mailsite key file! "+ioe.getMessage()); } Added: trunk/apps/Freemail/src/freemail/FreenetURI.java =================================================================== --- trunk/apps/Freemail/src/freemail/FreenetURI.java 2006-06-23 03:24:18 UTC (rev 9356) +++ trunk/apps/Freemail/src/freemail/FreenetURI.java 2006-06-23 11:16:15 UTC (rev 9357) @@ -0,0 +1,73 @@ +package freemail; + +import java.net.MalformedURLException; + +/* + * Represents a Freenet URI + * If this gets complicated, look at using Freenet's own class of the same name (has dependancies though) + */ +public class FreenetURI { + private String keytype; + private String keybody; + private String suffix; // this includes USK versions etc for now + + public FreenetURI(String uri) throws MalformedURLException { + String[] parts = uri.split(":", 2); + + if (parts.length == 2 && !parts[0].equals("freenet")) { + throw new MalformedURLException("Invalid scheme - not a freenet address"); + } else if (parts.length == 2) { + uri = parts[1]; + } + + // now split on the '@' + parts = uri.split("@", 2); + + if (parts.length < 2) { + this.keytype = "KSK"; + } else { + this.keytype = parts[0]; + uri = parts[1]; + } + + // finally, separate the body from the metastrings + parts = uri.split("/", 2); + + if (parts.length < 2) { + this.keybody = uri; + this.suffix = null; + } else { + this.keybody = parts[0]; + this.suffix = parts[1]; + } + } + + public String getKeyType() { + return this.keytype; + } + + public String getKeyBody() { + return this.keybody; + } + + public String getSuffix() { + return this.suffix; + } + + /* + * Read a freenet URI from args and print out in parts to test + */ + public static void main(String args[]) { + FreenetURI uri; + try { + uri = new FreenetURI(args[0]); + } catch (MalformedURLException mue) { + mue.printStackTrace(); + return; + } + + System.out.println("Key type: "+uri.getKeyType()); + System.out.println("Key body: "+uri.getKeyBody()); + System.out.println("Suffix: "+uri.getSuffix()); + } +} Modified: trunk/apps/Freemail/src/freemail/utils/EmailAddress.java =================================================================== --- trunk/apps/Freemail/src/freemail/utils/EmailAddress.java 2006-06-23 03:24:18 UTC (rev 9356) +++ trunk/apps/Freemail/src/freemail/utils/EmailAddress.java 2006-06-23 11:16:15 UTC (rev 9357) @@ -2,6 +2,8 @@ import org.bouncycastle.util.encoders.Hex; +import org.archive.util.Base32; + public class EmailAddress { public String realname; public String user; @@ -59,13 +61,7 @@ if (domparts.length < 2) return null; - try { - return new String (Hex.decode(domparts[0].getBytes())); - } catch (ArrayIndexOutOfBoundsException aiobe) { - // the Hex decoder just generates this exception if the input is not hex - // (since it looks up a non-hex charecter in the decoding table) - return null; - } + return new String (Base32.decode(domparts[0])); } public String toString() { Added: trunk/apps/Freemail/src/org/archive/util/Base32.java =================================================================== --- trunk/apps/Freemail/src/org/archive/util/Base32.java 2006-06-23 03:24:18 UTC (rev 9356) +++ trunk/apps/Freemail/src/org/archive/util/Base32.java 2006-06-23 11:16:15 UTC (rev 9357) @@ -0,0 +1,165 @@ +/* Base32 +* +* $Id: Base32.java,v 1.4 2004/04/15 19:04:01 stack-sf Exp $ +* +* Created on Jan 21, 2004 +* +* Copyright (C) 2004 Internet Archive. +* +* This file is part of the Heritrix web crawler (crawler.archive.org). +* +* Heritrix is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser Public License as published by +* the Free Software Foundation; either version 2.1 of the License, or +* any later version. +* +* Heritrix is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser Public License for more details. +* +* You should have received a copy of the GNU Lesser Public License +* along with Heritrix; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +package org.archive.util; + +/** + * Base32 - encodes and decodes RFC3548 Base32 + * (see http://www.faqs.org/rfcs/rfc3548.html ) + * + * Imported public-domain code of Bitzi. + * + * @author Robert Kaye + * @author Gordon Mohr + */ +public class Base32 { + private static final String base32Chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + private static final int[] base32Lookup = + { 0xFF,0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, // '0', '1', '2', '3', '4', '5', '6', '7' + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, // '8', '9', ':', ';', '<', '=', '>', '?' + 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G' + 0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O' + 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W' + 0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF, // 'X', 'Y', 'Z', '[', '\', ']', '^', '_' + 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g' + 0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o' + 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'p', 'q', 'r', 's', 't', 'u', 'v', 'w' + 0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF // 'x', 'y', 'z', '{', '|', '}', '~', 'DEL' + }; + + /** + * Encodes byte array to Base32 String. + * + * @param bytes Bytes to encode. + * @return Encoded byte array bytes as a String. + * + */ + static public String encode(final byte[] bytes) { + int i = 0, index = 0, digit = 0; + int currByte, nextByte; + StringBuffer base32 = new StringBuffer((bytes.length + 7) * 8 / 5); + + while (i < bytes.length) { + currByte = (bytes[i] >= 0) ? bytes[i] : (bytes[i] + 256); // unsign + + /* Is the current digit going to span a byte boundary? */ + if (index > 3) { + if ((i + 1) < bytes.length) { + nextByte = + (bytes[i + 1] >= 0) ? bytes[i + 1] : (bytes[i + 1] + 256); + } else { + nextByte = 0; + } + + digit = currByte & (0xFF >> index); + index = (index + 5) % 8; + digit <<= index; + digit |= nextByte >> (8 - index); + i++; + } else { + digit = (currByte >> (8 - (index + 5))) & 0x1F; + index = (index + 5) % 8; + if (index == 0) + i++; + } + base32.append(base32Chars.charAt(digit)); + } + + return base32.toString(); + } + + /** + * Decodes the given Base32 String to a raw byte array. + * + * @param base32 + * @return Decoded base32 String as a raw byte array. + */ + static public byte[] decode(final String base32) { + int i, index, lookup, offset, digit; + byte[] bytes = new byte[base32.length() * 5 / 8]; + + for (i = 0, index = 0, offset = 0; i < base32.length(); i++) { + lookup = base32.charAt(i) - '0'; + + /* Skip chars outside the lookup table */ + if (lookup < 0 || lookup >= base32Lookup.length) { + continue; + } + + digit = base32Lookup[lookup]; + + /* If this digit is not in the table, ignore it */ + if (digit == 0xFF) { + continue; + } + + if (index <= 3) { + index = (index + 5) % 8; + if (index == 0) { + bytes[offset] |= digit; + offset++; + if (offset >= bytes.length) + break; + } else { + bytes[offset] |= digit << (8 - index); + } + } else { + index = (index + 5) % 8; + bytes[offset] |= (digit >>> index); + offset++; + + if (offset >= bytes.length) { + break; + } + bytes[offset] |= digit << (8 - index); + } + } + return bytes; + } + + /** For testing, take a command-line argument in Base32, decode, print in hex, + * encode, print + * + * @param args + */ + static public void main(String[] args) { + if (args.length == 0) { + System.out.println("Supply a Base32-encoded argument."); + return; + } + System.out.println(" Original: " + args[0]); + byte[] decoded = Base32.decode(args[0]); + System.out.print(" Hex: "); + for (int i = 0; i < decoded.length; i++) { + int b = decoded[i]; + if (b < 0) { + b += 256; + } + System.out.print((Integer.toHexString(b + 256)).substring(1)); + } + System.out.println(); + System.out.println("Reencoded: " + Base32.encode(decoded)); + } +} From dbkr at freenetproject.org Fri Jun 23 12:50:46 2006 From: dbkr at freenetproject.org (dbkr at freenetproject.org) Date: Fri, 23 Jun 2006 12:50:46 +0000 (UTC) Subject: [Freemail] r9364 - in trunk/apps/Freemail/src/freemail: . fcp Message-ID: <20060623125046.C8B8F9C811@emu.freenetproject.org> Author: dbkr Date: 2006-06-23 12:50:43 +0000 (Fri, 23 Jun 2006) New Revision: 9364 Modified: trunk/apps/Freemail/src/freemail/MailSite.java trunk/apps/Freemail/src/freemail/MessageSender.java trunk/apps/Freemail/src/freemail/OutboundContact.java trunk/apps/Freemail/src/freemail/fcp/HighLevelFCPClient.java Log: Fix issue with slot inserting. Improve outbound contact initialisation. Modified: trunk/apps/Freemail/src/freemail/MailSite.java =================================================================== --- trunk/apps/Freemail/src/freemail/MailSite.java 2006-06-23 12:48:04 UTC (rev 9363) +++ trunk/apps/Freemail/src/freemail/MailSite.java 2006-06-23 12:50:43 UTC (rev 9364) @@ -53,8 +53,6 @@ mailpage = mailsite_s.getBytes(); } - ByteArrayInputStream bis = new ByteArrayInputStream(mailpage); - String key = this.accprops.get("mailsite.privkey"); if (key == null) return -1; @@ -68,7 +66,7 @@ minslot = 1; } - int actualslot = cli.SlotInsert(bis, key, 1, "/"+MAILPAGE); + int actualslot = cli.SlotInsert(mailpage, key, 1, "/"+MAILPAGE); this.accprops.put("mailsite.slot", new Integer(actualslot).toString()); Modified: trunk/apps/Freemail/src/freemail/MessageSender.java =================================================================== --- trunk/apps/Freemail/src/freemail/MessageSender.java 2006-06-23 12:48:04 UTC (rev 9363) +++ trunk/apps/Freemail/src/freemail/MessageSender.java 2006-06-23 12:50:43 UTC (rev 9364) @@ -119,14 +119,7 @@ if (addr.domain.equalsIgnoreCase("nim.freemail")) { HighLevelFCPClient cli = new HighLevelFCPClient(); - FileInputStream fis; - try { - fis = new FileInputStream(msg); - } catch (FileNotFoundException fnfe) { - return; - } - - if (cli.SlotInsert(fis, NIM_KEY_PREFIX+addr.user+"-"+DateStringFactory.getKeyString(), 1, "") > -1) { + if (cli.SlotInsert(msg, NIM_KEY_PREFIX+addr.user+"-"+DateStringFactory.getKeyString(), 1, "") > -1) { msg.delete(); } } else { @@ -146,7 +139,7 @@ return true; } boolean ready; - if (!ct.exists()) { + if (!ct.ready()) { try { System.out.println("initing outbound contact"); ready = ct.init(); Modified: trunk/apps/Freemail/src/freemail/OutboundContact.java =================================================================== --- trunk/apps/Freemail/src/freemail/OutboundContact.java 2006-06-23 12:48:04 UTC (rev 9363) +++ trunk/apps/Freemail/src/freemail/OutboundContact.java 2006-06-23 12:50:43 UTC (rev 9364) @@ -24,7 +24,7 @@ private final File accdir; private final EmailAddress address; private static final String OUTBOUND_DIR = "outbound"; - + public OutboundContact(File accdir, EmailAddress a) throws BadFreemailAddressException { this.address = a; @@ -46,44 +46,88 @@ } } - public boolean exists() { - return this.contactfile.exists(); + /* + * Whether or not we're ready to communicate with the other party + */ + public boolean ready() { + if (!this.contactfile.exists()) return false; + + String status = this.contactfile.get("status"); + if (status == null) return false; + // don't wait for an ack before inserting the message, but be ready to insert it again + // if the ack never arrives + if (status.equals("rts-sent")) return true; + return false; } - /** - * Set up an outbound contact. Fetch the mailsite, generate a new SSK keypair and post an RTS message to the appropriate KSK. - * Will block for mailsite retrieval and RTS insertion - * - * @return true for success - */ - public boolean init() throws OutboundContactFatalException { - HighLevelFCPClient cli = new HighLevelFCPClient(); + private SSKKeyPair getCommKeyPair() { + SSKKeyPair ssk = new SSKKeyPair(); - System.out.println("Attempting to fetch "+this.getMailpageKey()); - File mailsite_file = cli.fetch(this.getMailpageKey()); + ssk.pubkey = this.contactfile.get("commssk.privkey"); + ssk.privkey = this.contactfile.get("commssk.pubkey"); - if (mailsite_file == null) { - // TODO: Give up for now, try later, count number of and limit attempts - System.out.println("Failed to retrieve mailsite for "+this.address); - return false; + + if (ssk.pubkey == null || ssk.privkey == null) { + HighLevelFCPClient cli = new HighLevelFCPClient(); + ssk = cli.makeSSK(); + + this.contactfile.put("commssk.privkey", ssk.privkey); + this.contactfile.put("commssk.pubkey", ssk.pubkey); + // we've just generated a new SSK, so the other party definately doesn't know about it + this.contactfile.put("status", "notsent"); + } else { + ssk = new SSKKeyPair(); } - System.out.println("got mailsite"); + return ssk; + } + + private RSAKeyParameters getPubKey() throws OutboundContactFatalException { + String mod_str = this.contactfile.get("asymkey.modulus"); + String exp_str = this.contactfile.get("asymkey.pubexponent"); - PropsFile mailsite = new PropsFile(mailsite_file); + if (mod_str == null || exp_str == null) { + // we don't have their mailsite - fetch it + if (this.fetchMailSite()) { + mod_str = this.contactfile.get("asymkey.modulus"); + exp_str = this.contactfile.get("asymkey.pubexponent"); + + // must be present now, or exception would have been thrown + } else { + return null; + } + } - String rtskey = mailsite.get("rtsksk"); - String keymod_str = mailsite.get("asymkey.modulus"); - String keyexp_str = mailsite.get("asymkey.pubexponent"); + return new RSAKeyParameters(false, new BigInteger(mod_str, 10), new BigInteger(exp_str, 10)); + } + + private String getRtsKsk() throws OutboundContactFatalException { + String rtsksk = this.contactfile.get("rtsksk"); - if (rtskey == null || keymod_str == null || keyexp_str == null) { - // TODO: More failure mechanisms - this is fatal. - System.out.println("Mailsite for "+this.address+" does not contain all necessary iformation!"); - throw new OutboundContactFatalException("Mailsite for "+this.address+" does not contain all necessary iformation!"); + if (rtsksk == null) { + // get it from their mailsite + if (!this.fetchMailSite()) return null; + + rtsksk = this.contactfile.get("rtsksk"); } - mailsite_file.delete(); - SSKKeyPair ssk = cli.makeSSK(); + return rtsksk; + } + + /** + * Set up an outbound contact. Fetch the mailsite, generate a new SSK keypair and post an RTS message to the appropriate KSK. + * Will block for mailsite retrieval and RTS insertion + * + * @return true for success + */ + public boolean init() throws OutboundContactFatalException { + // try to fetch get all necessary info. will fetch mailsite / generate new keys if necessary + SSKKeyPair ssk = this.getCommKeyPair(); + if (ssk == null) return false; + RSAKeyParameters their_pub_key = this.getPubKey(); + if (their_pub_key == null) return false; + String rtsksk = this.getRtsKsk(); + if (rtsksk == null) return false; StringBuffer rtsmessage = new StringBuffer(); @@ -137,13 +181,6 @@ } // now encrypt it - - BigInteger keymodulus = new BigInteger(keymod_str, 10); - BigInteger keyexponent = new BigInteger(keyexp_str, 10); - - // is not private - RSAKeyParameters their_pub_key = new RSAKeyParameters(false, keymodulus, keyexponent); - AsymmetricBlockCipher enccipher = new RSAEngine(); enccipher.init(true, their_pub_key); byte[] encmsg = null; @@ -155,17 +192,49 @@ } // insert it! - ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + HighLevelFCPClient cli = new HighLevelFCPClient(); + if (cli.SlotInsert(encmsg, "KSK@"+rtsksk+"-"+DateStringFactory.getKeyString(), 1, "") < 0) { + return false; + } - if (cli.SlotInsert(bis, "KSK@"+rtskey+"-"+DateStringFactory.getKeyString(), 1, "") < 0) { + // remember the fact that we have successfully inserted the rts + this.contactfile.put("status", "rts-sent"); + + return true; + } + + private boolean fetchMailSite() throws OutboundContactFatalException { + HighLevelFCPClient cli = new HighLevelFCPClient(); + + System.out.println("Attempting to fetch "+this.getMailpageKey()); + File mailsite_file = cli.fetch(this.getMailpageKey()); + + if (mailsite_file == null) { + // TODO: Give up for now, try later, count number of and limit attempts + System.out.println("Failed to retrieve mailsite for "+this.address); return false; } - // now we can create a new outbound contact file - this.contactfile.put("rtskey", rtskey); + System.out.println("got mailsite"); + + PropsFile mailsite = new PropsFile(mailsite_file); + + String rtsksk = mailsite.get("rtsksk"); + String keymod_str = mailsite.get("asymkey.modulus"); + String keyexp_str = mailsite.get("asymkey.pubexponent"); + + mailsite_file.delete(); + + if (rtsksk == null || keymod_str == null || keyexp_str == null) { + // TODO: More failure mechanisms - this is fatal. + System.out.println("Mailsite for "+this.address+" does not contain all necessary iformation!"); + throw new OutboundContactFatalException("Mailsite for "+this.address+" does not contain all necessary iformation!"); + } + + // add this to a new outbound contact file + this.contactfile.put("rtsksk", rtsksk); this.contactfile.put("asymkey.modulus", keymod_str); - this.contactfile.put("asymnkey.exponent", keyexp_str); - this.contactfile.put("commssk", ssk.privkey); + this.contactfile.put("asymkey.pubexponent", keyexp_str); return true; } Modified: trunk/apps/Freemail/src/freemail/fcp/HighLevelFCPClient.java =================================================================== --- trunk/apps/Freemail/src/freemail/fcp/HighLevelFCPClient.java 2006-06-23 12:48:04 UTC (rev 9363) +++ trunk/apps/Freemail/src/freemail/fcp/HighLevelFCPClient.java 2006-06-23 12:50:43 UTC (rev 9364) @@ -2,6 +2,9 @@ import java.io.File; import java.io.InputStream; +import java.io.FileInputStream; +import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; import freemail.Freemail; @@ -122,15 +125,22 @@ } } - public int SlotInsert(InputStream data, String basekey, int minslot, String suffix) { + public int SlotInsert(File data, String basekey, int minslot, String suffix) { int slot = minslot; boolean carryon = true; + FileInputStream fis; while (carryon) { System.out.println("trying slotinsert to "+basekey+"-"+slot+suffix); + try { + fis = new FileInputStream(data); + } catch (FileNotFoundException fnfe) { + return -1; + } + FCPInsertErrorMessage emsg; try { - emsg = this.put(data, basekey+"-"+slot+suffix); + emsg = this.put(fis, basekey+"-"+slot+suffix); } catch (FCPBadFileException bfe) { return -1; } @@ -149,6 +159,36 @@ return -1; } + public int SlotInsert(byte[] data, String basekey, int minslot, String suffix) { + int slot = minslot; + boolean carryon = true; + ByteArrayInputStream bis; + while (carryon) { + System.out.println("trying slotinsert to "+basekey+"-"+slot+suffix); + + bis = new ByteArrayInputStream(data); + + FCPInsertErrorMessage emsg; + try { + emsg = this.put(bis, basekey+"-"+slot+suffix); + } catch (FCPBadFileException bfe) { + return -1; + } + if (emsg == null) { + System.out.println("insert successful"); + return slot; + } else if (emsg.errorcode == FCPInsertErrorMessage.COLLISION) { + slot++; + System.out.println("collision"); + } else { + System.out.println("nope - error code is "+emsg.errorcode); + // try again later + return -1; + } + } + return -1; + } + public void requestStatus(FCPMessage msg) { } From dbkr at freenetproject.org Fri Jun 23 14:01:41 2006 From: dbkr at freenetproject.org (dbkr at freenetproject.org) Date: Fri, 23 Jun 2006 14:01:41 +0000 (UTC) Subject: [Freemail] r9366 - in trunk/apps/Freemail/src/freemail: . utils Message-ID: <20060623140141.DD3B19BD15@emu.freenetproject.org> Author: dbkr Date: 2006-06-23 14:01:35 +0000 (Fri, 23 Jun 2006) New Revision: 9366 Added: trunk/apps/Freemail/src/freemail/utils/ChainedAsymmetricBlockCipher.java Modified: trunk/apps/Freemail/src/freemail/OutboundContact.java Log: Chain the RSA blocks together in RTS messages Modified: trunk/apps/Freemail/src/freemail/OutboundContact.java =================================================================== --- trunk/apps/Freemail/src/freemail/OutboundContact.java 2006-06-23 13:12:48 UTC (rev 9365) +++ trunk/apps/Freemail/src/freemail/OutboundContact.java 2006-06-23 14:01:35 UTC (rev 9366) @@ -11,6 +11,7 @@ import freemail.utils.EmailAddress; import freemail.utils.PropsFile; import freemail.utils.DateStringFactory; +import freemail.utils.ChainedAsymmetricBlockCipher; import freemail.fcp.HighLevelFCPClient; import freemail.fcp.SSKKeyPair; @@ -131,6 +132,10 @@ StringBuffer rtsmessage = new StringBuffer(); + // the public part of the SSK keypair we generated + // put this first to avoid messages with the same first block, since we don't (currently) use CBC + rtsmessage.append("commssk="+ssk.pubkey+"\r\n"); + rtsmessage.append("messagetype=rts\r\n"); // must include who this RTS is to, otherwise we're vulnerable to surruptitious forwarding @@ -141,9 +146,6 @@ rtsmessage.append("mailsite="+our_mailsite_uri+"\r\n"); - // the public part of the SSK keypair we generated - rtsmessage.append("commssk="+ssk.pubkey+"\r\n"); - rtsmessage.append("\r\n"); // sign the message @@ -185,7 +187,7 @@ enccipher.init(true, their_pub_key); byte[] encmsg = null; try { - encmsg = sigcipher.processBlock(bos.toByteArray(), 0, bos.toByteArray().length); + encmsg = ChainedAsymmetricBlockCipher.encrypt(sigcipher, bos.toByteArray()); } catch (InvalidCipherTextException e) { e.printStackTrace(); return false; Added: trunk/apps/Freemail/src/freemail/utils/ChainedAsymmetricBlockCipher.java =================================================================== --- trunk/apps/Freemail/src/freemail/utils/ChainedAsymmetricBlockCipher.java 2006-06-23 13:12:48 UTC (rev 9365) +++ trunk/apps/Freemail/src/freemail/utils/ChainedAsymmetricBlockCipher.java 2006-06-23 14:01:35 UTC (rev 9366) @@ -0,0 +1,35 @@ +package freemail.utils; + +import java.io.IOException; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; + +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.InvalidCipherTextException; + +/* + * A wrapper around AsymmetricBlockCipher to chain several blocks together. + * This class just concatentates them, ie. without CBC or suchlike. + * + * Clearly this is intended for use with small amounts of data, where it's not worthwhile encrypting a symmetric key and using that + */ +public class ChainedAsymmetricBlockCipher { + public static byte[] encrypt(AsymmetricBlockCipher cipher, byte[] in) throws InvalidCipherTextException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ByteArrayInputStream bis = new ByteArrayInputStream(in); + + int read; + byte[] buf = new byte[cipher.getInputBlockSize()]; + + while ( (read = bis.read(buf, 0, cipher.getInputBlockSize())) > 0) { + byte[] obuf = cipher.processBlock(buf, 0, read); + try { + bos.write(obuf); + } catch (IOException ioe) { + throw new InvalidCipherTextException(); + } + } + + return bos.toByteArray(); + } +} From dbkr at freenetproject.org Mon Jun 26 10:43:01 2006 From: dbkr at freenetproject.org (Dave Baker) Date: Mon, 26 Jun 2006 11:43:01 +0100 Subject: [Freemail] SoC Mid Term Evaluation Message-ID: <200606261143.02289.dbkr@freenetproject.org> Hi, Firstly, sorry I haven't been quite as vocal as I could have. As it seems with most people, our term hasn't finished yet, and I've also had to sort our where I'm living afterwards (notwithstanding the fact that both our Internet connection and electricity both decided to take a day off last week). It will all be over soon and I'll be able to concentrate on important things! So far, the progress on Freemail is: General: - Testing the initial nim-mode with rguerra. Verified to work between our two systems. Also fixed a bug with temporary files ending up on different partitions. Protocol & Crypto: - Create and store all the information to go in the mailsites - Automatically upload the mailsites - Lots of research on public key crypto. Investigation of what's best to use in Java as far as this goes. Quite a lot of fiddling with with a test program trying bouncycastle and GNU crypto's APIs, as well as native Java crypto. Attempts with both using the API directly and through the JCE. Never got the JCE to work simply and reliably with external crypto providers (native Java 1.4 is very lacking in public key algorithms) - Imported bouncycastle's lightweight crypto API, which appears to be the simplest and most robust solution that maintains compatibility with Java 1.4. - Decided on RSA as an encryption algorithm (proven, now freely available and has an implementation at least in Java 1.5) - Got RSA signing and encryption working in a test program - Read all about naive sign and encrypt, surreptitious forwarding, and the like. - Start on the sending of RTS messages. Now mostly done, and just needs some debugging. Made resilient to network failures and designed to expect that inserting a message may take several attempts. - Some debate on Freemail addresses. No conclusion on these as of yet (see tech list.) What's still to do: - Write full protocol documentation, specifying order, format and encryption of messages. Build in acknowledgement messages since simulations suggest that not every messages inserted will be retrievable between a single sender and recipient. - Finish the protocol - RTS Messages, CTS messages, the emails, and ACKs. - Better IMAP support - Simple and intuitive front end for users. Potentally webmail for those less confident setting up an email client with nonstandard ports / multiple accounts, in which case may consider integration with the node / plugin in order to avoid having two separate web interfaces. So that's the progress so far. I'm hoping to get a fair amount done this week, although I'll be moving house on the Friday and hence without an Internet connection for however long an ADSL activation period is these days. Dave