aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/de/pixart/messenger/crypto
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de/pixart/messenger/crypto')
-rw-r--r--src/main/java/de/pixart/messenger/crypto/OtrService.java486
-rw-r--r--src/main/java/de/pixart/messenger/crypto/PgpDecryptionService.java24
-rw-r--r--src/main/java/de/pixart/messenger/crypto/PgpEngine.java536
-rw-r--r--src/main/java/de/pixart/messenger/crypto/XmppDomainVerifier.java188
-rw-r--r--src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java2090
-rw-r--r--src/main/java/de/pixart/messenger/crypto/axolotl/CryptoFailedException.java6
-rw-r--r--src/main/java/de/pixart/messenger/crypto/axolotl/FingerprintStatus.java6
-rw-r--r--src/main/java/de/pixart/messenger/crypto/axolotl/NoSessionsCreatedException.java2
-rw-r--r--src/main/java/de/pixart/messenger/crypto/axolotl/OnMessageCreatedCallback.java2
-rw-r--r--src/main/java/de/pixart/messenger/crypto/axolotl/SQLiteAxolotlStore.java834
-rw-r--r--src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java446
-rw-r--r--src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlSession.java218
-rw-r--r--src/main/java/de/pixart/messenger/crypto/sasl/Anonymous.java30
-rw-r--r--src/main/java/de/pixart/messenger/crypto/sasl/DigestMd5.java140
-rw-r--r--src/main/java/de/pixart/messenger/crypto/sasl/External.java36
-rw-r--r--src/main/java/de/pixart/messenger/crypto/sasl/Plain.java32
-rw-r--r--src/main/java/de/pixart/messenger/crypto/sasl/SaslMechanism.java107
-rw-r--r--src/main/java/de/pixart/messenger/crypto/sasl/ScramSha1.java423
-rw-r--r--src/main/java/de/pixart/messenger/crypto/sasl/Tokenizer.java120
19 files changed, 2865 insertions, 2861 deletions
diff --git a/src/main/java/de/pixart/messenger/crypto/OtrService.java b/src/main/java/de/pixart/messenger/crypto/OtrService.java
index 18f9670d8..459f49c38 100644
--- a/src/main/java/de/pixart/messenger/crypto/OtrService.java
+++ b/src/main/java/de/pixart/messenger/crypto/OtrService.java
@@ -38,25 +38,25 @@ import de.pixart.messenger.xmpp.stanzas.MessagePacket;
public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
- private Account account;
- private OtrPolicy otrPolicy;
- private KeyPair keyPair;
- private XmppConnectionService mXmppConnectionService;
-
- public OtrService(XmppConnectionService service, Account account) {
- this.account = account;
- this.otrPolicy = new OtrPolicyImpl();
- this.otrPolicy.setAllowV1(false);
- this.otrPolicy.setAllowV2(true);
- this.otrPolicy.setAllowV3(true);
- this.keyPair = loadKey(account.getKeys());
- this.mXmppConnectionService = service;
- }
-
- private KeyPair loadKey(final JSONObject keys) {
- if (keys == null) {
- return null;
- }
+ private Account account;
+ private OtrPolicy otrPolicy;
+ private KeyPair keyPair;
+ private XmppConnectionService mXmppConnectionService;
+
+ public OtrService(XmppConnectionService service, Account account) {
+ this.account = account;
+ this.otrPolicy = new OtrPolicyImpl();
+ this.otrPolicy.setAllowV1(false);
+ this.otrPolicy.setAllowV2(true);
+ this.otrPolicy.setAllowV3(true);
+ this.keyPair = loadKey(account.getKeys());
+ this.mXmppConnectionService = service;
+ }
+
+ private KeyPair loadKey(final JSONObject keys) {
+ if (keys == null) {
+ return null;
+ }
synchronized (keys) {
try {
BigInteger x = new BigInteger(keys.getString("otr_x"), 16);
@@ -78,232 +78,232 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
return null;
}
}
- }
-
- private void saveKey() {
- PublicKey publicKey = keyPair.getPublic();
- PrivateKey privateKey = keyPair.getPrivate();
- KeyFactory keyFactory;
- try {
- keyFactory = KeyFactory.getInstance("DSA");
- DSAPrivateKeySpec privateKeySpec = keyFactory.getKeySpec(
- privateKey, DSAPrivateKeySpec.class);
- DSAPublicKeySpec publicKeySpec = keyFactory.getKeySpec(publicKey,
- DSAPublicKeySpec.class);
- this.account.setKey("otr_x", privateKeySpec.getX().toString(16));
- this.account.setKey("otr_g", privateKeySpec.getG().toString(16));
- this.account.setKey("otr_p", privateKeySpec.getP().toString(16));
- this.account.setKey("otr_q", privateKeySpec.getQ().toString(16));
- this.account.setKey("otr_y", publicKeySpec.getY().toString(16));
- } catch (final NoSuchAlgorithmException | InvalidKeySpecException e) {
- e.printStackTrace();
- }
+ }
+
+ private void saveKey() {
+ PublicKey publicKey = keyPair.getPublic();
+ PrivateKey privateKey = keyPair.getPrivate();
+ KeyFactory keyFactory;
+ try {
+ keyFactory = KeyFactory.getInstance("DSA");
+ DSAPrivateKeySpec privateKeySpec = keyFactory.getKeySpec(
+ privateKey, DSAPrivateKeySpec.class);
+ DSAPublicKeySpec publicKeySpec = keyFactory.getKeySpec(publicKey,
+ DSAPublicKeySpec.class);
+ this.account.setKey("otr_x", privateKeySpec.getX().toString(16));
+ this.account.setKey("otr_g", privateKeySpec.getG().toString(16));
+ this.account.setKey("otr_p", privateKeySpec.getP().toString(16));
+ this.account.setKey("otr_q", privateKeySpec.getQ().toString(16));
+ this.account.setKey("otr_y", publicKeySpec.getY().toString(16));
+ } catch (final NoSuchAlgorithmException | InvalidKeySpecException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ @Override
+ public void askForSecret(SessionID id, InstanceTag instanceTag, String question) {
+ try {
+ final Jid jid = Jid.fromSessionID(id);
+ Conversation conversation = this.mXmppConnectionService.find(this.account, jid);
+ if (conversation != null) {
+ conversation.smp().hint = question;
+ conversation.smp().status = Conversation.Smp.STATUS_CONTACT_REQUESTED;
+ mXmppConnectionService.updateConversationUi();
+ }
+ } catch (InvalidJidException e) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": smp in invalid session " + id.toString());
+ }
+ }
+
+ @Override
+ public void finishedSessionMessage(SessionID arg0, String arg1)
+ throws OtrException {
+
+ }
+
+ @Override
+ public String getFallbackMessage(SessionID arg0) {
+ return MessageGenerator.OTR_FALLBACK_MESSAGE;
+ }
+
+ @Override
+ public byte[] getLocalFingerprintRaw(SessionID arg0) {
+ try {
+ return getFingerprintRaw(getPublicKey());
+ } catch (OtrCryptoException e) {
+ return null;
+ }
+ }
+
+ public PublicKey getPublicKey() {
+ if (this.keyPair == null) {
+ return null;
+ }
+ return this.keyPair.getPublic();
+ }
+
+ @Override
+ public KeyPair getLocalKeyPair(SessionID arg0) throws OtrException {
+ if (this.keyPair == null) {
+ KeyPairGenerator kg;
+ try {
+ kg = KeyPairGenerator.getInstance("DSA");
+ this.keyPair = kg.genKeyPair();
+ this.saveKey();
+ mXmppConnectionService.databaseBackend.updateAccount(account);
+ } catch (NoSuchAlgorithmException e) {
+ Log.d(Config.LOGTAG,
+ "error generating key pair " + e.getMessage());
+ }
+ }
+ return this.keyPair;
+ }
+
+ @Override
+ public String getReplyForUnreadableMessage(SessionID arg0) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public OtrPolicy getSessionPolicy(SessionID arg0) {
+ return otrPolicy;
+ }
+
+ @Override
+ public void injectMessage(SessionID session, String body)
+ throws OtrException {
+ MessagePacket packet = new MessagePacket();
+ packet.setFrom(account.getJid());
+ if (session.getUserID().isEmpty()) {
+ packet.setAttribute("to", session.getAccountID());
+ } else {
+ packet.setAttribute("to", session.getAccountID() + "/" + session.getUserID());
+ }
+ packet.setBody(body);
+ MessageGenerator.addMessageHints(packet);
+ try {
+ Jid jid = Jid.fromSessionID(session);
+ Conversation conversation = mXmppConnectionService.find(account, jid);
+ if (conversation != null && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
+ if (mXmppConnectionService.sendChatStates()) {
+ packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
+ }
+ }
+ } catch (final InvalidJidException ignored) {
+
+ }
+
+ packet.setType(MessagePacket.TYPE_CHAT);
+ account.getXmppConnection().sendMessagePacket(packet);
+ }
+
+ @Override
+ public void messageFromAnotherInstanceReceived(SessionID session) {
+ sendOtrErrorMessage(session, "Message from another OTR-instance received");
+ }
+
+ @Override
+ public void multipleInstancesDetected(SessionID arg0) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void requireEncryptedMessage(SessionID arg0, String arg1)
+ throws OtrException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void showError(SessionID arg0, String arg1) throws OtrException {
+ Log.d(Config.LOGTAG, "show error");
+ }
+
+ @Override
+ public void smpAborted(SessionID id) throws OtrException {
+ setSmpStatus(id, Conversation.Smp.STATUS_NONE);
+ }
+
+ private void setSmpStatus(SessionID id, int status) {
+ try {
+ final Jid jid = Jid.fromSessionID(id);
+ Conversation conversation = this.mXmppConnectionService.find(this.account, jid);
+ if (conversation != null) {
+ conversation.smp().status = status;
+ mXmppConnectionService.updateConversationUi();
+ }
+ } catch (final InvalidJidException ignored) {
+
+ }
+ }
+
+ @Override
+ public void smpError(SessionID id, int arg1, boolean arg2)
+ throws OtrException {
+ setSmpStatus(id, Conversation.Smp.STATUS_NONE);
+ }
+
+ @Override
+ public void unencryptedMessageReceived(SessionID arg0, String arg1)
+ throws OtrException {
+ throw new OtrException(new Exception("unencrypted message received"));
+ }
+
+ @Override
+ public void unreadableMessageReceived(SessionID session) throws OtrException {
+ Log.d(Config.LOGTAG, "unreadable message received");
+ sendOtrErrorMessage(session, "You sent me an unreadable OTR-encrypted message");
+ }
+
+ public void sendOtrErrorMessage(SessionID session, String errorText) {
+ try {
+ Jid jid = Jid.fromSessionID(session);
+ Conversation conversation = mXmppConnectionService.find(account, jid);
+ String id = conversation == null ? null : conversation.getLastReceivedOtrMessageId();
+ if (id != null) {
+ MessagePacket packet = mXmppConnectionService.getMessageGenerator()
+ .generateOtrError(jid, id, errorText);
+ packet.setFrom(account.getJid());
+ mXmppConnectionService.sendMessagePacket(account, packet);
+ Log.d(Config.LOGTAG, packet.toString());
+ Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
+ + ": unreadable OTR message in " + conversation.getName());
+ }
+ } catch (InvalidJidException e) {
+ return;
+ }
+ }
+ @Override
+ public void unverify(SessionID id, String arg1) {
+ setSmpStatus(id, Conversation.Smp.STATUS_FAILED);
}
- @Override
- public void askForSecret(SessionID id, InstanceTag instanceTag, String question) {
- try {
- final Jid jid = Jid.fromSessionID(id);
- Conversation conversation = this.mXmppConnectionService.find(this.account,jid);
- if (conversation!=null) {
- conversation.smp().hint = question;
- conversation.smp().status = Conversation.Smp.STATUS_CONTACT_REQUESTED;
- mXmppConnectionService.updateConversationUi();
- }
- } catch (InvalidJidException e) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": smp in invalid session "+id.toString());
- }
- }
-
- @Override
- public void finishedSessionMessage(SessionID arg0, String arg1)
- throws OtrException {
-
- }
-
- @Override
- public String getFallbackMessage(SessionID arg0) {
- return MessageGenerator.OTR_FALLBACK_MESSAGE;
- }
-
- @Override
- public byte[] getLocalFingerprintRaw(SessionID arg0) {
- try {
- return getFingerprintRaw(getPublicKey());
- } catch (OtrCryptoException e) {
- return null;
- }
- }
-
- public PublicKey getPublicKey() {
- if (this.keyPair == null) {
- return null;
- }
- return this.keyPair.getPublic();
- }
-
- @Override
- public KeyPair getLocalKeyPair(SessionID arg0) throws OtrException {
- if (this.keyPair == null) {
- KeyPairGenerator kg;
- try {
- kg = KeyPairGenerator.getInstance("DSA");
- this.keyPair = kg.genKeyPair();
- this.saveKey();
- mXmppConnectionService.databaseBackend.updateAccount(account);
- } catch (NoSuchAlgorithmException e) {
- Log.d(Config.LOGTAG,
- "error generating key pair " + e.getMessage());
- }
- }
- return this.keyPair;
- }
-
- @Override
- public String getReplyForUnreadableMessage(SessionID arg0) {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public OtrPolicy getSessionPolicy(SessionID arg0) {
- return otrPolicy;
- }
-
- @Override
- public void injectMessage(SessionID session, String body)
- throws OtrException {
- MessagePacket packet = new MessagePacket();
- packet.setFrom(account.getJid());
- if (session.getUserID().isEmpty()) {
- packet.setAttribute("to", session.getAccountID());
- } else {
- packet.setAttribute("to", session.getAccountID() + "/" + session.getUserID());
- }
- packet.setBody(body);
- MessageGenerator.addMessageHints(packet);
- try {
- Jid jid = Jid.fromSessionID(session);
- Conversation conversation = mXmppConnectionService.find(account,jid);
- if (conversation != null && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
- if (mXmppConnectionService.sendChatStates()) {
- packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
- }
- }
- } catch (final InvalidJidException ignored) {
-
- }
-
- packet.setType(MessagePacket.TYPE_CHAT);
- account.getXmppConnection().sendMessagePacket(packet);
- }
-
- @Override
- public void messageFromAnotherInstanceReceived(SessionID session) {
- sendOtrErrorMessage(session, "Message from another OTR-instance received");
- }
-
- @Override
- public void multipleInstancesDetected(SessionID arg0) {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void requireEncryptedMessage(SessionID arg0, String arg1)
- throws OtrException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void showError(SessionID arg0, String arg1) throws OtrException {
- Log.d(Config.LOGTAG,"show error");
- }
-
- @Override
- public void smpAborted(SessionID id) throws OtrException {
- setSmpStatus(id, Conversation.Smp.STATUS_NONE);
- }
-
- private void setSmpStatus(SessionID id, int status) {
- try {
- final Jid jid = Jid.fromSessionID(id);
- Conversation conversation = this.mXmppConnectionService.find(this.account,jid);
- if (conversation!=null) {
- conversation.smp().status = status;
- mXmppConnectionService.updateConversationUi();
- }
- } catch (final InvalidJidException ignored) {
-
- }
- }
-
- @Override
- public void smpError(SessionID id, int arg1, boolean arg2)
- throws OtrException {
- setSmpStatus(id, Conversation.Smp.STATUS_NONE);
- }
-
- @Override
- public void unencryptedMessageReceived(SessionID arg0, String arg1)
- throws OtrException {
- throw new OtrException(new Exception("unencrypted message received"));
- }
-
- @Override
- public void unreadableMessageReceived(SessionID session) throws OtrException {
- Log.d(Config.LOGTAG,"unreadable message received");
- sendOtrErrorMessage(session, "You sent me an unreadable OTR-encrypted message");
- }
-
- public void sendOtrErrorMessage(SessionID session, String errorText) {
- try {
- Jid jid = Jid.fromSessionID(session);
- Conversation conversation = mXmppConnectionService.find(account, jid);
- String id = conversation == null ? null : conversation.getLastReceivedOtrMessageId();
- if (id != null) {
- MessagePacket packet = mXmppConnectionService.getMessageGenerator()
- .generateOtrError(jid, id, errorText);
- packet.setFrom(account.getJid());
- mXmppConnectionService.sendMessagePacket(account,packet);
- Log.d(Config.LOGTAG,packet.toString());
- Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()
- +": unreadable OTR message in "+conversation.getName());
- }
- } catch (InvalidJidException e) {
- return;
- }
- }
-
- @Override
- public void unverify(SessionID id, String arg1) {
- setSmpStatus(id, Conversation.Smp.STATUS_FAILED);
- }
-
- @Override
- public void verify(SessionID id, String fingerprint, boolean approved) {
- Log.d(Config.LOGTAG,"OtrService.verify("+id.toString()+","+fingerprint+","+String.valueOf(approved)+")");
- try {
- final Jid jid = Jid.fromSessionID(id);
- Conversation conversation = this.mXmppConnectionService.find(this.account,jid);
- if (conversation!=null) {
- if (approved) {
- conversation.getContact().addOtrFingerprint(fingerprint);
- }
- conversation.smp().hint = null;
- conversation.smp().status = Conversation.Smp.STATUS_VERIFIED;
- mXmppConnectionService.updateConversationUi();
- mXmppConnectionService.syncRosterToDisk(conversation.getAccount());
- }
- } catch (final InvalidJidException ignored) {
- }
- }
-
- @Override
- public FragmenterInstructions getFragmenterInstructions(SessionID sessionID) {
- return null;
- }
+ @Override
+ public void verify(SessionID id, String fingerprint, boolean approved) {
+ Log.d(Config.LOGTAG, "OtrService.verify(" + id.toString() + "," + fingerprint + "," + String.valueOf(approved) + ")");
+ try {
+ final Jid jid = Jid.fromSessionID(id);
+ Conversation conversation = this.mXmppConnectionService.find(this.account, jid);
+ if (conversation != null) {
+ if (approved) {
+ conversation.getContact().addOtrFingerprint(fingerprint);
+ }
+ conversation.smp().hint = null;
+ conversation.smp().status = Conversation.Smp.STATUS_VERIFIED;
+ mXmppConnectionService.updateConversationUi();
+ mXmppConnectionService.syncRosterToDisk(conversation.getAccount());
+ }
+ } catch (final InvalidJidException ignored) {
+ }
+ }
+
+ @Override
+ public FragmenterInstructions getFragmenterInstructions(SessionID sessionID) {
+ return null;
+ }
}
diff --git a/src/main/java/de/pixart/messenger/crypto/PgpDecryptionService.java b/src/main/java/de/pixart/messenger/crypto/PgpDecryptionService.java
index ffa853fda..4b0e78764 100644
--- a/src/main/java/de/pixart/messenger/crypto/PgpDecryptionService.java
+++ b/src/main/java/de/pixart/messenger/crypto/PgpDecryptionService.java
@@ -28,9 +28,9 @@ public class PgpDecryptionService {
private final XmppConnectionService mXmppConnectionService;
private OpenPgpApi openPgpApi = null;
- protected final ArrayDeque<Message> messages = new ArrayDeque();
+ protected final ArrayDeque<Message> messages = new ArrayDeque();
protected final HashSet<Message> pendingNotifications = new HashSet<>();
- Message currentMessage;
+ Message currentMessage;
private PendingIntent pendingIntent;
@@ -38,7 +38,7 @@ public class PgpDecryptionService {
this.mXmppConnectionService = service;
}
- public synchronized boolean decrypt(final Message message, boolean notify) {
+ public synchronized boolean decrypt(final Message message, boolean notify) {
messages.add(message);
if (notify && pendingIntent == null) {
pendingNotifications.add(message);
@@ -48,10 +48,10 @@ public class PgpDecryptionService {
continueDecryption();
return notify;
}
- }
+ }
public synchronized void decrypt(final List<Message> list) {
- for(Message message : list) {
+ for (Message message : list) {
if (message.getEncryption() == Message.ENCRYPTION_PGP) {
messages.add(message);
}
@@ -69,19 +69,19 @@ public class PgpDecryptionService {
this.pendingNotifications.remove(message);
}
- protected synchronized void decryptNext() {
- if (pendingIntent == null
+ protected synchronized void decryptNext() {
+ if (pendingIntent == null
&& getOpenPgpApi() != null
- && (currentMessage = messages.poll()) != null) {
- new Thread(new Runnable() {
+ && (currentMessage = messages.poll()) != null) {
+ new Thread(new Runnable() {
@Override
public void run() {
executeApi(currentMessage);
decryptNext();
}
}).start();
- }
- }
+ }
+ }
public synchronized void continueDecryption(boolean resetPending) {
if (resetPending) {
@@ -200,7 +200,7 @@ public class PgpDecryptionService {
if (pendingIntent == null) {
return false;
} else {
- for(Message message : messages) {
+ for (Message message : messages) {
if (message.getConversation() == conversation) {
return true;
}
diff --git a/src/main/java/de/pixart/messenger/crypto/PgpEngine.java b/src/main/java/de/pixart/messenger/crypto/PgpEngine.java
index 38d097ec3..c906c7514 100644
--- a/src/main/java/de/pixart/messenger/crypto/PgpEngine.java
+++ b/src/main/java/de/pixart/messenger/crypto/PgpEngine.java
@@ -29,290 +29,290 @@ import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.ui.UiCallback;
public class PgpEngine {
- private OpenPgpApi api;
- private XmppConnectionService mXmppConnectionService;
+ private OpenPgpApi api;
+ private XmppConnectionService mXmppConnectionService;
- public PgpEngine(OpenPgpApi api, XmppConnectionService service) {
- this.api = api;
- this.mXmppConnectionService = service;
- }
+ public PgpEngine(OpenPgpApi api, XmppConnectionService service) {
+ this.api = api;
+ this.mXmppConnectionService = service;
+ }
- public void encrypt(final Message message, final UiCallback<Message> callback) {
- Intent params = new Intent();
- params.setAction(OpenPgpApi.ACTION_ENCRYPT);
- final Conversation conversation = message.getConversation();
- if (conversation.getMode() == Conversation.MODE_SINGLE) {
- long[] keys = {
- conversation.getContact().getPgpKeyId(),
- conversation.getAccount().getPgpId()
- };
- params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
- } else {
- params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, conversation.getMucOptions().getPgpKeyIds());
- }
+ public void encrypt(final Message message, final UiCallback<Message> callback) {
+ Intent params = new Intent();
+ params.setAction(OpenPgpApi.ACTION_ENCRYPT);
+ final Conversation conversation = message.getConversation();
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ long[] keys = {
+ conversation.getContact().getPgpKeyId(),
+ conversation.getAccount().getPgpId()
+ };
+ params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
+ } else {
+ params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, conversation.getMucOptions().getPgpKeyIds());
+ }
- if (!message.needsUploading()) {
- params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
- String body;
- if (message.hasFileOnRemoteHost()) {
- body = message.getFileParams().url.toString();
- } else {
- body = message.getBody();
- }
- InputStream is = new ByteArrayInputStream(body.getBytes());
- final OutputStream os = new ByteArrayOutputStream();
- api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
+ if (!message.needsUploading()) {
+ params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
+ String body;
+ if (message.hasFileOnRemoteHost()) {
+ body = message.getFileParams().url.toString();
+ } else {
+ body = message.getBody();
+ }
+ InputStream is = new ByteArrayInputStream(body.getBytes());
+ final OutputStream os = new ByteArrayOutputStream();
+ api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
- @Override
- public void onReturn(Intent result) {
- switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
- OpenPgpApi.RESULT_CODE_ERROR)) {
- case OpenPgpApi.RESULT_CODE_SUCCESS:
- try {
- os.flush();
- StringBuilder encryptedMessageBody = new StringBuilder();
- String[] lines = os.toString().split("\n");
- for (int i = 2; i < lines.length - 1; ++i) {
- if (!lines[i].contains("Version")) {
- encryptedMessageBody.append(lines[i].trim());
- }
- }
- message.setEncryptedBody(encryptedMessageBody
- .toString());
- callback.success(message);
- } catch (IOException e) {
- callback.error(R.string.openpgp_error, message);
- }
+ @Override
+ public void onReturn(Intent result) {
+ switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
+ OpenPgpApi.RESULT_CODE_ERROR)) {
+ case OpenPgpApi.RESULT_CODE_SUCCESS:
+ try {
+ os.flush();
+ StringBuilder encryptedMessageBody = new StringBuilder();
+ String[] lines = os.toString().split("\n");
+ for (int i = 2; i < lines.length - 1; ++i) {
+ if (!lines[i].contains("Version")) {
+ encryptedMessageBody.append(lines[i].trim());
+ }
+ }
+ message.setEncryptedBody(encryptedMessageBody
+ .toString());
+ callback.success(message);
+ } catch (IOException e) {
+ callback.error(R.string.openpgp_error, message);
+ }
- break;
- case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- callback.userInputRequried((PendingIntent) result
- .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
- message);
- break;
- case OpenPgpApi.RESULT_CODE_ERROR:
- logError(conversation.getAccount(), (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
- callback.error(R.string.openpgp_error, message);
- break;
- }
- }
- });
- } else {
- try {
- DownloadableFile inputFile = this.mXmppConnectionService
- .getFileBackend().getFile(message, true);
- DownloadableFile outputFile = this.mXmppConnectionService
- .getFileBackend().getFile(message, false);
- outputFile.getParentFile().mkdirs();
- outputFile.createNewFile();
- final InputStream is = new FileInputStream(inputFile);
- final OutputStream os = new FileOutputStream(outputFile);
- api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
+ break;
+ case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
+ callback.userInputRequried((PendingIntent) result
+ .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
+ message);
+ break;
+ case OpenPgpApi.RESULT_CODE_ERROR:
+ logError(conversation.getAccount(), (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
+ callback.error(R.string.openpgp_error, message);
+ break;
+ }
+ }
+ });
+ } else {
+ try {
+ DownloadableFile inputFile = this.mXmppConnectionService
+ .getFileBackend().getFile(message, true);
+ DownloadableFile outputFile = this.mXmppConnectionService
+ .getFileBackend().getFile(message, false);
+ outputFile.getParentFile().mkdirs();
+ outputFile.createNewFile();
+ final InputStream is = new FileInputStream(inputFile);
+ final OutputStream os = new FileOutputStream(outputFile);
+ api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
- @Override
- public void onReturn(Intent result) {
- switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
- OpenPgpApi.RESULT_CODE_ERROR)) {
- case OpenPgpApi.RESULT_CODE_SUCCESS:
- try {
- os.flush();
- } catch (IOException ignored) {
- //ignored
- }
- FileBackend.close(os);
- callback.success(message);
- break;
- case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- callback.userInputRequried(
- (PendingIntent) result
- .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
- message);
- break;
- case OpenPgpApi.RESULT_CODE_ERROR:
- logError(conversation.getAccount(), (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
- callback.error(R.string.openpgp_error, message);
- break;
- }
- }
- });
- } catch (final IOException e) {
- callback.error(R.string.openpgp_error, message);
- }
- }
- }
+ @Override
+ public void onReturn(Intent result) {
+ switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
+ OpenPgpApi.RESULT_CODE_ERROR)) {
+ case OpenPgpApi.RESULT_CODE_SUCCESS:
+ try {
+ os.flush();
+ } catch (IOException ignored) {
+ //ignored
+ }
+ FileBackend.close(os);
+ callback.success(message);
+ break;
+ case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
+ callback.userInputRequried(
+ (PendingIntent) result
+ .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
+ message);
+ break;
+ case OpenPgpApi.RESULT_CODE_ERROR:
+ logError(conversation.getAccount(), (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
+ callback.error(R.string.openpgp_error, message);
+ break;
+ }
+ }
+ });
+ } catch (final IOException e) {
+ callback.error(R.string.openpgp_error, message);
+ }
+ }
+ }
- public long fetchKeyId(Account account, String status, String signature) {
- if ((signature == null) || (api == null)) {
- return 0;
- }
- if (status == null) {
- status = "";
- }
- final StringBuilder pgpSig = new StringBuilder();
- pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
- pgpSig.append('\n');
- pgpSig.append('\n');
- pgpSig.append(status);
- pgpSig.append('\n');
- pgpSig.append("-----BEGIN PGP SIGNATURE-----");
- pgpSig.append('\n');
- pgpSig.append('\n');
- pgpSig.append(signature.replace("\n", "").trim());
- pgpSig.append('\n');
- pgpSig.append("-----END PGP SIGNATURE-----");
- Intent params = new Intent();
- params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
- params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
- InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- Intent result = api.executeApi(params, is, os);
- switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
- OpenPgpApi.RESULT_CODE_ERROR)) {
- case OpenPgpApi.RESULT_CODE_SUCCESS:
- OpenPgpSignatureResult sigResult = result
- .getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
- if (sigResult != null) {
- return sigResult.getKeyId();
- } else {
- return 0;
- }
- case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- return 0;
- case OpenPgpApi.RESULT_CODE_ERROR:
- logError(account, (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
- return 0;
- }
- return 0;
- }
+ public long fetchKeyId(Account account, String status, String signature) {
+ if ((signature == null) || (api == null)) {
+ return 0;
+ }
+ if (status == null) {
+ status = "";
+ }
+ final StringBuilder pgpSig = new StringBuilder();
+ pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
+ pgpSig.append('\n');
+ pgpSig.append('\n');
+ pgpSig.append(status);
+ pgpSig.append('\n');
+ pgpSig.append("-----BEGIN PGP SIGNATURE-----");
+ pgpSig.append('\n');
+ pgpSig.append('\n');
+ pgpSig.append(signature.replace("\n", "").trim());
+ pgpSig.append('\n');
+ pgpSig.append("-----END PGP SIGNATURE-----");
+ Intent params = new Intent();
+ params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
+ params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
+ InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ Intent result = api.executeApi(params, is, os);
+ switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
+ OpenPgpApi.RESULT_CODE_ERROR)) {
+ case OpenPgpApi.RESULT_CODE_SUCCESS:
+ OpenPgpSignatureResult sigResult = result
+ .getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
+ if (sigResult != null) {
+ return sigResult.getKeyId();
+ } else {
+ return 0;
+ }
+ case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
+ return 0;
+ case OpenPgpApi.RESULT_CODE_ERROR:
+ logError(account, (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
+ return 0;
+ }
+ return 0;
+ }
- public void chooseKey(final Account account, final UiCallback<Account> callback) {
- Intent p = new Intent();
- p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
- api.executeApiAsync(p, null, null, new IOpenPgpCallback() {
+ public void chooseKey(final Account account, final UiCallback<Account> callback) {
+ Intent p = new Intent();
+ p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
+ api.executeApiAsync(p, null, null, new IOpenPgpCallback() {
- @Override
- public void onReturn(Intent result) {
- switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
- case OpenPgpApi.RESULT_CODE_SUCCESS:
- callback.success(account);
- return;
- case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- callback.userInputRequried((PendingIntent) result
- .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
- account);
- return;
- case OpenPgpApi.RESULT_CODE_ERROR:
- logError(account, (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
- callback.error(R.string.openpgp_error, account);
- }
- }
- });
- }
+ @Override
+ public void onReturn(Intent result) {
+ switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
+ case OpenPgpApi.RESULT_CODE_SUCCESS:
+ callback.success(account);
+ return;
+ case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
+ callback.userInputRequried((PendingIntent) result
+ .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
+ account);
+ return;
+ case OpenPgpApi.RESULT_CODE_ERROR:
+ logError(account, (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
+ callback.error(R.string.openpgp_error, account);
+ }
+ }
+ });
+ }
- public void generateSignature(final Account account, String status,
- final UiCallback<Account> callback) {
- if (account.getPgpId() == 0) {
- return;
- }
- Intent params = new Intent();
- params.setAction(OpenPgpApi.ACTION_CLEARTEXT_SIGN);
- params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
- params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId());
- InputStream is = new ByteArrayInputStream(status.getBytes());
- final OutputStream os = new ByteArrayOutputStream();
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": signing status message \""+status+"\"");
- api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
+ public void generateSignature(final Account account, String status,
+ final UiCallback<Account> callback) {
+ if (account.getPgpId() == 0) {
+ return;
+ }
+ Intent params = new Intent();
+ params.setAction(OpenPgpApi.ACTION_CLEARTEXT_SIGN);
+ params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
+ params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId());
+ InputStream is = new ByteArrayInputStream(status.getBytes());
+ final OutputStream os = new ByteArrayOutputStream();
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": signing status message \"" + status + "\"");
+ api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
- @Override
- public void onReturn(Intent result) {
- switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
- case OpenPgpApi.RESULT_CODE_SUCCESS:
- StringBuilder signatureBuilder = new StringBuilder();
- try {
- os.flush();
- String[] lines = os.toString().split("\n");
- boolean sig = false;
- for (String line : lines) {
- if (sig) {
- if (line.contains("END PGP SIGNATURE")) {
- sig = false;
- } else {
- if (!line.contains("Version")) {
- signatureBuilder.append(line.trim());
- }
- }
- }
- if (line.contains("BEGIN PGP SIGNATURE")) {
- sig = true;
- }
- }
- } catch (IOException e) {
- callback.error(R.string.openpgp_error, account);
- return;
- }
- account.setPgpSignature(signatureBuilder.toString());
- callback.success(account);
- return;
- case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- callback.userInputRequried((PendingIntent) result
- .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
- account);
- return;
- case OpenPgpApi.RESULT_CODE_ERROR:
- OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
- if (error != null && "signing subkey not found!".equals(error.getMessage())) {
- callback.error(0,account);
- } else {
- logError(account, error);
- callback.error(R.string.unable_to_connect_to_keychain, null);
- }
+ @Override
+ public void onReturn(Intent result) {
+ switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
+ case OpenPgpApi.RESULT_CODE_SUCCESS:
+ StringBuilder signatureBuilder = new StringBuilder();
+ try {
+ os.flush();
+ String[] lines = os.toString().split("\n");
+ boolean sig = false;
+ for (String line : lines) {
+ if (sig) {
+ if (line.contains("END PGP SIGNATURE")) {
+ sig = false;
+ } else {
+ if (!line.contains("Version")) {
+ signatureBuilder.append(line.trim());
+ }
+ }
+ }
+ if (line.contains("BEGIN PGP SIGNATURE")) {
+ sig = true;
+ }
+ }
+ } catch (IOException e) {
+ callback.error(R.string.openpgp_error, account);
+ return;
+ }
+ account.setPgpSignature(signatureBuilder.toString());
+ callback.success(account);
+ return;
+ case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
+ callback.userInputRequried((PendingIntent) result
+ .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
+ account);
+ return;
+ case OpenPgpApi.RESULT_CODE_ERROR:
+ OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
+ if (error != null && "signing subkey not found!".equals(error.getMessage())) {
+ callback.error(0, account);
+ } else {
+ logError(account, error);
+ callback.error(R.string.unable_to_connect_to_keychain, null);
+ }
}
- }
- });
- }
+ }
+ });
+ }
- public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
- Intent params = new Intent();
- params.setAction(OpenPgpApi.ACTION_GET_KEY);
- params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
- api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
+ public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
+ Intent params = new Intent();
+ params.setAction(OpenPgpApi.ACTION_GET_KEY);
+ params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
+ api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
- @Override
- public void onReturn(Intent result) {
- switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
- case OpenPgpApi.RESULT_CODE_SUCCESS:
- callback.success(contact);
- return;
- case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- callback.userInputRequried((PendingIntent) result
- .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
- contact);
- return;
- case OpenPgpApi.RESULT_CODE_ERROR:
- logError(contact.getAccount(), (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
- callback.error(R.string.openpgp_error, contact);
+ @Override
+ public void onReturn(Intent result) {
+ switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
+ case OpenPgpApi.RESULT_CODE_SUCCESS:
+ callback.success(contact);
+ return;
+ case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
+ callback.userInputRequried((PendingIntent) result
+ .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
+ contact);
+ return;
+ case OpenPgpApi.RESULT_CODE_ERROR:
+ logError(contact.getAccount(), (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
+ callback.error(R.string.openpgp_error, contact);
}
- }
- });
- }
+ }
+ });
+ }
- private static void logError(Account account, OpenPgpError error) {
- if (error != null) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": OpenKeychain error '"+error.getMessage()+"' code="+error.getErrorId());
- } else {
- Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": OpenKeychain error with no message");
- }
- }
+ private static void logError(Account account, OpenPgpError error) {
+ if (error != null) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": OpenKeychain error '" + error.getMessage() + "' code=" + error.getErrorId());
+ } else {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": OpenKeychain error with no message");
+ }
+ }
- public PendingIntent getIntentForKey(Contact contact) {
- return getIntentForKey(contact.getPgpKeyId());
- }
+ public PendingIntent getIntentForKey(Contact contact) {
+ return getIntentForKey(contact.getPgpKeyId());
+ }
- public PendingIntent getIntentForKey(long pgpKeyId) {
- Intent params = new Intent();
- params.setAction(OpenPgpApi.ACTION_GET_KEY);
- params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
- Intent result = api.executeApi(params, null, null);
- return (PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
- }
+ public PendingIntent getIntentForKey(long pgpKeyId) {
+ Intent params = new Intent();
+ params.setAction(OpenPgpApi.ACTION_GET_KEY);
+ params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
+ Intent result = api.executeApi(params, null, null);
+ return (PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
+ }
}
diff --git a/src/main/java/de/pixart/messenger/crypto/XmppDomainVerifier.java b/src/main/java/de/pixart/messenger/crypto/XmppDomainVerifier.java
index 250516daa..d305a33d5 100644
--- a/src/main/java/de/pixart/messenger/crypto/XmppDomainVerifier.java
+++ b/src/main/java/de/pixart/messenger/crypto/XmppDomainVerifier.java
@@ -26,102 +26,102 @@ import javax.net.ssl.SSLSession;
public class XmppDomainVerifier implements HostnameVerifier {
- private static final String LOGTAG = "XmppDomainVerifier";
+ private static final String LOGTAG = "XmppDomainVerifier";
- private final String SRVName = "1.3.6.1.5.5.7.8.7";
- private final String xmppAddr = "1.3.6.1.5.5.7.8.5";
+ private final String SRVName = "1.3.6.1.5.5.7.8.7";
+ private final String xmppAddr = "1.3.6.1.5.5.7.8.5";
- @Override
- public boolean verify(String domain, SSLSession sslSession) {
- try {
- Certificate[] chain = sslSession.getPeerCertificates();
- if (chain.length == 0 || !(chain[0] instanceof X509Certificate)) {
- return false;
- }
- X509Certificate certificate = (X509Certificate) chain[0];
- Collection<List<?>> alternativeNames = certificate.getSubjectAlternativeNames();
- List<String> xmppAddrs = new ArrayList<>();
- List<String> srvNames = new ArrayList<>();
- List<String> domains = new ArrayList<>();
- if (alternativeNames != null) {
- for (List<?> san : alternativeNames) {
- Integer type = (Integer) san.get(0);
- if (type == 0) {
- Pair<String, String> otherName = parseOtherName((byte[]) san.get(1));
- if (otherName != null) {
- switch (otherName.first) {
- case SRVName:
- srvNames.add(otherName.second);
- break;
- case xmppAddr:
- xmppAddrs.add(otherName.second);
- break;
- default:
- Log.d(LOGTAG, "oid: " + otherName.first + " value: " + otherName.second);
- }
- }
- } else if (type == 2) {
- Object value = san.get(1);
- if (value instanceof String) {
- domains.add((String) value);
- }
- }
- }
- }
- if (srvNames.size() == 0 && xmppAddrs.size() == 0 && domains.size() == 0) {
- X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject();
- RDN[] rdns = x500name.getRDNs(BCStyle.CN);
- for (int i = 0; i < rdns.length; ++i) {
- domains.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[i].getFirst().getValue()));
- }
- }
- Log.d(LOGTAG, "searching for " + domain + " in srvNames: " + srvNames + " xmppAddrs: " + xmppAddrs + " domains:" + domains);
- return xmppAddrs.contains(domain) || srvNames.contains("_xmpp-client." + domain) || matchDomain(domain, domains);
- } catch (Exception e) {
- return false;
- }
- }
+ @Override
+ public boolean verify(String domain, SSLSession sslSession) {
+ try {
+ Certificate[] chain = sslSession.getPeerCertificates();
+ if (chain.length == 0 || !(chain[0] instanceof X509Certificate)) {
+ return false;
+ }
+ X509Certificate certificate = (X509Certificate) chain[0];
+ Collection<List<?>> alternativeNames = certificate.getSubjectAlternativeNames();
+ List<String> xmppAddrs = new ArrayList<>();
+ List<String> srvNames = new ArrayList<>();
+ List<String> domains = new ArrayList<>();
+ if (alternativeNames != null) {
+ for (List<?> san : alternativeNames) {
+ Integer type = (Integer) san.get(0);
+ if (type == 0) {
+ Pair<String, String> otherName = parseOtherName((byte[]) san.get(1));
+ if (otherName != null) {
+ switch (otherName.first) {
+ case SRVName:
+ srvNames.add(otherName.second);
+ break;
+ case xmppAddr:
+ xmppAddrs.add(otherName.second);
+ break;
+ default:
+ Log.d(LOGTAG, "oid: " + otherName.first + " value: " + otherName.second);
+ }
+ }
+ } else if (type == 2) {
+ Object value = san.get(1);
+ if (value instanceof String) {
+ domains.add((String) value);
+ }
+ }
+ }
+ }
+ if (srvNames.size() == 0 && xmppAddrs.size() == 0 && domains.size() == 0) {
+ X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject();
+ RDN[] rdns = x500name.getRDNs(BCStyle.CN);
+ for (int i = 0; i < rdns.length; ++i) {
+ domains.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[i].getFirst().getValue()));
+ }
+ }
+ Log.d(LOGTAG, "searching for " + domain + " in srvNames: " + srvNames + " xmppAddrs: " + xmppAddrs + " domains:" + domains);
+ return xmppAddrs.contains(domain) || srvNames.contains("_xmpp-client." + domain) || matchDomain(domain, domains);
+ } catch (Exception e) {
+ return false;
+ }
+ }
- private static Pair<String, String> parseOtherName(byte[] otherName) {
- try {
- ASN1Primitive asn1Primitive = ASN1Primitive.fromByteArray(otherName);
- if (asn1Primitive instanceof DERTaggedObject) {
- ASN1Primitive inner = ((DERTaggedObject) asn1Primitive).getObject();
- if (inner instanceof DLSequence) {
- DLSequence sequence = (DLSequence) inner;
- if (sequence.size() >= 2 && sequence.getObjectAt(1) instanceof DERTaggedObject) {
- String oid = sequence.getObjectAt(0).toString();
- ASN1Primitive value = ((DERTaggedObject) sequence.getObjectAt(1)).getObject();
- if (value instanceof DERUTF8String) {
- return new Pair<>(oid, ((DERUTF8String) value).getString());
- } else if (value instanceof DERIA5String) {
- return new Pair<>(oid, ((DERIA5String) value).getString());
- }
- }
- }
- }
- return null;
- } catch (IOException e) {
- return null;
- }
- }
+ private static Pair<String, String> parseOtherName(byte[] otherName) {
+ try {
+ ASN1Primitive asn1Primitive = ASN1Primitive.fromByteArray(otherName);
+ if (asn1Primitive instanceof DERTaggedObject) {
+ ASN1Primitive inner = ((DERTaggedObject) asn1Primitive).getObject();
+ if (inner instanceof DLSequence) {
+ DLSequence sequence = (DLSequence) inner;
+ if (sequence.size() >= 2 && sequence.getObjectAt(1) instanceof DERTaggedObject) {
+ String oid = sequence.getObjectAt(0).toString();
+ ASN1Primitive value = ((DERTaggedObject) sequence.getObjectAt(1)).getObject();
+ if (value instanceof DERUTF8String) {
+ return new Pair<>(oid, ((DERUTF8String) value).getString());
+ } else if (value instanceof DERIA5String) {
+ return new Pair<>(oid, ((DERIA5String) value).getString());
+ }
+ }
+ }
+ }
+ return null;
+ } catch (IOException e) {
+ return null;
+ }
+ }
- private static boolean matchDomain(String needle, List<String> haystack) {
- for (String entry : haystack) {
- if (entry.startsWith("*.")) {
- int i = needle.indexOf('.');
- Log.d(LOGTAG, "comparing " + needle.substring(i) + " and " + entry.substring(1));
- if (i != -1 && needle.substring(i).equals(entry.substring(1))) {
- Log.d(LOGTAG, "domain " + needle + " matched " + entry);
- return true;
- }
- } else {
- if (entry.equals(needle)) {
- Log.d(LOGTAG, "domain " + needle + " matched " + entry);
- return true;
- }
- }
- }
- return false;
- }
+ private static boolean matchDomain(String needle, List<String> haystack) {
+ for (String entry : haystack) {
+ if (entry.startsWith("*.")) {
+ int i = needle.indexOf('.');
+ Log.d(LOGTAG, "comparing " + needle.substring(i) + " and " + entry.substring(1));
+ if (i != -1 && needle.substring(i).equals(entry.substring(1))) {
+ Log.d(LOGTAG, "domain " + needle + " matched " + entry);
+ return true;
+ }
+ } else {
+ if (entry.equals(needle)) {
+ Log.d(LOGTAG, "domain " + needle + " matched " + entry);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
}
diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java b/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java
index 532fbe470..1ed410fdb 100644
--- a/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java
+++ b/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java
@@ -51,1049 +51,1049 @@ import de.pixart.messenger.xmpp.stanzas.IqPacket;
public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
- public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl";
- public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist";
- public static final String PEP_DEVICE_LIST_NOTIFY = PEP_DEVICE_LIST + "+notify";
- public static final String PEP_BUNDLES = PEP_PREFIX + ".bundles";
- public static final String PEP_VERIFICATION = PEP_PREFIX + ".verification";
-
- public static final String LOGPREFIX = "AxolotlService";
-
- public static final int NUM_KEYS_TO_PUBLISH = 100;
- public static final int publishTriesThreshold = 3;
-
- private final Account account;
- private final XmppConnectionService mXmppConnectionService;
- private final SQLiteAxolotlStore axolotlStore;
- private final SessionMap sessions;
- private final Map<Jid, Set<Integer>> deviceIds;
- private final Map<String, XmppAxolotlMessage> messageCache;
- private final FetchStatusMap fetchStatusMap;
- private final SerialSingleThreadExecutor executor;
- private int numPublishTriesOnEmptyPep = 0;
- private boolean pepBroken = false;
-
- @Override
- public void onAdvancedStreamFeaturesAvailable(Account account) {
- if (Config.supportOmemo()
- && account.getXmppConnection() != null
- && account.getXmppConnection().getFeatures().pep()) {
- publishBundlesIfNeeded(true, false);
- } else {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": skipping OMEMO initialization");
- }
- }
-
- public boolean fetchMapHasErrors(List<Jid> jids) {
- for(Jid jid : jids) {
- if (deviceIds.get(jid) != null) {
- for (Integer foreignId : this.deviceIds.get(jid)) {
- AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
- if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- public void preVerifyFingerprint(Contact contact, String fingerprint) {
- axolotlStore.preVerifyFingerprint(contact.getAccount(), contact.getJid().toBareJid().toPreppedString(), fingerprint);
- }
-
- public void preVerifyFingerprint(Account account, String fingerprint) {
- axolotlStore.preVerifyFingerprint(account, account.getJid().toBareJid().toPreppedString(), fingerprint);
- }
-
- private static class AxolotlAddressMap<T> {
- protected Map<String, Map<Integer, T>> map;
- protected final Object MAP_LOCK = new Object();
-
- public AxolotlAddressMap() {
- this.map = new HashMap<>();
- }
-
- public void put(AxolotlAddress address, T value) {
- synchronized (MAP_LOCK) {
- Map<Integer, T> devices = map.get(address.getName());
- if (devices == null) {
- devices = new HashMap<>();
- map.put(address.getName(), devices);
- }
- devices.put(address.getDeviceId(), value);
- }
- }
-
- public T get(AxolotlAddress address) {
- synchronized (MAP_LOCK) {
- Map<Integer, T> devices = map.get(address.getName());
- if (devices == null) {
- return null;
- }
- return devices.get(address.getDeviceId());
- }
- }
-
- public Map<Integer, T> getAll(AxolotlAddress address) {
- synchronized (MAP_LOCK) {
- Map<Integer, T> devices = map.get(address.getName());
- if (devices == null) {
- return new HashMap<>();
- }
- return devices;
- }
- }
-
- public boolean hasAny(AxolotlAddress address) {
- synchronized (MAP_LOCK) {
- Map<Integer, T> devices = map.get(address.getName());
- return devices != null && !devices.isEmpty();
- }
- }
-
- public void clear() {
- map.clear();
- }
-
- }
-
- private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> {
- private final XmppConnectionService xmppConnectionService;
- private final Account account;
-
- public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) {
- super();
- this.xmppConnectionService = service;
- this.account = account;
- this.fillMap(store);
- }
-
- private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) {
- for (Integer deviceId : deviceIds) {
- AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId);
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building session for remote address: " + axolotlAddress.toString());
- IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
- if(Config.X509_VERIFICATION) {
- X509Certificate certificate = store.getFingerprintCertificate(identityKey.getFingerprint().replaceAll("\\s", ""));
- if (certificate != null) {
- Bundle information = CryptoHelper.extractCertificateInformation(certificate);
- try {
- final String cn = information.getString("subject_cn");
- final Jid jid = Jid.fromString(bareJid);
- Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
- account.getRoster().getContact(jid).setCommonName(cn);
- } catch (final InvalidJidException ignored) {
- //ignored
- }
- }
- }
- this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey));
- }
- }
-
- private void fillMap(SQLiteAxolotlStore store) {
- List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toPreppedString());
- putDevicesForJid(account.getJid().toBareJid().toPreppedString(), deviceIds, store);
- for (Contact contact : account.getRoster().getContacts()) {
- Jid bareJid = contact.getJid().toBareJid();
- String address = bareJid.toString();
- deviceIds = store.getSubDeviceSessions(address);
- putDevicesForJid(address, deviceIds, store);
- }
-
- }
-
- @Override
- public void put(AxolotlAddress address, XmppAxolotlSession value) {
- super.put(address, value);
- value.setNotFresh();
- xmppConnectionService.syncRosterToDisk(account); //TODO why?
- }
-
- public void put(XmppAxolotlSession session) {
- this.put(session.getRemoteAddress(), session);
- }
- }
-
- public enum FetchStatus {
- PENDING,
- SUCCESS,
- SUCCESS_VERIFIED,
- TIMEOUT,
- ERROR
- }
-
- private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> {
-
- public void clearErrorFor(Jid jid) {
- synchronized (MAP_LOCK) {
- Map<Integer, FetchStatus> devices = this.map.get(jid.toBareJid().toPreppedString());
- if (devices == null) {
- return;
- }
- for(Map.Entry<Integer, FetchStatus> entry : devices.entrySet()) {
- if (entry.getValue() == FetchStatus.ERROR) {
- Log.d(Config.LOGTAG,"resetting error for "+jid.toBareJid()+"("+entry.getKey()+")");
- entry.setValue(FetchStatus.TIMEOUT);
- }
- }
- }
- }
- }
-
- public static String getLogprefix(Account account) {
- return LOGPREFIX + " (" + account.getJid().toBareJid().toString() + "): ";
- }
-
- public AxolotlService(Account account, XmppConnectionService connectionService) {
- if (Security.getProvider("BC") == null) {
- Security.addProvider(new BouncyCastleProvider());
- }
- this.mXmppConnectionService = connectionService;
- this.account = account;
- this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
- this.deviceIds = new HashMap<>();
- this.messageCache = new HashMap<>();
- this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account);
- this.fetchStatusMap = new FetchStatusMap();
- this.executor = new SerialSingleThreadExecutor();
- }
-
- public String getOwnFingerprint() {
- return axolotlStore.getIdentityKeyPair().getPublicKey().getFingerprint().replaceAll("\\s", "");
- }
-
- public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status) {
- return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toPreppedString(), status);
- }
-
- public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status, Jid jid) {
- return axolotlStore.getContactKeysWithTrust(jid.toBareJid().toPreppedString(), status);
- }
-
- public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status, List<Jid> jids) {
- Set<IdentityKey> keys = new HashSet<>();
- for(Jid jid : jids) {
- keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toPreppedString(), status));
- }
- return keys;
- }
-
- public long getNumTrustedKeys(Jid jid) {
- return axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toPreppedString());
- }
-
- public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) {
- for(Jid jid : jids) {
- if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toPreppedString()) == 0) {
- return true;
- }
- }
- return false;
- }
-
- private AxolotlAddress getAddressForJid(Jid jid) {
- return new AxolotlAddress(jid.toPreppedString(), 0);
- }
-
- public Set<XmppAxolotlSession> findOwnSessions() {
- AxolotlAddress ownAddress = getAddressForJid(account.getJid().toBareJid());
- return new HashSet<>(this.sessions.getAll(ownAddress).values());
- }
-
- private Set<XmppAxolotlSession> findSessionsForContact(Contact contact) {
- AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
- return new HashSet<>(this.sessions.getAll(contactAddress).values());
- }
-
- private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) {
- HashSet<XmppAxolotlSession> sessions = new HashSet<>();
- for(Jid jid : conversation.getAcceptedCryptoTargets()) {
- sessions.addAll(this.sessions.getAll(getAddressForJid(jid)).values());
- }
- return sessions;
- }
-
- public Set<String> getFingerprintsForOwnSessions() {
- Set<String> fingerprints = new HashSet<>();
- for (XmppAxolotlSession session : findOwnSessions()) {
- fingerprints.add(session.getFingerprint());
- }
- return fingerprints;
- }
-
- public Set<String> getFingerprintsForContact(final Contact contact) {
- Set<String> fingerprints = new HashSet<>();
- for (XmppAxolotlSession session : findSessionsForContact(contact)) {
- fingerprints.add(session.getFingerprint());
- }
- return fingerprints;
- }
-
- private boolean hasAny(Jid jid) {
- return sessions.hasAny(getAddressForJid(jid));
- }
-
- public boolean isPepBroken() {
- return this.pepBroken;
- }
-
- public void resetBrokenness() {
- this.pepBroken = false;
- numPublishTriesOnEmptyPep = 0;
- }
-
- public void clearErrorsInFetchStatusMap(Jid jid) {
- fetchStatusMap.clearErrorFor(jid);
- }
-
- public void regenerateKeys(boolean wipeOther) {
- axolotlStore.regenerate();
- sessions.clear();
- fetchStatusMap.clear();
- publishBundlesIfNeeded(true, wipeOther);
- }
-
- public int getOwnDeviceId() {
- return axolotlStore.getLocalRegistrationId();
- }
-
- public Set<Integer> getOwnDeviceIds() {
- return this.deviceIds.get(account.getJid().toBareJid());
- }
-
- public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) {
- if (jid.toBareJid().equals(account.getJid().toBareJid())) {
- if (!deviceIds.isEmpty()) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Received non-empty own device list. Resetting publish attempts and pepBroken status.");
- pepBroken = false;
- numPublishTriesOnEmptyPep = 0;
- }
- if (deviceIds.contains(getOwnDeviceId())) {
- deviceIds.remove(getOwnDeviceId());
- } else {
- publishOwnDeviceId(deviceIds);
- }
- for (Integer deviceId : deviceIds) {
- AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId);
- if (sessions.get(ownDeviceAddress) == null) {
- buildSessionFromPEP(ownDeviceAddress);
- }
- }
- }
- Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toPreppedString()));
- expiredDevices.removeAll(deviceIds);
- for (Integer deviceId : expiredDevices) {
- AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId);
- XmppAxolotlSession session = sessions.get(address);
- if (session != null && session.getFingerprint() != null) {
- if (session.getTrust().isActive()) {
- session.setTrust(session.getTrust().toInactive());
- }
- }
- }
- Set<Integer> newDevices = new HashSet<>(deviceIds);
- for (Integer deviceId : newDevices) {
- AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId);
- XmppAxolotlSession session = sessions.get(address);
- if (session != null && session.getFingerprint() != null) {
- if (!session.getTrust().isActive()) {
- session.setTrust(session.getTrust().toActive());
- }
- }
- }
- this.deviceIds.put(jid, deviceIds);
- mXmppConnectionService.keyStatusUpdated(null);
- }
-
- public void wipeOtherPepDevices() {
- if (pepBroken) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "wipeOtherPepDevices called, but PEP is broken. Ignoring... ");
- return;
- }
- Set<Integer> deviceIds = new HashSet<>();
- deviceIds.add(getOwnDeviceId());
- IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds);
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Wiping all other devices from Pep:" + publish);
- mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- // TODO: implement this!
- }
- });
- }
-
- public void purgeKey(final String fingerprint) {
- axolotlStore.setFingerprintStatus(fingerprint.replaceAll("\\s", ""), FingerprintStatus.createCompromised());
- }
-
- public void publishOwnDeviceIdIfNeeded() {
- if (pepBroken) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
- return;
- }
- IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid());
- mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids.");
- } else {
- Element item = mXmppConnectionService.getIqParser().getItem(packet);
- Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
- if (!deviceIds.contains(getOwnDeviceId())) {
- publishOwnDeviceId(deviceIds);
- }
- }
- }
- });
- }
-
- public void publishOwnDeviceId(Set<Integer> deviceIds) {
- Set<Integer> deviceIdsCopy = new HashSet<>(deviceIds);
- if (!deviceIdsCopy.contains(getOwnDeviceId())) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Own device " + getOwnDeviceId() + " not in PEP devicelist.");
- if (deviceIdsCopy.isEmpty()) {
- if (numPublishTriesOnEmptyPep >= publishTriesThreshold) {
- Log.w(Config.LOGTAG, getLogprefix(account) + "Own device publish attempt threshold exceeded, aborting...");
- pepBroken = true;
- return;
- } else {
- numPublishTriesOnEmptyPep++;
- Log.w(Config.LOGTAG, getLogprefix(account) + "Own device list empty, attempting to publish (try " + numPublishTriesOnEmptyPep + ")");
- }
- } else {
- numPublishTriesOnEmptyPep = 0;
- }
- deviceIdsCopy.add(getOwnDeviceId());
- IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy);
- mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.ERROR) {
- pepBroken = true;
- Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error"));
- }
- }
- });
- }
- }
-
- public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord,
- final Set<PreKeyRecord> preKeyRecords,
- final boolean announceAfter,
- final boolean wipe) {
- try {
- IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey();
- PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
- X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias());
- Signature verifier = Signature.getInstance("sha256WithRSA");
- verifier.initSign(x509PrivateKey,mXmppConnectionService.getRNG());
- verifier.update(axolotlPublicKey.serialize());
- byte[] signature = verifier.sign();
- IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device "+getOwnDeviceId());
- mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
- }
- });
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) {
- if (pepBroken) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... ");
- return;
- }
- IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), getOwnDeviceId());
- mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
-
- if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
- return; //ignore timeout. do nothing
- }
-
- if (packet.getType() == IqPacket.TYPE.ERROR) {
- Element error = packet.findChild("error");
- if (error == null || !error.hasChild("item-not-found")) {
- pepBroken = true;
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet);
- return;
- }
- }
-
- PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet);
- Map<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet);
- boolean flush = false;
- if (bundle == null) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + packet);
- bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
- flush = true;
- }
- if (keys == null) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + packet);
- }
- try {
- boolean changed = false;
- // Validate IdentityKey
- IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
- if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP.");
- changed = true;
- }
-
- // Validate signedPreKeyRecord + ID
- SignedPreKeyRecord signedPreKeyRecord;
- int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size();
- try {
- signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
- if (flush
- || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
- || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
- signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
- axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
- changed = true;
- }
- } catch (InvalidKeyIdException e) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
- signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
- axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
- changed = true;
- }
-
- // Validate PreKeys
- Set<PreKeyRecord> preKeyRecords = new HashSet<>();
- if (keys != null) {
- for (Integer id : keys.keySet()) {
- try {
- PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id);
- if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) {
- preKeyRecords.add(preKeyRecord);
- }
- } catch (InvalidKeyIdException ignored) {
- }
- }
- }
- int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
- if (newKeys > 0) {
- List<PreKeyRecord> newRecords = KeyHelper.generatePreKeys(
- axolotlStore.getCurrentPreKeyId() + 1, newKeys);
- preKeyRecords.addAll(newRecords);
- for (PreKeyRecord record : newRecords) {
- axolotlStore.storePreKey(record.getId(), record);
- }
- changed = true;
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP.");
- }
-
-
- if (changed) {
- if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) {
- mXmppConnectionService.publishDisplayName(account);
- publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
- } else {
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
- }
- } else {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current");
- if (wipe) {
- wipeOtherPepDevices();
- } else if (announce) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
- publishOwnDeviceIdIfNeeded();
- }
- }
- } catch (InvalidKeyException e) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
- }
- }
- });
- }
-
- private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord,
- Set<PreKeyRecord> preKeyRecords,
- final boolean announceAfter,
- final boolean wipe) {
- IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
- signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
- preKeyRecords, getOwnDeviceId());
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish);
- mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
- if (wipe) {
- wipeOtherPepDevices();
- } else if (announceAfter) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
- publishOwnDeviceIdIfNeeded();
- }
- } else if (packet.getType() == IqPacket.TYPE.ERROR) {
- pepBroken = true;
- Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error"));
- }
- }
- });
- }
-
- public enum AxolotlCapability {
- FULL,
- MISSING_PRESENCE,
- MISSING_KEYS,
- WRONG_CONFIGURATION,
- NO_MEMBERS
- }
-
- public boolean isConversationAxolotlCapable(Conversation conversation) {
- return isConversationAxolotlCapableDetailed(conversation).first == AxolotlCapability.FULL;
- }
-
- public Pair<AxolotlCapability,Jid> isConversationAxolotlCapableDetailed(Conversation conversation) {
- if (conversation.getMode() == Conversation.MODE_SINGLE || (conversation.getMucOptions().membersOnly() && conversation.getMucOptions().nonanonymous())) {
- final List<Jid> jids = getCryptoTargets(conversation);
- for(Jid jid : jids) {
- if (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) {
- if (conversation.getAccount().getRoster().getContact(jid).mutualPresenceSubscription()) {
- return new Pair<>(AxolotlCapability.MISSING_KEYS,jid);
- } else {
- return new Pair<>(AxolotlCapability.MISSING_PRESENCE,jid);
- }
- }
- }
- if (jids.size() > 0) {
- return new Pair<>(AxolotlCapability.FULL, null);
- } else {
- return new Pair<>(AxolotlCapability.NO_MEMBERS, null);
- }
- } else {
- return new Pair<>(AxolotlCapability.WRONG_CONFIGURATION, null);
- }
- }
-
- public List<Jid> getCryptoTargets(Conversation conversation) {
- final List<Jid> jids;
- if (conversation.getMode() == Conversation.MODE_SINGLE) {
- jids = Arrays.asList(conversation.getJid().toBareJid());
- } else {
- jids = conversation.getMucOptions().getMembers();
- }
- return jids;
- }
-
- public FingerprintStatus getFingerprintTrust(String fingerprint) {
- return axolotlStore.getFingerprintStatus(fingerprint);
- }
-
- public X509Certificate getFingerprintCertificate(String fingerprint) {
- return axolotlStore.getFingerprintCertificate(fingerprint);
- }
-
- public void setFingerprintTrust(String fingerprint, FingerprintStatus status) {
- axolotlStore.setFingerprintStatus(fingerprint, status);
- }
-
- private void verifySessionWithPEP(final XmppAxolotlSession session) {
- Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
- final AxolotlAddress address = session.getRemoteAddress();
- final IdentityKey identityKey = session.getIdentityKey();
- try {
- IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.fromString(address.getName()), address.getDeviceId());
- mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- Pair<X509Certificate[],byte[]> verification = mXmppConnectionService.getIqParser().verification(packet);
- if (verification != null) {
- try {
- Signature verifier = Signature.getInstance("sha256WithRSA");
- verifier.initVerify(verification.first[0]);
- verifier.update(identityKey.serialize());
- if (verifier.verify(verification.second)) {
- try {
- mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
- String fingerprint = session.getFingerprint();
- Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: "+fingerprint);
- setFingerprintTrust(fingerprint, FingerprintStatus.createActiveVerified(true));
- axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
- fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
- Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
- try {
- final String cn = information.getString("subject_cn");
- final Jid jid = Jid.fromString(address.getName());
- Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
- account.getRoster().getContact(jid).setCommonName(cn);
- } catch (final InvalidJidException ignored) {
- //ignored
- }
- finishBuildingSessionsFromPEP(address);
- return;
- } catch (Exception e) {
- Log.d(Config.LOGTAG,"could not verify certificate");
- }
- }
- } catch (Exception e) {
- Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
- }
- } else {
- Log.d(Config.LOGTAG,"no verification found");
- }
- fetchStatusMap.put(address, FetchStatus.SUCCESS);
- finishBuildingSessionsFromPEP(address);
- }
- });
- } catch (InvalidJidException e) {
- fetchStatusMap.put(address, FetchStatus.SUCCESS);
- finishBuildingSessionsFromPEP(address);
- }
- }
-
- private void finishBuildingSessionsFromPEP(final AxolotlAddress address) {
- AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), 0);
- Map<Integer, FetchStatus> own = fetchStatusMap.getAll(ownAddress);
- Map<Integer, FetchStatus> remote = fetchStatusMap.getAll(address);
- if (!own.containsValue(FetchStatus.PENDING) && !remote.containsValue(FetchStatus.PENDING)) {
- FetchStatus report = null;
- if (own.containsValue(FetchStatus.SUCCESS) || remote.containsValue(FetchStatus.SUCCESS)) {
- report = FetchStatus.SUCCESS;
- } else if (own.containsValue(FetchStatus.SUCCESS_VERIFIED) || remote.containsValue(FetchStatus.SUCCESS_VERIFIED)) {
- report = FetchStatus.SUCCESS_VERIFIED;
- } else if (own.containsValue(FetchStatus.ERROR) || remote.containsValue(FetchStatus.ERROR)) {
- report = FetchStatus.ERROR;
- }
- mXmppConnectionService.keyStatusUpdated(report);
- }
- }
-
- private void buildSessionFromPEP(final AxolotlAddress address) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.toString());
- if (address.getDeviceId() == getOwnDeviceId()) {
- throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
- }
-
- try {
- IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(
- Jid.fromString(address.getName()), address.getDeviceId());
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket);
- mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() {
-
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
- fetchStatusMap.put(address, FetchStatus.TIMEOUT);
- } else if (packet.getType() == IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
- final IqParser parser = mXmppConnectionService.getIqParser();
- final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
- final PreKeyBundle bundle = parser.bundle(packet);
- if (preKeyBundleList.isEmpty() || bundle == null) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
- fetchStatusMap.put(address, FetchStatus.ERROR);
- finishBuildingSessionsFromPEP(address);
- return;
- }
- Random random = new Random();
- final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size()));
- if (preKey == null) {
- //should never happen
- fetchStatusMap.put(address, FetchStatus.ERROR);
- finishBuildingSessionsFromPEP(address);
- return;
- }
-
- final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(),
- preKey.getPreKeyId(), preKey.getPreKey(),
- bundle.getSignedPreKeyId(), bundle.getSignedPreKey(),
- bundle.getSignedPreKeySignature(), bundle.getIdentityKey());
-
- try {
- SessionBuilder builder = new SessionBuilder(axolotlStore, address);
- builder.process(preKeyBundle);
- XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
- sessions.put(address, session);
- if (Config.X509_VERIFICATION) {
- verifySessionWithPEP(session);
- } else {
- FingerprintStatus status = getFingerprintTrust(bundle.getIdentityKey().getFingerprint().replaceAll("\\s",""));
- boolean verified = status != null && status.isVerified();
- fetchStatusMap.put(address, verified ? FetchStatus.SUCCESS_VERIFIED : FetchStatus.SUCCESS);
- finishBuildingSessionsFromPEP(address);
- }
- } catch (UntrustedIdentityException | InvalidKeyException e) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
- + e.getClass().getName() + ", " + e.getMessage());
- fetchStatusMap.put(address, FetchStatus.ERROR);
- finishBuildingSessionsFromPEP(address);
- }
- } else {
- fetchStatusMap.put(address, FetchStatus.ERROR);
- Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error"));
- finishBuildingSessionsFromPEP(address);
- }
- }
- });
- } catch (InvalidJidException e) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got address with invalid jid: " + address.getName());
- }
- }
-
- public Set<AxolotlAddress> findDevicesWithoutSession(final Conversation conversation) {
- Set<AxolotlAddress> addresses = new HashSet<>();
- for(Jid jid : getCryptoTargets(conversation)) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid);
- if (deviceIds.get(jid) != null) {
- for (Integer foreignId : this.deviceIds.get(jid)) {
- AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
- if (sessions.get(address) == null) {
- IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
- if (identityKey != null) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
- XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
- sessions.put(address, session);
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + jid + ":" + foreignId);
- if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
- addresses.add(address);
- } else {
- Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
- }
- }
- }
- }
- } else {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
- }
- }
- if (deviceIds.get(account.getJid().toBareJid()) != null) {
- for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) {
- AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), ownId);
- if (sessions.get(address) == null) {
- IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
- if (identityKey != null) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
- XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
- sessions.put(address, session);
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId);
- if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
- addresses.add(address);
- } else {
- Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken");
- }
- }
- }
- }
- }
-
- return addresses;
- }
-
- public boolean createSessionsIfNeeded(final Conversation conversation) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed...");
- boolean newSessions = false;
- Set<AxolotlAddress> addresses = findDevicesWithoutSession(conversation);
- for (AxolotlAddress address : addresses) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString());
- FetchStatus status = fetchStatusMap.get(address);
- if (status == null || status == FetchStatus.TIMEOUT) {
- fetchStatusMap.put(address, FetchStatus.PENDING);
- this.buildSessionFromPEP(address);
- newSessions = true;
- } else if (status == FetchStatus.PENDING) {
- newSessions = true;
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString());
- }
- }
-
- return newSessions;
- }
-
- public boolean trustedSessionVerified(final Conversation conversation) {
- Set<XmppAxolotlSession> sessions = findSessionsForConversation(conversation);
- sessions.addAll(findOwnSessions());
- boolean verified = false;
- for(XmppAxolotlSession session : sessions) {
- if (session.getTrust().isTrustedAndActive()) {
- if (session.getTrust().getTrust() == FingerprintStatus.Trust.VERIFIED_X509) {
- verified = true;
- } else {
- return false;
- }
- }
- }
- return verified;
- }
-
- public boolean hasPendingKeyFetches(Account account, List<Jid> jids) {
- AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), 0);
- if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)) {
- return true;
- }
- for(Jid jid : jids) {
- AxolotlAddress foreignAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), 0);
- if (fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) {
- return true;
- }
- }
- return false;
- }
-
- @Nullable
- private XmppAxolotlMessage buildHeader(Conversation conversation) {
- final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(
- account.getJid().toBareJid(), getOwnDeviceId());
-
- Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(conversation);
- Set<XmppAxolotlSession> ownSessions = findOwnSessions();
- if (remoteSessions.isEmpty()) {
- return null;
- }
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl foreign keyElements...");
- for (XmppAxolotlSession session : remoteSessions) {
- Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
- axolotlMessage.addDevice(session);
- }
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl own keyElements...");
- for (XmppAxolotlSession session : ownSessions) {
- Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
- axolotlMessage.addDevice(session);
- }
-
- return axolotlMessage;
- }
-
- @Nullable
- public XmppAxolotlMessage encrypt(Message message) {
- XmppAxolotlMessage axolotlMessage = buildHeader(message.getConversation());
-
- if (axolotlMessage != null) {
- final String content;
- if (message.hasFileOnRemoteHost()) {
- content = message.getFileParams().url.toString();
- } else {
- content = message.getBody();
- }
- try {
- axolotlMessage.encrypt(content);
- } catch (CryptoFailedException e) {
- Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
- return null;
- }
- }
-
- return axolotlMessage;
- }
-
- public void preparePayloadMessage(final Message message, final boolean delay) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- XmppAxolotlMessage axolotlMessage = encrypt(message);
- if (axolotlMessage == null) {
- mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
- //mXmppConnectionService.updateConversationUi();
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
- messageCache.put(message.getUuid(), axolotlMessage);
- mXmppConnectionService.resendMessage(message, delay);
- }
- }
- });
- }
-
- public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- XmppAxolotlMessage axolotlMessage = buildHeader(conversation);
- onMessageCreatedCallback.run(axolotlMessage);
- }
- });
- }
-
- public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
- XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
- if (axolotlMessage != null) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid());
- messageCache.remove(message.getUuid());
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid());
- }
- return axolotlMessage;
- }
-
- private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) {
- IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
- return (identityKey != null)
- ? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
- : null;
- }
-
- private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
- AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(),
- message.getSenderDeviceId());
- 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);
- session = recreateUncachedSession(senderAddress);
- if (session == null) {
- session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
- }
- }
- return session;
- }
-
- public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) {
- XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
-
- XmppAxolotlSession session = getReceivingSession(message);
- try {
- plaintextMessage = message.decrypt(session, getOwnDeviceId());
- Integer preKeyId = session.getPreKeyId();
- if (preKeyId != null) {
- publishBundlesIfNeeded(false, false);
- session.resetPreKeyId();
- }
- } catch (CryptoFailedException e) {
- Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage());
- }
-
- if (session.isFresh() && plaintextMessage != null) {
- putFreshSession(session);
- }
-
- return plaintextMessage;
- }
-
- public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) {
- XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
-
- XmppAxolotlSession session = getReceivingSession(message);
- keyTransportMessage = message.getParameters(session, getOwnDeviceId());
-
- if (session.isFresh() && keyTransportMessage != null) {
- putFreshSession(session);
- }
-
- return keyTransportMessage;
- }
-
- private void putFreshSession(XmppAxolotlSession session) {
- Log.d(Config.LOGTAG,"put fresh session");
- sessions.put(session);
- if (Config.X509_VERIFICATION) {
- if (session.getIdentityKey() != null) {
- verifySessionWithPEP(session);
- } else {
- Log.e(Config.LOGTAG,account.getJid().toBareJid()+": identity key was empty after reloading for x509 verification");
- }
- }
- }
+ public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl";
+ public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist";
+ public static final String PEP_DEVICE_LIST_NOTIFY = PEP_DEVICE_LIST + "+notify";
+ public static final String PEP_BUNDLES = PEP_PREFIX + ".bundles";
+ public static final String PEP_VERIFICATION = PEP_PREFIX + ".verification";
+
+ public static final String LOGPREFIX = "AxolotlService";
+
+ public static final int NUM_KEYS_TO_PUBLISH = 100;
+ public static final int publishTriesThreshold = 3;
+
+ private final Account account;
+ private final XmppConnectionService mXmppConnectionService;
+ private final SQLiteAxolotlStore axolotlStore;
+ private final SessionMap sessions;
+ private final Map<Jid, Set<Integer>> deviceIds;
+ private final Map<String, XmppAxolotlMessage> messageCache;
+ private final FetchStatusMap fetchStatusMap;
+ private final SerialSingleThreadExecutor executor;
+ private int numPublishTriesOnEmptyPep = 0;
+ private boolean pepBroken = false;
+
+ @Override
+ public void onAdvancedStreamFeaturesAvailable(Account account) {
+ if (Config.supportOmemo()
+ && account.getXmppConnection() != null
+ && account.getXmppConnection().getFeatures().pep()) {
+ publishBundlesIfNeeded(true, false);
+ } else {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": skipping OMEMO initialization");
+ }
+ }
+
+ public boolean fetchMapHasErrors(List<Jid> jids) {
+ for (Jid jid : jids) {
+ if (deviceIds.get(jid) != null) {
+ for (Integer foreignId : this.deviceIds.get(jid)) {
+ AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
+ if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ public void preVerifyFingerprint(Contact contact, String fingerprint) {
+ axolotlStore.preVerifyFingerprint(contact.getAccount(), contact.getJid().toBareJid().toPreppedString(), fingerprint);
+ }
+
+ public void preVerifyFingerprint(Account account, String fingerprint) {
+ axolotlStore.preVerifyFingerprint(account, account.getJid().toBareJid().toPreppedString(), fingerprint);
+ }
+
+ private static class AxolotlAddressMap<T> {
+ protected Map<String, Map<Integer, T>> map;
+ protected final Object MAP_LOCK = new Object();
+
+ public AxolotlAddressMap() {
+ this.map = new HashMap<>();
+ }
+
+ public void put(AxolotlAddress address, T value) {
+ synchronized (MAP_LOCK) {
+ Map<Integer, T> devices = map.get(address.getName());
+ if (devices == null) {
+ devices = new HashMap<>();
+ map.put(address.getName(), devices);
+ }
+ devices.put(address.getDeviceId(), value);
+ }
+ }
+
+ public T get(AxolotlAddress address) {
+ synchronized (MAP_LOCK) {
+ Map<Integer, T> devices = map.get(address.getName());
+ if (devices == null) {
+ return null;
+ }
+ return devices.get(address.getDeviceId());
+ }
+ }
+
+ public Map<Integer, T> getAll(AxolotlAddress address) {
+ synchronized (MAP_LOCK) {
+ Map<Integer, T> devices = map.get(address.getName());
+ if (devices == null) {
+ return new HashMap<>();
+ }
+ return devices;
+ }
+ }
+
+ public boolean hasAny(AxolotlAddress address) {
+ synchronized (MAP_LOCK) {
+ Map<Integer, T> devices = map.get(address.getName());
+ return devices != null && !devices.isEmpty();
+ }
+ }
+
+ public void clear() {
+ map.clear();
+ }
+
+ }
+
+ private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> {
+ private final XmppConnectionService xmppConnectionService;
+ private final Account account;
+
+ public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) {
+ super();
+ this.xmppConnectionService = service;
+ this.account = account;
+ this.fillMap(store);
+ }
+
+ private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) {
+ for (Integer deviceId : deviceIds) {
+ AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId);
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building session for remote address: " + axolotlAddress.toString());
+ IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
+ if (Config.X509_VERIFICATION) {
+ X509Certificate certificate = store.getFingerprintCertificate(identityKey.getFingerprint().replaceAll("\\s", ""));
+ if (certificate != null) {
+ Bundle information = CryptoHelper.extractCertificateInformation(certificate);
+ try {
+ final String cn = information.getString("subject_cn");
+ final Jid jid = Jid.fromString(bareJid);
+ Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn);
+ account.getRoster().getContact(jid).setCommonName(cn);
+ } catch (final InvalidJidException ignored) {
+ //ignored
+ }
+ }
+ }
+ this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey));
+ }
+ }
+
+ private void fillMap(SQLiteAxolotlStore store) {
+ List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toPreppedString());
+ putDevicesForJid(account.getJid().toBareJid().toPreppedString(), deviceIds, store);
+ for (Contact contact : account.getRoster().getContacts()) {
+ Jid bareJid = contact.getJid().toBareJid();
+ String address = bareJid.toString();
+ deviceIds = store.getSubDeviceSessions(address);
+ putDevicesForJid(address, deviceIds, store);
+ }
+
+ }
+
+ @Override
+ public void put(AxolotlAddress address, XmppAxolotlSession value) {
+ super.put(address, value);
+ value.setNotFresh();
+ xmppConnectionService.syncRosterToDisk(account); //TODO why?
+ }
+
+ public void put(XmppAxolotlSession session) {
+ this.put(session.getRemoteAddress(), session);
+ }
+ }
+
+ public enum FetchStatus {
+ PENDING,
+ SUCCESS,
+ SUCCESS_VERIFIED,
+ TIMEOUT,
+ ERROR
+ }
+
+ private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> {
+
+ public void clearErrorFor(Jid jid) {
+ synchronized (MAP_LOCK) {
+ Map<Integer, FetchStatus> devices = this.map.get(jid.toBareJid().toPreppedString());
+ if (devices == null) {
+ return;
+ }
+ for (Map.Entry<Integer, FetchStatus> entry : devices.entrySet()) {
+ if (entry.getValue() == FetchStatus.ERROR) {
+ Log.d(Config.LOGTAG, "resetting error for " + jid.toBareJid() + "(" + entry.getKey() + ")");
+ entry.setValue(FetchStatus.TIMEOUT);
+ }
+ }
+ }
+ }
+ }
+
+ public static String getLogprefix(Account account) {
+ return LOGPREFIX + " (" + account.getJid().toBareJid().toString() + "): ";
+ }
+
+ public AxolotlService(Account account, XmppConnectionService connectionService) {
+ if (Security.getProvider("BC") == null) {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+ this.mXmppConnectionService = connectionService;
+ this.account = account;
+ this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
+ this.deviceIds = new HashMap<>();
+ this.messageCache = new HashMap<>();
+ this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account);
+ this.fetchStatusMap = new FetchStatusMap();
+ this.executor = new SerialSingleThreadExecutor();
+ }
+
+ public String getOwnFingerprint() {
+ return axolotlStore.getIdentityKeyPair().getPublicKey().getFingerprint().replaceAll("\\s", "");
+ }
+
+ public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status) {
+ return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toPreppedString(), status);
+ }
+
+ public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status, Jid jid) {
+ return axolotlStore.getContactKeysWithTrust(jid.toBareJid().toPreppedString(), status);
+ }
+
+ public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status, List<Jid> jids) {
+ Set<IdentityKey> keys = new HashSet<>();
+ for (Jid jid : jids) {
+ keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toPreppedString(), status));
+ }
+ return keys;
+ }
+
+ public long getNumTrustedKeys(Jid jid) {
+ return axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toPreppedString());
+ }
+
+ public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) {
+ for (Jid jid : jids) {
+ if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toPreppedString()) == 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private AxolotlAddress getAddressForJid(Jid jid) {
+ return new AxolotlAddress(jid.toPreppedString(), 0);
+ }
+
+ public Set<XmppAxolotlSession> findOwnSessions() {
+ AxolotlAddress ownAddress = getAddressForJid(account.getJid().toBareJid());
+ return new HashSet<>(this.sessions.getAll(ownAddress).values());
+ }
+
+ private Set<XmppAxolotlSession> findSessionsForContact(Contact contact) {
+ AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
+ return new HashSet<>(this.sessions.getAll(contactAddress).values());
+ }
+
+ private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) {
+ HashSet<XmppAxolotlSession> sessions = new HashSet<>();
+ for (Jid jid : conversation.getAcceptedCryptoTargets()) {
+ sessions.addAll(this.sessions.getAll(getAddressForJid(jid)).values());
+ }
+ return sessions;
+ }
+
+ public Set<String> getFingerprintsForOwnSessions() {
+ Set<String> fingerprints = new HashSet<>();
+ for (XmppAxolotlSession session : findOwnSessions()) {
+ fingerprints.add(session.getFingerprint());
+ }
+ return fingerprints;
+ }
+
+ public Set<String> getFingerprintsForContact(final Contact contact) {
+ Set<String> fingerprints = new HashSet<>();
+ for (XmppAxolotlSession session : findSessionsForContact(contact)) {
+ fingerprints.add(session.getFingerprint());
+ }
+ return fingerprints;
+ }
+
+ private boolean hasAny(Jid jid) {
+ return sessions.hasAny(getAddressForJid(jid));
+ }
+
+ public boolean isPepBroken() {
+ return this.pepBroken;
+ }
+
+ public void resetBrokenness() {
+ this.pepBroken = false;
+ numPublishTriesOnEmptyPep = 0;
+ }
+
+ public void clearErrorsInFetchStatusMap(Jid jid) {
+ fetchStatusMap.clearErrorFor(jid);
+ }
+
+ public void regenerateKeys(boolean wipeOther) {
+ axolotlStore.regenerate();
+ sessions.clear();
+ fetchStatusMap.clear();
+ publishBundlesIfNeeded(true, wipeOther);
+ }
+
+ public int getOwnDeviceId() {
+ return axolotlStore.getLocalRegistrationId();
+ }
+
+ public Set<Integer> getOwnDeviceIds() {
+ return this.deviceIds.get(account.getJid().toBareJid());
+ }
+
+ public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) {
+ if (jid.toBareJid().equals(account.getJid().toBareJid())) {
+ if (!deviceIds.isEmpty()) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Received non-empty own device list. Resetting publish attempts and pepBroken status.");
+ pepBroken = false;
+ numPublishTriesOnEmptyPep = 0;
+ }
+ if (deviceIds.contains(getOwnDeviceId())) {
+ deviceIds.remove(getOwnDeviceId());
+ } else {
+ publishOwnDeviceId(deviceIds);
+ }
+ for (Integer deviceId : deviceIds) {
+ AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId);
+ if (sessions.get(ownDeviceAddress) == null) {
+ buildSessionFromPEP(ownDeviceAddress);
+ }
+ }
+ }
+ Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toPreppedString()));
+ expiredDevices.removeAll(deviceIds);
+ for (Integer deviceId : expiredDevices) {
+ AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId);
+ XmppAxolotlSession session = sessions.get(address);
+ if (session != null && session.getFingerprint() != null) {
+ if (session.getTrust().isActive()) {
+ session.setTrust(session.getTrust().toInactive());
+ }
+ }
+ }
+ Set<Integer> newDevices = new HashSet<>(deviceIds);
+ for (Integer deviceId : newDevices) {
+ AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId);
+ XmppAxolotlSession session = sessions.get(address);
+ if (session != null && session.getFingerprint() != null) {
+ if (!session.getTrust().isActive()) {
+ session.setTrust(session.getTrust().toActive());
+ }
+ }
+ }
+ this.deviceIds.put(jid, deviceIds);
+ mXmppConnectionService.keyStatusUpdated(null);
+ }
+
+ public void wipeOtherPepDevices() {
+ if (pepBroken) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "wipeOtherPepDevices called, but PEP is broken. Ignoring... ");
+ return;
+ }
+ Set<Integer> deviceIds = new HashSet<>();
+ deviceIds.add(getOwnDeviceId());
+ IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds);
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Wiping all other devices from Pep:" + publish);
+ mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ // TODO: implement this!
+ }
+ });
+ }
+
+ public void purgeKey(final String fingerprint) {
+ axolotlStore.setFingerprintStatus(fingerprint.replaceAll("\\s", ""), FingerprintStatus.createCompromised());
+ }
+
+ public void publishOwnDeviceIdIfNeeded() {
+ if (pepBroken) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
+ return;
+ }
+ IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid());
+ mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids.");
+ } else {
+ Element item = mXmppConnectionService.getIqParser().getItem(packet);
+ Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
+ if (!deviceIds.contains(getOwnDeviceId())) {
+ publishOwnDeviceId(deviceIds);
+ }
+ }
+ }
+ });
+ }
+
+ public void publishOwnDeviceId(Set<Integer> deviceIds) {
+ Set<Integer> deviceIdsCopy = new HashSet<>(deviceIds);
+ if (!deviceIdsCopy.contains(getOwnDeviceId())) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Own device " + getOwnDeviceId() + " not in PEP devicelist.");
+ if (deviceIdsCopy.isEmpty()) {
+ if (numPublishTriesOnEmptyPep >= publishTriesThreshold) {
+ Log.w(Config.LOGTAG, getLogprefix(account) + "Own device publish attempt threshold exceeded, aborting...");
+ pepBroken = true;
+ return;
+ } else {
+ numPublishTriesOnEmptyPep++;
+ Log.w(Config.LOGTAG, getLogprefix(account) + "Own device list empty, attempting to publish (try " + numPublishTriesOnEmptyPep + ")");
+ }
+ } else {
+ numPublishTriesOnEmptyPep = 0;
+ }
+ deviceIdsCopy.add(getOwnDeviceId());
+ IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy);
+ mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.ERROR) {
+ pepBroken = true;
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error"));
+ }
+ }
+ });
+ }
+ }
+
+ public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord,
+ final Set<PreKeyRecord> preKeyRecords,
+ final boolean announceAfter,
+ final boolean wipe) {
+ try {
+ IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey();
+ PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
+ X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias());
+ Signature verifier = Signature.getInstance("sha256WithRSA");
+ verifier.initSign(x509PrivateKey, mXmppConnectionService.getRNG());
+ verifier.update(axolotlPublicKey.serialize());
+ byte[] signature = verifier.sign();
+ IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device " + getOwnDeviceId());
+ mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) {
+ if (pepBroken) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... ");
+ return;
+ }
+ IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), getOwnDeviceId());
+ mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+
+ if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
+ return; //ignore timeout. do nothing
+ }
+
+ if (packet.getType() == IqPacket.TYPE.ERROR) {
+ Element error = packet.findChild("error");
+ if (error == null || !error.hasChild("item-not-found")) {
+ pepBroken = true;
+ Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet);
+ return;
+ }
+ }
+
+ PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet);
+ Map<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet);
+ boolean flush = false;
+ if (bundle == null) {
+ Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + packet);
+ bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
+ flush = true;
+ }
+ if (keys == null) {
+ Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + packet);
+ }
+ try {
+ boolean changed = false;
+ // Validate IdentityKey
+ IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
+ if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) {
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP.");
+ changed = true;
+ }
+
+ // Validate signedPreKeyRecord + ID
+ SignedPreKeyRecord signedPreKeyRecord;
+ int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size();
+ try {
+ signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
+ if (flush
+ || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
+ || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) {
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
+ signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
+ axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
+ changed = true;
+ }
+ } catch (InvalidKeyIdException e) {
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
+ signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
+ axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
+ changed = true;
+ }
+
+ // Validate PreKeys
+ Set<PreKeyRecord> preKeyRecords = new HashSet<>();
+ if (keys != null) {
+ for (Integer id : keys.keySet()) {
+ try {
+ PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id);
+ if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) {
+ preKeyRecords.add(preKeyRecord);
+ }
+ } catch (InvalidKeyIdException ignored) {
+ }
+ }
+ }
+ int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
+ if (newKeys > 0) {
+ List<PreKeyRecord> newRecords = KeyHelper.generatePreKeys(
+ axolotlStore.getCurrentPreKeyId() + 1, newKeys);
+ preKeyRecords.addAll(newRecords);
+ for (PreKeyRecord record : newRecords) {
+ axolotlStore.storePreKey(record.getId(), record);
+ }
+ changed = true;
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP.");
+ }
+
+
+ if (changed) {
+ if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) {
+ mXmppConnectionService.publishDisplayName(account);
+ publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
+ } else {
+ publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
+ }
+ } else {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current");
+ if (wipe) {
+ wipeOtherPepDevices();
+ } else if (announce) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
+ publishOwnDeviceIdIfNeeded();
+ }
+ }
+ } catch (InvalidKeyException e) {
+ Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
+ }
+ }
+ });
+ }
+
+ private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord,
+ Set<PreKeyRecord> preKeyRecords,
+ final boolean announceAfter,
+ final boolean wipe) {
+ IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
+ signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
+ preKeyRecords, getOwnDeviceId());
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish);
+ mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
+ if (wipe) {
+ wipeOtherPepDevices();
+ } else if (announceAfter) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
+ publishOwnDeviceIdIfNeeded();
+ }
+ } else if (packet.getType() == IqPacket.TYPE.ERROR) {
+ pepBroken = true;
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error"));
+ }
+ }
+ });
+ }
+
+ public enum AxolotlCapability {
+ FULL,
+ MISSING_PRESENCE,
+ MISSING_KEYS,
+ WRONG_CONFIGURATION,
+ NO_MEMBERS
+ }
+
+ public boolean isConversationAxolotlCapable(Conversation conversation) {
+ return isConversationAxolotlCapableDetailed(conversation).first == AxolotlCapability.FULL;
+ }
+
+ public Pair<AxolotlCapability, Jid> isConversationAxolotlCapableDetailed(Conversation conversation) {
+ if (conversation.getMode() == Conversation.MODE_SINGLE || (conversation.getMucOptions().membersOnly() && conversation.getMucOptions().nonanonymous())) {
+ final List<Jid> jids = getCryptoTargets(conversation);
+ for (Jid jid : jids) {
+ if (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) {
+ if (conversation.getAccount().getRoster().getContact(jid).mutualPresenceSubscription()) {
+ return new Pair<>(AxolotlCapability.MISSING_KEYS, jid);
+ } else {
+ return new Pair<>(AxolotlCapability.MISSING_PRESENCE, jid);
+ }
+ }
+ }
+ if (jids.size() > 0) {
+ return new Pair<>(AxolotlCapability.FULL, null);
+ } else {
+ return new Pair<>(AxolotlCapability.NO_MEMBERS, null);
+ }
+ } else {
+ return new Pair<>(AxolotlCapability.WRONG_CONFIGURATION, null);
+ }
+ }
+
+ public List<Jid> getCryptoTargets(Conversation conversation) {
+ final List<Jid> jids;
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ jids = Arrays.asList(conversation.getJid().toBareJid());
+ } else {
+ jids = conversation.getMucOptions().getMembers();
+ }
+ return jids;
+ }
+
+ public FingerprintStatus getFingerprintTrust(String fingerprint) {
+ return axolotlStore.getFingerprintStatus(fingerprint);
+ }
+
+ public X509Certificate getFingerprintCertificate(String fingerprint) {
+ return axolotlStore.getFingerprintCertificate(fingerprint);
+ }
+
+ public void setFingerprintTrust(String fingerprint, FingerprintStatus status) {
+ axolotlStore.setFingerprintStatus(fingerprint, status);
+ }
+
+ private void verifySessionWithPEP(final XmppAxolotlSession session) {
+ Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
+ final AxolotlAddress address = session.getRemoteAddress();
+ final IdentityKey identityKey = session.getIdentityKey();
+ try {
+ IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.fromString(address.getName()), address.getDeviceId());
+ mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ Pair<X509Certificate[], byte[]> verification = mXmppConnectionService.getIqParser().verification(packet);
+ if (verification != null) {
+ try {
+ Signature verifier = Signature.getInstance("sha256WithRSA");
+ verifier.initVerify(verification.first[0]);
+ verifier.update(identityKey.serialize());
+ if (verifier.verify(verification.second)) {
+ try {
+ mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
+ String fingerprint = session.getFingerprint();
+ Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: " + fingerprint);
+ setFingerprintTrust(fingerprint, FingerprintStatus.createActiveVerified(true));
+ axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
+ fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
+ Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
+ try {
+ final String cn = information.getString("subject_cn");
+ final Jid jid = Jid.fromString(address.getName());
+ Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn);
+ account.getRoster().getContact(jid).setCommonName(cn);
+ } catch (final InvalidJidException ignored) {
+ //ignored
+ }
+ finishBuildingSessionsFromPEP(address);
+ return;
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, "could not verify certificate");
+ }
+ }
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
+ }
+ } else {
+ Log.d(Config.LOGTAG, "no verification found");
+ }
+ fetchStatusMap.put(address, FetchStatus.SUCCESS);
+ finishBuildingSessionsFromPEP(address);
+ }
+ });
+ } catch (InvalidJidException e) {
+ fetchStatusMap.put(address, FetchStatus.SUCCESS);
+ finishBuildingSessionsFromPEP(address);
+ }
+ }
+
+ private void finishBuildingSessionsFromPEP(final AxolotlAddress address) {
+ AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), 0);
+ Map<Integer, FetchStatus> own = fetchStatusMap.getAll(ownAddress);
+ Map<Integer, FetchStatus> remote = fetchStatusMap.getAll(address);
+ if (!own.containsValue(FetchStatus.PENDING) && !remote.containsValue(FetchStatus.PENDING)) {
+ FetchStatus report = null;
+ if (own.containsValue(FetchStatus.SUCCESS) || remote.containsValue(FetchStatus.SUCCESS)) {
+ report = FetchStatus.SUCCESS;
+ } else if (own.containsValue(FetchStatus.SUCCESS_VERIFIED) || remote.containsValue(FetchStatus.SUCCESS_VERIFIED)) {
+ report = FetchStatus.SUCCESS_VERIFIED;
+ } else if (own.containsValue(FetchStatus.ERROR) || remote.containsValue(FetchStatus.ERROR)) {
+ report = FetchStatus.ERROR;
+ }
+ mXmppConnectionService.keyStatusUpdated(report);
+ }
+ }
+
+ private void buildSessionFromPEP(final AxolotlAddress address) {
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.toString());
+ if (address.getDeviceId() == getOwnDeviceId()) {
+ throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
+ }
+
+ try {
+ IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(
+ Jid.fromString(address.getName()), address.getDeviceId());
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket);
+ mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() {
+
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
+ fetchStatusMap.put(address, FetchStatus.TIMEOUT);
+ } else if (packet.getType() == IqPacket.TYPE.RESULT) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
+ final IqParser parser = mXmppConnectionService.getIqParser();
+ final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
+ final PreKeyBundle bundle = parser.bundle(packet);
+ if (preKeyBundleList.isEmpty() || bundle == null) {
+ Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
+ fetchStatusMap.put(address, FetchStatus.ERROR);
+ finishBuildingSessionsFromPEP(address);
+ return;
+ }
+ Random random = new Random();
+ final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size()));
+ if (preKey == null) {
+ //should never happen
+ fetchStatusMap.put(address, FetchStatus.ERROR);
+ finishBuildingSessionsFromPEP(address);
+ return;
+ }
+
+ final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(),
+ preKey.getPreKeyId(), preKey.getPreKey(),
+ bundle.getSignedPreKeyId(), bundle.getSignedPreKey(),
+ bundle.getSignedPreKeySignature(), bundle.getIdentityKey());
+
+ try {
+ SessionBuilder builder = new SessionBuilder(axolotlStore, address);
+ builder.process(preKeyBundle);
+ XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
+ sessions.put(address, session);
+ if (Config.X509_VERIFICATION) {
+ verifySessionWithPEP(session);
+ } else {
+ FingerprintStatus status = getFingerprintTrust(bundle.getIdentityKey().getFingerprint().replaceAll("\\s", ""));
+ boolean verified = status != null && status.isVerified();
+ fetchStatusMap.put(address, verified ? FetchStatus.SUCCESS_VERIFIED : FetchStatus.SUCCESS);
+ finishBuildingSessionsFromPEP(address);
+ }
+ } catch (UntrustedIdentityException | InvalidKeyException e) {
+ Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
+ + e.getClass().getName() + ", " + e.getMessage());
+ fetchStatusMap.put(address, FetchStatus.ERROR);
+ finishBuildingSessionsFromPEP(address);
+ }
+ } else {
+ fetchStatusMap.put(address, FetchStatus.ERROR);
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error"));
+ finishBuildingSessionsFromPEP(address);
+ }
+ }
+ });
+ } catch (InvalidJidException e) {
+ Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got address with invalid jid: " + address.getName());
+ }
+ }
+
+ public Set<AxolotlAddress> findDevicesWithoutSession(final Conversation conversation) {
+ Set<AxolotlAddress> addresses = new HashSet<>();
+ for (Jid jid : getCryptoTargets(conversation)) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid);
+ if (deviceIds.get(jid) != null) {
+ for (Integer foreignId : this.deviceIds.get(jid)) {
+ AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
+ if (sessions.get(address) == null) {
+ IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
+ if (identityKey != null) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
+ XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
+ sessions.put(address, session);
+ } else {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + jid + ":" + foreignId);
+ if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
+ addresses.add(address);
+ } else {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
+ }
+ }
+ }
+ }
+ } else {
+ Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
+ }
+ }
+ if (deviceIds.get(account.getJid().toBareJid()) != null) {
+ for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) {
+ AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), ownId);
+ if (sessions.get(address) == null) {
+ IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
+ if (identityKey != null) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
+ XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
+ sessions.put(address, session);
+ } else {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId);
+ if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
+ addresses.add(address);
+ } else {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
+ }
+ }
+ }
+ }
+ }
+
+ return addresses;
+ }
+
+ public boolean createSessionsIfNeeded(final Conversation conversation) {
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed...");
+ boolean newSessions = false;
+ Set<AxolotlAddress> addresses = findDevicesWithoutSession(conversation);
+ for (AxolotlAddress address : addresses) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString());
+ FetchStatus status = fetchStatusMap.get(address);
+ if (status == null || status == FetchStatus.TIMEOUT) {
+ fetchStatusMap.put(address, FetchStatus.PENDING);
+ this.buildSessionFromPEP(address);
+ newSessions = true;
+ } else if (status == FetchStatus.PENDING) {
+ newSessions = true;
+ } else {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString());
+ }
+ }
+
+ return newSessions;
+ }
+
+ public boolean trustedSessionVerified(final Conversation conversation) {
+ Set<XmppAxolotlSession> sessions = findSessionsForConversation(conversation);
+ sessions.addAll(findOwnSessions());
+ boolean verified = false;
+ for (XmppAxolotlSession session : sessions) {
+ if (session.getTrust().isTrustedAndActive()) {
+ if (session.getTrust().getTrust() == FingerprintStatus.Trust.VERIFIED_X509) {
+ verified = true;
+ } else {
+ return false;
+ }
+ }
+ }
+ return verified;
+ }
+
+ public boolean hasPendingKeyFetches(Account account, List<Jid> jids) {
+ AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), 0);
+ if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)) {
+ return true;
+ }
+ for (Jid jid : jids) {
+ AxolotlAddress foreignAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), 0);
+ if (fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Nullable
+ private XmppAxolotlMessage buildHeader(Conversation conversation) {
+ final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(
+ account.getJid().toBareJid(), getOwnDeviceId());
+
+ Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(conversation);
+ Set<XmppAxolotlSession> ownSessions = findOwnSessions();
+ if (remoteSessions.isEmpty()) {
+ return null;
+ }
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl foreign keyElements...");
+ for (XmppAxolotlSession session : remoteSessions) {
+ Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
+ axolotlMessage.addDevice(session);
+ }
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl own keyElements...");
+ for (XmppAxolotlSession session : ownSessions) {
+ Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
+ axolotlMessage.addDevice(session);
+ }
+
+ return axolotlMessage;
+ }
+
+ @Nullable
+ public XmppAxolotlMessage encrypt(Message message) {
+ XmppAxolotlMessage axolotlMessage = buildHeader(message.getConversation());
+
+ if (axolotlMessage != null) {
+ final String content;
+ if (message.hasFileOnRemoteHost()) {
+ content = message.getFileParams().url.toString();
+ } else {
+ content = message.getBody();
+ }
+ try {
+ axolotlMessage.encrypt(content);
+ } catch (CryptoFailedException e) {
+ Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
+ return null;
+ }
+ }
+
+ return axolotlMessage;
+ }
+
+ public void preparePayloadMessage(final Message message, final boolean delay) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ XmppAxolotlMessage axolotlMessage = encrypt(message);
+ if (axolotlMessage == null) {
+ mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
+ //mXmppConnectionService.updateConversationUi();
+ } else {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
+ messageCache.put(message.getUuid(), axolotlMessage);
+ mXmppConnectionService.resendMessage(message, delay);
+ }
+ }
+ });
+ }
+
+ public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ XmppAxolotlMessage axolotlMessage = buildHeader(conversation);
+ onMessageCreatedCallback.run(axolotlMessage);
+ }
+ });
+ }
+
+ public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
+ XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
+ if (axolotlMessage != null) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid());
+ messageCache.remove(message.getUuid());
+ } else {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid());
+ }
+ return axolotlMessage;
+ }
+
+ private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) {
+ IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
+ return (identityKey != null)
+ ? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
+ : null;
+ }
+
+ private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
+ AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(),
+ message.getSenderDeviceId());
+ 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);
+ session = recreateUncachedSession(senderAddress);
+ if (session == null) {
+ session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
+ }
+ }
+ return session;
+ }
+
+ public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) {
+ XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
+
+ XmppAxolotlSession session = getReceivingSession(message);
+ try {
+ plaintextMessage = message.decrypt(session, getOwnDeviceId());
+ Integer preKeyId = session.getPreKeyId();
+ if (preKeyId != null) {
+ publishBundlesIfNeeded(false, false);
+ session.resetPreKeyId();
+ }
+ } catch (CryptoFailedException e) {
+ Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage());
+ }
+
+ if (session.isFresh() && plaintextMessage != null) {
+ putFreshSession(session);
+ }
+
+ return plaintextMessage;
+ }
+
+ public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) {
+ XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
+
+ XmppAxolotlSession session = getReceivingSession(message);
+ keyTransportMessage = message.getParameters(session, getOwnDeviceId());
+
+ if (session.isFresh() && keyTransportMessage != null) {
+ putFreshSession(session);
+ }
+
+ return keyTransportMessage;
+ }
+
+ private void putFreshSession(XmppAxolotlSession session) {
+ Log.d(Config.LOGTAG, "put fresh session");
+ sessions.put(session);
+ if (Config.X509_VERIFICATION) {
+ if (session.getIdentityKey() != null) {
+ verifySessionWithPEP(session);
+ } else {
+ Log.e(Config.LOGTAG, account.getJid().toBareJid() + ": identity key was empty after reloading for x509 verification");
+ }
+ }
+ }
}
diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/CryptoFailedException.java b/src/main/java/de/pixart/messenger/crypto/axolotl/CryptoFailedException.java
index b29d7cb30..da847393b 100644
--- a/src/main/java/de/pixart/messenger/crypto/axolotl/CryptoFailedException.java
+++ b/src/main/java/de/pixart/messenger/crypto/axolotl/CryptoFailedException.java
@@ -1,7 +1,7 @@
package de.pixart.messenger.crypto.axolotl;
public class CryptoFailedException extends Exception {
- public CryptoFailedException(Exception e){
- super(e);
- }
+ public CryptoFailedException(Exception e) {
+ super(e);
+ }
}
diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/FingerprintStatus.java b/src/main/java/de/pixart/messenger/crypto/axolotl/FingerprintStatus.java
index db67617b9..3be469b5a 100644
--- a/src/main/java/de/pixart/messenger/crypto/axolotl/FingerprintStatus.java
+++ b/src/main/java/de/pixart/messenger/crypto/axolotl/FingerprintStatus.java
@@ -32,8 +32,8 @@ public class FingerprintStatus {
public ContentValues toContentValues() {
final ContentValues contentValues = new ContentValues();
- contentValues.put(SQLiteAxolotlStore.TRUST,trust.toString());
- contentValues.put(SQLiteAxolotlStore.ACTIVE,active ? 1 : 0);
+ contentValues.put(SQLiteAxolotlStore.TRUST, trust.toString());
+ contentValues.put(SQLiteAxolotlStore.ACTIVE, active ? 1 : 0);
return contentValues;
}
@@ -41,7 +41,7 @@ public class FingerprintStatus {
final FingerprintStatus status = new FingerprintStatus();
try {
status.trust = Trust.valueOf(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.TRUST)));
- } catch(IllegalArgumentException e) {
+ } catch (IllegalArgumentException e) {
status.trust = Trust.UNTRUSTED;
}
status.active = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.ACTIVE)) > 0;
diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/NoSessionsCreatedException.java b/src/main/java/de/pixart/messenger/crypto/axolotl/NoSessionsCreatedException.java
index 5d0a7547a..92635a364 100644
--- a/src/main/java/de/pixart/messenger/crypto/axolotl/NoSessionsCreatedException.java
+++ b/src/main/java/de/pixart/messenger/crypto/axolotl/NoSessionsCreatedException.java
@@ -1,4 +1,4 @@
package de.pixart.messenger.crypto.axolotl;
-public class NoSessionsCreatedException extends Throwable{
+public class NoSessionsCreatedException extends Throwable {
}
diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/OnMessageCreatedCallback.java b/src/main/java/de/pixart/messenger/crypto/axolotl/OnMessageCreatedCallback.java
index a6daeb196..088831661 100644
--- a/src/main/java/de/pixart/messenger/crypto/axolotl/OnMessageCreatedCallback.java
+++ b/src/main/java/de/pixart/messenger/crypto/axolotl/OnMessageCreatedCallback.java
@@ -1,5 +1,5 @@
package de.pixart.messenger.crypto.axolotl;
public interface OnMessageCreatedCallback {
- void run(XmppAxolotlMessage message);
+ void run(XmppAxolotlMessage message);
}
diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/SQLiteAxolotlStore.java b/src/main/java/de/pixart/messenger/crypto/axolotl/SQLiteAxolotlStore.java
index 2980bf7cc..c45e5045e 100644
--- a/src/main/java/de/pixart/messenger/crypto/axolotl/SQLiteAxolotlStore.java
+++ b/src/main/java/de/pixart/messenger/crypto/axolotl/SQLiteAxolotlStore.java
@@ -25,421 +25,421 @@ import de.pixart.messenger.services.XmppConnectionService;
public class SQLiteAxolotlStore implements AxolotlStore {
- public static final String PREKEY_TABLENAME = "prekeys";
- public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys";
- public static final String SESSION_TABLENAME = "sessions";
- public static final String IDENTITIES_TABLENAME = "identities";
- public static final String ACCOUNT = "account";
- public static final String DEVICE_ID = "device_id";
- public static final String ID = "id";
- public static final String KEY = "key";
- public static final String FINGERPRINT = "fingerprint";
- public static final String NAME = "name";
- public static final String TRUSTED = "trusted"; //no longer used
- public static final String TRUST = "trust";
- public static final String ACTIVE = "active";
- public static final String OWN = "ownkey";
- public static final String CERTIFICATE = "certificate";
-
- public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id";
- public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id";
-
- private static final int NUM_TRUSTS_TO_CACHE = 100;
-
- private final Account account;
- private final XmppConnectionService mXmppConnectionService;
-
- private IdentityKeyPair identityKeyPair;
- private int localRegistrationId;
- private int currentPreKeyId = 0;
-
- private final LruCache<String, FingerprintStatus> trustCache =
- new LruCache<String, FingerprintStatus>(NUM_TRUSTS_TO_CACHE) {
- @Override
- protected FingerprintStatus create(String fingerprint) {
- return mXmppConnectionService.databaseBackend.getFingerprintStatus(account, fingerprint);
- }
- };
-
- private static IdentityKeyPair generateIdentityKeyPair() {
- Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl IdentityKeyPair...");
- ECKeyPair identityKeyPairKeys = Curve.generateKeyPair();
- return new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()),
- identityKeyPairKeys.getPrivateKey());
- }
-
- private static int generateRegistrationId() {
- Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl registration ID...");
- return KeyHelper.generateRegistrationId(true);
- }
-
- public SQLiteAxolotlStore(Account account, XmppConnectionService service) {
- this.account = account;
- this.mXmppConnectionService = service;
- this.localRegistrationId = loadRegistrationId();
- this.currentPreKeyId = loadCurrentPreKeyId();
- for (SignedPreKeyRecord record : loadSignedPreKeys()) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got Axolotl signed prekey record:" + record.getId());
- }
- }
-
- public int getCurrentPreKeyId() {
- return currentPreKeyId;
- }
-
- // --------------------------------------
- // IdentityKeyStore
- // --------------------------------------
-
- private IdentityKeyPair loadIdentityKeyPair() {
- synchronized (mXmppConnectionService) {
- IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account);
-
- if (ownKey != null) {
- return ownKey;
- } else {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve own IdentityKeyPair");
- ownKey = generateIdentityKeyPair();
- mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownKey);
- }
- return ownKey;
- }
- }
-
- private int loadRegistrationId() {
- return loadRegistrationId(false);
- }
-
- private int loadRegistrationId(boolean regenerate) {
- String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID);
- int reg_id;
- if (!regenerate && regIdString != null) {
- reg_id = Integer.valueOf(regIdString);
- } else {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve axolotl registration id for account " + account.getJid());
- reg_id = generateRegistrationId();
- boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID, Integer.toString(reg_id));
- if (success) {
- mXmppConnectionService.databaseBackend.updateAccount(account);
- } else {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new key to the database!");
- }
- }
- return reg_id;
- }
-
- private int loadCurrentPreKeyId() {
- String prekeyIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID);
- int prekey_id;
- if (prekeyIdString != null) {
- prekey_id = Integer.valueOf(prekeyIdString);
- } else {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve current prekey id for account " + account.getJid());
- prekey_id = 0;
- }
- return prekey_id;
- }
-
- public void regenerate() {
- mXmppConnectionService.databaseBackend.wipeAxolotlDb(account);
- trustCache.evictAll();
- account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(0));
- identityKeyPair = loadIdentityKeyPair();
- localRegistrationId = loadRegistrationId(true);
- currentPreKeyId = 0;
- mXmppConnectionService.updateAccountUi();
- }
-
- /**
- * Get the local client's identity key pair.
- *
- * @return The local client's persistent identity key pair.
- */
- @Override
- public IdentityKeyPair getIdentityKeyPair() {
- if (identityKeyPair == null) {
- identityKeyPair = loadIdentityKeyPair();
- }
- return identityKeyPair;
- }
-
- /**
- * Return the local client's registration ID.
- * <p/>
- * Clients should maintain a registration ID, a random number
- * between 1 and 16380 that's generated once at install time.
- *
- * @return the local client's registration ID.
- */
- @Override
- public int getLocalRegistrationId() {
- return localRegistrationId;
- }
-
- /**
- * Save a remote client's identity key
- * <p/>
- * Store a remote client's identity key as trusted.
- *
- * @param name The name of the remote client.
- * @param identityKey The remote client's identity key.
- */
- @Override
- public void saveIdentity(String name, IdentityKey identityKey) {
- if (!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name).contains(identityKey)) {
- String fingerprint = identityKey.getFingerprint().replaceAll("\\s", "");
- FingerprintStatus status = getFingerprintStatus(fingerprint);
- if (status == null) {
- status = FingerprintStatus.createActiveUndecided(); //default for new keys
- } else {
- status = status.toActive();
- }
- mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey, status);
- trustCache.remove(fingerprint);
- }
- }
-
- /**
- * Verify a remote client's identity key.
- * <p/>
- * Determine whether a remote client's identity is trusted. Convention is
- * that the TextSecure protocol is 'trust on first use.' This means that
- * an identity key is considered 'trusted' if there is no entry for the recipient
- * in the local store, or if it matches the saved key for a recipient in the local
- * store. Only if it mismatches an entry in the local store is it considered
- * 'untrusted.'
- *
- * @param name The name of the remote client.
- * @param identityKey The identity key to verify.
- * @return true if trusted, false if untrusted.
- */
- @Override
- public boolean isTrustedIdentity(String name, IdentityKey identityKey) {
- return true;
- }
-
- public FingerprintStatus getFingerprintStatus(String fingerprint) {
- return (fingerprint == null)? null : trustCache.get(fingerprint);
- }
-
- public void setFingerprintStatus(String fingerprint, FingerprintStatus status) {
- mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, status);
- trustCache.remove(fingerprint);
- }
-
- public void setFingerprintCertificate(String fingerprint, X509Certificate x509Certificate) {
- mXmppConnectionService.databaseBackend.setIdentityKeyCertificate(account, fingerprint, x509Certificate);
- }
-
- public X509Certificate getFingerprintCertificate(String fingerprint) {
- return mXmppConnectionService.databaseBackend.getIdentityKeyCertifcate(account, fingerprint);
- }
-
- public Set<IdentityKey> getContactKeysWithTrust(String bareJid, FingerprintStatus status) {
- return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, status);
- }
-
- public long getContactNumTrustedKeys(String bareJid) {
- return mXmppConnectionService.databaseBackend.numTrustedKeys(account, bareJid);
- }
-
- // --------------------------------------
- // SessionStore
- // --------------------------------------
-
- /**
- * Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple,
- * or a new SessionRecord if one does not currently exist.
- * <p/>
- * It is important that implementations return a copy of the current durable information. The
- * returned SessionRecord may be modified, but those changes should not have an effect on the
- * durable session state (what is returned by subsequent calls to this method) without the
- * store method being called here first.
- *
- * @param address The name and device ID of the remote client.
- * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or
- * a new SessionRecord if one does not currently exist.
- */
- @Override
- public SessionRecord loadSession(AxolotlAddress address) {
- SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address);
- return (session != null) ? session : new SessionRecord();
- }
-
- /**
- * Returns all known devices with active sessions for a recipient
- *
- * @param name the name of the client.
- * @return all known sub-devices with active sessions.
- */
- @Override
- public List<Integer> getSubDeviceSessions(String name) {
- return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account,
- new AxolotlAddress(name, 0));
- }
-
- /**
- * Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple.
- *
- * @param address the address of the remote client.
- * @param record the current SessionRecord for the remote client.
- */
- @Override
- public void storeSession(AxolotlAddress address, SessionRecord record) {
- mXmppConnectionService.databaseBackend.storeSession(account, address, record);
- }
-
- /**
- * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple.
- *
- * @param address the address of the remote client.
- * @return true if a {@link SessionRecord} exists, false otherwise.
- */
- @Override
- public boolean containsSession(AxolotlAddress address) {
- return mXmppConnectionService.databaseBackend.containsSession(account, address);
- }
-
- /**
- * Remove a {@link SessionRecord} for a recipientId + deviceId tuple.
- *
- * @param address the address of the remote client.
- */
- @Override
- public void deleteSession(AxolotlAddress address) {
- mXmppConnectionService.databaseBackend.deleteSession(account, address);
- }
-
- /**
- * Remove the {@link SessionRecord}s corresponding to all devices of a recipientId.
- *
- * @param name the name of the remote client.
- */
- @Override
- public void deleteAllSessions(String name) {
- AxolotlAddress address = new AxolotlAddress(name, 0);
- mXmppConnectionService.databaseBackend.deleteAllSessions(account,
- address);
- }
-
- // --------------------------------------
- // PreKeyStore
- // --------------------------------------
-
- /**
- * Load a local PreKeyRecord.
- *
- * @param preKeyId the ID of the local PreKeyRecord.
- * @return the corresponding PreKeyRecord.
- * @throws InvalidKeyIdException when there is no corresponding PreKeyRecord.
- */
- @Override
- public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
- PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId);
- if (record == null) {
- throw new InvalidKeyIdException("No such PreKeyRecord: " + preKeyId);
- }
- return record;
- }
-
- /**
- * Store a local PreKeyRecord.
- *
- * @param preKeyId the ID of the PreKeyRecord to store.
- * @param record the PreKeyRecord.
- */
- @Override
- public void storePreKey(int preKeyId, PreKeyRecord record) {
- mXmppConnectionService.databaseBackend.storePreKey(account, record);
- currentPreKeyId = preKeyId;
- boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(preKeyId));
- if (success) {
- mXmppConnectionService.databaseBackend.updateAccount(account);
- } else {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new prekey id to the database!");
- }
- }
-
- /**
- * @param preKeyId A PreKeyRecord ID.
- * @return true if the store has a record for the preKeyId, otherwise false.
- */
- @Override
- public boolean containsPreKey(int preKeyId) {
- return mXmppConnectionService.databaseBackend.containsPreKey(account, preKeyId);
- }
-
- /**
- * Delete a PreKeyRecord from local storage.
- *
- * @param preKeyId The ID of the PreKeyRecord to remove.
- */
- @Override
- public void removePreKey(int preKeyId) {
- mXmppConnectionService.databaseBackend.deletePreKey(account, preKeyId);
- }
-
- // --------------------------------------
- // SignedPreKeyStore
- // --------------------------------------
-
- /**
- * Load a local SignedPreKeyRecord.
- *
- * @param signedPreKeyId the ID of the local SignedPreKeyRecord.
- * @return the corresponding SignedPreKeyRecord.
- * @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord.
- */
- @Override
- public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
- SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId);
- if (record == null) {
- throw new InvalidKeyIdException("No such SignedPreKeyRecord: " + signedPreKeyId);
- }
- return record;
- }
-
- /**
- * Load all local SignedPreKeyRecords.
- *
- * @return All stored SignedPreKeyRecords.
- */
- @Override
- public List<SignedPreKeyRecord> loadSignedPreKeys() {
- return mXmppConnectionService.databaseBackend.loadSignedPreKeys(account);
- }
-
- /**
- * Store a local SignedPreKeyRecord.
- *
- * @param signedPreKeyId the ID of the SignedPreKeyRecord to store.
- * @param record the SignedPreKeyRecord.
- */
- @Override
- public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
- mXmppConnectionService.databaseBackend.storeSignedPreKey(account, record);
- }
-
- /**
- * @param signedPreKeyId A SignedPreKeyRecord ID.
- * @return true if the store has a record for the signedPreKeyId, otherwise false.
- */
- @Override
- public boolean containsSignedPreKey(int signedPreKeyId) {
- return mXmppConnectionService.databaseBackend.containsSignedPreKey(account, signedPreKeyId);
- }
-
- /**
- * Delete a SignedPreKeyRecord from local storage.
- *
- * @param signedPreKeyId The ID of the SignedPreKeyRecord to remove.
- */
- @Override
- public void removeSignedPreKey(int signedPreKeyId) {
- mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId);
- }
-
- public void preVerifyFingerprint(Account account, String name, String fingerprint) {
- mXmppConnectionService.databaseBackend.storePreVerification(account,name,fingerprint,FingerprintStatus.createInactiveVerified());
- }
+ public static final String PREKEY_TABLENAME = "prekeys";
+ public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys";
+ public static final String SESSION_TABLENAME = "sessions";
+ public static final String IDENTITIES_TABLENAME = "identities";
+ public static final String ACCOUNT = "account";
+ public static final String DEVICE_ID = "device_id";
+ public static final String ID = "id";
+ public static final String KEY = "key";
+ public static final String FINGERPRINT = "fingerprint";
+ public static final String NAME = "name";
+ public static final String TRUSTED = "trusted"; //no longer used
+ public static final String TRUST = "trust";
+ public static final String ACTIVE = "active";
+ public static final String OWN = "ownkey";
+ public static final String CERTIFICATE = "certificate";
+
+ public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id";
+ public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id";
+
+ private static final int NUM_TRUSTS_TO_CACHE = 100;
+
+ private final Account account;
+ private final XmppConnectionService mXmppConnectionService;
+
+ private IdentityKeyPair identityKeyPair;
+ private int localRegistrationId;
+ private int currentPreKeyId = 0;
+
+ private final LruCache<String, FingerprintStatus> trustCache =
+ new LruCache<String, FingerprintStatus>(NUM_TRUSTS_TO_CACHE) {
+ @Override
+ protected FingerprintStatus create(String fingerprint) {
+ return mXmppConnectionService.databaseBackend.getFingerprintStatus(account, fingerprint);
+ }
+ };
+
+ private static IdentityKeyPair generateIdentityKeyPair() {
+ Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl IdentityKeyPair...");
+ ECKeyPair identityKeyPairKeys = Curve.generateKeyPair();
+ return new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()),
+ identityKeyPairKeys.getPrivateKey());
+ }
+
+ private static int generateRegistrationId() {
+ Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl registration ID...");
+ return KeyHelper.generateRegistrationId(true);
+ }
+
+ public SQLiteAxolotlStore(Account account, XmppConnectionService service) {
+ this.account = account;
+ this.mXmppConnectionService = service;
+ this.localRegistrationId = loadRegistrationId();
+ this.currentPreKeyId = loadCurrentPreKeyId();
+ for (SignedPreKeyRecord record : loadSignedPreKeys()) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got Axolotl signed prekey record:" + record.getId());
+ }
+ }
+
+ public int getCurrentPreKeyId() {
+ return currentPreKeyId;
+ }
+
+ // --------------------------------------
+ // IdentityKeyStore
+ // --------------------------------------
+
+ private IdentityKeyPair loadIdentityKeyPair() {
+ synchronized (mXmppConnectionService) {
+ IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account);
+
+ if (ownKey != null) {
+ return ownKey;
+ } else {
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve own IdentityKeyPair");
+ ownKey = generateIdentityKeyPair();
+ mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownKey);
+ }
+ return ownKey;
+ }
+ }
+
+ private int loadRegistrationId() {
+ return loadRegistrationId(false);
+ }
+
+ private int loadRegistrationId(boolean regenerate) {
+ String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID);
+ int reg_id;
+ if (!regenerate && regIdString != null) {
+ reg_id = Integer.valueOf(regIdString);
+ } else {
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve axolotl registration id for account " + account.getJid());
+ reg_id = generateRegistrationId();
+ boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID, Integer.toString(reg_id));
+ if (success) {
+ mXmppConnectionService.databaseBackend.updateAccount(account);
+ } else {
+ Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new key to the database!");
+ }
+ }
+ return reg_id;
+ }
+
+ private int loadCurrentPreKeyId() {
+ String prekeyIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID);
+ int prekey_id;
+ if (prekeyIdString != null) {
+ prekey_id = Integer.valueOf(prekeyIdString);
+ } else {
+ Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve current prekey id for account " + account.getJid());
+ prekey_id = 0;
+ }
+ return prekey_id;
+ }
+
+ public void regenerate() {
+ mXmppConnectionService.databaseBackend.wipeAxolotlDb(account);
+ trustCache.evictAll();
+ account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(0));
+ identityKeyPair = loadIdentityKeyPair();
+ localRegistrationId = loadRegistrationId(true);
+ currentPreKeyId = 0;
+ mXmppConnectionService.updateAccountUi();
+ }
+
+ /**
+ * Get the local client's identity key pair.
+ *
+ * @return The local client's persistent identity key pair.
+ */
+ @Override
+ public IdentityKeyPair getIdentityKeyPair() {
+ if (identityKeyPair == null) {
+ identityKeyPair = loadIdentityKeyPair();
+ }
+ return identityKeyPair;
+ }
+
+ /**
+ * Return the local client's registration ID.
+ * <p/>
+ * Clients should maintain a registration ID, a random number
+ * between 1 and 16380 that's generated once at install time.
+ *
+ * @return the local client's registration ID.
+ */
+ @Override
+ public int getLocalRegistrationId() {
+ return localRegistrationId;
+ }
+
+ /**
+ * Save a remote client's identity key
+ * <p/>
+ * Store a remote client's identity key as trusted.
+ *
+ * @param name The name of the remote client.
+ * @param identityKey The remote client's identity key.
+ */
+ @Override
+ public void saveIdentity(String name, IdentityKey identityKey) {
+ if (!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name).contains(identityKey)) {
+ String fingerprint = identityKey.getFingerprint().replaceAll("\\s", "");
+ FingerprintStatus status = getFingerprintStatus(fingerprint);
+ if (status == null) {
+ status = FingerprintStatus.createActiveUndecided(); //default for new keys
+ } else {
+ status = status.toActive();
+ }
+ mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey, status);
+ trustCache.remove(fingerprint);
+ }
+ }
+
+ /**
+ * Verify a remote client's identity key.
+ * <p/>
+ * Determine whether a remote client's identity is trusted. Convention is
+ * that the TextSecure protocol is 'trust on first use.' This means that
+ * an identity key is considered 'trusted' if there is no entry for the recipient
+ * in the local store, or if it matches the saved key for a recipient in the local
+ * store. Only if it mismatches an entry in the local store is it considered
+ * 'untrusted.'
+ *
+ * @param name The name of the remote client.
+ * @param identityKey The identity key to verify.
+ * @return true if trusted, false if untrusted.
+ */
+ @Override
+ public boolean isTrustedIdentity(String name, IdentityKey identityKey) {
+ return true;
+ }
+
+ public FingerprintStatus getFingerprintStatus(String fingerprint) {
+ return (fingerprint == null) ? null : trustCache.get(fingerprint);
+ }
+
+ public void setFingerprintStatus(String fingerprint, FingerprintStatus status) {
+ mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, status);
+ trustCache.remove(fingerprint);
+ }
+
+ public void setFingerprintCertificate(String fingerprint, X509Certificate x509Certificate) {
+ mXmppConnectionService.databaseBackend.setIdentityKeyCertificate(account, fingerprint, x509Certificate);
+ }
+
+ public X509Certificate getFingerprintCertificate(String fingerprint) {
+ return mXmppConnectionService.databaseBackend.getIdentityKeyCertifcate(account, fingerprint);
+ }
+
+ public Set<IdentityKey> getContactKeysWithTrust(String bareJid, FingerprintStatus status) {
+ return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, status);
+ }
+
+ public long getContactNumTrustedKeys(String bareJid) {
+ return mXmppConnectionService.databaseBackend.numTrustedKeys(account, bareJid);
+ }
+
+ // --------------------------------------
+ // SessionStore
+ // --------------------------------------
+
+ /**
+ * Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple,
+ * or a new SessionRecord if one does not currently exist.
+ * <p/>
+ * It is important that implementations return a copy of the current durable information. The
+ * returned SessionRecord may be modified, but those changes should not have an effect on the
+ * durable session state (what is returned by subsequent calls to this method) without the
+ * store method being called here first.
+ *
+ * @param address The name and device ID of the remote client.
+ * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or
+ * a new SessionRecord if one does not currently exist.
+ */
+ @Override
+ public SessionRecord loadSession(AxolotlAddress address) {
+ SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address);
+ return (session != null) ? session : new SessionRecord();
+ }
+
+ /**
+ * Returns all known devices with active sessions for a recipient
+ *
+ * @param name the name of the client.
+ * @return all known sub-devices with active sessions.
+ */
+ @Override
+ public List<Integer> getSubDeviceSessions(String name) {
+ return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account,
+ new AxolotlAddress(name, 0));
+ }
+
+ /**
+ * Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple.
+ *
+ * @param address the address of the remote client.
+ * @param record the current SessionRecord for the remote client.
+ */
+ @Override
+ public void storeSession(AxolotlAddress address, SessionRecord record) {
+ mXmppConnectionService.databaseBackend.storeSession(account, address, record);
+ }
+
+ /**
+ * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple.
+ *
+ * @param address the address of the remote client.
+ * @return true if a {@link SessionRecord} exists, false otherwise.
+ */
+ @Override
+ public boolean containsSession(AxolotlAddress address) {
+ return mXmppConnectionService.databaseBackend.containsSession(account, address);
+ }
+
+ /**
+ * Remove a {@link SessionRecord} for a recipientId + deviceId tuple.
+ *
+ * @param address the address of the remote client.
+ */
+ @Override
+ public void deleteSession(AxolotlAddress address) {
+ mXmppConnectionService.databaseBackend.deleteSession(account, address);
+ }
+
+ /**
+ * Remove the {@link SessionRecord}s corresponding to all devices of a recipientId.
+ *
+ * @param name the name of the remote client.
+ */
+ @Override
+ public void deleteAllSessions(String name) {
+ AxolotlAddress address = new AxolotlAddress(name, 0);
+ mXmppConnectionService.databaseBackend.deleteAllSessions(account,
+ address);
+ }
+
+ // --------------------------------------
+ // PreKeyStore
+ // --------------------------------------
+
+ /**
+ * Load a local PreKeyRecord.
+ *
+ * @param preKeyId the ID of the local PreKeyRecord.
+ * @return the corresponding PreKeyRecord.
+ * @throws InvalidKeyIdException when there is no corresponding PreKeyRecord.
+ */
+ @Override
+ public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
+ PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId);
+ if (record == null) {
+ throw new InvalidKeyIdException("No such PreKeyRecord: " + preKeyId);
+ }
+ return record;
+ }
+
+ /**
+ * Store a local PreKeyRecord.
+ *
+ * @param preKeyId the ID of the PreKeyRecord to store.
+ * @param record the PreKeyRecord.
+ */
+ @Override
+ public void storePreKey(int preKeyId, PreKeyRecord record) {
+ mXmppConnectionService.databaseBackend.storePreKey(account, record);
+ currentPreKeyId = preKeyId;
+ boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(preKeyId));
+ if (success) {
+ mXmppConnectionService.databaseBackend.updateAccount(account);
+ } else {
+ Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new prekey id to the database!");
+ }
+ }
+
+ /**
+ * @param preKeyId A PreKeyRecord ID.
+ * @return true if the store has a record for the preKeyId, otherwise false.
+ */
+ @Override
+ public boolean containsPreKey(int preKeyId) {
+ return mXmppConnectionService.databaseBackend.containsPreKey(account, preKeyId);
+ }
+
+ /**
+ * Delete a PreKeyRecord from local storage.
+ *
+ * @param preKeyId The ID of the PreKeyRecord to remove.
+ */
+ @Override
+ public void removePreKey(int preKeyId) {
+ mXmppConnectionService.databaseBackend.deletePreKey(account, preKeyId);
+ }
+
+ // --------------------------------------
+ // SignedPreKeyStore
+ // --------------------------------------
+
+ /**
+ * Load a local SignedPreKeyRecord.
+ *
+ * @param signedPreKeyId the ID of the local SignedPreKeyRecord.
+ * @return the corresponding SignedPreKeyRecord.
+ * @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord.
+ */
+ @Override
+ public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
+ SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId);
+ if (record == null) {
+ throw new InvalidKeyIdException("No such SignedPreKeyRecord: " + signedPreKeyId);
+ }
+ return record;
+ }
+
+ /**
+ * Load all local SignedPreKeyRecords.
+ *
+ * @return All stored SignedPreKeyRecords.
+ */
+ @Override
+ public List<SignedPreKeyRecord> loadSignedPreKeys() {
+ return mXmppConnectionService.databaseBackend.loadSignedPreKeys(account);
+ }
+
+ /**
+ * Store a local SignedPreKeyRecord.
+ *
+ * @param signedPreKeyId the ID of the SignedPreKeyRecord to store.
+ * @param record the SignedPreKeyRecord.
+ */
+ @Override
+ public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
+ mXmppConnectionService.databaseBackend.storeSignedPreKey(account, record);
+ }
+
+ /**
+ * @param signedPreKeyId A SignedPreKeyRecord ID.
+ * @return true if the store has a record for the signedPreKeyId, otherwise false.
+ */
+ @Override
+ public boolean containsSignedPreKey(int signedPreKeyId) {
+ return mXmppConnectionService.databaseBackend.containsSignedPreKey(account, signedPreKeyId);
+ }
+
+ /**
+ * Delete a SignedPreKeyRecord from local storage.
+ *
+ * @param signedPreKeyId The ID of the SignedPreKeyRecord to remove.
+ */
+ @Override
+ public void removeSignedPreKey(int signedPreKeyId) {
+ mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId);
+ }
+
+ public void preVerifyFingerprint(Account account, String name, String fingerprint) {
+ mXmppConnectionService.databaseBackend.storePreVerification(account, name, fingerprint, FingerprintStatus.createInactiveVerified());
+ }
}
diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java
index 431f81d9b..f49eefb5a 100644
--- a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java
+++ b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java
@@ -26,227 +26,227 @@ import de.pixart.messenger.xml.Element;
import de.pixart.messenger.xmpp.jid.Jid;
public class XmppAxolotlMessage {
- public static final String CONTAINERTAG = "encrypted";
- public static final String HEADER = "header";
- public static final String SOURCEID = "sid";
- public static final String KEYTAG = "key";
- public static final String REMOTEID = "rid";
- 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 = null;
- private byte[] iv = null;
- private final Map<Integer, byte[]> keys;
- private final Jid from;
- private final int sourceDeviceId;
-
- public static class XmppAxolotlPlaintextMessage {
- private final String plaintext;
- private final String fingerprint;
-
- public XmppAxolotlPlaintextMessage(String plaintext, String fingerprint) {
- this.plaintext = plaintext;
- this.fingerprint = fingerprint;
- }
-
- public String getPlaintext() {
- return plaintext;
- }
-
-
- public String getFingerprint() {
- return fingerprint;
- }
- }
-
- public static class XmppAxolotlKeyTransportMessage {
- private final String fingerprint;
- private final byte[] key;
- private final byte[] iv;
-
- public XmppAxolotlKeyTransportMessage(String fingerprint, byte[] key, byte[] iv) {
- this.fingerprint = fingerprint;
- this.key = key;
- this.iv = iv;
- }
-
- public String getFingerprint() {
- return fingerprint;
- }
-
- public byte[] getKey() {
- return key;
- }
-
- public byte[] getIv() {
- return iv;
- }
- }
-
- private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException {
- this.from = from;
- Element header = axolotlMessage.findChild(HEADER);
- try {
- this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID));
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("invalid source id");
- }
- List<Element> keyElements = header.getChildren();
- this.keys = new HashMap<>(keyElements.size());
- for (Element keyElement : keyElements) {
- switch (keyElement.getName()) {
- case KEYTAG:
- try {
- Integer recipientId = Integer.parseInt(keyElement.getAttribute(REMOTEID));
- byte[] key = Base64.decode(keyElement.getContent().trim(), Base64.DEFAULT);
- this.keys.put(recipientId, key);
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("invalid remote id");
- }
- break;
- case IVTAG:
- if (this.iv != null) {
- throw new IllegalArgumentException("Duplicate iv entry");
- }
- iv = Base64.decode(keyElement.getContent().trim(), 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().trim(), Base64.DEFAULT);
- }
- }
-
- public XmppAxolotlMessage(Jid from, int sourceDeviceId) {
- this.from = from;
- this.sourceDeviceId = sourceDeviceId;
- this.keys = new HashMap<>();
- this.iv = generateIv();
- this.innerKey = generateKey();
- }
-
- public static XmppAxolotlMessage fromElement(Element element, Jid from) {
- return new XmppAxolotlMessage(element, from);
- }
-
- 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;
- }
-
- public void encrypt(String plaintext) throws CryptoFailedException {
- try {
- SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE);
- IvParameterSpec ivSpec = new IvParameterSpec(iv);
- Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER);
- cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
- this.ciphertext = cipher.doFinal(plaintext.getBytes());
- } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
- | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException
- | InvalidAlgorithmParameterException e) {
- throw new CryptoFailedException(e);
- }
- }
-
- public Jid getFrom() {
- return this.from;
- }
-
- public int getSenderDeviceId() {
- return sourceDeviceId;
- }
-
- public byte[] getCiphertext() {
- return ciphertext;
- }
-
- public void addDevice(XmppAxolotlSession session) {
- byte[] key = session.processSending(innerKey);
- if (key != null) {
- keys.put(session.getRemoteAddress().getDeviceId(), key);
- }
- }
-
- public byte[] getInnerKey() {
- return innerKey;
- }
-
- public byte[] getIV() {
- return this.iv;
- }
-
- public Element toElement() {
- Element encryptionElement = new Element(CONTAINERTAG, AxolotlService.PEP_PREFIX);
- Element headerElement = encryptionElement.addChild(HEADER);
- headerElement.setAttribute(SOURCEID, sourceDeviceId);
- for (Map.Entry<Integer, byte[]> keyEntry : keys.entrySet()) {
- Element keyElement = new Element(KEYTAG);
- keyElement.setAttribute(REMOTEID, keyEntry.getKey());
- keyElement.setContent(Base64.encodeToString(keyEntry.getValue(), Base64.NO_WRAP));
- headerElement.addChild(keyElement);
- }
- headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.NO_WRAP));
- if (ciphertext != null) {
- Element payload = encryptionElement.addChild(PAYLOAD);
- payload.setContent(Base64.encodeToString(ciphertext, Base64.NO_WRAP));
- }
- return encryptionElement;
- }
-
- private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) {
- byte[] encryptedKey = keys.get(sourceDeviceId);
- return (encryptedKey != null) ? session.processReceiving(encryptedKey) : null;
- }
-
- public XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) {
- byte[] key = unpackKey(session, sourceDeviceId);
- return (key != null)
- ? new XmppAxolotlKeyTransportMessage(session.getFingerprint(), key, getIV())
- : null;
- }
-
- public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException {
- XmppAxolotlPlaintextMessage plaintextMessage = null;
- byte[] key = unpackKey(session, sourceDeviceId);
- if (key != null) {
- try {
- Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER);
- SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
- IvParameterSpec ivSpec = new IvParameterSpec(iv);
-
- cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
-
- String plaintext = new String(cipher.doFinal(ciphertext));
- plaintextMessage = new XmppAxolotlPlaintextMessage(plaintext, session.getFingerprint());
-
- } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
- | InvalidAlgorithmParameterException | IllegalBlockSizeException
- | BadPaddingException | NoSuchProviderException e) {
- throw new CryptoFailedException(e);
- }
- }
- return plaintextMessage;
- }
+ public static final String CONTAINERTAG = "encrypted";
+ public static final String HEADER = "header";
+ public static final String SOURCEID = "sid";
+ public static final String KEYTAG = "key";
+ public static final String REMOTEID = "rid";
+ 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 = null;
+ private byte[] iv = null;
+ private final Map<Integer, byte[]> keys;
+ private final Jid from;
+ private final int sourceDeviceId;
+
+ public static class XmppAxolotlPlaintextMessage {
+ private final String plaintext;
+ private final String fingerprint;
+
+ public XmppAxolotlPlaintextMessage(String plaintext, String fingerprint) {
+ this.plaintext = plaintext;
+ this.fingerprint = fingerprint;
+ }
+
+ public String getPlaintext() {
+ return plaintext;
+ }
+
+
+ public String getFingerprint() {
+ return fingerprint;
+ }
+ }
+
+ public static class XmppAxolotlKeyTransportMessage {
+ private final String fingerprint;
+ private final byte[] key;
+ private final byte[] iv;
+
+ public XmppAxolotlKeyTransportMessage(String fingerprint, byte[] key, byte[] iv) {
+ this.fingerprint = fingerprint;
+ this.key = key;
+ this.iv = iv;
+ }
+
+ public String getFingerprint() {
+ return fingerprint;
+ }
+
+ public byte[] getKey() {
+ return key;
+ }
+
+ public byte[] getIv() {
+ return iv;
+ }
+ }
+
+ private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException {
+ this.from = from;
+ Element header = axolotlMessage.findChild(HEADER);
+ try {
+ this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID));
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("invalid source id");
+ }
+ List<Element> keyElements = header.getChildren();
+ this.keys = new HashMap<>(keyElements.size());
+ for (Element keyElement : keyElements) {
+ switch (keyElement.getName()) {
+ case KEYTAG:
+ try {
+ Integer recipientId = Integer.parseInt(keyElement.getAttribute(REMOTEID));
+ byte[] key = Base64.decode(keyElement.getContent().trim(), Base64.DEFAULT);
+ this.keys.put(recipientId, key);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("invalid remote id");
+ }
+ break;
+ case IVTAG:
+ if (this.iv != null) {
+ throw new IllegalArgumentException("Duplicate iv entry");
+ }
+ iv = Base64.decode(keyElement.getContent().trim(), 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().trim(), Base64.DEFAULT);
+ }
+ }
+
+ public XmppAxolotlMessage(Jid from, int sourceDeviceId) {
+ this.from = from;
+ this.sourceDeviceId = sourceDeviceId;
+ this.keys = new HashMap<>();
+ this.iv = generateIv();
+ this.innerKey = generateKey();
+ }
+
+ public static XmppAxolotlMessage fromElement(Element element, Jid from) {
+ return new XmppAxolotlMessage(element, from);
+ }
+
+ 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;
+ }
+
+ public void encrypt(String plaintext) throws CryptoFailedException {
+ try {
+ SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE);
+ IvParameterSpec ivSpec = new IvParameterSpec(iv);
+ Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER);
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
+ this.ciphertext = cipher.doFinal(plaintext.getBytes());
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
+ | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException
+ | InvalidAlgorithmParameterException e) {
+ throw new CryptoFailedException(e);
+ }
+ }
+
+ public Jid getFrom() {
+ return this.from;
+ }
+
+ public int getSenderDeviceId() {
+ return sourceDeviceId;
+ }
+
+ public byte[] getCiphertext() {
+ return ciphertext;
+ }
+
+ public void addDevice(XmppAxolotlSession session) {
+ byte[] key = session.processSending(innerKey);
+ if (key != null) {
+ keys.put(session.getRemoteAddress().getDeviceId(), key);
+ }
+ }
+
+ public byte[] getInnerKey() {
+ return innerKey;
+ }
+
+ public byte[] getIV() {
+ return this.iv;
+ }
+
+ public Element toElement() {
+ Element encryptionElement = new Element(CONTAINERTAG, AxolotlService.PEP_PREFIX);
+ Element headerElement = encryptionElement.addChild(HEADER);
+ headerElement.setAttribute(SOURCEID, sourceDeviceId);
+ for (Map.Entry<Integer, byte[]> keyEntry : keys.entrySet()) {
+ Element keyElement = new Element(KEYTAG);
+ keyElement.setAttribute(REMOTEID, keyEntry.getKey());
+ keyElement.setContent(Base64.encodeToString(keyEntry.getValue(), Base64.NO_WRAP));
+ headerElement.addChild(keyElement);
+ }
+ headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.NO_WRAP));
+ if (ciphertext != null) {
+ Element payload = encryptionElement.addChild(PAYLOAD);
+ payload.setContent(Base64.encodeToString(ciphertext, Base64.NO_WRAP));
+ }
+ return encryptionElement;
+ }
+
+ private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) {
+ byte[] encryptedKey = keys.get(sourceDeviceId);
+ return (encryptedKey != null) ? session.processReceiving(encryptedKey) : null;
+ }
+
+ public XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) {
+ byte[] key = unpackKey(session, sourceDeviceId);
+ return (key != null)
+ ? new XmppAxolotlKeyTransportMessage(session.getFingerprint(), key, getIV())
+ : null;
+ }
+
+ public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException {
+ XmppAxolotlPlaintextMessage plaintextMessage = null;
+ byte[] key = unpackKey(session, sourceDeviceId);
+ if (key != null) {
+ try {
+ Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER);
+ SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
+ IvParameterSpec ivSpec = new IvParameterSpec(iv);
+
+ cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
+
+ String plaintext = new String(cipher.doFinal(ciphertext));
+ plaintextMessage = new XmppAxolotlPlaintextMessage(plaintext, session.getFingerprint());
+
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
+ | InvalidAlgorithmParameterException | IllegalBlockSizeException
+ | BadPaddingException | NoSuchProviderException e) {
+ throw new CryptoFailedException(e);
+ }
+ }
+ return plaintextMessage;
+ }
}
diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlSession.java
index 566afef9a..27647fb66 100644
--- a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlSession.java
+++ b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlSession.java
@@ -23,113 +23,113 @@ import de.pixart.messenger.Config;
import de.pixart.messenger.entities.Account;
public class XmppAxolotlSession {
- private final SessionCipher cipher;
- private final SQLiteAxolotlStore sqLiteAxolotlStore;
- private final AxolotlAddress remoteAddress;
- private final Account account;
- private IdentityKey identityKey;
- private Integer preKeyId = null;
- private boolean fresh = true;
-
- public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, IdentityKey identityKey) {
- this(account, store, remoteAddress);
- this.identityKey = identityKey;
- }
-
- public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) {
- this.cipher = new SessionCipher(store, remoteAddress);
- this.remoteAddress = remoteAddress;
- this.sqLiteAxolotlStore = store;
- this.account = account;
- }
-
- public Integer getPreKeyId() {
- return preKeyId;
- }
-
- public void resetPreKeyId() {
-
- preKeyId = null;
- }
-
- public String getFingerprint() {
- return identityKey == null ? null : identityKey.getFingerprint().replaceAll("\\s", "");
- }
-
- public IdentityKey getIdentityKey() {
- return identityKey;
- }
-
- public AxolotlAddress getRemoteAddress() {
- return remoteAddress;
- }
-
- public boolean isFresh() {
- return fresh;
- }
-
- public void setNotFresh() {
- this.fresh = false;
- }
-
- protected void setTrust(FingerprintStatus status) {
- sqLiteAxolotlStore.setFingerprintStatus(getFingerprint(), status);
- }
-
- public FingerprintStatus getTrust() {
- FingerprintStatus status = sqLiteAxolotlStore.getFingerprintStatus(getFingerprint());
- return (status == null) ? FingerprintStatus.createActiveUndecided() : status;
- }
-
- @Nullable
- public byte[] processReceiving(byte[] encryptedKey) {
- byte[] plaintext = null;
- FingerprintStatus status = getTrust();
- if (!status.isCompromised()) {
- try {
- try {
- PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey);
- if (!message.getPreKeyId().isPresent()) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage did not contain a PreKeyId");
- return null;
- }
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
- IdentityKey msgIdentityKey = message.getIdentityKey();
- if (this.identityKey != null && !this.identityKey.equals(msgIdentityKey)) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint());
- } else {
- this.identityKey = msgIdentityKey;
- plaintext = cipher.decrypt(message);
- preKeyId = message.getPreKeyId().get();
- }
- } catch (InvalidMessageException | InvalidVersionException e) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received");
- WhisperMessage message = new WhisperMessage(encryptedKey);
- plaintext = cipher.decrypt(message);
- } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
- }
- } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
- }
-
- if (plaintext != null) {
- if (!status.isActive()) {
- setTrust(status.toActive());
- }
- }
- }
- return plaintext;
- }
-
- @Nullable
- public byte[] processSending(@NonNull byte[] outgoingMessage) {
- FingerprintStatus status = getTrust();
- if (status.isTrustedAndActive()) {
- CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
- return ciphertextMessage.serialize();
- } else {
- return null;
- }
- }
+ private final SessionCipher cipher;
+ private final SQLiteAxolotlStore sqLiteAxolotlStore;
+ private final AxolotlAddress remoteAddress;
+ private final Account account;
+ private IdentityKey identityKey;
+ private Integer preKeyId = null;
+ private boolean fresh = true;
+
+ public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, IdentityKey identityKey) {
+ this(account, store, remoteAddress);
+ this.identityKey = identityKey;
+ }
+
+ public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) {
+ this.cipher = new SessionCipher(store, remoteAddress);
+ this.remoteAddress = remoteAddress;
+ this.sqLiteAxolotlStore = store;
+ this.account = account;
+ }
+
+ public Integer getPreKeyId() {
+ return preKeyId;
+ }
+
+ public void resetPreKeyId() {
+
+ preKeyId = null;
+ }
+
+ public String getFingerprint() {
+ return identityKey == null ? null : identityKey.getFingerprint().replaceAll("\\s", "");
+ }
+
+ public IdentityKey getIdentityKey() {
+ return identityKey;
+ }
+
+ public AxolotlAddress getRemoteAddress() {
+ return remoteAddress;
+ }
+
+ public boolean isFresh() {
+ return fresh;
+ }
+
+ public void setNotFresh() {
+ this.fresh = false;
+ }
+
+ protected void setTrust(FingerprintStatus status) {
+ sqLiteAxolotlStore.setFingerprintStatus(getFingerprint(), status);
+ }
+
+ public FingerprintStatus getTrust() {
+ FingerprintStatus status = sqLiteAxolotlStore.getFingerprintStatus(getFingerprint());
+ return (status == null) ? FingerprintStatus.createActiveUndecided() : status;
+ }
+
+ @Nullable
+ public byte[] processReceiving(byte[] encryptedKey) {
+ byte[] plaintext = null;
+ FingerprintStatus status = getTrust();
+ if (!status.isCompromised()) {
+ try {
+ try {
+ PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey);
+ if (!message.getPreKeyId().isPresent()) {
+ Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage did not contain a PreKeyId");
+ return null;
+ }
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
+ IdentityKey msgIdentityKey = message.getIdentityKey();
+ if (this.identityKey != null && !this.identityKey.equals(msgIdentityKey)) {
+ Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint());
+ } else {
+ this.identityKey = msgIdentityKey;
+ plaintext = cipher.decrypt(message);
+ preKeyId = message.getPreKeyId().get();
+ }
+ } catch (InvalidMessageException | InvalidVersionException e) {
+ Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received");
+ WhisperMessage message = new WhisperMessage(encryptedKey);
+ plaintext = cipher.decrypt(message);
+ } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) {
+ Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
+ }
+ } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) {
+ Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
+ }
+
+ if (plaintext != null) {
+ if (!status.isActive()) {
+ setTrust(status.toActive());
+ }
+ }
+ }
+ return plaintext;
+ }
+
+ @Nullable
+ public byte[] processSending(@NonNull byte[] outgoingMessage) {
+ FingerprintStatus status = getTrust();
+ if (status.isTrustedAndActive()) {
+ CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
+ return ciphertextMessage.serialize();
+ } else {
+ return null;
+ }
+ }
}
diff --git a/src/main/java/de/pixart/messenger/crypto/sasl/Anonymous.java b/src/main/java/de/pixart/messenger/crypto/sasl/Anonymous.java
index 7e4e98bc6..93c3de64b 100644
--- a/src/main/java/de/pixart/messenger/crypto/sasl/Anonymous.java
+++ b/src/main/java/de/pixart/messenger/crypto/sasl/Anonymous.java
@@ -7,22 +7,22 @@ import de.pixart.messenger.xml.TagWriter;
public class Anonymous extends SaslMechanism {
- public Anonymous(TagWriter tagWriter, Account account, SecureRandom rng) {
- super(tagWriter, account, rng);
- }
+ public Anonymous(TagWriter tagWriter, Account account, SecureRandom rng) {
+ super(tagWriter, account, rng);
+ }
- @Override
- public int getPriority() {
- return 0;
- }
+ @Override
+ public int getPriority() {
+ return 0;
+ }
- @Override
- public String getMechanism() {
- return "ANONYMOUS";
- }
+ @Override
+ public String getMechanism() {
+ return "ANONYMOUS";
+ }
- @Override
- public String getClientFirstMessage() {
- return "";
- }
+ @Override
+ public String getClientFirstMessage() {
+ return "";
+ }
}
diff --git a/src/main/java/de/pixart/messenger/crypto/sasl/DigestMd5.java b/src/main/java/de/pixart/messenger/crypto/sasl/DigestMd5.java
index 09ac4865a..e974d8cd4 100644
--- a/src/main/java/de/pixart/messenger/crypto/sasl/DigestMd5.java
+++ b/src/main/java/de/pixart/messenger/crypto/sasl/DigestMd5.java
@@ -13,79 +13,79 @@ import de.pixart.messenger.utils.CryptoHelper;
import de.pixart.messenger.xml.TagWriter;
public class DigestMd5 extends SaslMechanism {
- public DigestMd5(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
- super(tagWriter, account, rng);
- }
+ public DigestMd5(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
+ super(tagWriter, account, rng);
+ }
- @Override
- public int getPriority() {
- return 10;
- }
+ @Override
+ public int getPriority() {
+ return 10;
+ }
- @Override
- public String getMechanism() {
- return "DIGEST-MD5";
- }
+ @Override
+ public String getMechanism() {
+ return "DIGEST-MD5";
+ }
- private State state = State.INITIAL;
+ private State state = State.INITIAL;
- @Override
- public String getResponse(final String challenge) throws AuthenticationException {
- switch (state) {
- case INITIAL:
- state = State.RESPONSE_SENT;
- final String encodedResponse;
- try {
- final Tokenizer tokenizer = new Tokenizer(Base64.decode(challenge, Base64.DEFAULT));
- String nonce = "";
- for (final String token : tokenizer) {
- final String[] parts = token.split("=", 2);
- if (parts[0].equals("nonce")) {
- nonce = parts[1].replace("\"", "");
- } else if (parts[0].equals("rspauth")) {
- return "";
- }
- }
- final String digestUri = "xmpp/" + account.getServer();
- final String nonceCount = "00000001";
- final String x = account.getUsername() + ":" + account.getServer() + ":"
- + account.getPassword();
- final MessageDigest md = MessageDigest.getInstance("MD5");
- final byte[] y = md.digest(x.getBytes(Charset.defaultCharset()));
- final String cNonce = new BigInteger(100, rng).toString(32);
- final byte[] a1 = CryptoHelper.concatenateByteArrays(y,
- (":" + nonce + ":" + cNonce).getBytes(Charset.defaultCharset()));
- final String a2 = "AUTHENTICATE:" + digestUri;
- final String ha1 = CryptoHelper.bytesToHex(md.digest(a1));
- final String ha2 = CryptoHelper.bytesToHex(md.digest(a2.getBytes(Charset
- .defaultCharset())));
- final String kd = ha1 + ":" + nonce + ":" + nonceCount + ":" + cNonce
- + ":auth:" + ha2;
- final String response = CryptoHelper.bytesToHex(md.digest(kd.getBytes(Charset
- .defaultCharset())));
- final String saslString = "username=\"" + account.getUsername()
- + "\",realm=\"" + account.getServer() + "\",nonce=\""
- + nonce + "\",cnonce=\"" + cNonce + "\",nc=" + nonceCount
- + ",qop=auth,digest-uri=\"" + digestUri + "\",response="
- + response + ",charset=utf-8";
- encodedResponse = Base64.encodeToString(
- saslString.getBytes(Charset.defaultCharset()),
- Base64.NO_WRAP);
- } catch (final NoSuchAlgorithmException e) {
- throw new AuthenticationException(e);
- }
+ @Override
+ public String getResponse(final String challenge) throws AuthenticationException {
+ switch (state) {
+ case INITIAL:
+ state = State.RESPONSE_SENT;
+ final String encodedResponse;
+ try {
+ final Tokenizer tokenizer = new Tokenizer(Base64.decode(challenge, Base64.DEFAULT));
+ String nonce = "";
+ for (final String token : tokenizer) {
+ final String[] parts = token.split("=", 2);
+ if (parts[0].equals("nonce")) {
+ nonce = parts[1].replace("\"", "");
+ } else if (parts[0].equals("rspauth")) {
+ return "";
+ }
+ }
+ final String digestUri = "xmpp/" + account.getServer();
+ final String nonceCount = "00000001";
+ final String x = account.getUsername() + ":" + account.getServer() + ":"
+ + account.getPassword();
+ final MessageDigest md = MessageDigest.getInstance("MD5");
+ final byte[] y = md.digest(x.getBytes(Charset.defaultCharset()));
+ final String cNonce = new BigInteger(100, rng).toString(32);
+ final byte[] a1 = CryptoHelper.concatenateByteArrays(y,
+ (":" + nonce + ":" + cNonce).getBytes(Charset.defaultCharset()));
+ final String a2 = "AUTHENTICATE:" + digestUri;
+ final String ha1 = CryptoHelper.bytesToHex(md.digest(a1));
+ final String ha2 = CryptoHelper.bytesToHex(md.digest(a2.getBytes(Charset
+ .defaultCharset())));
+ final String kd = ha1 + ":" + nonce + ":" + nonceCount + ":" + cNonce
+ + ":auth:" + ha2;
+ final String response = CryptoHelper.bytesToHex(md.digest(kd.getBytes(Charset
+ .defaultCharset())));
+ final String saslString = "username=\"" + account.getUsername()
+ + "\",realm=\"" + account.getServer() + "\",nonce=\""
+ + nonce + "\",cnonce=\"" + cNonce + "\",nc=" + nonceCount
+ + ",qop=auth,digest-uri=\"" + digestUri + "\",response="
+ + response + ",charset=utf-8";
+ encodedResponse = Base64.encodeToString(
+ saslString.getBytes(Charset.defaultCharset()),
+ Base64.NO_WRAP);
+ } catch (final NoSuchAlgorithmException e) {
+ throw new AuthenticationException(e);
+ }
- return encodedResponse;
- case RESPONSE_SENT:
- state = State.VALID_SERVER_RESPONSE;
- break;
- case VALID_SERVER_RESPONSE:
- if (challenge==null) {
- return null; //everything is fine
- }
- default:
- throw new InvalidStateException(state);
- }
- return null;
- }
+ return encodedResponse;
+ case RESPONSE_SENT:
+ state = State.VALID_SERVER_RESPONSE;
+ break;
+ case VALID_SERVER_RESPONSE:
+ if (challenge == null) {
+ return null; //everything is fine
+ }
+ default:
+ throw new InvalidStateException(state);
+ }
+ return null;
+ }
}
diff --git a/src/main/java/de/pixart/messenger/crypto/sasl/External.java b/src/main/java/de/pixart/messenger/crypto/sasl/External.java
index a1a79a0a8..b80d174f0 100644
--- a/src/main/java/de/pixart/messenger/crypto/sasl/External.java
+++ b/src/main/java/de/pixart/messenger/crypto/sasl/External.java
@@ -9,22 +9,22 @@ import de.pixart.messenger.xml.TagWriter;
public class External extends SaslMechanism {
- public External(TagWriter tagWriter, Account account, SecureRandom rng) {
- super(tagWriter, account, rng);
- }
-
- @Override
- public int getPriority() {
- return 25;
- }
-
- @Override
- public String getMechanism() {
- return "EXTERNAL";
- }
-
- @Override
- public String getClientFirstMessage() {
- return Base64.encodeToString(account.getJid().toBareJid().toString().getBytes(),Base64.NO_WRAP);
- }
+ public External(TagWriter tagWriter, Account account, SecureRandom rng) {
+ super(tagWriter, account, rng);
+ }
+
+ @Override
+ public int getPriority() {
+ return 25;
+ }
+
+ @Override
+ public String getMechanism() {
+ return "EXTERNAL";
+ }
+
+ @Override
+ public String getClientFirstMessage() {
+ return Base64.encodeToString(account.getJid().toBareJid().toString().getBytes(), Base64.NO_WRAP);
+ }
}
diff --git a/src/main/java/de/pixart/messenger/crypto/sasl/Plain.java b/src/main/java/de/pixart/messenger/crypto/sasl/Plain.java
index d0aa90b49..27de45247 100644
--- a/src/main/java/de/pixart/messenger/crypto/sasl/Plain.java
+++ b/src/main/java/de/pixart/messenger/crypto/sasl/Plain.java
@@ -8,23 +8,23 @@ import de.pixart.messenger.entities.Account;
import de.pixart.messenger.xml.TagWriter;
public class Plain extends SaslMechanism {
- public Plain(final TagWriter tagWriter, final Account account) {
- super(tagWriter, account, null);
- }
+ public Plain(final TagWriter tagWriter, final Account account) {
+ super(tagWriter, account, null);
+ }
- @Override
- public int getPriority() {
- return 10;
- }
+ @Override
+ public int getPriority() {
+ return 10;
+ }
- @Override
- public String getMechanism() {
- return "PLAIN";
- }
+ @Override
+ public String getMechanism() {
+ return "PLAIN";
+ }
- @Override
- public String getClientFirstMessage() {
- final String sasl = '\u0000' + account.getUsername() + '\u0000' + account.getPassword();
- return Base64.encodeToString(sasl.getBytes(Charset.defaultCharset()), Base64.NO_WRAP);
- }
+ @Override
+ public String getClientFirstMessage() {
+ final String sasl = '\u0000' + account.getUsername() + '\u0000' + account.getPassword();
+ return Base64.encodeToString(sasl.getBytes(Charset.defaultCharset()), Base64.NO_WRAP);
+ }
}
diff --git a/src/main/java/de/pixart/messenger/crypto/sasl/SaslMechanism.java b/src/main/java/de/pixart/messenger/crypto/sasl/SaslMechanism.java
index 19e8f3591..8b8883b9f 100644
--- a/src/main/java/de/pixart/messenger/crypto/sasl/SaslMechanism.java
+++ b/src/main/java/de/pixart/messenger/crypto/sasl/SaslMechanism.java
@@ -7,56 +7,59 @@ import de.pixart.messenger.xml.TagWriter;
public abstract class SaslMechanism {
- final protected TagWriter tagWriter;
- final protected Account account;
- final protected SecureRandom rng;
-
- protected enum State {
- INITIAL,
- AUTH_TEXT_SENT,
- RESPONSE_SENT,
- VALID_SERVER_RESPONSE,
- }
-
- public static class AuthenticationException extends Exception {
- public AuthenticationException(final String message) {
- super(message);
- }
-
- public AuthenticationException(final Exception inner) {
- super(inner);
- }
- }
-
- public static class InvalidStateException extends AuthenticationException {
- public InvalidStateException(final String message) {
- super(message);
- }
-
- public InvalidStateException(final State state) {
- this("Invalid state: " + state.toString());
- }
- }
-
- public SaslMechanism(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
- this.tagWriter = tagWriter;
- this.account = account;
- this.rng = rng;
- }
-
- /**
- * The priority is used to pin the authentication mechanism. If authentication fails, it MAY be retried with another
- * mechanism of the same priority, but MUST NOT be tried with a mechanism of lower priority (to prevent downgrade
- * attacks).
- * @return An arbitrary int representing the priority
- */
- public abstract int getPriority();
-
- public abstract String getMechanism();
- public String getClientFirstMessage() {
- return "";
- }
- public String getResponse(final String challenge) throws AuthenticationException {
- return "";
- }
+ final protected TagWriter tagWriter;
+ final protected Account account;
+ final protected SecureRandom rng;
+
+ protected enum State {
+ INITIAL,
+ AUTH_TEXT_SENT,
+ RESPONSE_SENT,
+ VALID_SERVER_RESPONSE,
+ }
+
+ public static class AuthenticationException extends Exception {
+ public AuthenticationException(final String message) {
+ super(message);
+ }
+
+ public AuthenticationException(final Exception inner) {
+ super(inner);
+ }
+ }
+
+ public static class InvalidStateException extends AuthenticationException {
+ public InvalidStateException(final String message) {
+ super(message);
+ }
+
+ public InvalidStateException(final State state) {
+ this("Invalid state: " + state.toString());
+ }
+ }
+
+ public SaslMechanism(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
+ this.tagWriter = tagWriter;
+ this.account = account;
+ this.rng = rng;
+ }
+
+ /**
+ * The priority is used to pin the authentication mechanism. If authentication fails, it MAY be retried with another
+ * mechanism of the same priority, but MUST NOT be tried with a mechanism of lower priority (to prevent downgrade
+ * attacks).
+ *
+ * @return An arbitrary int representing the priority
+ */
+ public abstract int getPriority();
+
+ public abstract String getMechanism();
+
+ public String getClientFirstMessage() {
+ return "";
+ }
+
+ public String getResponse(final String challenge) throws AuthenticationException {
+ return "";
+ }
}
diff --git a/src/main/java/de/pixart/messenger/crypto/sasl/ScramSha1.java b/src/main/java/de/pixart/messenger/crypto/sasl/ScramSha1.java
index abff542d4..d21cad37d 100644
--- a/src/main/java/de/pixart/messenger/crypto/sasl/ScramSha1.java
+++ b/src/main/java/de/pixart/messenger/crypto/sasl/ScramSha1.java
@@ -18,221 +18,222 @@ import de.pixart.messenger.utils.CryptoHelper;
import de.pixart.messenger.xml.TagWriter;
public class ScramSha1 extends SaslMechanism {
- // TODO: When channel binding (SCRAM-SHA1-PLUS) is supported in future, generalize this to indicate support and/or usage.
- final private static String GS2_HEADER = "n,,";
- private String clientFirstMessageBare;
- final private String clientNonce;
- private byte[] serverSignature = null;
- private static HMac HMAC;
- private static Digest DIGEST;
- private static final byte[] CLIENT_KEY_BYTES = "Client Key".getBytes();
- private static final byte[] SERVER_KEY_BYTES = "Server Key".getBytes();
-
- public static class KeyPair {
- final public byte[] clientKey;
- final public byte[] serverKey;
-
- public KeyPair(final byte[] clientKey, final byte[] serverKey) {
- this.clientKey = clientKey;
- this.serverKey = serverKey;
- }
- }
-
- private static final LruCache<String, KeyPair> CACHE;
-
- static {
- DIGEST = new SHA1Digest();
- HMAC = new HMac(new SHA1Digest());
- CACHE = new LruCache<String, KeyPair>(10) {
- protected KeyPair create(final String k) {
- // Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations".
- // Changing any of these values forces a cache miss. `CryptoHelper.bytesToHex()'
- // is applied to prevent commas in the strings breaking things.
- final String[] kparts = k.split(",", 4);
- try {
- final byte[] saltedPassword, serverKey, clientKey;
- saltedPassword = hi(CryptoHelper.hexToString(kparts[1]).getBytes(),
- Base64.decode(CryptoHelper.hexToString(kparts[2]), Base64.DEFAULT), Integer.valueOf(kparts[3]));
- serverKey = hmac(saltedPassword, SERVER_KEY_BYTES);
- clientKey = hmac(saltedPassword, CLIENT_KEY_BYTES);
-
- return new KeyPair(clientKey, serverKey);
- } catch (final InvalidKeyException | NumberFormatException e) {
- return null;
- }
- }
- };
- }
-
- private State state = State.INITIAL;
-
- public ScramSha1(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
- super(tagWriter, account, rng);
-
- // This nonce should be different for each authentication attempt.
- clientNonce = new BigInteger(100, this.rng).toString(32);
- clientFirstMessageBare = "";
- }
-
- @Override
- public int getPriority() {
- return 20;
- }
-
- @Override
- public String getMechanism() {
- return "SCRAM-SHA-1";
- }
-
- @Override
- public String getClientFirstMessage() {
- if (clientFirstMessageBare.isEmpty() && state == State.INITIAL) {
- clientFirstMessageBare = "n=" + CryptoHelper.saslEscape(CryptoHelper.saslPrep(account.getUsername())) +
- ",r=" + this.clientNonce;
- state = State.AUTH_TEXT_SENT;
- }
- return Base64.encodeToString(
- (GS2_HEADER + clientFirstMessageBare).getBytes(Charset.defaultCharset()),
- Base64.NO_WRAP);
- }
-
- @Override
- public String getResponse(final String challenge) throws AuthenticationException {
- switch (state) {
- case AUTH_TEXT_SENT:
- if (challenge == null) {
- throw new AuthenticationException("challenge can not be null");
- }
- byte[] serverFirstMessage = Base64.decode(challenge, Base64.DEFAULT);
- final Tokenizer tokenizer = new Tokenizer(serverFirstMessage);
- String nonce = "";
- int iterationCount = -1;
- String salt = "";
- for (final String token : tokenizer) {
- if (token.charAt(1) == '=') {
- switch (token.charAt(0)) {
- case 'i':
- try {
- iterationCount = Integer.parseInt(token.substring(2));
- } catch (final NumberFormatException e) {
- throw new AuthenticationException(e);
- }
- break;
- case 's':
- salt = token.substring(2);
- break;
- case 'r':
- nonce = token.substring(2);
- break;
- case 'm':
- /*
+ // TODO: When channel binding (SCRAM-SHA1-PLUS) is supported in future, generalize this to indicate support and/or usage.
+ final private static String GS2_HEADER = "n,,";
+ private String clientFirstMessageBare;
+ final private String clientNonce;
+ private byte[] serverSignature = null;
+ private static HMac HMAC;
+ private static Digest DIGEST;
+ private static final byte[] CLIENT_KEY_BYTES = "Client Key".getBytes();
+ private static final byte[] SERVER_KEY_BYTES = "Server Key".getBytes();
+
+ public static class KeyPair {
+ final public byte[] clientKey;
+ final public byte[] serverKey;
+
+ public KeyPair(final byte[] clientKey, final byte[] serverKey) {
+ this.clientKey = clientKey;
+ this.serverKey = serverKey;
+ }
+ }
+
+ private static final LruCache<String, KeyPair> CACHE;
+
+ static {
+ DIGEST = new SHA1Digest();
+ HMAC = new HMac(new SHA1Digest());
+ CACHE = new LruCache<String, KeyPair>(10) {
+ protected KeyPair create(final String k) {
+ // Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations".
+ // Changing any of these values forces a cache miss. `CryptoHelper.bytesToHex()'
+ // is applied to prevent commas in the strings breaking things.
+ final String[] kparts = k.split(",", 4);
+ try {
+ final byte[] saltedPassword, serverKey, clientKey;
+ saltedPassword = hi(CryptoHelper.hexToString(kparts[1]).getBytes(),
+ Base64.decode(CryptoHelper.hexToString(kparts[2]), Base64.DEFAULT), Integer.valueOf(kparts[3]));
+ serverKey = hmac(saltedPassword, SERVER_KEY_BYTES);
+ clientKey = hmac(saltedPassword, CLIENT_KEY_BYTES);
+
+ return new KeyPair(clientKey, serverKey);
+ } catch (final InvalidKeyException | NumberFormatException e) {
+ return null;
+ }
+ }
+ };
+ }
+
+ private State state = State.INITIAL;
+
+ public ScramSha1(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
+ super(tagWriter, account, rng);
+
+ // This nonce should be different for each authentication attempt.
+ clientNonce = new BigInteger(100, this.rng).toString(32);
+ clientFirstMessageBare = "";
+ }
+
+ @Override
+ public int getPriority() {
+ return 20;
+ }
+
+ @Override
+ public String getMechanism() {
+ return "SCRAM-SHA-1";
+ }
+
+ @Override
+ public String getClientFirstMessage() {
+ if (clientFirstMessageBare.isEmpty() && state == State.INITIAL) {
+ clientFirstMessageBare = "n=" + CryptoHelper.saslEscape(CryptoHelper.saslPrep(account.getUsername())) +
+ ",r=" + this.clientNonce;
+ state = State.AUTH_TEXT_SENT;
+ }
+ return Base64.encodeToString(
+ (GS2_HEADER + clientFirstMessageBare).getBytes(Charset.defaultCharset()),
+ Base64.NO_WRAP);
+ }
+
+ @Override
+ public String getResponse(final String challenge) throws AuthenticationException {
+ switch (state) {
+ case AUTH_TEXT_SENT:
+ if (challenge == null) {
+ throw new AuthenticationException("challenge can not be null");
+ }
+ byte[] serverFirstMessage = Base64.decode(challenge, Base64.DEFAULT);
+ final Tokenizer tokenizer = new Tokenizer(serverFirstMessage);
+ String nonce = "";
+ int iterationCount = -1;
+ String salt = "";
+ for (final String token : tokenizer) {
+ if (token.charAt(1) == '=') {
+ switch (token.charAt(0)) {
+ case 'i':
+ try {
+ iterationCount = Integer.parseInt(token.substring(2));
+ } catch (final NumberFormatException e) {
+ throw new AuthenticationException(e);
+ }
+ break;
+ case 's':
+ salt = token.substring(2);
+ break;
+ case 'r':
+ nonce = token.substring(2);
+ break;
+ case 'm':
+ /*
* RFC 5802:
* m: This attribute is reserved for future extensibility. In this
* version of SCRAM, its presence in a client or a server message
* MUST cause authentication failure when the attribute is parsed by
* the other end.
*/
- throw new AuthenticationException("Server sent reserved token: `m'");
- }
- }
- }
-
- if (iterationCount < 0) {
- throw new AuthenticationException("Server did not send iteration count");
- }
- if (nonce.isEmpty() || !nonce.startsWith(clientNonce)) {
- throw new AuthenticationException("Server nonce does not contain client nonce: " + nonce);
- }
- if (salt.isEmpty()) {
- throw new AuthenticationException("Server sent empty salt");
- }
-
- final String clientFinalMessageWithoutProof = "c=" + Base64.encodeToString(
- GS2_HEADER.getBytes(), Base64.NO_WRAP) + ",r=" + nonce;
- final byte[] authMessage = (clientFirstMessageBare + ',' + new String(serverFirstMessage) + ','
- + clientFinalMessageWithoutProof).getBytes();
-
- // Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations".
- final KeyPair keys = CACHE.get(
- CryptoHelper.bytesToHex(account.getJid().toBareJid().toString().getBytes()) + ","
- + CryptoHelper.bytesToHex(account.getPassword().getBytes()) + ","
- + CryptoHelper.bytesToHex(salt.getBytes()) + ","
- + String.valueOf(iterationCount)
- );
- if (keys == null) {
- throw new AuthenticationException("Invalid keys generated");
- }
- final byte[] clientSignature;
- try {
- serverSignature = hmac(keys.serverKey, authMessage);
- final byte[] storedKey = digest(keys.clientKey);
-
- clientSignature = hmac(storedKey, authMessage);
-
- } catch (final InvalidKeyException e) {
- throw new AuthenticationException(e);
- }
-
- final byte[] clientProof = new byte[keys.clientKey.length];
-
- for (int i = 0; i < clientProof.length; i++) {
- clientProof[i] = (byte) (keys.clientKey[i] ^ clientSignature[i]);
- }
-
-
- final String clientFinalMessage = clientFinalMessageWithoutProof + ",p=" +
- Base64.encodeToString(clientProof, Base64.NO_WRAP);
- state = State.RESPONSE_SENT;
- return Base64.encodeToString(clientFinalMessage.getBytes(), Base64.NO_WRAP);
- case RESPONSE_SENT:
- try {
- final String clientCalculatedServerFinalMessage = "v=" +
- Base64.encodeToString(serverSignature, Base64.NO_WRAP);
- if (!clientCalculatedServerFinalMessage.equals(new String(Base64.decode(challenge, Base64.DEFAULT)))) {
- throw new Exception();
- };
- state = State.VALID_SERVER_RESPONSE;
- return "";
- } catch(Exception e) {
- throw new AuthenticationException("Server final message does not match calculated final message");
- }
- default:
- throw new InvalidStateException(state);
- }
- }
-
- public static synchronized byte[] hmac(final byte[] key, final byte[] input)
- throws InvalidKeyException {
- HMAC.init(new KeyParameter(key));
- HMAC.update(input, 0, input.length);
- final byte[] out = new byte[HMAC.getMacSize()];
- HMAC.doFinal(out, 0);
- return out;
- }
-
- public static synchronized byte[] digest(byte[] bytes) {
- DIGEST.reset();
- DIGEST.update(bytes, 0, bytes.length);
- final byte[] out = new byte[DIGEST.getDigestSize()];
- DIGEST.doFinal(out, 0);
- return out;
- }
-
- /*
- * Hi() is, essentially, PBKDF2 [RFC2898] with HMAC() as the
- * pseudorandom function (PRF) and with dkLen == output length of
- * HMAC() == output length of H().
- */
- private static synchronized byte[] hi(final byte[] key, final byte[] salt, final int iterations)
- throws InvalidKeyException {
- byte[] u = hmac(key, CryptoHelper.concatenateByteArrays(salt, CryptoHelper.ONE));
- byte[] out = u.clone();
- for (int i = 1; i < iterations; i++) {
- u = hmac(key, u);
- for (int j = 0; j < u.length; j++) {
- out[j] ^= u[j];
- }
- }
- return out;
- }
+ throw new AuthenticationException("Server sent reserved token: `m'");
+ }
+ }
+ }
+
+ if (iterationCount < 0) {
+ throw new AuthenticationException("Server did not send iteration count");
+ }
+ if (nonce.isEmpty() || !nonce.startsWith(clientNonce)) {
+ throw new AuthenticationException("Server nonce does not contain client nonce: " + nonce);
+ }
+ if (salt.isEmpty()) {
+ throw new AuthenticationException("Server sent empty salt");
+ }
+
+ final String clientFinalMessageWithoutProof = "c=" + Base64.encodeToString(
+ GS2_HEADER.getBytes(), Base64.NO_WRAP) + ",r=" + nonce;
+ final byte[] authMessage = (clientFirstMessageBare + ',' + new String(serverFirstMessage) + ','
+ + clientFinalMessageWithoutProof).getBytes();
+
+ // Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations".
+ final KeyPair keys = CACHE.get(
+ CryptoHelper.bytesToHex(account.getJid().toBareJid().toString().getBytes()) + ","
+ + CryptoHelper.bytesToHex(account.getPassword().getBytes()) + ","
+ + CryptoHelper.bytesToHex(salt.getBytes()) + ","
+ + String.valueOf(iterationCount)
+ );
+ if (keys == null) {
+ throw new AuthenticationException("Invalid keys generated");
+ }
+ final byte[] clientSignature;
+ try {
+ serverSignature = hmac(keys.serverKey, authMessage);
+ final byte[] storedKey = digest(keys.clientKey);
+
+ clientSignature = hmac(storedKey, authMessage);
+
+ } catch (final InvalidKeyException e) {
+ throw new AuthenticationException(e);
+ }
+
+ final byte[] clientProof = new byte[keys.clientKey.length];
+
+ for (int i = 0; i < clientProof.length; i++) {
+ clientProof[i] = (byte) (keys.clientKey[i] ^ clientSignature[i]);
+ }
+
+
+ final String clientFinalMessage = clientFinalMessageWithoutProof + ",p=" +
+ Base64.encodeToString(clientProof, Base64.NO_WRAP);
+ state = State.RESPONSE_SENT;
+ return Base64.encodeToString(clientFinalMessage.getBytes(), Base64.NO_WRAP);
+ case RESPONSE_SENT:
+ try {
+ final String clientCalculatedServerFinalMessage = "v=" +
+ Base64.encodeToString(serverSignature, Base64.NO_WRAP);
+ if (!clientCalculatedServerFinalMessage.equals(new String(Base64.decode(challenge, Base64.DEFAULT)))) {
+ throw new Exception();
+ }
+ ;
+ state = State.VALID_SERVER_RESPONSE;
+ return "";
+ } catch (Exception e) {
+ throw new AuthenticationException("Server final message does not match calculated final message");
+ }
+ default:
+ throw new InvalidStateException(state);
+ }
+ }
+
+ public static synchronized byte[] hmac(final byte[] key, final byte[] input)
+ throws InvalidKeyException {
+ HMAC.init(new KeyParameter(key));
+ HMAC.update(input, 0, input.length);
+ final byte[] out = new byte[HMAC.getMacSize()];
+ HMAC.doFinal(out, 0);
+ return out;
+ }
+
+ public static synchronized byte[] digest(byte[] bytes) {
+ DIGEST.reset();
+ DIGEST.update(bytes, 0, bytes.length);
+ final byte[] out = new byte[DIGEST.getDigestSize()];
+ DIGEST.doFinal(out, 0);
+ return out;
+ }
+
+ /*
+ * Hi() is, essentially, PBKDF2 [RFC2898] with HMAC() as the
+ * pseudorandom function (PRF) and with dkLen == output length of
+ * HMAC() == output length of H().
+ */
+ private static synchronized byte[] hi(final byte[] key, final byte[] salt, final int iterations)
+ throws InvalidKeyException {
+ byte[] u = hmac(key, CryptoHelper.concatenateByteArrays(salt, CryptoHelper.ONE));
+ byte[] out = u.clone();
+ for (int i = 1; i < iterations; i++) {
+ u = hmac(key, u);
+ for (int j = 0; j < u.length; j++) {
+ out[j] ^= u[j];
+ }
+ }
+ return out;
+ }
}
diff --git a/src/main/java/de/pixart/messenger/crypto/sasl/Tokenizer.java b/src/main/java/de/pixart/messenger/crypto/sasl/Tokenizer.java
index 01cd07929..67e4418dd 100644
--- a/src/main/java/de/pixart/messenger/crypto/sasl/Tokenizer.java
+++ b/src/main/java/de/pixart/messenger/crypto/sasl/Tokenizer.java
@@ -10,69 +10,69 @@ import java.util.NoSuchElementException;
* A tokenizer for GS2 header strings
*/
public final class Tokenizer implements Iterator<String>, Iterable<String> {
- private final List<String> parts;
- private int index;
+ private final List<String> parts;
+ private int index;
- public Tokenizer(final byte[] challenge) {
- final String challengeString = new String(challenge);
- parts = new ArrayList<>(Arrays.asList(challengeString.split(",")));
- // Trim parts.
- for (int i = 0; i < parts.size(); i++) {
- parts.set(i, parts.get(i).trim());
- }
- index = 0;
- }
+ public Tokenizer(final byte[] challenge) {
+ final String challengeString = new String(challenge);
+ parts = new ArrayList<>(Arrays.asList(challengeString.split(",")));
+ // Trim parts.
+ for (int i = 0; i < parts.size(); i++) {
+ parts.set(i, parts.get(i).trim());
+ }
+ index = 0;
+ }
- /**
- * Returns true if there is at least one more element, false otherwise.
- *
- * @see #next
- */
- @Override
- public boolean hasNext() {
- return parts.size() != index + 1;
- }
+ /**
+ * Returns true if there is at least one more element, false otherwise.
+ *
+ * @see #next
+ */
+ @Override
+ public boolean hasNext() {
+ return parts.size() != index + 1;
+ }
- /**
- * Returns the next object and advances the iterator.
- *
- * @return the next object.
- * @throws java.util.NoSuchElementException if there are no more elements.
- * @see #hasNext
- */
- @Override
- public String next() {
- if (hasNext()) {
- return parts.get(index++);
- } else {
- throw new NoSuchElementException("No such element. Size is: " + parts.size());
- }
- }
+ /**
+ * Returns the next object and advances the iterator.
+ *
+ * @return the next object.
+ * @throws java.util.NoSuchElementException if there are no more elements.
+ * @see #hasNext
+ */
+ @Override
+ public String next() {
+ if (hasNext()) {
+ return parts.get(index++);
+ } else {
+ throw new NoSuchElementException("No such element. Size is: " + parts.size());
+ }
+ }
- /**
- * Removes the last object returned by {@code next} from the collection.
- * This method can only be called once between each call to {@code next}.
- *
- * @throws UnsupportedOperationException if removing is not supported by the collection being
- * iterated.
- * @throws IllegalStateException if {@code next} has not been called, or {@code remove} has
- * already been called after the last call to {@code next}.
- */
- @Override
- public void remove() {
- if(index <= 0) {
- throw new IllegalStateException("You can't delete an element before first next() method call");
- }
- parts.remove(--index);
- }
+ /**
+ * Removes the last object returned by {@code next} from the collection.
+ * This method can only be called once between each call to {@code next}.
+ *
+ * @throws UnsupportedOperationException if removing is not supported by the collection being
+ * iterated.
+ * @throws IllegalStateException if {@code next} has not been called, or {@code remove} has
+ * already been called after the last call to {@code next}.
+ */
+ @Override
+ public void remove() {
+ if (index <= 0) {
+ throw new IllegalStateException("You can't delete an element before first next() method call");
+ }
+ parts.remove(--index);
+ }
- /**
- * Returns an {@link java.util.Iterator} for the elements in this object.
- *
- * @return An {@code Iterator} instance.
- */
- @Override
- public Iterator<String> iterator() {
- return parts.iterator();
- }
+ /**
+ * Returns an {@link java.util.Iterator} for the elements in this object.
+ *
+ * @return An {@code Iterator} instance.
+ */
+ @Override
+ public Iterator<String> iterator() {
+ return parts.iterator();
+ }
}