Autocrypt setup message import fails with error "File does not contain a private key"
Reproducable with sending autocrypt setup message between desktop 0.104.0 and also android 0.304.0 and 0.104.0.

EDIT: Desktop release of course has to be from master with rust core.
diff --git a/python/install_py_bindings.sh b/python/install_py_bindings.sh
index 1b5c71cf..71822925 100755
--- a/python/install_py_bindings.sh
+++ b/python/install_py_bindings.sh
@@ -3,5 +3,5 @@
 set -ex
 
 cargo build -p deltachat_ffi --release
-rm -rf build/ 
+rm -rf build/ src/deltachat/*.so
 DCC_RS_DEV=`pwd`/.. pip install -e .
diff --git a/python/src/deltachat/__init__.py b/python/src/deltachat/__init__.py
index 7ad9d605..624b58c6 100644
--- a/python/src/deltachat/__init__.py
+++ b/python/src/deltachat/__init__.py
@@ -2,7 +2,7 @@
 from deltachat.capi import ffi
 from deltachat.account import Account  # noqa
 
-__version__ = "0.10.0.dev3"
+__version__ = "0.10.0.dev4"
 
 
 _DC_CALLBACK_MAP = {}
diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py
index a5404f04..6fb32b73 100644
--- a/python/src/deltachat/account.py
+++ b/python/src/deltachat/account.py
@@ -305,6 +305,19 @@ def import_from_file(self, path):
             lib.dc_perform_imap_jobs(self._dc_context)
         self._imex_completed.wait()
 
+    def initiate_key_transfer(self):
+        """return setup code after a Autocrypt setup message
+        has been successfully sent to our own e-mail address ("self-sent message").
+        If sending out was unsuccessful, a RuntimeError is raised.
+        """
+        self.check_is_configured()
+        if not self._threads.is_started():
+            raise RuntimeError("threads not running, can not send out")
+        res = lib.dc_initiate_key_transfer(self._dc_context)
+        if res == ffi.NULL:
+            raise RuntimeError("could not send out autocrypt setup message")
+        return from_dc_charpointer(res)
+
     def start_threads(self):
         """ start IMAP/SMTP threads (and configure account if it hasn't happened).
 
@@ -315,9 +328,10 @@ def start_threads(self):
             self.configure()
         self._threads.start()
 
-    def stop_threads(self):
+    def stop_threads(self, wait=True):
         """ stop IMAP/SMTP threads. """
-        self._threads.stop(wait=True)
+        lib.dc_stop_ongoing_process(self._dc_context)
+        self._threads.stop(wait=wait)
 
     def _process_event(self, ctx, evt_name, data1, data2):
         assert ctx == self._dc_context
@@ -362,14 +376,12 @@ def stop(self, wait=False):
                 thread.join()
 
     def imap_thread_run(self):
-        print("starting imap thread")
         while not self._thread_quitflag:
             lib.dc_perform_imap_jobs(self._dc_context)
             lib.dc_perform_imap_fetch(self._dc_context)
             lib.dc_perform_imap_idle(self._dc_context)
 
     def smtp_thread_run(self):
-        print("starting smtp thread")
         while not self._thread_quitflag:
             lib.dc_perform_smtp_jobs(self._dc_context)
             lib.dc_perform_smtp_idle(self._dc_context)
diff --git a/python/src/deltachat/message.py b/python/src/deltachat/message.py
index 4719e0b4..87d4e944 100644
--- a/python/src/deltachat/message.py
+++ b/python/src/deltachat/message.py
@@ -93,6 +93,14 @@ def view_type(self):
         """
         return MessageType(lib.dc_msg_get_viewtype(self._dc_msg))
 
+    def is_setup_message(self):
+        """ return True if this message is a setup message. """
+        return lib.dc_msg_is_setupmessage(self._dc_msg)
+
+    def continue_key_transfer(self, setup_code):
+        """ extract key and use it as primary key for this account. """
+        lib.dc_continue_key_transfer(self._dc_context, self.id, as_dc_charpointer(setup_code))
+
     @props.with_doc
     def time_sent(self):
         """UTC time when the message was sent.
diff --git a/python/tests/conftest.py b/python/tests/conftest.py
index ff1d4425..64668d93 100644
--- a/python/tests/conftest.py
+++ b/python/tests/conftest.py
@@ -103,7 +103,18 @@ def get_online_configuring_account(self):
             ac._evlogger.set_timeout(30)
             ac.configure(**configdict)
             ac.start_threads()
-            self._finalizers.append(ac.stop_threads)
+            self._finalizers.append(lambda: ac.stop_threads(wait=False))
+            return ac
+
+        def clone_online_account(self, account):
+            self.live_count += 1
+            tmpdb = tmpdir.join("livedb%d" % self.live_count)
+            ac = Account(tmpdb.strpath, logid="ac{}".format(self.live_count))
+            ac._evlogger.init_time = self.init_time
+            ac._evlogger.set_timeout(30)
+            ac.configure(addr=account.get_config("addr"), mail_pw=account.get_config("mail_pw"))
+            ac.start_threads()
+            self._finalizers.append(lambda: ac.stop_threads(wait=False))
             return ac
 
     return AccountMaker()
diff --git a/python/tests/test_account.py b/python/tests/test_account.py
index b1e4aa00..b24ebb36 100644
--- a/python/tests/test_account.py
+++ b/python/tests/test_account.py
@@ -252,6 +252,11 @@ def test_import_export_one_contact(self, acfactory, tmpdir):
         assert messages[0].text == "msg1"
         assert os.path.exists(messages[1].filename)
 
+    def test_ac_setup_message_fails(self, acfactory):
+        ac1 = acfactory.get_configured_offline_account()
+        with pytest.raises(RuntimeError):
+            ac1.initiate_key_transfer()
+
 
 class TestOnlineAccount:
     def test_one_account_init(self, acfactory):
@@ -441,3 +446,22 @@ def test_import_export_online(self, acfactory, tmpdir):
         messages = chat2.get_messages()
         assert len(messages) == 1
         assert messages[0].text == "msg1"
+
+    def test_ac_setup_message(self, acfactory):
+        # note that the receiving account needs to be configured and running
+        # before ther setup message is send. DC does not read old messages
+        # as of Jul2019
+        ac1 = acfactory.get_online_configuring_account()
+        ac2 = acfactory.clone_online_account(ac1)
+        wait_configuration_progress(ac2, 1000)
+        wait_configuration_progress(ac1, 1000)
+        assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
+        setup_code = ac1.initiate_key_transfer()
+        ac2._evlogger.set_timeout(10)
+        ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
+        msg = ac2.get_message_by_id(ev[2])
+        assert msg.is_setup_message()
+        print("*************** Incoming ASM File at: ", msg.filename)
+        print("*************** Setup Code: ", setup_code)
+        msg.continue_key_transfer(setup_code)
+        assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"]
diff --git a/src/dc_imex.rs b/src/dc_imex.rs
index 22b81c89..172b7790 100644
--- a/src/dc_imex.rs
+++ b/src/dc_imex.rs
@@ -1,4 +1,4 @@
-use std::ffi::{CStr, CString};
+use std::ffi::CString;
 
 use mmime::mailmime_content::*;
 use mmime::mmapstring::*;
@@ -362,11 +362,7 @@ pub unsafe fn dc_continue_key_transfer(
             }
             || *filename.offset(0isize) as libc::c_int == 0i32
         {
-            dc_log_error(
-                context,
-                0i32,
-                b"Message is no Autocrypt Setup Message.\x00" as *const u8 as *const libc::c_char,
-            );
+            error!(context, 0, "Message is no Autocrypt Setup Message.",);
         } else if 0
             == dc_read_file(
                 context,
@@ -377,29 +373,15 @@ pub unsafe fn dc_continue_key_transfer(
             || filecontent.is_null()
             || filebytes <= 0
         {
-            dc_log_error(
-                context,
-                0i32,
-                b"Cannot read Autocrypt Setup Message file.\x00" as *const u8
-                    as *const libc::c_char,
-            );
+            error!(context, 0, "Cannot read Autocrypt Setup Message file.",);
         } else {
             norm_sc = dc_normalize_setup_code(context, setup_code);
             if norm_sc.is_null() {
-                dc_log_warning(
-                    context,
-                    0i32,
-                    b"Cannot normalize Setup Code.\x00" as *const u8 as *const libc::c_char,
-                );
+                warn!(context, 0, "Cannot normalize Setup Code.",);
             } else {
                 armored_key = dc_decrypt_setup_file(context, norm_sc, filecontent);
                 if armored_key.is_null() {
-                    dc_log_warning(
-                        context,
-                        0i32,
-                        b"Cannot decrypt Autocrypt Setup Message.\x00" as *const u8
-                            as *const libc::c_char,
-                    );
+                    warn!(context, 0, "Cannot decrypt Autocrypt Setup Message.",);
                 } else if !(0 == set_self_key(context, armored_key, 1i32)) {
                     /*set default*/
                     /* error already logged */
@@ -420,136 +402,96 @@ pub unsafe fn dc_continue_key_transfer(
 // TODO should return bool /rtn
 unsafe fn set_self_key(
     context: &Context,
-    armored: *const libc::c_char,
+    armored_c: *const libc::c_char,
     set_default: libc::c_int,
 ) -> libc::c_int {
-    let mut success: libc::c_int = 0i32;
-    let buf: *mut libc::c_char;
-    // pointer inside buf, MUST NOT be free()'d
-    let mut buf_headerline: *const libc::c_char = 0 as *const libc::c_char;
-    //   - " -
-    let mut buf_preferencrypt: *const libc::c_char = 0 as *const libc::c_char;
-    //   - " -
-    let mut buf_base64: *const libc::c_char = 0 as *const libc::c_char;
+    let mut success = 0;
+
     let mut stmt: *mut sqlite3_stmt = 0 as *mut sqlite3_stmt;
     let mut self_addr: *mut libc::c_char = 0 as *mut libc::c_char;
-    buf = dc_strdup(armored);
-    if 0 == dc_split_armored_data(
-        buf,
-        &mut buf_headerline,
-        0 as *mut *const libc::c_char,
-        &mut buf_preferencrypt,
-        &mut buf_base64,
-    ) || strcmp(
-        buf_headerline,
-        b"-----BEGIN PGP PRIVATE KEY BLOCK-----\x00" as *const u8 as *const libc::c_char,
-    ) != 0i32
-        || buf_base64.is_null()
+
+    assert!(!armored_c.is_null(), "invalid buffer");
+    let armored = as_str(armored_c);
+
+    if let Some((private_key, public_key, header)) =
+        Key::from_armored_string(armored, KeyType::Private)
+            .and_then(|(k, h)| if k.verify() { Some((k, h)) } else { None })
+            .and_then(|(k, h)| k.split_key().map(|pub_key| (k, pub_key, h)))
     {
-        dc_log_warning(
+        stmt = dc_sqlite3_prepare(
             context,
-            0i32,
-            b"File does not contain a private key.\x00" as *const u8 as *const libc::c_char,
+            &context.sql,
+            b"DELETE FROM keypairs WHERE public_key=? OR private_key=?;\x00" as *const u8
+                as *const libc::c_char,
         );
-    } else {
-        if let Some((private_key, public_key)) = Key::from_base64(
-            CStr::from_ptr(buf_base64).to_str().unwrap(),
-            KeyType::Private,
-        )
-        .and_then(|k| if k.verify() { Some(k) } else { None })
-        .and_then(|k| k.split_key().map(|pub_key| (k, pub_key)))
-        {
-            stmt = dc_sqlite3_prepare(
-                context,
-                &context.sql,
-                b"DELETE FROM keypairs WHERE public_key=? OR private_key=?;\x00" as *const u8
-                    as *const libc::c_char,
-            );
-            let pub_bytes = public_key.to_bytes();
-            sqlite3_bind_blob(
-                stmt,
-                1,
-                pub_bytes.as_ptr() as *const _,
-                pub_bytes.len() as libc::c_int,
-                None,
-            );
-            let priv_bytes = private_key.to_bytes();
-            sqlite3_bind_blob(
-                stmt,
-                2,
-                priv_bytes.as_ptr() as *const _,
-                priv_bytes.len() as libc::c_int,
-                None,
-            );
-            sqlite3_step(stmt);
-            sqlite3_finalize(stmt);
-            stmt = 0 as *mut sqlite3_stmt;
-            if 0 != set_default {
-                dc_sqlite3_execute(
-                    context,
-                    &context.sql,
-                    b"UPDATE keypairs SET is_default=0;\x00" as *const u8 as *const libc::c_char,
-                );
-            }
-            self_addr = dc_sqlite3_get_config(
+        let pub_bytes = public_key.to_bytes();
+        sqlite3_bind_blob(
+            stmt,
+            1,
+            pub_bytes.as_ptr() as *const _,
+            pub_bytes.len() as libc::c_int,
+            None,
+        );
+        let priv_bytes = private_key.to_bytes();
+        sqlite3_bind_blob(
+            stmt,
+            2,
+            priv_bytes.as_ptr() as *const _,
+            priv_bytes.len() as libc::c_int,
+            None,
+        );
+        sqlite3_step(stmt);
+        sqlite3_finalize(stmt);
+        stmt = 0 as *mut sqlite3_stmt;
+        if 0 != set_default {
+            dc_sqlite3_execute(
                 context,
                 &context.sql,
-                b"configured_addr\x00" as *const u8 as *const libc::c_char,
-                0 as *const libc::c_char,
+                b"UPDATE keypairs SET is_default=0;\x00" as *const u8 as *const libc::c_char,
             );
-            if !dc_key_save_self_keypair(
-                context,
-                &public_key,
-                &private_key,
-                self_addr,
-                set_default,
-                &context.sql,
-            ) {
-                dc_log_error(
-                    context,
-                    0i32,
-                    b"Cannot save keypair.\x00" as *const u8 as *const libc::c_char,
-                );
-            } else {
-                if !buf_preferencrypt.is_null() {
-                    if strcmp(
-                        buf_preferencrypt,
-                        b"nopreference\x00" as *const u8 as *const libc::c_char,
-                    ) == 0i32
-                    {
-                        dc_sqlite3_set_config_int(
-                            context,
-                            &context.sql,
-                            b"e2ee_enabled\x00" as *const u8 as *const libc::c_char,
-                            0i32,
-                        );
-                    } else if strcmp(
-                        buf_preferencrypt,
-                        b"mutual\x00" as *const u8 as *const libc::c_char,
-                    ) == 0i32
-                    {
-                        dc_sqlite3_set_config_int(
-                            context,
-                            &context.sql,
-                            b"e2ee_enabled\x00" as *const u8 as *const libc::c_char,
-                            1i32,
-                        );
-                    }
+        }
+        self_addr = dc_sqlite3_get_config(
+            context,
+            &context.sql,
+            b"configured_addr\x00" as *const u8 as *const libc::c_char,
+            0 as *const libc::c_char,
+        );
+        if !dc_key_save_self_keypair(
+            context,
+            &public_key,
+            &private_key,
+            self_addr,
+            set_default,
+            &context.sql,
+        ) {
+            error!(context, 0, "Cannot save keypair.",);
+        } else {
+            let prefer_encrypt = header.get("Autocrypt-Prefer-Encrypt");
+
+            if let Some(prefer_encrypt) = prefer_encrypt {
+                if prefer_encrypt == "nopreference" {
+                    dc_sqlite3_set_config_int(
+                        context,
+                        &context.sql,
+                        b"e2ee_enabled\x00" as *const u8 as *const libc::c_char,
+                        0i32,
+                    );
+                } else if prefer_encrypt == "mutual" {
+                    dc_sqlite3_set_config_int(
+                        context,
+                        &context.sql,
+                        b"e2ee_enabled\x00" as *const u8 as *const libc::c_char,
+                        1i32,
+                    );
                 }
-                success = 1;
             }
-        } else {
-            dc_log_error(
-                context,
-                0i32,
-                b"File does not contain a valid private key.\x00" as *const u8
-                    as *const libc::c_char,
-            );
+            success = 1;
         }
+    } else {
+        error!(context, 0, "File does not contain a private key.",);
     }
 
     sqlite3_finalize(stmt);
-    free(buf as *mut libc::c_void);
     free(self_addr as *mut libc::c_void);
 
     success
diff --git a/src/key.rs b/src/key.rs
index 7744132a..0b1fc789 100644
--- a/src/key.rs
+++ b/src/key.rs
@@ -126,11 +126,31 @@ pub fn from_stmt(
         Self::from_binary(data, len, key_type)
     }
 
+    pub fn from_armored_string(
+        data: &str,
+        key_type: KeyType,
+    ) -> Option<(Self, BTreeMap<String, String>)> {
+        let bytes = data.as_bytes();
+        let res: Result<(Key, _), _> = match key_type {
+            KeyType::Public => SignedPublicKey::from_armor_single(Cursor::new(bytes))
+                .map(|(k, h)| (Into::into(k), h)),
+            KeyType::Private => SignedSecretKey::from_armor_single(Cursor::new(bytes))
+                .map(|(k, h)| (Into::into(k), h)),
+        };
+
+        match res {
+            Ok(res) => Some(res),
+            Err(err) => {
+                eprintln!("Invalid key bytes: {:?}", err);
+                None
+            }
+        }
+    }
+
     pub fn from_base64(encoded_data: &str, key_type: KeyType) -> Option<Self> {
         // strip newlines and other whitespace
         let cleaned: String = encoded_data.trim().split_whitespace().collect();
         let bytes = cleaned.as_bytes();
-
         base64::decode(bytes)
             .ok()
             .and_then(|decoded| Self::from_slice(&decoded, key_type))
@@ -226,16 +246,6 @@ pub fn to_base64(&self, break_every: usize) -> String {
             .to_string()
     }
 
-    /// the result must be freed
-    pub fn to_base64_c(&self, break_every: usize) -> *mut libc::c_char {
-        let res = self.to_base64(break_every);
-        let res_c = CString::new(res.trim()).unwrap();
-
-        // need to use strdup to allocate the result with malloc
-        // so it can be `free`d later.
-        unsafe { strdup(res_c.as_ptr()) }
-    }
-
     pub fn to_armored_string(
         &self,
         headers: Option<&BTreeMap<String, String>>,
@@ -436,6 +446,73 @@ fn test_normalize_fingerprint() {
         assert_eq!(fingerprint, "1234567890ABCDABCDEFABCDEF");
     }
 
+    #[test]
+    fn test_from_armored_string() {
+        let (private_key, _) = Key::from_armored_string(
+            "-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+7yPJeQ==
+=KZk/
+-----END PGP PRIVATE KEY BLOCK-----",
+            KeyType::Private,
+        )
+        .expect("failed to decode"); // NOTE: if you take out the ===GU1/ part, everything passes!
+        let binary = private_key.to_bytes();
+        Key::from_slice(&binary, KeyType::Private).expect("invalid private key");
+    }
+
     #[test]
     fn test_format_fingerprint() {
         let fingerprint = dc_format_fingerprint("1234567890ABCDABCDEFABCDEF1234567890ABCD");
@@ -459,4 +536,21 @@ fn test_from_slice_roundtrip() {
         let private_key2 = Key::from_slice(&binary, KeyType::Private).expect("invalid private key");
         assert_eq!(private_key, private_key2);
     }
+
+    #[test]
+    fn test_ascii_roundtrip() {
+        let (public_key, private_key) =
+            crate::pgp::dc_pgp_create_keypair(CString::new("hello").unwrap().as_ptr()).unwrap();
+
+        let s = public_key.to_armored_string(None).unwrap();
+        let (public_key2, _) =
+            Key::from_armored_string(&s, KeyType::Public).expect("invalid public key");
+        assert_eq!(public_key, public_key2);
+
+        let s = private_key.to_armored_string(None).unwrap();
+        println!("{}", &s);
+        let (private_key2, _) =
+            Key::from_armored_string(&s, KeyType::Private).expect("invalid private key");
+        assert_eq!(private_key, private_key2);
+    }
 }
['src/dc_imex.rs', 'src/key.rs', 'python/tests/test_account.py', 'python/install_py_bindings.sh', 'python/src/deltachat/message.py', 'python/tests/conftest.py', 'python/src/deltachat/__init__.py', 'python/src/deltachat/account.py']