Author: dbkr
Date: 2006-07-26 12:18:56 +0000 (Wed, 26 Jul 2006)
New Revision: 9768
Modified:
trunk/apps/Freemail/docs/spec/spec.tex
trunk/apps/Freemail/src/freemail/AccountManager.java
trunk/apps/Freemail/src/freemail/AckProcrastinator.java
trunk/apps/Freemail/src/freemail/Freemail.java
trunk/apps/Freemail/src/freemail/MessageSender.java
trunk/apps/Freemail/src/freemail/OutboundContact.java
trunk/apps/Freemail/src/freemail/RTSFetcher.java
trunk/apps/Freemail/src/freemail/SingleAccountWatcher.java
Log:
Sort messages into the right outboud contact directory ready for insertion.
Also change CTS messages to go on the ACK SSK and some changes to the spec
pointed out by toad.
Modified: trunk/apps/Freemail/docs/spec/spec.tex
===================================================================
--- trunk/apps/Freemail/docs/spec/spec.tex 2006-07-26 02:10:41 UTC (rev
9767)
+++ trunk/apps/Freemail/docs/spec/spec.tex 2006-07-26 12:18:56 UTC (rev
9768)
@@ -10,7 +10,7 @@
\subsection{What is Freemail}
Freemail is an email-like messaging system that transports all messages over
Freenet 0.7 in order to achieve anonymity and censorship-resilience. Its
protocol is designed to be as resistant as possible to attacks such as message
floods and denial of service. Unlike traditional email, it makes it extremely
difficult for others to discover what you have been communicating, who you have
been communicating with, and even that you have been communicating at all.
-Freemail uses IMAP and SMTP to interface with standard email clients, making
taking advantage of interfaces that people are already accustomed to.
+Freemail uses IMAP and SMTP to interface with standard email clients, taking
advantage of interfaces that people are already accustomed to.
\section{Channel Setup}
\subsection{Mailsites}
@@ -18,7 +18,7 @@
All Freemail users have an Freemail address, which one may give out to others
in order to allow them to contact you. From this Freemail address, it is
possible to derive a Freenet SSK URI. This is the user's 'mailsite'.
-A Freemail address comprises an arbitrary text string, followed by an '@'
character. Following this is the mailsite address encoded in base 32 - that is,
a valid Freenet uri that points to the mailsite. The URI must be base 32
encoded in order to make the address case insensitive to maintain compatibility
with traditional email clients. The string '.freenet' is appended to the whole
address. An example Freemail address follows:
+A Freemail address comprises an arbitrary text string, followed by an '@'
character. Following this is the mailsite address encoded in base 32 - that is,
a valid Freenet uri that points to the mailsite. The URI must be base 32
encoded in order to make the address case insensitive to maintain compatibility
with traditional email clients. The string '.freemail' is appended to the whole
address. An example Freemail address follows:
bob at
JRHXORDZIQZFIVDJ\-GBDHEVLDO43TATSCGJST\-C3CXJ5NHSTL6NM2HQ\-NDONNXG632COJFD\-ALBVKJ2HS5LLNQ3E\-OLKQOFKUKNCMG5F\-GYODBJBY\-VSYLPOVZ\-WMV3GOBRHGOLVMJ\-ZUSY3DOY2CY\-QKRIFBECQKF.freemail
@@ -46,6 +46,7 @@
\item messagetype - This should be 'rts', to indicate that this message is an
RTS.
\item to - The Freenet URI that appears encoded in Bob's Freemail address.
This is necessary in order to prevent surreptitious forwarding to support the
encryption explained later.
\item mailsite - Alice's mailsite URI
+\item initialslot - 256 bits of random data, encoded in base32. Used to form
the message slots on the commssk.
\end{itemize}
Following the last data item, there are two carriage-return-line-feeds,
followed by Alice's signature. This is the SHA-256 hash of the message RSA
encrypted with Alice's private key, included as raw bytes.
@@ -65,21 +66,19 @@
Bob then records the value of the 'commssk' key so that he can poll this SSK
for messages periodically.
-Before doing so, Bob inserts some data to the value of 'ackssk', followed by
the string 'cts'. That is, he inserts to "SSK@<long SSK key base>/cts". The
data he inserts is irrelevant - the presence of the key is sufficient to prove
to Alice that he has received the message. This completes Bob's part of the
channel setup procedure.
+Before doing so, Bob inserts some data to the value of 'ackssk', followed by
the string 'cts'. That is, he inserts to "SSK@$<$long SSK key base$>$/cts". The
data he inserts is irrelevant - the presence of the key is sufficient to prove
to Alice that he has received the message. This completes Bob's part of the
channel setup procedure.
-This message contains no valuable information and so does not need to be
encrypted. It also does not need to be signed since only Alice and Bob know the
KSK to which it must be inserted, so Alice knows that Bob must have inserted
the message. The KSK that Bob inserts this message to tells Alice what RTS it
relates to if there is any ambiguity.
-
Alice should check periodically for the insertion of this CTS message. If it
does not arrive, Alice should re-send the RTS message. The client may try
several times before declaring the message undeliverable.
\section{Message Exchange}
\subsection{The Messages}
Once Bob has inserted this CTS message, he begins polling for messages on keys
derived from the value of the 'commssk' key which he obtained from the RTS
message. These keys are the value of 'commssk' a 256 bit base32 encoded hash
appended. The hash is initially the value of 'initialslot' in the RTS message.
Each subsequent slot's hash is the SHA-256 digest of the previous slot's hash,
forming a seqence of message slots. This hash sequence gives forward security,
provided that clients destroy values of the hash, and 'initialslot' once they
have been used. Alice should insert a new message to the first slot to which
inserting does not causes a collision. Formulaically, Bob first polls the key:
-SSK@<SSK key base>/<initialslot>
+SSK@$<$SSK key base$>$/$<$initialslot$>$
Once Bob has successfully retrieved this key, he begins to periodically
request the key:
-SSK@<SSK key base>/<H(initialslot)>
+SSK@$<$SSK key base$>$/$<$H(initialslot)$>$
It is recommended that clients poll several messages ahead rather than just
the immediately next message, since simulations suggest that it is possible for
single keys not to be retrievable in this kind of circumstance. So for example,
once Bob has sent his CTS messages, he should start polling for the keys: \\
\\
Modified: trunk/apps/Freemail/src/freemail/AccountManager.java
===================================================================
--- trunk/apps/Freemail/src/freemail/AccountManager.java 2006-07-26
02:10:41 UTC (rev 9767)
+++ trunk/apps/Freemail/src/freemail/AccountManager.java 2006-07-26
12:18:56 UTC (rev 9768)
@@ -7,6 +7,7 @@
import java.util.Random;
import java.security.SecureRandom;
import java.math.BigInteger;
+import java.net.MalformedURLException;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
@@ -17,9 +18,11 @@
import org.archive.util.Base32;
+import freemail.FreenetURI;
import freemail.fcp.HighLevelFCPClient;
import freemail.fcp.SSKKeyPair;
import freemail.utils.PropsFile;
+import freemail.utils.EmailAddress;
public class AccountManager {
public static final String DATADIR = "data";
@@ -96,6 +99,24 @@
return accfile;
}
+ public static EmailAddress getFreemailAddress(File accdir) {
+ PropsFile accfile = getAccountFile(accdir);
+
+ return getFreemailAddress(accfile);
+ }
+
+ public static EmailAddress getFreemailAddress(PropsFile accfile) {
+ FreenetURI mailsite;
+ try {
+ mailsite = new
FreenetURI(accfile.get("mailsite.pubkey"));
+ } catch (MalformedURLException mfue) {
+ System.out.println("Warning: Couldn't fetch mailsite
public key from account file! Your account file is probably corrupt.");
+ return null;
+ }
+
+ return new
EmailAddress("anything@"+Base32.encode(mailsite.getKeyBody().getBytes())+".freemail");
+ }
+
public static RSAKeyParameters getPrivateKey(File accdir) {
PropsFile props = getAccountFile(accdir);
@@ -146,12 +167,7 @@
}
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:
<anything>@"+base32body+".freemail");
+ System.out.println("Your Freemail address is:
"+getFreemailAddress(accfile));
} catch (IOException ioe) {
System.out.println("Couldn't create mailsite key file!
"+ioe.getMessage());
}
Modified: trunk/apps/Freemail/src/freemail/AckProcrastinator.java
===================================================================
--- trunk/apps/Freemail/src/freemail/AckProcrastinator.java 2006-07-26
02:10:41 UTC (rev 9767)
+++ trunk/apps/Freemail/src/freemail/AckProcrastinator.java 2006-07-26
12:18:56 UTC (rev 9768)
@@ -5,6 +5,7 @@
import java.io.ByteArrayInputStream;
import java.lang.InterruptedException;
import java.util.Random;
+import java.security.SecureRandom;
import freemail.utils.PropsFile;
import freemail.fcp.HighLevelFCPClient;
@@ -18,6 +19,7 @@
*/
public class AckProcrastinator implements Runnable {
private static final long MAX_DELAY = 12 * 60 * 60 * 1000;
+ private static final int RANDOM_ACK_SIZE = 512;
private static File ackdir;
private static Random rnd;
@@ -51,17 +53,26 @@
String s_it = ack.get("nominalInsertTime");
String key = ack.get("key");
- String data = ack.get("data");
- if (s_it == null || key == null || data ==
null) {
+ String s_data = ack.get("data");
+ if (s_it == null || key == null) {
acks[i].delete();
continue;
}
+ byte[] data;
+ if (s_data == null) {
+ SecureRandom rnd = new SecureRandom();
+
+ data = new byte[RANDOM_ACK_SIZE];
+ rnd.nextBytes(data);
+ } else {
+ data = s_data.getBytes();
+ }
long instime = Long.parseLong(s_it);
if (instime < System.currentTimeMillis()) {
HighLevelFCPClient fcpcli = new
HighLevelFCPClient();
- ByteArrayInputStream bis = new
ByteArrayInputStream(data.getBytes());
+ ByteArrayInputStream bis = new
ByteArrayInputStream(data);
System.out.println("Inserting ack to
"+key);
try {
@@ -84,6 +95,12 @@
}
}
+ /** As put(String key, String data), but insert random data
+ */
+ public static synchronized void put(String key) {
+ put(key, null);
+ }
+
/** Insert some data at some random point in the future, but ideally
before
* 'by' (in milliseconds).
*/
@@ -95,7 +112,8 @@
PropsFile ackfile= new
PropsFile(File.createTempFile("delayed-ack", "", getAckDir()));
ackfile.put("key", key);
- ackfile.put("data", data);
+ if (data != null)
+ ackfile.put("data", data);
ackfile.put("by", Long.toString(by));
long insertTime = System.currentTimeMillis();
Modified: trunk/apps/Freemail/src/freemail/Freemail.java
===================================================================
--- trunk/apps/Freemail/src/freemail/Freemail.java 2006-07-26 02:10:41 UTC
(rev 9767)
+++ trunk/apps/Freemail/src/freemail/Freemail.java 2006-07-26 12:18:56 UTC
(rev 9768)
@@ -86,7 +86,7 @@
// for now
AccountManager.setupNIM(account);
System.out.println("Account created for
"+account+". You may now set a password with --passwd <password>");
- System.out.println("For the time being, you
address is "+account+"@nim.freemail");
+ //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());
}
Modified: trunk/apps/Freemail/src/freemail/MessageSender.java
===================================================================
--- trunk/apps/Freemail/src/freemail/MessageSender.java 2006-07-26 02:10:41 UTC
(rev 9767)
+++ trunk/apps/Freemail/src/freemail/MessageSender.java 2006-07-26 12:18:56 UTC
(rev 9768)
@@ -95,6 +95,9 @@
File outbounddir = new File(contactsdir,
OutboundContact.OUTBOUND_DIR);
+ if (!outbounddir.exists())
+ outbounddir.mkdir();
+
File[] contacts = outbounddir.listFiles();
int i;
@@ -171,6 +174,6 @@
if (!ready) return false;
- return false;
+ return ct.sendMessage(msg);
}
}
Modified: trunk/apps/Freemail/src/freemail/OutboundContact.java
===================================================================
--- trunk/apps/Freemail/src/freemail/OutboundContact.java 2006-07-26
02:10:41 UTC (rev 9767)
+++ trunk/apps/Freemail/src/freemail/OutboundContact.java 2006-07-26
12:18:56 UTC (rev 9768)
@@ -1,10 +1,12 @@
package freemail;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
-import java.util.Random;
+import java.security.SecureRandom;
import freemail.utils.EmailAddress;
import freemail.utils.PropsFile;
@@ -23,14 +25,16 @@
public class OutboundContact {
public static final String OUTBOUND_DIR = "outbound";
+ private static final String OUTBOX_DIR = "outbox";
private final PropsFile contactfile;
private final File accdir;
+ private final File ctoutbox;
private final EmailAddress address;
- private static final int CTS_KSK_LENGTH = 32;
// how long to wait for a CTS before sending the message again
// slightly over 24 hours since some people are likley to fire Freemail
// up and roughly the same time every day
private static final long CTS_WAIT_TIME = 26 * 60 * 60 * 1000;
+ private static final String PROPSFILE_NAME = "props";
public OutboundContact(File accdir, EmailAddress a) throws
BadFreemailAddressException {
this.address = a;
@@ -49,7 +53,15 @@
if (!outbounddir.exists())
outbounddir.mkdir();
- this.contactfile = new PropsFile(new File(outbounddir,
this.address.getMailsiteKey()));
+ File obctdir = new File(outbounddir,
this.address.getMailsiteKey());
+
+ if (!obctdir.exists())
+ obctdir.mkdir();
+
+ this.contactfile = new PropsFile(new File(obctdir,
PROPSFILE_NAME));
+ this.ctoutbox = new File(obctdir, OUTBOX_DIR);
+ if (!this.ctoutbox.exists())
+ this.ctoutbox.mkdir();
}
}
@@ -58,7 +70,10 @@
this.address = new EmailAddress();
this.address.domain =
Base32.encode(ctfile.getName().getBytes())+".freemail";
- this.contactfile = new PropsFile(ctfile);
+ this.contactfile = new PropsFile(new File(ctfile,
PROPSFILE_NAME));
+ this.ctoutbox = new File(accdir, OUTBOX_DIR);
+ if (!this.ctoutbox.exists())
+ this.ctoutbox.mkdir();
}
public void checkCTS() throws OutboundContactFatalException {
@@ -72,14 +87,15 @@
} else if (status.equals("rts-sent")) {
// poll for the CTS message
- String ctsksk = this.contactfile.get("ctsksk");
- if (ctsksk == null) {
+ String ctskey = this.contactfile.get("ackssk.pubkey");
+ if (ctskey == null) {
this.init();
}
+ ctskey += "ack";
HighLevelFCPClient fcpcli = new HighLevelFCPClient();
- File cts = fcpcli.fetch(ctsksk);
+ File cts = fcpcli.fetch(ctskey);
if (cts == null) {
// haven't got the CTS message. should we give
up yet?
@@ -118,8 +134,8 @@
private SSKKeyPair getCommKeyPair() {
SSKKeyPair ssk = new SSKKeyPair();
- ssk.pubkey = this.contactfile.get("commssk.privkey");
- ssk.privkey = this.contactfile.get("commssk.pubkey");
+ ssk.pubkey = this.contactfile.get("commssk.pubkey");
+ ssk.privkey = this.contactfile.get("commssk.privkey");
if (ssk.pubkey == null || ssk.privkey == null) {
@@ -138,8 +154,8 @@
private SSKKeyPair getAckKeyPair() {
SSKKeyPair ssk = new SSKKeyPair();
- ssk.pubkey = this.contactfile.get("ackssk.privkey");
- ssk.privkey = this.contactfile.get("ackssk.pubkey");
+ ssk.pubkey = this.contactfile.get("ackssk.pubkey");
+ ssk.privkey = this.contactfile.get("ackssk.privkey");
if (ssk.pubkey == null || ssk.privkey == null) {
@@ -185,19 +201,20 @@
return rtsksk;
}
- private String getCTSKSK() {
- String retval = this.contactfile.get("ctsksk");
+ private String getInitialSlot() {
+ String retval = this.contactfile.get("initialslot");
if (retval != null) return retval;
- Random rnd = new Random();
- retval = new String("KSK@");
-
- int i;
- for (i = 0; i < CTS_KSK_LENGTH; i++) {
- retval += (char)(rnd.nextInt(25) + (int)'a');
- }
- return retval;
+ SecureRandom rnd = new SecureRandom();
+ SHA256Digest sha256 = new SHA256Digest();
+ byte[] buf = new byte[sha256.getDigestSize()];
+
+ rnd.nextBytes(buf);
+
+ this.contactfile.put("initialslot", Base32.encode(buf));
+
+ return Base32.encode(buf);
}
/**
@@ -224,10 +241,10 @@
rtsmessage.append("ackssk="+ackssk.privkey+"\r\n");
- String ctsksk = this.getCTSKSK();
+ String initialslot = this.getInitialSlot();
- this.contactfile.put("ctsksk", ctsksk);
- rtsmessage.append("ctsksk="+ctsksk+"\r\n");
+ this.contactfile.put("nextslot", initialslot);
+ rtsmessage.append("initialslot="+initialslot+"\r\n");
rtsmessage.append("messagetype=rts\r\n");
@@ -240,6 +257,7 @@
rtsmessage.append("mailsite="+our_mailsite_uri+"\r\n");
rtsmessage.append("\r\n");
+ System.out.println(rtsmessage.toString());
// sign the message
@@ -334,4 +352,75 @@
private String getMailpageKey() {
return
"USK@"+this.address.getMailsiteKey()+"/"+AccountManager.MAILSITE_SUFFIX+"/1/"+MailSite.MAILPAGE;
}
+
+ private String popNextSlot() {
+ String slot = this.contactfile.get("nextslot");
+ SHA256Digest sha256 = new SHA256Digest();
+ sha256.update(slot.getBytes(), 0, Base32.decode(slot).length);
+ byte[] nextslot = new byte[sha256.getDigestSize()];
+ sha256.doFinal(nextslot, 0);
+ this.contactfile.put("nextslot", Base32.encode(nextslot));
+
+ return slot;
+ }
+
+ private int popNextUid() {
+ String nextuid_s = this.contactfile.get("nextuid");
+
+ int nextuid;
+ if (nextuid_s == null)
+ nextuid = 1;
+ else
+ nextuid = Integer.parseInt(nextuid_s);
+
+ this.contactfile.put("nextuid", Integer.toString(nextuid + 1));
+ return nextuid;
+ }
+
+ public boolean sendMessage(File body) {
+ int uid = this.popNextUid();
+
+ // create a new file that contains the complete Freemail
+ // message, with Freemail headers
+ File msg;
+ FileOutputStream fos;
+ try {
+ msg = File.createTempFile("ogm", "msg",
Freemail.getTempDir());
+
+ fos = new FileOutputStream(msg);
+ } catch (IOException ioe) {
+ System.out.println("IO Error encountered whilst trying
to send message: "+ioe.getMessage()+" Will try again soon");
+ return false;
+ }
+
+ FileInputStream fis;
+ try {
+ fos.write(new String("id="+uid+"\r\n\r\n").getBytes());
+
+ fis = new FileInputStream(body);
+
+ byte[] buf = new byte[1024];
+ int read;
+ while ( (read = fis.read(buf)) > 0) {
+ fos.write(buf, 0, read);
+ }
+
+ fos.close();
+ } catch (IOException ioe) {
+ System.out.println("IO Error encountered whilst trying
to send message: "+ioe.getMessage()+" Will try again soon");
+ msg.delete();
+ return false;
+ }
+
+ String slot = this.popNextSlot();
+
+ File outbox_msg = new File(this.ctoutbox,
Integer.toString(uid)+","+slot+",notsent");
+
+ System.out.println("renaming temp file to "+outbox_msg);
+
+ if (msg.renameTo(outbox_msg))
+ return true;
+
+ return false;
+ }
}
Modified: trunk/apps/Freemail/src/freemail/RTSFetcher.java
===================================================================
--- trunk/apps/Freemail/src/freemail/RTSFetcher.java 2006-07-26 02:10:41 UTC
(rev 9767)
+++ trunk/apps/Freemail/src/freemail/RTSFetcher.java 2006-07-26 12:18:56 UTC
(rev 9768)
@@ -327,11 +327,12 @@
InboundContact ibct = new InboundContact(this.contact_dir,
their_mailsite_furi);
ibct.setProp("commssk", rtsprops.get("commssk"));
- ibct.setProp("ackssk", rtsprops.get("ackssk"));
- //ibct.setProp("ctsksk", rtsprops.get("ctsksk"));
+ String ackssk = rtsprops.get("ackssk");
+ if (!ackssk.endsWith("/")) ackssk += "/";
+ ibct.setProp("ackssk", ackssk);
// insert the cts at some point
- AckProcrastinator.put(rtsprops.get("ctsksk"),
"messagetype=cts");
+ AckProcrastinator.put(ackssk+"cts");
msfile.delete();
rtsfile.delete();
@@ -381,8 +382,8 @@
if (rts.get("mailsite") == null) {
missing.append("mailsite, ");
}
- if (rts.get("ctsksk") == null) {
- missing.append("ctsksk, ");
+ if (rts.get("initialslot") == null) {
+ missing.append("initialslot, ");
}
if (missing.length() == 0) return;
Modified: trunk/apps/Freemail/src/freemail/SingleAccountWatcher.java
===================================================================
--- trunk/apps/Freemail/src/freemail/SingleAccountWatcher.java 2006-07-26
02:10:41 UTC (rev 9767)
+++ trunk/apps/Freemail/src/freemail/SingleAccountWatcher.java 2006-07-26
12:18:56 UTC (rev 9768)
@@ -39,6 +39,9 @@
this.rtsf = new
RTSFetcher("KSK@"+this.accprops.get("rtskey")+"-", inbound_dir, accdir);
//this.mf = new MailFetcher(this.mb, inbound_dir,
Freemail.getFCPConnection());
+
+ // temporary info message until there's a nicer UI :)
+ System.out.println("Freemail address:
"+AccountManager.getFreemailAddress(accdir));
}
public void run() {