chiark / gitweb /
Merge branch 'support-all-signing-key-types' into 'master'
authorCiaran Gultnieks <ciaran@ciarang.com>
Thu, 14 May 2015 16:12:04 +0000 (16:12 +0000)
committerCiaran Gultnieks <ciaran@ciarang.com>
Thu, 14 May 2015 16:12:04 +0000 (16:12 +0000)
support all APK signing key types: DSA, EC, RSA

The lovely python getsig replacement was only looking for .RSA files, but the signing key is defined as: "A signature block file with a .DSA, .RSA, or .EC extension"
https://docs.oracle.com/javase/7/docs/technotes/tools/windows/jarsigner.html

For more info:
https://f-droid.org/forums/topic/binary-repo-whatsapp-error

See merge request !51

fdroidserver/common.py
fdroidserver/init.py
fdroidserver/server.py
fdroidserver/update.py
jenkins-build
tests/run-tests

index 9618727ae19a41e74e8ee348a4507270a0a560c6..985afb4abe2627158bb042ac4a6cf2f38b19cf86 100644 (file)
@@ -30,6 +30,9 @@ import Queue
 import threading
 import magic
 import logging
+import hashlib
+import socket
+
 from distutils.version import LooseVersion
 from zipfile import ZipFile
 
@@ -61,7 +64,7 @@ default_config = {
     'stats_to_carbon': False,
     'repo_maxage': 0,
     'build_server_always': False,
-    'keystore': os.path.join("$HOME", '.local', 'share', 'fdroidserver', 'keystore.jks'),
+    'keystore': 'keystore.jks',
     'smartcardoptions': [],
     'char_limits': {
         'Summary': 50,
@@ -2016,3 +2019,63 @@ def find_command(command):
                 return exe_file
 
     return None
+
+
+def genpassword():
+    '''generate a random password for when generating keys'''
+    h = hashlib.sha256()
+    h.update(os.urandom(16))  # salt
+    h.update(bytes(socket.getfqdn()))
+    return h.digest().encode('base64').strip()
+
+
+def genkeystore(localconfig):
+    '''Generate a new key with random passwords and add it to new keystore'''
+    logging.info('Generating a new key in "' + localconfig['keystore'] + '"...')
+    keystoredir = os.path.dirname(localconfig['keystore'])
+    if keystoredir is None or keystoredir == '':
+        keystoredir = os.path.join(os.getcwd(), keystoredir)
+    if not os.path.exists(keystoredir):
+        os.makedirs(keystoredir, mode=0o700)
+
+    write_password_file("keystorepass", localconfig['keystorepass'])
+    write_password_file("keypass", localconfig['keypass'])
+    p = FDroidPopen(['keytool', '-genkey',
+                     '-keystore', localconfig['keystore'],
+                     '-alias', localconfig['repo_keyalias'],
+                     '-keyalg', 'RSA', '-keysize', '4096',
+                     '-sigalg', 'SHA256withRSA',
+                     '-validity', '10000',
+                     '-storepass:file', config['keystorepassfile'],
+                     '-keypass:file', config['keypassfile'],
+                     '-dname', localconfig['keydname']])
+    # TODO keypass should be sent via stdin
+    os.chmod(localconfig['keystore'], 0o0600)
+    if p.returncode != 0:
+        raise BuildException("Failed to generate key", p.output)
+    # now show the lovely key that was just generated
+    p = FDroidPopen(['keytool', '-list', '-v',
+                     '-keystore', localconfig['keystore'],
+                     '-alias', localconfig['repo_keyalias'],
+                     '-storepass:file', config['keystorepassfile']])
+    logging.info(p.output.strip() + '\n\n')
+
+
+def write_to_config(thisconfig, key, value=None):
+    '''write a key/value to the local config.py'''
+    if value is None:
+        origkey = key + '_orig'
+        value = thisconfig[origkey] if origkey in thisconfig else thisconfig[key]
+    with open('config.py', 'r') as f:
+        data = f.read()
+    pattern = '\n[\s#]*' + key + '\s*=\s*"[^"]*"'
+    repl = '\n' + key + ' = "' + value + '"'
+    data = re.sub(pattern, repl, data)
+    # if this key is not in the file, append it
+    if not re.match('\s*' + key + '\s*=\s*"', data):
+        data += repl
+    # make sure the file ends with a carraige return
+    if not re.match('\n$', data):
+        data += '\n'
+    with open('config.py', 'w') as f:
+        f.writelines(data)
index 2e16efbd3b96df868338eb3f0538c577ad1cd1b7..c49cb303a3932104923527d9da4c9e04e993ecc7 100644 (file)
@@ -20,7 +20,6 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import glob
-import hashlib
 import os
 import re
 import shutil
@@ -30,26 +29,11 @@ from optparse import OptionParser
 import logging
 
 import common
-from common import FDroidPopen, BuildException
 
 config = {}
 options = None
 
 
-def write_to_config(thisconfig, key, value=None):
-    '''write a key/value to the local config.py'''
-    if value is None:
-        origkey = key + '_orig'
-        value = thisconfig[origkey] if origkey in thisconfig else thisconfig[key]
-    with open('config.py', 'r') as f:
-        data = f.read()
-    pattern = '\n[\s#]*' + key + '\s*=\s*"[^"]*"'
-    repl = '\n' + key + ' = "' + value + '"'
-    data = re.sub(pattern, repl, data)
-    with open('config.py', 'w') as f:
-        f.writelines(data)
-
-
 def disable_in_config(key, value):
     '''write a key/value to the local config.py, then comment it out'''
     with open('config.py', 'r') as f:
@@ -61,37 +45,6 @@ def disable_in_config(key, value):
         f.writelines(data)
 
 
-def genpassword():
-    '''generate a random password for when generating keys'''
-    h = hashlib.sha256()
-    h.update(os.urandom(16))  # salt
-    h.update(bytes(socket.getfqdn()))
-    return h.digest().encode('base64').strip()
-
-
-def genkey(keystore, repo_keyalias, password, keydname):
-    '''generate a new keystore with a new key in it for signing repos'''
-    logging.info('Generating a new key in "' + keystore + '"...')
-    common.write_password_file("keystorepass", password)
-    common.write_password_file("keypass", password)
-    p = FDroidPopen(['keytool', '-genkey',
-                     '-keystore', keystore, '-alias', repo_keyalias,
-                     '-keyalg', 'RSA', '-keysize', '4096',
-                     '-sigalg', 'SHA256withRSA',
-                     '-validity', '10000',
-                     '-storepass:file', config['keystorepassfile'],
-                     '-keypass:file', config['keypassfile'],
-                     '-dname', keydname])
-    # TODO keypass should be sent via stdin
-    if p.returncode != 0:
-        raise BuildException("Failed to generate key", p.output)
-    # now show the lovely key that was just generated
-    p = FDroidPopen(['keytool', '-list', '-v',
-                     '-keystore', keystore, '-alias', repo_keyalias,
-                     '-storepass:file', config['keystorepassfile']])
-    logging.info(p.output.strip() + '\n\n')
-
-
 def main():
 
     global options, config
@@ -171,7 +124,7 @@ def main():
         # If android_home is not None, the path given from the command line
         # will be directly written in the config.
         if 'sdk_path' in test_config:
-            write_to_config(test_config, 'sdk_path', options.android_home)
+            common.write_to_config(test_config, 'sdk_path', options.android_home)
     else:
         logging.warn('Looks like this is already an F-Droid repo, cowardly refusing to overwrite it...')
         logging.info('Try running `fdroid init` in an empty directory.')
@@ -197,7 +150,7 @@ def main():
                 test_config['build_tools'] = ''
             else:
                 test_config['build_tools'] = dirname
-            write_to_config(test_config, 'build_tools')
+            common.write_to_config(test_config, 'build_tools')
         common.ensure_build_tools_exists(test_config)
 
     # now that we have a local config.py, read configuration...
@@ -222,21 +175,21 @@ def main():
             if not os.path.exists(keystore):
                 logging.info('"' + keystore
                              + '" does not exist, creating a new keystore there.')
-    write_to_config(test_config, 'keystore', keystore)
+    common.write_to_config(test_config, 'keystore', keystore)
     repo_keyalias = None
     if options.repo_keyalias:
         repo_keyalias = options.repo_keyalias
-        write_to_config(test_config, 'repo_keyalias', repo_keyalias)
+        common.write_to_config(test_config, 'repo_keyalias', repo_keyalias)
     if options.distinguished_name:
         keydname = options.distinguished_name
-        write_to_config(test_config, 'keydname', keydname)
+        common.write_to_config(test_config, 'keydname', keydname)
     if keystore == 'NONE':  # we're using a smartcard
-        write_to_config(test_config, 'repo_keyalias', '1')  # seems to be the default
+        common.write_to_config(test_config, 'repo_keyalias', '1')  # seems to be the default
         disable_in_config('keypass', 'never used with smartcard')
-        write_to_config(test_config, 'smartcardoptions',
-                        ('-storetype PKCS11 -providerName SunPKCS11-OpenSC '
-                         + '-providerClass sun.security.pkcs11.SunPKCS11 '
-                         + '-providerArg opensc-fdroid.cfg'))
+        common.write_to_config(test_config, 'smartcardoptions',
+                               ('-storetype PKCS11 -providerName SunPKCS11-OpenSC '
+                                + '-providerClass sun.security.pkcs11.SunPKCS11 '
+                                + '-providerArg opensc-fdroid.cfg'))
         # find opensc-pkcs11.so
         if not os.path.exists('opensc-fdroid.cfg'):
             if os.path.exists('/usr/lib/opensc-pkcs11.so'):
@@ -258,20 +211,17 @@ def main():
             with open('opensc-fdroid.cfg', 'w') as f:
                 f.write(opensc_fdroid)
     elif not os.path.exists(keystore):
-        # no existing or specified keystore, generate the whole thing
-        keystoredir = os.path.dirname(keystore)
-        if not os.path.exists(keystoredir):
-            os.makedirs(keystoredir, mode=0o700)
-        password = genpassword()
-        write_to_config(test_config, 'keystorepass', password)
-        write_to_config(test_config, 'keypass', password)
-        if options.repo_keyalias is None:
-            repo_keyalias = socket.getfqdn()
-            write_to_config(test_config, 'repo_keyalias', repo_keyalias)
-        if not options.distinguished_name:
-            keydname = 'CN=' + repo_keyalias + ', OU=F-Droid'
-            write_to_config(test_config, 'keydname', keydname)
-        genkey(keystore, repo_keyalias, password, keydname)
+        password = common.genpassword()
+        c = dict(test_config)
+        c['keystorepass'] = password
+        c['keypass'] = password
+        c['repo_keyalias'] = socket.getfqdn()
+        c['keydname'] = 'CN=' + c['repo_keyalias'] + ', OU=F-Droid'
+        common.write_to_config(test_config, 'keystorepass', password)
+        common.write_to_config(test_config, 'keypass', password)
+        common.write_to_config(test_config, 'repo_keyalias', c['repo_keyalias'])
+        common.write_to_config(test_config, 'keydname', c['keydname'])
+        common.genkeystore(c)
 
     logging.info('Built repo based in "' + fdroiddir + '"')
     logging.info('with this config:')
index c5496dee8fdd2e23f37a768d9fddc26e65fcaa46..93447767ab91d0a6a259ad72c402a71281f3ee97 100644 (file)
@@ -123,7 +123,7 @@ def update_awsbucket(repo_section):
 def update_serverwebroot(serverwebroot, repo_section):
     # use a checksum comparison for accurate comparisons on different
     # filesystems, for example, FAT has a low resolution timestamp
-    rsyncargs = ['rsync', '--archive', '--delete', '--safe-links']
+    rsyncargs = ['rsync', '--archive', '--delete-after', '--safe-links']
     if not options.no_checksum:
         rsyncargs.append('--checksum')
     if options.verbose:
@@ -136,13 +136,14 @@ def update_serverwebroot(serverwebroot, repo_section):
         rsyncargs += ['-e', 'ssh -i ' + config['identity_file']]
     indexxml = os.path.join(repo_section, 'index.xml')
     indexjar = os.path.join(repo_section, 'index.jar')
-    # upload the first time without the index so that the repo stays working
-    # while this update is running.  Then once it is complete, rerun the
-    # command again to upload the index.  Always using the same target with
-    # rsync allows for very strict settings on the receiving server, you can
-    # literally specify the one rsync command that is allowed to run in
-    # ~/.ssh/authorized_keys.  (serverwebroot is guaranteed to have a trailing
-    # slash in common.py)
+    # Upload the first time without the index files and delay the deletion as
+    # much as possible, that keeps the repo functional while this update is
+    # running.  Then once it is complete, rerun the command again to upload
+    # the index files.  Always using the same target with rsync allows for
+    # very strict settings on the receiving server, you can literally specify
+    # the one rsync command that is allowed to run in ~/.ssh/authorized_keys.
+    # (serverwebroot is guaranteed to have a trailing slash in common.py)
+    logging.info('rsyncing ' + repo_section + ' to ' + serverwebroot)
     if subprocess.call(rsyncargs +
                        ['--exclude', indexxml, '--exclude', indexjar,
                         repo_section, serverwebroot]) != 0:
@@ -225,7 +226,15 @@ def main():
         standardwebroot = True
 
     for serverwebroot in config.get('serverwebroot', []):
-        host, fdroiddir = serverwebroot.rstrip('/').split(':')
+        # this supports both an ssh host:path and just a path
+        s = serverwebroot.rstrip('/').split(':')
+        if len(s) == 1:
+            fdroiddir = s[0]
+        elif len(s) == 2:
+            host, fdroiddir = s
+        else:
+            logging.error('Malformed serverwebroot line: ' + serverwebroot)
+            sys.exit(1)
         repobase = os.path.basename(fdroiddir)
         if standardwebroot and repobase != 'fdroid':
             logging.error('serverwebroot path does not end with "fdroid", '
index b5fac3b8718df099b758d885b0798e3a0419f3fb..9ff3c0436d7fdda4706fb458ad92e24b1faa8796 100644 (file)
@@ -23,6 +23,7 @@ import os
 import shutil
 import glob
 import re
+import socket
 import zipfile
 import hashlib
 import pickle
@@ -665,6 +666,36 @@ def scan_apks(apps, apkcache, repodir, knownapks):
 repo_pubkey_fingerprint = None
 
 
+# Generate a certificate fingerprint the same way keytool does it
+# (but with slightly different formatting)
+def cert_fingerprint(data):
+    digest = hashlib.sha256(data).digest()
+    ret = []
+    ret.append(' '.join("%02X" % ord(b) for b in digest))
+    return " ".join(ret)
+
+
+def extract_pubkey():
+    global repo_pubkey_fingerprint
+    if 'repo_pubkey' in config:
+        pubkey = unhexlify(config['repo_pubkey'])
+    else:
+        p = FDroidPopen(['keytool', '-exportcert',
+                         '-alias', config['repo_keyalias'],
+                         '-keystore', config['keystore'],
+                         '-storepass:file', config['keystorepassfile']]
+                        + config['smartcardoptions'], output=False)
+        if p.returncode != 0 or len(p.output) < 20:
+            msg = "Failed to get repo pubkey!"
+            if config['keystore'] == 'NONE':
+                msg += ' Is your crypto smartcard plugged in?'
+            logging.critical(msg)
+            sys.exit(1)
+        pubkey = p.output
+    repo_pubkey_fingerprint = cert_fingerprint(pubkey)
+    return hexlify(pubkey)
+
+
 def make_index(apps, sortedids, apks, repodir, archive, categories):
     """Make a repo index.
 
@@ -712,38 +743,28 @@ def make_index(apps, sortedids, apks, repodir, archive, categories):
     repoel.setAttribute("version", "12")
     repoel.setAttribute("timestamp", str(int(time.time())))
 
-    if 'repo_keyalias' in config:
-
-        # Generate a certificate fingerprint the same way keytool does it
-        # (but with slightly different formatting)
-        def cert_fingerprint(data):
-            digest = hashlib.sha256(data).digest()
-            ret = []
-            ret.append(' '.join("%02X" % ord(b) for b in digest))
-            return " ".join(ret)
-
-        def extract_pubkey():
-            global repo_pubkey_fingerprint
-            if 'repo_pubkey' in config:
-                pubkey = unhexlify(config['repo_pubkey'])
-            else:
-                p = FDroidPopen(['keytool', '-exportcert',
-                                 '-alias', config['repo_keyalias'],
-                                 '-keystore', config['keystore'],
-                                 '-storepass:file', config['keystorepassfile']]
-                                + config['smartcardoptions'], output=False)
-                if p.returncode != 0:
-                    msg = "Failed to get repo pubkey!"
-                    if config['keystore'] == 'NONE':
-                        msg += ' Is your crypto smartcard plugged in?'
-                    logging.critical(msg)
-                    sys.exit(1)
-                pubkey = p.output
-            repo_pubkey_fingerprint = cert_fingerprint(pubkey)
-            return hexlify(pubkey)
-
-        repoel.setAttribute("pubkey", extract_pubkey())
-
+    nosigningkey = False
+    if not 'repo_keyalias' in config:
+        nosigningkey = True
+        logging.critical("'repo_keyalias' not found in config.py!")
+    if not 'keystore' in config:
+        nosigningkey = True
+        logging.critical("'keystore' not found in config.py!")
+    if not 'keystorepass' in config:
+        nosigningkey = True
+        logging.critical("'keystorepass' not found in config.py!")
+    if not 'keypass' in config:
+        nosigningkey = True
+        logging.critical("'keypass' not found in config.py!")
+    if not os.path.exists(config['keystore']):
+        nosigningkey = True
+        logging.critical("'" + config['keystore'] + "' does not exist!")
+    if nosigningkey:
+        logging.warning("`fdroid update` requires a signing key, you can create one using:")
+        logging.warning("\tfdroid update --create-key")
+        sys.exit(1)
+
+    repoel.setAttribute("pubkey", extract_pubkey())
     root.appendChild(repoel)
 
     for appid in sortedids:
@@ -996,6 +1017,8 @@ def main():
 
     # Parse command line...
     parser = OptionParser()
+    parser.add_option("--create-key", action="store_true", default=False,
+                      help="Create a repo signing key in a keystore")
     parser.add_option("-c", "--create-metadata", action="store_true", default=False,
                       help="Create skeleton metadata files that are missing")
     parser.add_option("--delete-unknown", action="store_true", default=False,
@@ -1042,6 +1065,32 @@ def main():
                 logging.critical(k + ' "' + config[k] + '" does not exist! Correct it in config.py.')
                 sys.exit(1)
 
+    # if the user asks to create a keystore, do it now, reusing whatever it can
+    if options.create_key:
+        if os.path.exists(config['keystore']):
+            logging.critical("Cowardily refusing to overwrite existing signing key setup!")
+            logging.critical("\t'" + config['keystore'] + "'")
+            sys.exit(1)
+
+        if not 'repo_keyalias' in config:
+            config['repo_keyalias'] = socket.getfqdn()
+            common.write_to_config(config, 'repo_keyalias', config['repo_keyalias'])
+        if not 'keydname' in config:
+            config['keydname'] = 'CN=' + config['repo_keyalias'] + ', OU=F-Droid'
+            common.write_to_config(config, 'keydname', config['keydname'])
+        if not 'keystore' in config:
+            config['keystore'] = common.default_config.keystore
+            common.write_to_config(config, 'keystore', config['keystore'])
+
+        password = common.genpassword()
+        if not 'keystorepass' in config:
+            config['keystorepass'] = password
+            common.write_to_config(config, 'keystorepass', config['keystorepass'])
+        if not 'keypass' in config:
+            config['keypass'] = password
+            common.write_to_config(config, 'keypass', config['keypass'])
+        common.genkeystore(config)
+
     # Get all apps...
     apps = metadata.read_metadata()
 
index 8a7ca9bdfa10c15a503e5e6338062ccc0037c797..cb94ebdf6c8ba9f98e7db9b1b54496d4ae0f49f4 100755 (executable)
@@ -41,8 +41,12 @@ export PATH=/usr/lib/jvm/java-7-openjdk-amd64/bin:$PATH
 
 #------------------------------------------------------------------------------#
 # run local tests, don't scan fdroidserver/ project for APKs
+
+# this is a local repo on the Guardian Project Jenkins server
+apksource=/var/www/fdroid
+
 cd $WORKSPACE/tests
-./run-tests ~jenkins/workspace/[[:upper:]a-eg-z]\*
+./run-tests $apksource
 
 
 #------------------------------------------------------------------------------#
@@ -62,7 +66,7 @@ python2 setup.py install
 
 # run tests in new pip+virtualenv install
 . $WORKSPACE/env/bin/activate
-fdroid=$WORKSPACE/env/bin/fdroid $WORKSPACE/tests/run-tests ~jenkins/
+fdroid=$WORKSPACE/env/bin/fdroid $WORKSPACE/tests/run-tests $apksource
 
 
 #------------------------------------------------------------------------------#
index d1f988fa64a3f5ef6e0983c272fb6162be80838b..8f14dadc31dc31c742f95bc344abe2f763760561 100755 (executable)
@@ -160,7 +160,7 @@ cd $REPOROOT
 $fdroid init
 copy_apks_into_repo $REPOROOT
 $fdroid update --create-metadata
-grep -F '<application id=' repo/index.xml
+grep -F '<application id=' repo/index.xml > /dev/null
 
 LOCALCOPYDIR=`create_test_dir`/fdroid
 $fdroid server update --local-copy-dir=$LOCALCOPYDIR
@@ -263,7 +263,7 @@ $fdroid init --keystore $KEYSTORE --android-home $STORED_ANDROID_HOME --no-promp
 test -e $KEYSTORE
 copy_apks_into_repo $REPOROOT
 $fdroid update --create-metadata
-grep -F '<application id=' repo/index.xml
+grep -F '<application id=' repo/index.xml > /dev/null
 test -e repo/index.xml
 test -e repo/index.jar
 export ANDROID_HOME=$STORED_ANDROID_HOME
@@ -278,7 +278,7 @@ mkdir repo
 copy_apks_into_repo $REPOROOT
 $fdroid init
 $fdroid update --create-metadata
-grep -F '<application id=' repo/index.xml
+grep -F '<application id=' repo/index.xml > /dev/null
 
 
 #------------------------------------------------------------------------------#
@@ -293,7 +293,34 @@ copy_apks_into_repo $REPOROOT
 $fdroid update --create-metadata
 test -e repo/index.xml
 test -e repo/index.jar
-grep -F '<application id=' repo/index.xml
+grep -F '<application id=' repo/index.xml > /dev/null
+
+
+#------------------------------------------------------------------------------#
+echo_header "setup a new repo manually and generate a keystore"
+
+REPOROOT=`create_test_dir`
+KEYSTORE=$REPOROOT/keystore.jks
+cd $REPOROOT
+touch config.py
+cp $WORKSPACE/examples/fdroid-icon.png $REPOROOT/
+! test -e $KEYSTORE
+set +e
+$fdroid update
+if [ $? -eq 0 ]; then
+    echo "This should have failed because this repo has no keystore!"
+    exit 1
+else
+    echo "`fdroid update` prompted to add keystore"
+fi
+set -e
+$fdroid update --create-key
+test -e $KEYSTORE
+copy_apks_into_repo $REPOROOT
+$fdroid update --create-metadata
+test -e repo/index.xml
+test -e repo/index.jar
+grep -F '<application id=' repo/index.xml > /dev/null
 
 
 #------------------------------------------------------------------------------#
@@ -308,12 +335,12 @@ copy_apks_into_repo $REPOROOT
 $fdroid update --create-metadata
 test -e repo/index.xml
 test -e repo/index.jar
-grep -F '<application id=' repo/index.xml
+grep -F '<application id=' repo/index.xml > /dev/null
 cp $WORKSPACE/tests/urzip.apk $REPOROOT/
 $fdroid update --create-metadata
 test -e repo/index.xml
 test -e repo/index.jar
-grep -F '<application id=' repo/index.xml
+grep -F '<application id=' repo/index.xml > /dev/null
 
 
 #------------------------------------------------------------------------------#
@@ -325,6 +352,92 @@ $fdroid init --keystore NONE
 test -e opensc-fdroid.cfg
 test ! -e NONE
 
+
+#------------------------------------------------------------------------------#
+echo_header "setup a new repo with no keystore, add APK, and update"
+
+REPOROOT=`create_test_dir`
+KEYSTORE=$REPOROOT/keystore.jks
+cd $REPOROOT
+touch config.py
+touch fdroid-icon.png
+mkdir repo/
+cp $WORKSPACE/tests/urzip.apk $REPOROOT/
+set +e
+$fdroid update --create-metadata
+if [ $? -eq 0 ]; then
+    echo "This should have failed because this repo has no keystore!"
+    exit 1
+else
+    echo "`fdroid update` prompted to add keystore"
+fi
+set -e
+
+# now set up fake, non-working keystore setup
+touch $KEYSTORE
+echo "keystore = \"$KEYSTORE\"" >> config.py
+echo 'repo_keyalias = "foo"' >> config.py
+echo 'keystorepass = "foo"' >> config.py
+echo 'keypass = "foo"' >> config.py
+set +e
+$fdroid update --create-metadata
+if [ $? -eq 0 ]; then
+    echo "This should have failed because this repo has a bad/fake keystore!"
+    exit 1
+else
+    echo "`fdroid update` prompted to add keystore"
+fi
+set -e
+
+
+#------------------------------------------------------------------------------#
+echo_header "setup a new repo with keystore with APK, update, then without key"
+
+REPOROOT=`create_test_dir`
+KEYSTORE=$REPOROOT/keystore.jks
+cd $REPOROOT
+$fdroid init --keystore $KEYSTORE
+test -e $KEYSTORE
+cp $WORKSPACE/tests/urzip.apk $REPOROOT/repo/
+$fdroid update --create-metadata
+test -e repo/index.xml
+test -e repo/index.jar
+grep -F '<application id=' repo/index.xml > /dev/null
+
+# now set fake repo_keyalias
+sed -i 's,^ *repo_keyalias.*,repo_keyalias = "fake",' $REPOROOT/config.py
+set +e
+$fdroid update
+if [ $? -eq 0 ]; then
+    echo "This should have failed because this repo has a bad repo_keyalias!"
+    exit 1
+else
+    echo "`fdroid update` prompted to add keystore"
+fi
+set -e
+
+# try creating a new keystore, but fail because the old one is there
+test -e $KEYSTORE
+set +e
+$fdroid update --create-key
+if [ $? -eq 0 ]; then
+    echo "This should have failed because a keystore is already there!"
+    exit 1
+else
+    echo "`fdroid update` complained about existing keystore"
+fi
+set -e
+
+# now actually create the key with the existing settings
+rm -f $KEYSTORE
+! test -e $KEYSTORE
+$fdroid update --create-key
+test -e $KEYSTORE
+
+
+#------------------------------------------------------------------------------#
+
+# remove this to prevent git conflicts and complaining
 rm -rf $WORKSPACE/fdroidserver.egg-info/
 
 echo SUCCESS