aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/de/thedevstack/conversationsplus/crypto
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de/thedevstack/conversationsplus/crypto')
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/OtrService.java310
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java373
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/sasl/DigestMd5.java91
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/sasl/Plain.java30
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/sasl/SaslMechanism.java62
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/sasl/ScramSha1.java232
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/sasl/Tokenizer.java78
7 files changed, 0 insertions, 1176 deletions
diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/OtrService.java b/src/main/java/de/thedevstack/conversationsplus/crypto/OtrService.java
deleted file mode 100644
index ee7f492f..00000000
--- a/src/main/java/de/thedevstack/conversationsplus/crypto/OtrService.java
+++ /dev/null
@@ -1,310 +0,0 @@
-package de.thedevstack.conversationsplus.crypto;
-
-import java.math.BigInteger;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.spec.DSAPrivateKeySpec;
-import java.security.spec.DSAPublicKeySpec;
-import java.security.spec.InvalidKeySpecException;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.util.Log;
-
-import de.thedevstack.conversationsplus.Config;
-import de.thedevstack.conversationsplus.entities.Account;
-import de.thedevstack.conversationsplus.entities.Conversation;
-import de.thedevstack.conversationsplus.services.XmppConnectionService;
-import de.thedevstack.conversationsplus.utils.CryptoHelper;
-import de.thedevstack.conversationsplus.xmpp.chatstate.ChatState;
-import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException;
-import de.thedevstack.conversationsplus.xmpp.jid.Jid;
-import de.thedevstack.conversationsplus.xmpp.stanzas.MessagePacket;
-
-import net.java.otr4j.OtrEngineHost;
-import net.java.otr4j.OtrException;
-import net.java.otr4j.OtrPolicy;
-import net.java.otr4j.OtrPolicyImpl;
-import net.java.otr4j.crypto.OtrCryptoEngineImpl;
-import net.java.otr4j.crypto.OtrCryptoException;
-import net.java.otr4j.session.InstanceTag;
-import net.java.otr4j.session.SessionID;
-import net.java.otr4j.session.FragmenterInstructions;
-
-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(JSONObject keys) {
- if (keys == null) {
- return null;
- }
- try {
- BigInteger x = new BigInteger(keys.getString("otr_x"), 16);
- BigInteger y = new BigInteger(keys.getString("otr_y"), 16);
- BigInteger p = new BigInteger(keys.getString("otr_p"), 16);
- BigInteger q = new BigInteger(keys.getString("otr_q"), 16);
- BigInteger g = new BigInteger(keys.getString("otr_g"), 16);
- KeyFactory keyFactory = KeyFactory.getInstance("DSA");
- DSAPublicKeySpec pubKeySpec = new DSAPublicKeySpec(y, p, q, g);
- DSAPrivateKeySpec privateKeySpec = new DSAPrivateKeySpec(x, p, q, g);
- PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
- PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
- return new KeyPair(publicKey, privateKey);
- } catch (JSONException e) {
- return null;
- } catch (NoSuchAlgorithmException e) {
- return null;
- } catch (InvalidKeySpecException e) {
- 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();
- }
-
- }
-
- @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 "I would like to start a private (OTR encrypted) conversation but your client doesn’t seem to support that";
- }
-
- @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);
- packet.addChild("private", "urn:xmpp:carbons:2");
- packet.addChild("no-copy", "urn:xmpp:hints");
- packet.addChild("no-permanent-store", "urn:xmpp:hints");
- packet.addChild("no-permanent-storage", "urn:xmpp:hints");
- 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;
- }
-
-}
diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java b/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java
deleted file mode 100644
index 49c8db54..00000000
--- a/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java
+++ /dev/null
@@ -1,373 +0,0 @@
-package de.thedevstack.conversationsplus.crypto;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URL;
-
-import org.openintents.openpgp.OpenPgpSignatureResult;
-import org.openintents.openpgp.util.OpenPgpApi;
-import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
-
-import de.tzur.conversations.Settings;
-import de.thedevstack.conversationsplus.R;
-import de.thedevstack.conversationsplus.entities.Account;
-import de.thedevstack.conversationsplus.entities.Contact;
-import de.thedevstack.conversationsplus.entities.Conversation;
-import de.thedevstack.conversationsplus.entities.DownloadableFile;
-import de.thedevstack.conversationsplus.entities.Message;
-import de.thedevstack.conversationsplus.http.HttpConnectionManager;
-import de.thedevstack.conversationsplus.services.XmppConnectionService;
-import de.thedevstack.conversationsplus.ui.UiCallback;
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.net.Uri;
-
-public class PgpEngine {
- private OpenPgpApi api;
- private XmppConnectionService mXmppConnectionService;
-
- public PgpEngine(OpenPgpApi api, XmppConnectionService service) {
- this.api = api;
- this.mXmppConnectionService = service;
- }
-
- public void decrypt(final Message message,
- final UiCallback<Message> callback) {
- Intent params = new Intent();
- params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
- params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
- .getConversation().getAccount().getJid().toBareJid().toString());
- if (message.getType() == Message.TYPE_TEXT) {
- InputStream is = new ByteArrayInputStream(message.getBody()
- .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();
- if (message.getEncryption() == Message.ENCRYPTION_PGP) {
- message.setBody(os.toString());
- message.setEncryption(Message.ENCRYPTION_DECRYPTED);
- final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
- if (message.trusted()
- && message.treatAsDownloadable() != Message.Decision.NEVER
- && Settings.DOWNLOAD_IMAGE_LINKS
- && mXmppConnectionService.isDownloadAllowedInConnection()
- && manager.getAutoAcceptFileSize() > 0) {
- manager.createNewDownloadConnection(message);
- }
- callback.success(message);
- }
- } catch (IOException e) {
- callback.error(R.string.openpgp_error, message);
- return;
- }
-
- return;
- case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- callback.userInputRequried((PendingIntent) result
- .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
- message);
- return;
- case OpenPgpApi.RESULT_CODE_ERROR:
- callback.error(R.string.openpgp_error, message);
- }
- }
- });
- } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
- try {
- final DownloadableFile inputFile = this.mXmppConnectionService
- .getFileBackend().getFile(message, false);
- final DownloadableFile outputFile = this.mXmppConnectionService
- .getFileBackend().getFile(message, true);
- outputFile.getParentFile().mkdirs();
- outputFile.createNewFile();
- InputStream is = new FileInputStream(inputFile);
- 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:
- URL url = message.getFileParams().url;
- mXmppConnectionService.getFileBackend().updateFileParams(message,url);
- message.setEncryption(Message.ENCRYPTION_DECRYPTED);
- PgpEngine.this.mXmppConnectionService
- .updateMessage(message);
- inputFile.delete();
- Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
- intent.setData(Uri.fromFile(outputFile));
- mXmppConnectionService.sendBroadcast(intent);
- callback.success(message);
- return;
- case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- callback.userInputRequried(
- (PendingIntent) result
- .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
- message);
- return;
- case OpenPgpApi.RESULT_CODE_ERROR:
- callback.error(R.string.openpgp_error, message);
- }
- }
- });
- } catch (final IOException e) {
- callback.error(R.string.error_decrypting_file, message);
- }
-
- }
- }
-
- public void encrypt(final Message message,
- final UiCallback<Message> callback) {
-
- Intent params = new Intent();
- params.setAction(OpenPgpApi.ACTION_ENCRYPT);
- if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
- long[] keys = { message.getConversation().getContact()
- .getPgpKeyId() };
- params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
- } else {
- params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, message.getConversation()
- .getMucOptions().getPgpKeyIds());
- }
- params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
- .getConversation().getAccount().getJid().toBareJid().toString());
-
- 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]);
- }
- }
- 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:
- 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();
- InputStream is = new FileInputStream(inputFile);
- 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:
- 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:
- 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", ""));
- 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);
- params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
- 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:
- return 0;
- }
- return 0;
- }
-
- public void generateSignature(final Account account, String status,
- final UiCallback<Account> callback) {
- Intent params = new Intent();
- params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
- params.setAction(OpenPgpApi.ACTION_SIGN);
- params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
- InputStream is = new ByteArrayInputStream(status.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, 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);
- }
- }
- }
- if (line.contains("BEGIN PGP SIGNATURE")) {
- sig = true;
- }
- }
- } catch (IOException e) {
- callback.error(R.string.openpgp_error, account);
- return;
- }
- account.setKey("pgp_signature", 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:
- callback.error(R.string.openpgp_error, account);
- }
- }
- });
- }
-
- 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());
- params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
- .getJid().toBareJid().toString());
- 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:
- callback.error(R.string.openpgp_error, contact);
- }
- }
- });
- }
-
- public PendingIntent getIntentForKey(Contact contact) {
- Intent params = new Intent();
- params.setAction(OpenPgpApi.ACTION_GET_KEY);
- params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
- params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
- .getJid().toBareJid().toString());
- Intent result = api.executeApi(params, null, null);
- return (PendingIntent) result
- .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
- }
-
- public PendingIntent getIntentForKey(Account account, long pgpKeyId) {
- Intent params = new Intent();
- params.setAction(OpenPgpApi.ACTION_GET_KEY);
- params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
- params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
- Intent result = api.executeApi(params, null, null);
- return (PendingIntent) result
- .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
- }
-}
diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/DigestMd5.java b/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/DigestMd5.java
deleted file mode 100644
index f4c3f28e..00000000
--- a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/DigestMd5.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package de.thedevstack.conversationsplus.crypto.sasl;
-
-import android.util.Base64;
-
-import java.math.BigInteger;
-import java.nio.charset.Charset;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-
-import de.thedevstack.conversationsplus.entities.Account;
-import de.thedevstack.conversationsplus.utils.CryptoHelper;
-import de.thedevstack.conversationsplus.xml.TagWriter;
-
-public class DigestMd5 extends SaslMechanism {
- public DigestMd5(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
- super(tagWriter, account, rng);
- }
-
- @Override
- public int getPriority() {
- return 10;
- }
-
- @Override
- public String getMechanism() {
- return "DIGEST-MD5";
- }
-
- 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);
- }
-
- 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/thedevstack/conversationsplus/crypto/sasl/Plain.java b/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/Plain.java
deleted file mode 100644
index 179c12de..00000000
--- a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/Plain.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package de.thedevstack.conversationsplus.crypto.sasl;
-
-import android.util.Base64;
-
-import java.nio.charset.Charset;
-
-import de.thedevstack.conversationsplus.entities.Account;
-import de.thedevstack.conversationsplus.xml.TagWriter;
-
-public class Plain extends SaslMechanism {
- public Plain(final TagWriter tagWriter, final Account account) {
- super(tagWriter, account, null);
- }
-
- @Override
- public int getPriority() {
- return 10;
- }
-
- @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);
- }
-}
diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/SaslMechanism.java b/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/SaslMechanism.java
deleted file mode 100644
index ed2764c8..00000000
--- a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/SaslMechanism.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package de.thedevstack.conversationsplus.crypto.sasl;
-
-import java.security.SecureRandom;
-
-import de.thedevstack.conversationsplus.entities.Account;
-import de.thedevstack.conversationsplus.xml.TagWriter;
-
-public abstract class SaslMechanism {
-
- final protected TagWriter tagWriter;
- final protected Account account;
- final protected SecureRandom rng;
-
- protected static 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/thedevstack/conversationsplus/crypto/sasl/ScramSha1.java b/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/ScramSha1.java
deleted file mode 100644
index a991b1d7..00000000
--- a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/ScramSha1.java
+++ /dev/null
@@ -1,232 +0,0 @@
-package de.thedevstack.conversationsplus.crypto.sasl;
-
-import android.util.Base64;
-import android.util.LruCache;
-
-import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.macs.HMac;
-import org.bouncycastle.crypto.params.KeyParameter;
-
-import java.math.BigInteger;
-import java.nio.charset.Charset;
-import java.security.InvalidKeyException;
-import java.security.SecureRandom;
-
-import de.thedevstack.conversationsplus.entities.Account;
-import de.thedevstack.conversationsplus.utils.CryptoHelper;
-import de.thedevstack.conversationsplus.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;
- private byte[] serverFirstMessage;
- 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:
- 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:
- final String clientCalculatedServerFinalMessage = "v=" +
- Base64.encodeToString(serverSignature, Base64.NO_WRAP);
- if (challenge == null || !clientCalculatedServerFinalMessage.equals(new String(Base64.decode(challenge, Base64.DEFAULT)))) {
- throw new AuthenticationException("Server final message does not match calculated final message");
- }
- state = State.VALID_SERVER_RESPONSE;
- return "";
- 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/thedevstack/conversationsplus/crypto/sasl/Tokenizer.java b/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/Tokenizer.java
deleted file mode 100644
index cc2805de..00000000
--- a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/Tokenizer.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package de.thedevstack.conversationsplus.crypto.sasl;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-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;
-
- 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 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);
- }
-
- /**
- * 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();
- }
-}