diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index d1bfe2d4..e4420898 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -583,23 +583,15 @@ public class AxolotlService { if(findSessionsforContact(message.getContact()).isEmpty()) { 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())) { Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString()); - //if(!session.isTrusted()) { - // TODO: handle this properly - // continue; - // } - axolotlMessage.addHeader(session.processSending(axolotlMessage.getInnerKey())); + axolotlMessage.addKeyElement(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()) { Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString()); - // if(!session.isTrusted()) { - // TODO: handle this properly - // continue; - // } - axolotlMessage.addHeader(session.processSending(axolotlMessage.getInnerKey())); + axolotlMessage.addKeyElement(session.processSending(axolotlMessage.getInnerKey())); } return axolotlMessage; @@ -651,7 +643,6 @@ public class AxolotlService { XmppAxolotlSession session = sessions.get(senderAddress); if (session == null) { 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(); if ( identityKey != null ) { session = new XmppAxolotlSession(account, axolotlStore, senderAddress, identityKey.getFingerprint().replaceAll("\\s", "")); @@ -661,12 +652,12 @@ public class AxolotlService { newSession = true; } - for (XmppAxolotlMessage.XmppAxolotlMessageHeader header : message.getHeaders()) { - if (header.getRecipientDeviceId() == getOwnDeviceId()) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found axolotl header matching own device ID, processing..."); - byte[] payloadKey = session.processReceiving(header); + for (XmppAxolotlMessage.XmppAxolotlKeyElement keyElement : message.getKeyElements()) { + if (keyElement.getRecipientDeviceId() == getOwnDeviceId()) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found axolotl keyElement matching own device ID, processing..."); + byte[] payloadKey = session.processReceiving(keyElement); 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{ plaintextMessage = message.decrypt(session, payloadKey, session.getFingerprint()); } catch (CryptoFailedException e) { diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java new file mode 100644 index 00000000..3d40a408 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java @@ -0,0 +1,5 @@ +package eu.siacs.conversations.crypto.axolotl; + +public interface OnMessageCreatedCallback { + void run(XmppAxolotlMessage message); +} diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java index 182c8f12..4725ce8a 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -2,6 +2,7 @@ package eu.siacs.conversations.crypto.axolotl; import android.support.annotation.Nullable; import android.util.Base64; +import android.util.Log; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; @@ -20,32 +21,46 @@ import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import eu.siacs.conversations.Config; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.jid.Jid; 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[] ciphertext; - private byte[] iv; - private final Set headers; + private byte[] ciphertext = null; + private byte[] iv = null; + private final Set keyElements; private final Jid from; 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 byte[] content; - public XmppAxolotlMessageHeader(int deviceId, byte[] content) { + public XmppAxolotlKeyElement(int deviceId, byte[] content) { this.recipientDeviceId = deviceId; this.content = content; } - public XmppAxolotlMessageHeader(Element header) { - if("header".equals(header.getName())) { - this.recipientDeviceId = Integer.parseInt(header.getAttribute("rid")); - this.content = Base64.decode(header.getContent(),Base64.DEFAULT); + public XmppAxolotlKeyElement(Element keyElement) { + if(TAGNAME.equals(keyElement.getName())) { + this.recipientDeviceId = Integer.parseInt(keyElement.getAttribute(REMOTEID)); + this.content = Base64.decode(keyElement.getContent(),Base64.DEFAULT); } else { - throw new IllegalArgumentException("Argument not a
Element!"); + throw new IllegalArgumentException("Argument not a <"+TAGNAME+"> Element!"); } } @@ -58,11 +73,10 @@ public class XmppAxolotlMessage { } public Element toXml() { - Element headerElement = new Element("header"); - // TODO: generate XML - headerElement.setAttribute("rid", getRecipientDeviceId()); - headerElement.setContent(Base64.encodeToString(getContents(), Base64.DEFAULT)); - return headerElement; + Element keyElement = new Element(TAGNAME); + keyElement.setAttribute(REMOTEID, getRecipientDeviceId()); + keyElement.setContent(Base64.encodeToString(getContents(), Base64.DEFAULT)); + return keyElement; } } @@ -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.sourceDeviceId = Integer.parseInt(axolotlMessage.getAttribute("id")); - this.headers = new HashSet<>(); - for(Element child:axolotlMessage.getChildren()) { - switch(child.getName()) { - case "header": - headers.add(new XmppAxolotlMessageHeader(child)); + Element header = axolotlMessage.findChild(HEADER); + this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID)); + this.keyElements = new HashSet<>(); + for(Element keyElement:header.getChildren()) { + switch(keyElement.getName()) { + case XmppAxolotlKeyElement.TAGNAME: + keyElements.add(new XmppAxolotlKeyElement(keyElement)); break; - case "message": - iv = Base64.decode(child.getAttribute("iv"),Base64.DEFAULT); - ciphertext = Base64.decode(child.getContent(),Base64.DEFAULT); + case IVTAG: + if ( this.iv != null) { + throw new IllegalArgumentException("Duplicate iv entry"); + } + iv = Base64.decode(keyElement.getContent(),Base64.DEFAULT); break; default: + Log.w(Config.LOGTAG, "Unexpected element in header: "+ keyElement.toString()); 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{ - this.from = from; - this.sourceDeviceId = sourceDeviceId; - this.headers = new HashSet<>(); + this(from, sourceDeviceId); 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 { try { - KeyGenerator generator = KeyGenerator.getInstance("AES"); - generator.init(128); - SecretKey secretKey = generator.generateKey(); - SecureRandom random = new SecureRandom(); - this.iv = new byte[16]; - random.nextBytes(iv); + SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE); 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); this.innerKey = secretKey.getEncoded(); this.ciphertext = cipher.doFinal(plaintext.getBytes()); @@ -148,13 +189,13 @@ public class XmppAxolotlMessage { return ciphertext; } - public Set getHeaders() { - return headers; + public Set getKeyElements() { + return keyElements; } - public void addHeader(@Nullable XmppAxolotlMessageHeader header) { - if (header != null) { - headers.add(header); + public void addKeyElement(@Nullable XmppAxolotlKeyElement keyElement) { + if (keyElement != null) { + keyElements.add(keyElement); } } @@ -167,16 +208,18 @@ public class XmppAxolotlMessage { } public Element toXml() { - // TODO: generate outer XML, add in header XML - Element message= new Element("axolotl_message", AxolotlService.PEP_PREFIX); - message.setAttribute("id", sourceDeviceId); - for(XmppAxolotlMessageHeader header: headers) { - message.addChild(header.toXml()); + Element encryptionElement= new Element(TAGNAME, AxolotlService.PEP_PREFIX); + Element headerElement = encryptionElement.addChild(HEADER); + headerElement.setAttribute(SOURCEID, sourceDeviceId); + for(XmppAxolotlKeyElement header: keyElements) { + headerElement.addChild(header.toXml()); } - Element payload = message.addChild("message"); - payload.setAttribute("iv",Base64.encodeToString(iv, Base64.DEFAULT)); - payload.setContent(Base64.encodeToString(ciphertext,Base64.DEFAULT)); - return message; + headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.DEFAULT)); + if ( ciphertext != null ) { + Element payload = encryptionElement.addChild(PAYLOAD); + payload.setContent(Base64.encodeToString(ciphertext, Base64.DEFAULT)); + } + return encryptionElement; } @@ -184,8 +227,8 @@ public class XmppAxolotlMessage { XmppAxolotlPlaintextMessage plaintextMessage = null; try { - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC"); - SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); + Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); + SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(iv); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java index d58b2dd8..dc4c6777 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java @@ -69,7 +69,7 @@ public class XmppAxolotlSession { } @Nullable - public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) { + public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlKeyElement incomingHeader) { byte[] plaintext = null; SQLiteAxolotlStore.Trust trust = getTrust(); switch (trust) { @@ -117,12 +117,12 @@ public class XmppAxolotlSession { } @Nullable - public XmppAxolotlMessage.XmppAxolotlMessageHeader processSending(@NonNull byte[] outgoingMessage) { + public XmppAxolotlMessage.XmppAxolotlKeyElement processSending(@NonNull byte[] outgoingMessage) { SQLiteAxolotlStore.Trust trust = getTrust(); if (trust == SQLiteAxolotlStore.Trust.TRUSTED) { CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); - XmppAxolotlMessage.XmppAxolotlMessageHeader header = - new XmppAxolotlMessage.XmppAxolotlMessageHeader(remoteAddress.getDeviceId(), + XmppAxolotlMessage.XmppAxolotlKeyElement header = + new XmppAxolotlMessage.XmppAxolotlKeyElement(remoteAddress.getDeviceId(), ciphertextMessage.serialize()); return header; } else { diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 4bee34a9..47c320f3 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -272,7 +272,7 @@ public class MessageParser extends AbstractParser implements final String body = packet.getBody(); final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user"); 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; final Jid counterpart; final Jid to = packet.getTo();