mirror of
https://github.com/f-droid/fdroidserver.git
synced 2025-11-06 15:30:28 +03:00
Merge branch 'master' into 'master'
implemeted support for using a smartcard for the repo signing key This changeset implements using a smartcard (HSM) as the keystore for the signing key. It also fixes lots of little bugs in the `fdroid init` process.
This commit is contained in:
commit
e8c47765ae
7 changed files with 149 additions and 45 deletions
|
|
@ -55,12 +55,23 @@ of applications from the main repository.
|
||||||
|
|
||||||
|
|
||||||
# The key (from the keystore defined below) to be used for signing the
|
# The key (from the keystore defined below) to be used for signing the
|
||||||
#repository itself. Can be None for an unsigned repository.
|
# repository itself. This is the same name you would give to keytool or
|
||||||
repo_keyalias = None
|
# jarsigner using -alias. (Not needed in an unsigned repository).
|
||||||
|
#repo_keyalias = "fdroidrepo"
|
||||||
|
|
||||||
# The keystore to use for release keys when building. This needs to be
|
# The keystore to use for release keys when building. This needs to be
|
||||||
#somewhere safe and secure, and backed up!
|
# somewhere safe and secure, and backed up! The best way to manage these
|
||||||
#keystore = "/home/me/.local/share/fdroidserver/keystore.jks"
|
# sensitive keys is to use a "smartcard" (aka Hardware Security Module). To
|
||||||
|
# configure FDroid to use a smartcard, set the keystore file using the keyword
|
||||||
|
# "NONE" (i.e. keystore = "NONE"). That makes Java find the keystore on the
|
||||||
|
# smartcard based on 'smartcardoptions' below.
|
||||||
|
#keystore = "~/.local/share/fdroidserver/keystore.jks"
|
||||||
|
|
||||||
|
# You should not need to change these at all, unless you have a very
|
||||||
|
# customized setup for using smartcards in Java with keytool/jarsigner
|
||||||
|
#smartcardoptions = "-storetype PKCS11 -providerName SunPKCS11-OpenSC \
|
||||||
|
# -providerClass sun.security.pkcs11.SunPKCS11 \
|
||||||
|
# -providerArg opensc-fdroid.cfg"
|
||||||
|
|
||||||
# The password for the keystore (at least 6 characters). If this password is
|
# The password for the keystore (at least 6 characters). If this password is
|
||||||
# different than the keypass below, it can be OK to store the password in this
|
# different than the keypass below, it can be OK to store the password in this
|
||||||
|
|
|
||||||
4
examples/opensc-fdroid.cfg
Normal file
4
examples/opensc-fdroid.cfg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
name = OpenSC
|
||||||
|
description = SunPKCS11 w/ OpenSC Smart card Framework
|
||||||
|
library = /usr/lib/opensc-pkcs11.so
|
||||||
|
slotListIndex = 1
|
||||||
|
|
@ -54,6 +54,16 @@ def read_config(opts, config_file='config.py'):
|
||||||
logging.debug("Reading %s" % config_file)
|
logging.debug("Reading %s" % config_file)
|
||||||
execfile(config_file, config)
|
execfile(config_file, config)
|
||||||
|
|
||||||
|
# smartcardoptions must be a list since its command line args for Popen
|
||||||
|
if 'smartcardoptions' in config:
|
||||||
|
config['smartcardoptions'] = config['smartcardoptions'].split(' ')
|
||||||
|
elif 'keystore' in config and config['keystore'] == 'NONE':
|
||||||
|
# keystore='NONE' means use smartcard, these are required defaults
|
||||||
|
config['smartcardoptions'] = ['-storetype', 'PKCS11', '-providerName',
|
||||||
|
'SunPKCS11-OpenSC', '-providerClass',
|
||||||
|
'sun.security.pkcs11.SunPKCS11',
|
||||||
|
'-providerArg', 'opensc-fdroid.cfg']
|
||||||
|
|
||||||
defconfig = {
|
defconfig = {
|
||||||
'sdk_path': "$ANDROID_HOME",
|
'sdk_path': "$ANDROID_HOME",
|
||||||
'ndk_path': "$ANDROID_NDK",
|
'ndk_path': "$ANDROID_NDK",
|
||||||
|
|
@ -66,8 +76,8 @@ def read_config(opts, config_file='config.py'):
|
||||||
'stats_to_carbon': False,
|
'stats_to_carbon': False,
|
||||||
'repo_maxage': 0,
|
'repo_maxage': 0,
|
||||||
'build_server_always': False,
|
'build_server_always': False,
|
||||||
'keystore': os.path.join(os.getenv('HOME'),
|
'keystore': '$HOME/.local/share/fdroidserver/keystore.jks',
|
||||||
'.local', 'share', 'fdroidserver', 'keystore.jks'),
|
'smartcardoptions': [],
|
||||||
'char_limits': {
|
'char_limits': {
|
||||||
'Summary' : 50,
|
'Summary' : 50,
|
||||||
'Description' : 1500
|
'Description' : 1500
|
||||||
|
|
@ -86,10 +96,13 @@ def read_config(opts, config_file='config.py'):
|
||||||
config[k] = os.path.expandvars(v)
|
config[k] = os.path.expandvars(v)
|
||||||
|
|
||||||
if not config['sdk_path']:
|
if not config['sdk_path']:
|
||||||
logging.critical("$ANDROID_HOME is not set!")
|
logging.critical("Neither $ANDROID_HOME nor sdk_path is set, no Android SDK found!")
|
||||||
|
sys.exit(3)
|
||||||
|
if not os.path.exists(config['sdk_path']):
|
||||||
|
logging.critical('Android SDK path "' + config['sdk_path'] + '" does not exist!')
|
||||||
sys.exit(3)
|
sys.exit(3)
|
||||||
if not os.path.isdir(config['sdk_path']):
|
if not os.path.isdir(config['sdk_path']):
|
||||||
logging.critical("$ANDROID_HOME points to a non-existing directory!")
|
logging.critical('Android SDK path "' + config['sdk_path'] + '" is not a directory!')
|
||||||
sys.exit(3)
|
sys.exit(3)
|
||||||
|
|
||||||
if any(k in config for k in ["keystore", "keystorepass", "keypass"]):
|
if any(k in config for k in ["keystore", "keystorepass", "keypass"]):
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import glob
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
@ -38,8 +39,18 @@ def write_to_config(key, value):
|
||||||
'''write a key/value to the local config.py'''
|
'''write a key/value to the local config.py'''
|
||||||
with open('config.py', 'r') as f:
|
with open('config.py', 'r') as f:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
pattern = key + '\s*=.*'
|
pattern = '\n[\s#]*' + key + '\s*=\s*"[^"]*"'
|
||||||
repl = key + ' = "' + value + '"'
|
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:
|
||||||
|
data = f.read()
|
||||||
|
pattern = '\n[\s#]*' + key + '\s*=\s*"[^"]*"'
|
||||||
|
repl = '\n#' + key + ' = "' + value + '"'
|
||||||
data = re.sub(pattern, repl, data)
|
data = re.sub(pattern, repl, data)
|
||||||
with open('config.py', 'w') as f:
|
with open('config.py', 'w') as f:
|
||||||
f.writelines(data)
|
f.writelines(data)
|
||||||
|
|
@ -71,8 +82,8 @@ def genkey(keystore, repo_keyalias, password, keydname):
|
||||||
raise BuildException("Failed to generate key", p.stdout)
|
raise BuildException("Failed to generate key", p.stdout)
|
||||||
# now show the lovely key that was just generated
|
# now show the lovely key that was just generated
|
||||||
p = FDroidPopen(['keytool', '-list', '-v',
|
p = FDroidPopen(['keytool', '-list', '-v',
|
||||||
'-keystore', keystore, '-alias', repo_keyalias],
|
'-keystore', keystore, '-alias', repo_keyalias,
|
||||||
'-storepass:file', config['keystorepassfile'])
|
'-storepass:file', config['keystorepassfile']])
|
||||||
logging.info(p.stdout.strip() + '\n\n')
|
logging.info(p.stdout.strip() + '\n\n')
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -175,33 +186,63 @@ def main():
|
||||||
# find or generate the keystore for the repo signing key. First try the
|
# find or generate the keystore for the repo signing key. First try the
|
||||||
# path written in the default config.py. Then check if the user has
|
# path written in the default config.py. Then check if the user has
|
||||||
# specified a path from the command line, which will trump all others.
|
# specified a path from the command line, which will trump all others.
|
||||||
# Otherwise, create ~/.local/share/fdroidserver and stick it in there.
|
# Otherwise, create ~/.local/share/fdroidserver and stick it in there. If
|
||||||
|
# keystore is set to NONE, that means that Java will look for keys in a
|
||||||
|
# Hardware Security Module aka Smartcard.
|
||||||
keystore = config['keystore']
|
keystore = config['keystore']
|
||||||
if options.keystore:
|
if options.keystore:
|
||||||
if os.path.isfile(options.keystore):
|
keystore = os.path.abspath(options.keystore)
|
||||||
|
if options.keystore == 'NONE':
|
||||||
keystore = options.keystore
|
keystore = options.keystore
|
||||||
write_to_config('keystore', keystore)
|
|
||||||
else:
|
else:
|
||||||
logging.info('"' + options.keystore + '" does not exist or is not a file!')
|
keystore = os.path.abspath(options.keystore)
|
||||||
sys.exit(1)
|
if not os.path.exists(keystore):
|
||||||
|
logging.info('"' + keystore
|
||||||
|
+ '" does not exist, creating a new keystore there.')
|
||||||
|
write_to_config('keystore', keystore)
|
||||||
|
repo_keyalias = None
|
||||||
if options.repo_keyalias:
|
if options.repo_keyalias:
|
||||||
repo_keyalias = options.repo_keyalias
|
repo_keyalias = options.repo_keyalias
|
||||||
write_to_config('repo_keyalias', repo_keyalias)
|
write_to_config('repo_keyalias', repo_keyalias)
|
||||||
if options.distinguished_name:
|
if options.distinguished_name:
|
||||||
keydname = options.distinguished_name
|
keydname = options.distinguished_name
|
||||||
write_to_config('keydname', keydname)
|
write_to_config('keydname', keydname)
|
||||||
if not os.path.isfile(keystore):
|
if keystore == 'NONE': # we're using a smartcard
|
||||||
|
write_to_config('repo_keyalias', '1') # seems to be the default
|
||||||
|
disable_in_config('keypass', 'never used with smartcard')
|
||||||
|
write_to_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'):
|
||||||
|
opensc_so = '/usr/lib/opensc-pkcs11.so'
|
||||||
|
elif os.path.exists('/usr/lib64/opensc-pkcs11.so'):
|
||||||
|
opensc_so = '/usr/lib64/opensc-pkcs11.so'
|
||||||
|
else:
|
||||||
|
files = glob.glob('/usr/lib/' + os.uname()[4] + '-*-gnu/opensc-pkcs11.so')
|
||||||
|
if len(files) > 0:
|
||||||
|
opensc_so = files[0]
|
||||||
|
else:
|
||||||
|
opensc_so = '/usr/lib/opensc-pkcs11.so'
|
||||||
|
logging.warn('No OpenSC PKCS#11 module found, ' +
|
||||||
|
'install OpenSC then edit "opensc-fdroid.cfg"!')
|
||||||
|
with open(os.path.join(examplesdir, 'opensc-fdroid.cfg'), 'r') as f:
|
||||||
|
opensc_fdroid = f.read()
|
||||||
|
opensc_fdroid = re.sub('^library.*', 'library = ' + opensc_so, opensc_fdroid,
|
||||||
|
flags=re.MULTILINE)
|
||||||
|
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
|
# no existing or specified keystore, generate the whole thing
|
||||||
keystoredir = os.path.join(os.getenv('HOME'),
|
keystoredir = os.path.dirname(keystore)
|
||||||
'.local', 'share', 'fdroidserver')
|
|
||||||
if not os.path.exists(keystoredir):
|
if not os.path.exists(keystoredir):
|
||||||
os.makedirs(keystoredir, mode=0o700)
|
os.makedirs(keystoredir, mode=0o700)
|
||||||
keystore = os.path.join(keystoredir, 'keystore.jks')
|
|
||||||
write_to_config('keystore', keystore)
|
|
||||||
password = genpassword()
|
password = genpassword()
|
||||||
write_to_config('keystorepass', password)
|
write_to_config('keystorepass', password)
|
||||||
write_to_config('keypass', password)
|
write_to_config('keypass', password)
|
||||||
if not options.repo_keyalias:
|
if options.repo_keyalias == None:
|
||||||
repo_keyalias = socket.getfqdn()
|
repo_keyalias = socket.getfqdn()
|
||||||
write_to_config('repo_keyalias', repo_keyalias)
|
write_to_config('repo_keyalias', repo_keyalias)
|
||||||
if not options.distinguished_name:
|
if not options.distinguished_name:
|
||||||
|
|
@ -215,12 +256,14 @@ def main():
|
||||||
logging.info(' Android SDK Build Tools:\t' + os.path.dirname(aapt))
|
logging.info(' Android SDK Build Tools:\t' + os.path.dirname(aapt))
|
||||||
logging.info(' Android NDK (optional):\t' + ndk_path)
|
logging.info(' Android NDK (optional):\t' + ndk_path)
|
||||||
logging.info(' Keystore for signing key:\t' + keystore)
|
logging.info(' Keystore for signing key:\t' + keystore)
|
||||||
|
if repo_keyalias != None:
|
||||||
|
logging.info(' Alias for key in store:\t' + repo_keyalias)
|
||||||
logging.info('\nTo complete the setup, add your APKs to "' +
|
logging.info('\nTo complete the setup, add your APKs to "' +
|
||||||
os.path.join(fdroiddir, 'repo') + '"' +
|
os.path.join(fdroiddir, 'repo') + '"' +
|
||||||
'''
|
'''
|
||||||
then run "fdroid update -c; fdroid update". You might also want to edit
|
then run "fdroid update -c; fdroid update". You might also want to edit
|
||||||
"config.py" to set the URL, repo name, and more. You should also set up
|
"config.py" to set the URL, repo name, and more. You should also set up
|
||||||
a signing key.
|
a signing key (a temporary one might have been automatically generated).
|
||||||
|
|
||||||
For more info: https://f-droid.org/manual/fdroid.html#Simple-Binary-Repository
|
For more info: https://f-droid.org/manual/fdroid.html#Simple-Binary-Repository
|
||||||
and https://f-droid.org/manual/fdroid.html#Signing
|
and https://f-droid.org/manual/fdroid.html#Signing
|
||||||
|
|
|
||||||
|
|
@ -627,7 +627,7 @@ def make_index(apps, apks, repodir, archive, categories):
|
||||||
repoel.setAttribute("version", "12")
|
repoel.setAttribute("version", "12")
|
||||||
repoel.setAttribute("timestamp", str(int(time.time())))
|
repoel.setAttribute("timestamp", str(int(time.time())))
|
||||||
|
|
||||||
if config['repo_keyalias']:
|
if 'repo_keyalias' in config:
|
||||||
|
|
||||||
# Generate a certificate fingerprint the same way keytool does it
|
# Generate a certificate fingerprint the same way keytool does it
|
||||||
# (but with slightly different formatting)
|
# (but with slightly different formatting)
|
||||||
|
|
@ -642,7 +642,8 @@ def make_index(apps, apks, repodir, archive, categories):
|
||||||
p = FDroidPopen(['keytool', '-exportcert',
|
p = FDroidPopen(['keytool', '-exportcert',
|
||||||
'-alias', config['repo_keyalias'],
|
'-alias', config['repo_keyalias'],
|
||||||
'-keystore', config['keystore'],
|
'-keystore', config['keystore'],
|
||||||
'-storepass:file', config['keystorepassfile']])
|
'-storepass:file', config['keystorepassfile']]
|
||||||
|
+ config['smartcardoptions'])
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
logging.critical("Failed to get repo pubkey")
|
logging.critical("Failed to get repo pubkey")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
@ -783,7 +784,7 @@ def make_index(apps, apks, repodir, archive, categories):
|
||||||
of.write(output)
|
of.write(output)
|
||||||
of.close()
|
of.close()
|
||||||
|
|
||||||
if config['repo_keyalias'] is not None:
|
if 'repo_keyalias' in config:
|
||||||
|
|
||||||
logging.info("Creating signed index.")
|
logging.info("Creating signed index.")
|
||||||
logging.info("Key fingerprint: %s" % repo_pubkey_fingerprint)
|
logging.info("Key fingerprint: %s" % repo_pubkey_fingerprint)
|
||||||
|
|
@ -795,11 +796,15 @@ def make_index(apps, apks, repodir, archive, categories):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Sign the index...
|
# Sign the index...
|
||||||
p = FDroidPopen(['jarsigner', '-keystore', config['keystore'],
|
args = ['jarsigner', '-keystore', config['keystore'],
|
||||||
'-storepass:file', config['keystorepassfile'],
|
'-storepass:file', config['keystorepassfile'],
|
||||||
'-keypass:file', config['keypassfile'],
|
|
||||||
'-digestalg', 'SHA1', '-sigalg', 'MD5withRSA',
|
'-digestalg', 'SHA1', '-sigalg', 'MD5withRSA',
|
||||||
os.path.join(repodir, 'index.jar') , config['repo_keyalias']])
|
os.path.join(repodir, 'index.jar'), config['repo_keyalias']]
|
||||||
|
if config['keystore'] == 'NONE':
|
||||||
|
args += config['smartcardoptions']
|
||||||
|
else: # smardcards never use -keypass
|
||||||
|
args += ['-keypass:file', config['keypassfile']]
|
||||||
|
p = FDroidPopen(args)
|
||||||
# TODO keypass should be sent via stdin
|
# TODO keypass should be sent via stdin
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
logging.info("Failed to sign index")
|
logging.info("Failed to sign index")
|
||||||
|
|
|
||||||
1
setup.py
1
setup.py
|
|
@ -23,6 +23,7 @@ setup(name='fdroidserver',
|
||||||
[ 'buildserver/config.buildserver.py',
|
[ 'buildserver/config.buildserver.py',
|
||||||
'examples/config.py',
|
'examples/config.py',
|
||||||
'examples/makebs.config.py',
|
'examples/makebs.config.py',
|
||||||
|
'examples/opensc-fdroid.cfg',
|
||||||
'examples/fdroid-icon.png']),
|
'examples/fdroid-icon.png']),
|
||||||
('fdroidserver/getsig', ['fdroidserver/getsig/getsig.class'])
|
('fdroidserver/getsig', ['fdroidserver/getsig/getsig.class'])
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,18 @@
|
||||||
set -e
|
set -e
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
|
copy_apks_into_repo() {
|
||||||
|
for f in `ls -1 ../../*/bin/*.apk`; do
|
||||||
|
name=$(basename $(dirname `dirname $f`))
|
||||||
|
echo "name $name"
|
||||||
|
apk=${name}_`basename $f`
|
||||||
|
echo "apk $apk"
|
||||||
|
cp $f $1/repo/$apk
|
||||||
|
done
|
||||||
|
# delete any 'unaligned' duplicates
|
||||||
|
rm -f $1/repo/*unaligned*.apk
|
||||||
|
}
|
||||||
|
|
||||||
if [ -z $WORKSPACE ]; then
|
if [ -z $WORKSPACE ]; then
|
||||||
WORKSPACE=`dirname $(pwd)`
|
WORKSPACE=`dirname $(pwd)`
|
||||||
echo "Setting Workspace to $WORKSPACE"
|
echo "Setting Workspace to $WORKSPACE"
|
||||||
|
|
@ -19,16 +31,31 @@ fi
|
||||||
REPOROOT=`mktemp --directory --tmpdir=$WORKSPACE`
|
REPOROOT=`mktemp --directory --tmpdir=$WORKSPACE`
|
||||||
cd $REPOROOT
|
cd $REPOROOT
|
||||||
$fdroid init
|
$fdroid init
|
||||||
for f in `ls -1 ../../*/bin/*.apk`; do
|
copy_apks_into_repo $REPOROOT
|
||||||
name=$(basename $(dirname `dirname $f`))
|
|
||||||
echo "name $name"
|
|
||||||
apk=${name}_`basename $f`
|
|
||||||
echo "apk $apk"
|
|
||||||
cp $f $REPOROOT/repo/$apk
|
|
||||||
done
|
|
||||||
# delete any 'unaligned' duplicates
|
|
||||||
rm -f $REPOROOT/repo/*unaligned*.apk
|
|
||||||
|
|
||||||
|
|
||||||
$fdroid update -c
|
$fdroid update -c
|
||||||
$fdroid update
|
$fdroid update
|
||||||
|
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------#
|
||||||
|
# setup a new repo from scratch and generate a keystore
|
||||||
|
|
||||||
|
REPOROOT=`mktemp --directory --tmpdir=$WORKSPACE`
|
||||||
|
KEYSTORE=$REPOROOT/keystore.jks
|
||||||
|
cd $REPOROOT
|
||||||
|
$fdroid init --keystore $KEYSTORE
|
||||||
|
test -e $KEYSTORE
|
||||||
|
copy_apks_into_repo $REPOROOT
|
||||||
|
$fdroid update -c
|
||||||
|
$fdroid update
|
||||||
|
test -e repo/index.xml
|
||||||
|
test -e repo/index.jar
|
||||||
|
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------#
|
||||||
|
# setup a new repo from scratch with a HSM/smartcard
|
||||||
|
|
||||||
|
REPOROOT=`mktemp --directory --tmpdir=$WORKSPACE`
|
||||||
|
cd $REPOROOT
|
||||||
|
$fdroid init --keystore NONE
|
||||||
|
test -e opensc-fdroid.cfg
|
||||||
|
test ! -e NONE
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue