Skip to content
Snippets Groups Projects
Commit 0391c4cf authored by akwizgran's avatar akwizgran
Browse files

Merged some redundant code.

parent c316ebcf
No related branches found
No related tags found
No related merge requests found
package net.sf.briar.api.crypto; package net.sf.briar.api.crypto;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.Signature; import java.security.Signature;
...@@ -15,8 +16,8 @@ public interface CryptoComponent { ...@@ -15,8 +16,8 @@ public interface CryptoComponent {
ErasableKey deriveMacKey(byte[] secret, boolean initiator); ErasableKey deriveMacKey(byte[] secret, boolean initiator);
byte[][] deriveInitialSecrets(byte[] theirPublicKey, KeyPair ourKeyPair, byte[][] deriveInitialSecrets(byte[] ourPublicKey, byte[] theirPublicKey,
int invitationCode, boolean initiator); PrivateKey ourPrivateKey, int invitationCode, boolean initiator);
int deriveConfirmationCode(byte[] secret, boolean initiator); int deriveConfirmationCode(byte[] secret, boolean initiator);
......
...@@ -5,6 +5,7 @@ import static net.sf.briar.api.plugins.InvitationConstants.CODE_BITS; ...@@ -5,6 +5,7 @@ import static net.sf.briar.api.plugins.InvitationConstants.CODE_BITS;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.Security; import java.security.Security;
...@@ -124,16 +125,16 @@ class CryptoComponentImpl implements CryptoComponent { ...@@ -124,16 +125,16 @@ class CryptoComponentImpl implements CryptoComponent {
} }
} }
public byte[][] deriveInitialSecrets(byte[] theirPublicKey, public byte[][] deriveInitialSecrets(byte[] ourPublicKey,
KeyPair ourKeyPair, int invitationCode, boolean initiator) { byte[] theirPublicKey, PrivateKey ourPrivateKey, int invitationCode,
boolean initiator) {
try { try {
PublicKey theirPublic = keyParser.parsePublicKey(theirPublicKey); PublicKey theirPublic = keyParser.parsePublicKey(theirPublicKey);
MessageDigest messageDigest = getMessageDigest(); MessageDigest messageDigest = getMessageDigest();
byte[] ourPublicKey = ourKeyPair.getPublic().getEncoded();
byte[] ourHash = messageDigest.digest(ourPublicKey); byte[] ourHash = messageDigest.digest(ourPublicKey);
byte[] theirHash = messageDigest.digest(theirPublicKey); byte[] theirHash = messageDigest.digest(theirPublicKey);
// The initiator and responder info for the KDF are the hashes of // The initiator and responder info for the concatenation KDF are
// the corresponding public keys // the hashes of the corresponding public keys
byte[] initiatorInfo, responderInfo; byte[] initiatorInfo, responderInfo;
if(initiator) { if(initiator) {
initiatorInfo = ourHash; initiatorInfo = ourHash;
...@@ -142,20 +143,23 @@ class CryptoComponentImpl implements CryptoComponent { ...@@ -142,20 +143,23 @@ class CryptoComponentImpl implements CryptoComponent {
initiatorInfo = theirHash; initiatorInfo = theirHash;
responderInfo = ourHash; responderInfo = ourHash;
} }
// The public info for the KDF is the invitation code as a uint32 // The public info for the concatenation KDF is the invitation code
// as a uint32
byte[] publicInfo = new byte[4]; byte[] publicInfo = new byte[4];
ByteUtils.writeUint32(invitationCode, publicInfo, 0); ByteUtils.writeUint32(invitationCode, publicInfo, 0);
// The raw secret comes from the key agreement algorithm // The raw secret comes from the key agreement algorithm
KeyAgreement keyAgreement = KeyAgreement.getInstance( KeyAgreement keyAgreement = KeyAgreement.getInstance(
KEY_AGREEMENT_ALGO, PROVIDER); KEY_AGREEMENT_ALGO, PROVIDER);
keyAgreement.init(ourKeyPair.getPrivate()); keyAgreement.init(ourPrivateKey);
keyAgreement.doPhase(theirPublic, true); keyAgreement.doPhase(theirPublic, true);
byte[] rawSecret = keyAgreement.generateSecret(); byte[] rawSecret = keyAgreement.generateSecret();
// Derive the cooked secret from the raw secret // Derive the cooked secret from the raw secret using the
// concatenation KDF
byte[] cookedSecret = concatenationKdf(rawSecret, FIRST, byte[] cookedSecret = concatenationKdf(rawSecret, FIRST,
initiatorInfo, responderInfo, publicInfo); initiatorInfo, responderInfo, publicInfo);
ByteUtils.erase(rawSecret); ByteUtils.erase(rawSecret);
// Derive the incoming and outgoing secrets from the cooked secret // Derive the incoming and outgoing secrets from the cooked secret
// using the CTR mode KDF
byte[][] secrets = new byte[2][]; byte[][] secrets = new byte[2][];
secrets[0] = counterModeKdf(cookedSecret, FIRST, INITIATOR); secrets[0] = counterModeKdf(cookedSecret, FIRST, INITIATOR);
secrets[1] = counterModeKdf(cookedSecret, FIRST, RESPONDER); secrets[1] = counterModeKdf(cookedSecret, FIRST, RESPONDER);
...@@ -166,7 +170,7 @@ class CryptoComponentImpl implements CryptoComponent { ...@@ -166,7 +170,7 @@ class CryptoComponentImpl implements CryptoComponent {
} }
} }
// Key derivation function based on a hash function - see NIST SP 800-65A, // Key derivation function based on a hash function - see NIST SP 800-56A,
// section 5.8 // section 5.8
private byte[] concatenationKdf(byte[] rawSecret, byte[] label, private byte[] concatenationKdf(byte[] rawSecret, byte[] label,
byte[] initiatorInfo, byte[] responderInfo, byte[] publicInfo) { byte[] initiatorInfo, byte[] responderInfo, byte[] publicInfo) {
......
...@@ -20,6 +20,7 @@ import net.sf.briar.api.crypto.PseudoRandom; ...@@ -20,6 +20,7 @@ import net.sf.briar.api.crypto.PseudoRandom;
import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.DbException;
import net.sf.briar.api.plugins.IncomingInvitationCallback; import net.sf.briar.api.plugins.IncomingInvitationCallback;
import net.sf.briar.api.plugins.InvitationCallback;
import net.sf.briar.api.plugins.InvitationStarter; import net.sf.briar.api.plugins.InvitationStarter;
import net.sf.briar.api.plugins.OutgoingInvitationCallback; import net.sf.briar.api.plugins.OutgoingInvitationCallback;
import net.sf.briar.api.plugins.PluginExecutor; import net.sf.briar.api.plugins.PluginExecutor;
...@@ -31,7 +32,6 @@ import net.sf.briar.api.serial.Writer; ...@@ -31,7 +32,6 @@ import net.sf.briar.api.serial.Writer;
import net.sf.briar.api.serial.WriterFactory; import net.sf.briar.api.serial.WriterFactory;
import net.sf.briar.util.ByteUtils; import net.sf.briar.util.ByteUtils;
// FIXME: Refactor this class to remove duplicated code
class InvitationStarterImpl implements InvitationStarter { class InvitationStarterImpl implements InvitationStarter {
private static final String TIMED_OUT = "INVITATION_TIMED_OUT"; private static final String TIMED_OUT = "INVITATION_TIMED_OUT";
...@@ -57,122 +57,80 @@ class InvitationStarterImpl implements InvitationStarter { ...@@ -57,122 +57,80 @@ class InvitationStarterImpl implements InvitationStarter {
this.writerFactory = writerFactory; this.writerFactory = writerFactory;
} }
public void startIncomingInvitation(final DuplexPlugin plugin, public void startIncomingInvitation(DuplexPlugin plugin,
final IncomingInvitationCallback callback) { IncomingInvitationCallback callback) {
pluginExecutor.execute(new Runnable() { pluginExecutor.execute(new IncomingInvitationWorker(plugin, callback));
public void run() { }
long end = System.currentTimeMillis() + INVITATION_TIMEOUT;
// Get the invitation code from the inviter public void startOutgoingInvitation(DuplexPlugin plugin,
int code = callback.enterInvitationCode(); OutgoingInvitationCallback callback) {
if(code == -1) return; pluginExecutor.execute(new OutgoingInvitationWorker(plugin, callback));
long remaining = end - System.currentTimeMillis(); }
if(remaining <= 0) return;
// Use the invitation code to seed the PRNG private abstract class InvitationWorker implements Runnable {
PseudoRandom r = crypto.getPseudoRandom(code);
// Connect to the inviter private final DuplexPlugin plugin;
DuplexTransportConnection conn = plugin.acceptInvitation(r, private final InvitationCallback callback;
remaining); private final boolean initiator;
if(callback.isCancelled()) {
if(conn != null) conn.dispose(false, false); protected InvitationWorker(DuplexPlugin plugin,
return; InvitationCallback callback, boolean initiator) {
} this.plugin = plugin;
if(conn == null) { this.callback = callback;
callback.showFailure(TIMED_OUT); this.initiator = initiator;
return; }
}
KeyPair ourKeyPair = crypto.generateKeyPair(); protected abstract int getInvitationCode();
MessageDigest messageDigest = crypto.getMessageDigest();
byte[] ourKey = ourKeyPair.getPublic().getEncoded(); public void run() {
byte[] ourHash = messageDigest.digest(ourKey); long end = System.currentTimeMillis() + INVITATION_TIMEOUT;
byte[] theirKey, theirHash; // Use the invitation code to seed the PRNG
try { int code = getInvitationCode();
if(code == -1) return; // Cancelled
PseudoRandom r = crypto.getPseudoRandom(code);
long timeout = end - System.currentTimeMillis();
if(timeout <= 0) {
callback.showFailure(TIMED_OUT);
return;
}
// Create a connection
DuplexTransportConnection conn;
if(initiator) conn = plugin.sendInvitation(r, timeout);
else conn = plugin.acceptInvitation(r, timeout);
if(callback.isCancelled()) {
if(conn != null) conn.dispose(false, false);
return;
}
if(conn == null) {
callback.showFailure(TIMED_OUT);
return;
}
// Use an ephemeral key pair for key agreement
KeyPair ourKeyPair = crypto.generateKeyPair();
MessageDigest messageDigest = crypto.getMessageDigest();
byte[] ourKey = ourKeyPair.getPublic().getEncoded();
byte[] ourHash = messageDigest.digest(ourKey);
byte[] theirKey, theirHash;
try {
OutputStream out = conn.getOutputStream();
Writer writer = writerFactory.createWriter(out);
InputStream in = conn.getInputStream();
Reader reader = readerFactory.createReader(in);
if(initiator) {
// Send the public key hash // Send the public key hash
OutputStream out = conn.getOutputStream();
Writer writer = writerFactory.createWriter(out);
writer.writeBytes(ourHash); writer.writeBytes(ourHash);
out.flush(); out.flush();
// Receive the public key hash // Receive the public key hash
InputStream in = conn.getInputStream();
Reader reader = readerFactory.createReader(in);
theirHash = reader.readBytes(HASH_LENGTH); theirHash = reader.readBytes(HASH_LENGTH);
// Send the public key // Send the public key
writer.writeBytes(ourKey); writer.writeBytes(ourKey);
out.flush(); out.flush();
// Receive the public key // Receive the public key
theirKey = reader.readBytes(MAX_PUBLIC_KEY_LENGTH); theirKey = reader.readBytes(MAX_PUBLIC_KEY_LENGTH);
} catch(IOException e) { } else {
conn.dispose(true, false);
callback.showFailure(IO_EXCEPTION);
return;
}
conn.dispose(false, false);
if(callback.isCancelled()) return;
// Check that the received hash matches the received key
if(!Arrays.equals(theirHash, messageDigest.digest(theirKey))) {
callback.showFailure(INVALID_KEY);
return;
}
// Derive the initial shared secrets and the confirmation codes
byte[][] secrets = crypto.deriveInitialSecrets(theirKey,
ourKeyPair, code, false);
if(secrets == null) {
callback.showFailure(INVALID_KEY);
return;
}
int theirCode = crypto.deriveConfirmationCode(secrets[0], true);
int ourCode = crypto.deriveConfirmationCode(secrets[1], false);
// Compare the confirmation codes
if(callback.enterConfirmationCode(ourCode) != theirCode) {
callback.showFailure(WRONG_CODE);
ByteUtils.erase(secrets[0]);
ByteUtils.erase(secrets[1]);
return;
}
// Add the contact to the database
try {
db.addContact(secrets[0], secrets[1]);
} catch(DbException e) {
callback.showFailure(DB_EXCEPTION);
ByteUtils.erase(secrets[0]);
ByteUtils.erase(secrets[1]);
return;
}
callback.showSuccess();
}
});
}
public void startOutgoingInvitation(final DuplexPlugin plugin,
final OutgoingInvitationCallback callback) {
pluginExecutor.execute(new Runnable() {
public void run() {
// Generate an invitation code and use it to seed the PRNG
int code = crypto.getSecureRandom().nextInt(MAX_CODE + 1);
PseudoRandom r = crypto.getPseudoRandom(code);
// Connect to the invitee
DuplexTransportConnection conn = plugin.sendInvitation(r,
INVITATION_TIMEOUT);
if(callback.isCancelled()) {
if(conn != null) conn.dispose(false, false);
return;
}
if(conn == null) {
callback.showFailure(TIMED_OUT);
return;
}
KeyPair ourKeyPair = crypto.generateKeyPair();
MessageDigest messageDigest = crypto.getMessageDigest();
byte[] ourKey = ourKeyPair.getPublic().getEncoded();
byte[] ourHash = messageDigest.digest(ourKey);
byte[] theirKey, theirHash;
try {
// Receive the public key hash // Receive the public key hash
InputStream in = conn.getInputStream();
Reader reader = readerFactory.createReader(in);
theirHash = reader.readBytes(HASH_LENGTH); theirHash = reader.readBytes(HASH_LENGTH);
// Send the public key hash // Send the public key hash
OutputStream out = conn.getOutputStream();
Writer writer = writerFactory.createWriter(out);
writer.writeBytes(ourHash); writer.writeBytes(ourHash);
out.flush(); out.flush();
// Receive the public key // Receive the public key
...@@ -180,46 +138,83 @@ class InvitationStarterImpl implements InvitationStarter { ...@@ -180,46 +138,83 @@ class InvitationStarterImpl implements InvitationStarter {
// Send the public key // Send the public key
writer.writeBytes(ourKey); writer.writeBytes(ourKey);
out.flush(); out.flush();
} catch(IOException e) {
conn.dispose(true, false);
callback.showFailure(IO_EXCEPTION);
return;
}
conn.dispose(false, false);
if(callback.isCancelled()) return;
// Check that the received hash matches the received key
if(!Arrays.equals(theirHash, messageDigest.digest(theirKey))) {
callback.showFailure(INVALID_KEY);
return;
}
// Derive the shared secret and the confirmation codes
byte[][] secrets = crypto.deriveInitialSecrets(theirKey,
ourKeyPair, code, true);
if(secrets == null) {
callback.showFailure(INVALID_KEY);
return;
}
int ourCode = crypto.deriveConfirmationCode(secrets[0], true);
int theirCode = crypto.deriveConfirmationCode(secrets[1],
false);
// Compare the confirmation codes
if(callback.enterConfirmationCode(ourCode) != theirCode) {
callback.showFailure(WRONG_CODE);
ByteUtils.erase(secrets[0]);
ByteUtils.erase(secrets[1]);
return;
} }
// Add the contact to the database } catch(IOException e) {
try { conn.dispose(true, false);
db.addContact(secrets[1], secrets[0]); callback.showFailure(IO_EXCEPTION);
} catch(DbException e) { return;
callback.showFailure(DB_EXCEPTION); }
ByteUtils.erase(secrets[0]); conn.dispose(false, false);
ByteUtils.erase(secrets[1]); if(callback.isCancelled()) return;
return; // Check that the received hash matches the received key
} if(!Arrays.equals(theirHash, messageDigest.digest(theirKey))) {
callback.showSuccess(); callback.showFailure(INVALID_KEY);
return;
}
// Derive the initial shared secrets and the confirmation codes
byte[][] secrets = crypto.deriveInitialSecrets(ourKey, theirKey,
ourKeyPair.getPrivate(), code, initiator);
if(secrets == null) {
callback.showFailure(INVALID_KEY);
return;
}
int initCode = crypto.deriveConfirmationCode(secrets[0], true);
int respCode = crypto.deriveConfirmationCode(secrets[1], false);
int ourCode = initiator ? initCode : respCode;
int theirCode = initiator ? respCode : initCode;
// Compare the confirmation codes
if(callback.enterConfirmationCode(ourCode) != theirCode) {
callback.showFailure(WRONG_CODE);
ByteUtils.erase(secrets[0]);
ByteUtils.erase(secrets[1]);
return;
}
// Add the contact to the database
byte[] inSecret = initiator ? secrets[1] : secrets[0];
byte[] outSecret = initiator ? secrets[0] : secrets[1];
try {
db.addContact(inSecret, outSecret);
} catch(DbException e) {
callback.showFailure(DB_EXCEPTION);
ByteUtils.erase(secrets[0]);
ByteUtils.erase(secrets[1]);
return;
} }
}); callback.showSuccess();
}
}
private class IncomingInvitationWorker extends InvitationWorker {
private final IncomingInvitationCallback callback;
IncomingInvitationWorker(DuplexPlugin plugin,
IncomingInvitationCallback callback) {
super(plugin, callback, false);
this.callback = callback;
}
@Override
protected int getInvitationCode() {
return callback.enterInvitationCode();
}
}
private class OutgoingInvitationWorker extends InvitationWorker {
private final OutgoingInvitationCallback callback;
OutgoingInvitationWorker(DuplexPlugin plugin,
OutgoingInvitationCallback callback) {
super(plugin, callback, true);
this.callback = callback;
}
@Override
protected int getInvitationCode() {
int code = crypto.getSecureRandom().nextInt(MAX_CODE + 1);
callback.showInvitationCode(code);
return code;
}
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment