diff --git a/src/codegen/gen_cpp_Engine.ysl2 b/src/codegen/gen_cpp_Engine.ysl2 index 479dcb4..f1a193c 100644 --- a/src/codegen/gen_cpp_Engine.ysl2 +++ b/src/codegen/gen_cpp_Engine.ysl2 @@ -17,10 +17,12 @@ tstylesheet { #include #include #include + #include + #include "foundation_pEp_jniadapter_«@name».h" #include "throw_pEp_exception.hh" #include "jniutils.hh" - #include + #include "passphrase_callback.hh" using pEp::Adapter::session; using pEp::passphrase_cache; @@ -71,8 +73,34 @@ tstylesheet { choose { when "@cached = 'true'" { || - pEpLog("cached passphrase"); - PEP_STATUS status = passphrase_cache.api(::«@name»,session()`apply "parm", mode=call`); + pEpLog("cached passphrase mode"); + bool retryAgain = false; + int maxRetries = 3; + int retryCount = 0; + PEP_STATUS status; + do { + // the actual target function + pEpLog("calling passphrase_cache.api(::«@name»())"); + status = passphrase_cache.api(::«@name»,session()`apply "parm", mode=call`); + pEpLog("PEP_STATUS:" << status); + if(status == PEP_PASSPHRASE_REQUIRED || status == PEP_WRONG_PASSPHRASE ) { + pEpLog("none of the cached passphrases worked"); + if(retryCount < maxRetries) { + // call the app + char* _passphrase = passphraseRequiredCallback(); + pEpLog("callback returned, config_passphrase() with new passphrase"); + PEP_STATUS status = ::config_passphrase(session(),passphrase_cache.add(_passphrase)); + retryAgain = true; + retryCount++; + } else { + pEpLog("max retries reached:" << maxRetries); + retryAgain = false; + } + } else { + retryAgain = false; + } + } while (retryAgain); + || } otherwise { || diff --git a/src/codegen/pEp.yml2 b/src/codegen/pEp.yml2 index ca00095..8810026 100644 --- a/src/codegen/pEp.yml2 +++ b/src/codegen/pEp.yml2 @@ -68,6 +68,7 @@ namespace pEp { sync_notify_accepted_device_accepted > 8 // sync_notify_overtaken > 9 // sync_notify_forming_group > 10 + sync_passphrase_required > 128 sync_notify_sole > 254 sync_notify_in_group > 255 }; diff --git a/src/cxx/foundation_pEp_jniadapter_AbstractEngine.cc b/src/cxx/foundation_pEp_jniadapter_AbstractEngine.cc index 6fcb77a..dc6b72d 100644 --- a/src/cxx/foundation_pEp_jniadapter_AbstractEngine.cc +++ b/src/cxx/foundation_pEp_jniadapter_AbstractEngine.cc @@ -9,6 +9,7 @@ #include #include "throw_pEp_exception.hh" #include "jniutils.hh" +#include "passphrase_callback.hh" namespace pEp { using namespace pEp::JNIAdapter; @@ -25,6 +26,7 @@ jmethodID messageConstructorMethodID = nullptr; jmethodID messageToSendMethodID = nullptr; jmethodID notifyHandShakeMethodID = nullptr; jmethodID needsFastPollMethodID = nullptr; +jmethodID passphraseRequiredMethodID = nullptr; jmethodID method_values = nullptr; jobject objj = nullptr; @@ -85,12 +87,33 @@ void jni_init() { engineClass, "notifyHandshakeCallFromC", "(Lfoundation/pEp/jniadapter/_Identity;Lfoundation/pEp/jniadapter/_Identity;Lfoundation/pEp/jniadapter/SyncHandshakeSignal;)I"); + passphraseRequiredMethodID = _env->GetMethodID( + engineClass, + "passphraseRequiredFromC", + "()[B"); method_values = JNISync::env()->GetStaticMethodID(signalClass, "values", "()[Lfoundation/pEp/jniadapter/SyncHandshakeSignal;"); field_value = JNISync::env()->GetFieldID(signalClass, "value", "I"); } +char* JNIAdapter::passphraseRequiredCallback() { + pEpLog("called"); + + assert(objj && passphraseRequiredMethodID); + + jobject ppJO = JNISync::env()->CallObjectMethod(objj, passphraseRequiredMethodID); + if (JNISync::env()->ExceptionCheck()) { + JNISync::env()->ExceptionDescribe(); + JNISync::env()->ExceptionClear(); + } + + jbyteArray ppJBA = reinterpret_cast(ppJO); + char* passphrase_ = to_string( JNISync::env(), ppJBA); + + return passphrase_; +} + PEP_STATUS messageToSend(message *msg) { std::lock_guard l(mutex_obj); diff --git a/src/cxx/passphrase_callback.hh b/src/cxx/passphrase_callback.hh new file mode 100644 index 0000000..357b2c6 --- /dev/null +++ b/src/cxx/passphrase_callback.hh @@ -0,0 +1,9 @@ + + +namespace pEp { + namespace JNIAdapter { + + char* passphraseRequiredCallback(); + + }; +}; \ No newline at end of file diff --git a/src/java/foundation/pEp/jniadapter/AbstractEngine.java b/src/java/foundation/pEp/jniadapter/AbstractEngine.java index d4005a8..ca2e09b 100644 --- a/src/java/foundation/pEp/jniadapter/AbstractEngine.java +++ b/src/java/foundation/pEp/jniadapter/AbstractEngine.java @@ -16,6 +16,7 @@ abstract class AbstractEngine extends UniquelyIdentifiable implements AbstractEn private Sync.MessageToSendCallback messageToSendCallback; private Sync.NotifyHandshakeCallback notifyHandshakeCallback; private Sync.NeedsFastPollCallback needsFastPollCallback; + private Sync.PassphraseRequiredCallback passphraseRequiredCallback; private final static DefaultCallback defaultCallback = new DefaultCallback(); @@ -32,7 +33,7 @@ abstract class AbstractEngine extends UniquelyIdentifiable implements AbstractEn } final public void close() { - synchronized (AbstractEngine.class) { + synchronized (AbstractEngine.class){ release(); } } @@ -95,6 +96,12 @@ abstract class AbstractEngine extends UniquelyIdentifiable implements AbstractEn this.needsFastPollCallback = needsFastPollCallback; } + public void setPassphraseRequiredCallback(Sync.PassphraseRequiredCallback passphraseRequiredCallback) { + System.out.println("passphraseRequiredCallback has been registered to:" + passphraseRequiredCallback.toString() + " on engine ObjID: " + getId()); + + this.passphraseRequiredCallback = passphraseRequiredCallback; + } + private int needsFastPollCallFromC(boolean fast_poll_needed) { if (needsFastPollCallback != null) { needsFastPollCallback.needsFastPollCallFromC(fast_poll_needed); @@ -107,7 +114,7 @@ abstract class AbstractEngine extends UniquelyIdentifiable implements AbstractEn private int notifyHandshakeCallFromC(_Identity _myself, _Identity _partner, SyncHandshakeSignal _signal) { Identity myself = new Identity(_myself); Identity partner = new Identity(_partner); - System.out.println("pEpSync" + "notifyHandshakeCallFromC: " + notifyHandshakeCallback); + System.out.println("pEpSync" +"notifyHandshakeCallFromC: " + notifyHandshakeCallback); if (notifyHandshakeCallback != null) { notifyHandshakeCallback.notifyHandshake(myself, partner, _signal); } else { @@ -116,8 +123,24 @@ abstract class AbstractEngine extends UniquelyIdentifiable implements AbstractEn return 0; } - private int messageToSendCallFromC(Message message) { - System.out.println("pEpSync" + "messageToSendCallFromC: " + messageToSendCallback); + private byte[] passphraseRequiredFromC() { + String ret = ""; + if (passphraseRequiredCallback != null) { + System.out.println("calling passphraseRequiredCallback on engine ObjID:" + getId()); + ret = passphraseRequiredCallback.passphraseRequired(); + } else { + System.out.println("no callback registered on engine ObjID:" + getId()); + // if this happens (no callback registered + // we simply return "" + // it will fail + // this repeats MaxRetries times (currentluy hardcoded to 3) + // Then the orig call will return with the PEP_STATUS (most likely PEP_PASSPHRASE_REQUIRED) + } + return Utils.toUTF8(ret); + } + + private int messageToSendCallFromC (Message message) { + System.out.println("pEpSync" + "messageToSendCallFromC: " + messageToSendCallback ); if (messageToSendCallback != null) { messageToSendCallback.messageToSend(message); } else { diff --git a/src/java/foundation/pEp/jniadapter/Sync.java b/src/java/foundation/pEp/jniadapter/Sync.java index 55f2e2c..b4fc818 100644 --- a/src/java/foundation/pEp/jniadapter/Sync.java +++ b/src/java/foundation/pEp/jniadapter/Sync.java @@ -15,6 +15,9 @@ public interface Sync { void notifyHandshake(Identity myself, Identity partner, SyncHandshakeSignal signal); } + interface PassphraseRequiredCallback { + String passphraseRequired(); + } public class DefaultCallback implements Sync.MessageToSendCallback, Sync.NotifyHandshakeCallback, Sync.NeedsFastPollCallback { @Override diff --git a/test/java/foundation/pEp/jniadapter/test/jni114/Makefile b/test/java/foundation/pEp/jniadapter/test/jni114/Makefile new file mode 100644 index 0000000..5180dc1 --- /dev/null +++ b/test/java/foundation/pEp/jniadapter/test/jni114/Makefile @@ -0,0 +1,37 @@ +include ../../../../../../../Makefile.conf +include ../Makefile.conf + +TEST_UNIT_NAME=jni114 + +JAVA_CLASSES = \ + TestAlice.class \ + ../utils/AdapterBaseTestContext.class \ + ../utils/AdapterTestUtils.class \ + ../utils/TestCallbacks.class + +.PHONY: pitytest compile alice test clean + +all: alice compile + +pitytest: + $(MAKE) -C $(PITYTEST_DIR) + +alice: compile clean-pep-home-alice + cd $(JAVA_CWD);pwd;HOME=$(JAVA_PEP_HOME_DIR_ALICE) $(JAVA) $(JAVA_PKG_BASENAME).$(TEST_UNIT_NAME).TestAlice + +compile: $(JAVA_CLASSES) pitytest + +%.class: %.java + cd $(JAVA_CWD);javac -cp $(CLASSPATH) $(JAVA_PKG_BASEPATH)/$(TEST_UNIT_NAME)/$< + +clean: + rm -f $(JAVA_CLASSES) + rm -f *.class + rm -f *.log + rm -Rf .gnupg + rm -Rf .lldb + +clean-pep-home: clean-pep-home-alice + +clean-pep-home-alice: + rm -rf $(PEP_HOME_DIR_ALICE)/.pEp diff --git a/test/java/foundation/pEp/jniadapter/test/jni114/TestAlice.java b/test/java/foundation/pEp/jniadapter/test/jni114/TestAlice.java new file mode 100644 index 0000000..4265690 --- /dev/null +++ b/test/java/foundation/pEp/jniadapter/test/jni114/TestAlice.java @@ -0,0 +1,77 @@ +package foundation.pEp.jniadapter.test.jni114; + +import static foundation.pEp.pitytest.TestLogger.*; +import static foundation.pEp.pitytest.utils.TestUtils.readKey; +import static foundation.pEp.pitytest.utils.TestUtils.sleep; + +import foundation.pEp.jniadapter.*; +import foundation.pEp.jniadapter.exceptions.*; +import foundation.pEp.pitytest.*; +import foundation.pEp.pitytest.utils.TestUtils; +import foundation.pEp.jniadapter.test.utils.*; + +import java.util.Vector; + + +// https://pep.foundation/jira/browse/JNI-111 + + +class TestAlice { + public static void main(String[] args) throws Exception { +// readKey(); + TestSuite.getDefault().setVerbose(true); + TestSuite.getDefault().setTestColor(TestUtils.TermColor.GREEN); + + AdapterBaseTestContext jni114Ctx = new AdapterBaseTestContext(); + new TestUnit("ImportKey/SetOwnKey", jni114Ctx, ctx -> { + // ImportKey and setOwnKey (with passphrase, of course) + ctx.alice = ctx.engine.importKey(ctx.keyAliceSecPassphrase).get(0); + log(AdapterTestUtils.identityToString(ctx.alice, true)); + ctx.alice.user_id = "23"; + ctx.alice = ctx.engine.setOwnKey(ctx.alice, ctx.alice.fpr); + assert ctx.alice != null : "Keyimport failed"; + assert ctx.alice.me == true; + assert ctx.alice.comm_type == CommType.PEP_ct_pEp; + }); + + + new TestUnit("no callback / encrypt fails nonblocking", jni114Ctx, ctx -> { + ctx.alice = ctx.engine.myself(ctx.alice); + try { + Message enc = ctx.engine.encrypt_message(ctx.msgToSelf, new Vector<>(), Message.EncFormat.PEP); + } catch (pEpException e) { + assert e instanceof pEpPassphraseRequired : "wrong exception type"; + return; + } + assert false : "encrypt_message() should have failed"; + }); + + + new TestUnit("use callback for encrypt", jni114Ctx, ctx -> { + // Register callback passphraseRequired() + ctx.engine.setPassphraseRequiredCallback(new Sync.PassphraseRequiredCallback() { + @Override + public String passphraseRequired() { + log("passphraseRequired() called"); + log("Please Enter Passphrase..."); + sleep(2000); + return "passphrase_alice"; + } + }); + + // myself + ctx.alice = ctx.engine.myself(ctx.alice); + log(AdapterTestUtils.identityToString(ctx.alice, true)); + + // Encrypt + assert ctx.msgToSelf.getEncFormat() == Message.EncFormat.None : "Orig msg not plain"; + Message enc = ctx.engine.encrypt_message(ctx.msgToSelf, new Vector<>(), Message.EncFormat.PEP); + assert enc.getEncFormat() == Message.EncFormat.PGPMIME : "Message not encrypted"; + assert !enc.getLongmsg().contains(ctx.msgToSelf.getLongmsg()) : "Message not encrypted"; + log(AdapterTestUtils.msgToString(enc, false)); + }); + + + TestSuite.getDefault().run(); + } +} \ No newline at end of file