Change to new wire protocol version

This commit is contained in:
Andreas Straub 2015-07-31 17:59:41 +02:00
parent 26ac7c9030
commit 5c421da1e1
5 changed files with 113 additions and 74 deletions

View file

@ -583,23 +583,15 @@ public class AxolotlService {
if(findSessionsforContact(message.getContact()).isEmpty()) { if(findSessionsforContact(message.getContact()).isEmpty()) {
return null; return null;
} }
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl foreign headers..."); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl foreign keyElements...");
for (XmppAxolotlSession session : findSessionsforContact(message.getContact())) { for (XmppAxolotlSession session : findSessionsforContact(message.getContact())) {
Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString()); Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString());
//if(!session.isTrusted()) { axolotlMessage.addKeyElement(session.processSending(axolotlMessage.getInnerKey()));
// TODO: handle this properly
// continue;
// }
axolotlMessage.addHeader(session.processSending(axolotlMessage.getInnerKey()));
} }
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl own headers..."); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl own keyElements...");
for (XmppAxolotlSession session : findOwnSessions()) { for (XmppAxolotlSession session : findOwnSessions()) {
Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString()); Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString());
// if(!session.isTrusted()) { axolotlMessage.addKeyElement(session.processSending(axolotlMessage.getInnerKey()));
// TODO: handle this properly
// continue;
// }
axolotlMessage.addHeader(session.processSending(axolotlMessage.getInnerKey()));
} }
return axolotlMessage; return axolotlMessage;
@ -651,7 +643,6 @@ public class AxolotlService {
XmppAxolotlSession session = sessions.get(senderAddress); XmppAxolotlSession session = sessions.get(senderAddress);
if (session == null) { if (session == null) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Account: "+account.getJid()+" No axolotl session found while parsing received message " + message); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Account: "+account.getJid()+" No axolotl session found while parsing received message " + message);
// TODO: handle this properly
IdentityKey identityKey = axolotlStore.loadSession(senderAddress).getSessionState().getRemoteIdentityKey(); IdentityKey identityKey = axolotlStore.loadSession(senderAddress).getSessionState().getRemoteIdentityKey();
if ( identityKey != null ) { if ( identityKey != null ) {
session = new XmppAxolotlSession(account, axolotlStore, senderAddress, identityKey.getFingerprint().replaceAll("\\s", "")); session = new XmppAxolotlSession(account, axolotlStore, senderAddress, identityKey.getFingerprint().replaceAll("\\s", ""));
@ -661,12 +652,12 @@ public class AxolotlService {
newSession = true; newSession = true;
} }
for (XmppAxolotlMessage.XmppAxolotlMessageHeader header : message.getHeaders()) { for (XmppAxolotlMessage.XmppAxolotlKeyElement keyElement : message.getKeyElements()) {
if (header.getRecipientDeviceId() == getOwnDeviceId()) { if (keyElement.getRecipientDeviceId() == getOwnDeviceId()) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found axolotl header matching own device ID, processing..."); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found axolotl keyElement matching own device ID, processing...");
byte[] payloadKey = session.processReceiving(header); byte[] payloadKey = session.processReceiving(keyElement);
if (payloadKey != null) { if (payloadKey != null) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got payload key from axolotl header. Decrypting message..."); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got payload key from axolotl keyElement. Decrypting message...");
try{ try{
plaintextMessage = message.decrypt(session, payloadKey, session.getFingerprint()); plaintextMessage = message.decrypt(session, payloadKey, session.getFingerprint());
} catch (CryptoFailedException e) { } catch (CryptoFailedException e) {

View file

@ -0,0 +1,5 @@
package eu.siacs.conversations.crypto.axolotl;
public interface OnMessageCreatedCallback {
void run(XmppAxolotlMessage message);
}

View file

@ -2,6 +2,7 @@ package eu.siacs.conversations.crypto.axolotl;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Base64; import android.util.Base64;
import android.util.Log;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
@ -20,32 +21,46 @@ import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.jid.Jid;
public class XmppAxolotlMessage { public class XmppAxolotlMessage {
public static final String TAGNAME = "encrypted";
public static final String HEADER = "header";
public static final String SOURCEID = "sid";
public static final String IVTAG = "iv";
public static final String PAYLOAD = "payload";
private static final String KEYTYPE = "AES";
private static final String CIPHERMODE = "AES/GCM/NoPadding";
private static final String PROVIDER = "BC";
private byte[] innerKey; private byte[] innerKey;
private byte[] ciphertext; private byte[] ciphertext = null;
private byte[] iv; private byte[] iv = null;
private final Set<XmppAxolotlMessageHeader> headers; private final Set<XmppAxolotlKeyElement> keyElements;
private final Jid from; private final Jid from;
private final int sourceDeviceId; private final int sourceDeviceId;
public static class XmppAxolotlMessageHeader { public static class XmppAxolotlKeyElement {
public static final String TAGNAME = "key";
public static final String REMOTEID = "rid";
private final int recipientDeviceId; private final int recipientDeviceId;
private final byte[] content; private final byte[] content;
public XmppAxolotlMessageHeader(int deviceId, byte[] content) { public XmppAxolotlKeyElement(int deviceId, byte[] content) {
this.recipientDeviceId = deviceId; this.recipientDeviceId = deviceId;
this.content = content; this.content = content;
} }
public XmppAxolotlMessageHeader(Element header) { public XmppAxolotlKeyElement(Element keyElement) {
if("header".equals(header.getName())) { if(TAGNAME.equals(keyElement.getName())) {
this.recipientDeviceId = Integer.parseInt(header.getAttribute("rid")); this.recipientDeviceId = Integer.parseInt(keyElement.getAttribute(REMOTEID));
this.content = Base64.decode(header.getContent(),Base64.DEFAULT); this.content = Base64.decode(keyElement.getContent(),Base64.DEFAULT);
} else { } else {
throw new IllegalArgumentException("Argument not a <header> Element!"); throw new IllegalArgumentException("Argument not a <"+TAGNAME+"> Element!");
} }
} }
@ -58,11 +73,10 @@ public class XmppAxolotlMessage {
} }
public Element toXml() { public Element toXml() {
Element headerElement = new Element("header"); Element keyElement = new Element(TAGNAME);
// TODO: generate XML keyElement.setAttribute(REMOTEID, getRecipientDeviceId());
headerElement.setAttribute("rid", getRecipientDeviceId()); keyElement.setContent(Base64.encodeToString(getContents(), Base64.DEFAULT));
headerElement.setContent(Base64.encodeToString(getContents(), Base64.DEFAULT)); return keyElement;
return headerElement;
} }
} }
@ -90,42 +104,69 @@ public class XmppAxolotlMessage {
} }
} }
public XmppAxolotlMessage(Jid from, Element axolotlMessage) { public XmppAxolotlMessage(Jid from, Element axolotlMessage) throws IllegalArgumentException {
this.from = from; this.from = from;
this.sourceDeviceId = Integer.parseInt(axolotlMessage.getAttribute("id")); Element header = axolotlMessage.findChild(HEADER);
this.headers = new HashSet<>(); this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID));
for(Element child:axolotlMessage.getChildren()) { this.keyElements = new HashSet<>();
switch(child.getName()) { for(Element keyElement:header.getChildren()) {
case "header": switch(keyElement.getName()) {
headers.add(new XmppAxolotlMessageHeader(child)); case XmppAxolotlKeyElement.TAGNAME:
keyElements.add(new XmppAxolotlKeyElement(keyElement));
break; break;
case "message": case IVTAG:
iv = Base64.decode(child.getAttribute("iv"),Base64.DEFAULT); if ( this.iv != null) {
ciphertext = Base64.decode(child.getContent(),Base64.DEFAULT); throw new IllegalArgumentException("Duplicate iv entry");
}
iv = Base64.decode(keyElement.getContent(),Base64.DEFAULT);
break; break;
default: default:
Log.w(Config.LOGTAG, "Unexpected element in header: "+ keyElement.toString());
break; break;
} }
} }
Element payloadElement = axolotlMessage.findChild(PAYLOAD);
if ( payloadElement != null ) {
ciphertext = Base64.decode(payloadElement.getContent(), Base64.DEFAULT);
}
}
public XmppAxolotlMessage(Jid from, int sourceDeviceId) {
this.from = from;
this.sourceDeviceId = sourceDeviceId;
this.keyElements = new HashSet<>();
this.iv = generateIv();
this.innerKey = generateKey();
} }
public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) throws CryptoFailedException{ public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) throws CryptoFailedException{
this.from = from; this(from, sourceDeviceId);
this.sourceDeviceId = sourceDeviceId;
this.headers = new HashSet<>();
this.encrypt(plaintext); this.encrypt(plaintext);
} }
private static byte[] generateKey() {
try {
KeyGenerator generator = KeyGenerator.getInstance(KEYTYPE);
generator.init(128);
return generator.generateKey().getEncoded();
} catch (NoSuchAlgorithmException e) {
Log.e(Config.LOGTAG, e.getMessage());
return null;
}
}
private static byte[] generateIv() {
SecureRandom random = new SecureRandom();
byte[] iv = new byte[16];
random.nextBytes(iv);
return iv;
}
private void encrypt(String plaintext) throws CryptoFailedException { private void encrypt(String plaintext) throws CryptoFailedException {
try { try {
KeyGenerator generator = KeyGenerator.getInstance("AES"); SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE);
generator.init(128);
SecretKey secretKey = generator.generateKey();
SecureRandom random = new SecureRandom();
this.iv = new byte[16];
random.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv); IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC"); Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
this.innerKey = secretKey.getEncoded(); this.innerKey = secretKey.getEncoded();
this.ciphertext = cipher.doFinal(plaintext.getBytes()); this.ciphertext = cipher.doFinal(plaintext.getBytes());
@ -148,13 +189,13 @@ public class XmppAxolotlMessage {
return ciphertext; return ciphertext;
} }
public Set<XmppAxolotlMessageHeader> getHeaders() { public Set<XmppAxolotlKeyElement> getKeyElements() {
return headers; return keyElements;
} }
public void addHeader(@Nullable XmppAxolotlMessageHeader header) { public void addKeyElement(@Nullable XmppAxolotlKeyElement keyElement) {
if (header != null) { if (keyElement != null) {
headers.add(header); keyElements.add(keyElement);
} }
} }
@ -167,16 +208,18 @@ public class XmppAxolotlMessage {
} }
public Element toXml() { public Element toXml() {
// TODO: generate outer XML, add in header XML Element encryptionElement= new Element(TAGNAME, AxolotlService.PEP_PREFIX);
Element message= new Element("axolotl_message", AxolotlService.PEP_PREFIX); Element headerElement = encryptionElement.addChild(HEADER);
message.setAttribute("id", sourceDeviceId); headerElement.setAttribute(SOURCEID, sourceDeviceId);
for(XmppAxolotlMessageHeader header: headers) { for(XmppAxolotlKeyElement header: keyElements) {
message.addChild(header.toXml()); headerElement.addChild(header.toXml());
} }
Element payload = message.addChild("message"); headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.DEFAULT));
payload.setAttribute("iv",Base64.encodeToString(iv, Base64.DEFAULT)); if ( ciphertext != null ) {
payload.setContent(Base64.encodeToString(ciphertext,Base64.DEFAULT)); Element payload = encryptionElement.addChild(PAYLOAD);
return message; payload.setContent(Base64.encodeToString(ciphertext, Base64.DEFAULT));
}
return encryptionElement;
} }
@ -184,8 +227,8 @@ public class XmppAxolotlMessage {
XmppAxolotlPlaintextMessage plaintextMessage = null; XmppAxolotlPlaintextMessage plaintextMessage = null;
try { try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC"); Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(iv); IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);

View file

@ -69,7 +69,7 @@ public class XmppAxolotlSession {
} }
@Nullable @Nullable
public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) { public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlKeyElement incomingHeader) {
byte[] plaintext = null; byte[] plaintext = null;
SQLiteAxolotlStore.Trust trust = getTrust(); SQLiteAxolotlStore.Trust trust = getTrust();
switch (trust) { switch (trust) {
@ -117,12 +117,12 @@ public class XmppAxolotlSession {
} }
@Nullable @Nullable
public XmppAxolotlMessage.XmppAxolotlMessageHeader processSending(@NonNull byte[] outgoingMessage) { public XmppAxolotlMessage.XmppAxolotlKeyElement processSending(@NonNull byte[] outgoingMessage) {
SQLiteAxolotlStore.Trust trust = getTrust(); SQLiteAxolotlStore.Trust trust = getTrust();
if (trust == SQLiteAxolotlStore.Trust.TRUSTED) { if (trust == SQLiteAxolotlStore.Trust.TRUSTED) {
CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
XmppAxolotlMessage.XmppAxolotlMessageHeader header = XmppAxolotlMessage.XmppAxolotlKeyElement header =
new XmppAxolotlMessage.XmppAxolotlMessageHeader(remoteAddress.getDeviceId(), new XmppAxolotlMessage.XmppAxolotlKeyElement(remoteAddress.getDeviceId(),
ciphertextMessage.serialize()); ciphertextMessage.serialize());
return header; return header;
} else { } else {

View file

@ -272,7 +272,7 @@ public class MessageParser extends AbstractParser implements
final String body = packet.getBody(); final String body = packet.getBody();
final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user"); final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user");
final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted"); final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
final Element axolotlEncrypted = packet.findChild("axolotl_message", AxolotlService.PEP_PREFIX); final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.TAGNAME, AxolotlService.PEP_PREFIX);
int status; int status;
final Jid counterpart; final Jid counterpart;
final Jid to = packet.getTo(); final Jid to = packet.getTo();