Skip to content

Commit

Permalink
Add command gengpgkey for generating GPG keys
Browse files Browse the repository at this point in the history
  • Loading branch information
mherrmann committed Nov 28, 2018
1 parent ba3dc15 commit 4f32d2b
Show file tree
Hide file tree
Showing 13 changed files with 206 additions and 31 deletions.
41 changes: 12 additions & 29 deletions fbs/builtin_commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
your Python build script and execute them there.
"""
from fbs import path, SETTINGS
from fbs.builtin_commands._util import prompt_for_value, \
require_existing_project
from fbs.cmdline import command
from fbs.resources import copy_with_filtering
from fbs_runtime import FbsError
Expand All @@ -30,16 +32,15 @@ def startproject():
if exists('src'):
raise FbsError('The src/ directory already exists. Aborting.')
try:
app = _prompt_for_value('App name [MyApp] : ', default='MyApp')
app = prompt_for_value('App name', default='MyApp')
user = getuser().title()
author = _prompt_for_value('Author [%s] : ' % user, default=user)
version = \
_prompt_for_value('Initial version [0.0.1] : ', default='0.0.1')
author = prompt_for_value('Author', default=user)
version = prompt_for_value('Initial version', default='0.0.1')
eg_bundle_id = 'com.%s.%s' % (
author.lower().split()[0], ''.join(app.lower().split())
)
mac_bundle_identifier = _prompt_for_value(
'Mac bundle identifier (eg. %s, optional) : ' % eg_bundle_id,
mac_bundle_identifier = prompt_for_value(
'Mac bundle identifier (eg. %s, optional)' % eg_bundle_id,
optional=True
)
except KeyboardInterrupt:
Expand Down Expand Up @@ -74,7 +75,7 @@ def run():
"""
Run your app from source
"""
_require_existing_project()
require_existing_project()
env = dict(os.environ)
pythonpath = path('src/main/python')
old_pythonpath = env.get('PYTHONPATH', '')
Expand All @@ -88,7 +89,7 @@ def freeze(debug=False):
"""
Compile your code to a standalone executable
"""
_require_existing_project()
require_existing_project()
# Import respective functions late to avoid circular import
# fbs <-> fbs.freeze.X.
app_name = SETTINGS['app_name']
Expand Down Expand Up @@ -127,7 +128,7 @@ def installer():
"""
Create an installer for your app
"""
_require_existing_project()
require_existing_project()
out_file = join('target', SETTINGS['installer'])
msg_parts = ['Created %s.' % out_file]
if is_windows():
Expand Down Expand Up @@ -171,7 +172,7 @@ def test():
"""
Execute your automated tests
"""
_require_existing_project()
require_existing_project()
sys.path.append(path('src/main/python'))
suite = TestSuite()
test_dirs = SETTINGS['test_dirs']
Expand Down Expand Up @@ -217,16 +218,6 @@ def clean():
elif islink(fpath):
unlink(fpath)

def _prompt_for_value(message, optional=False, default=''):
result = input(message).strip()
if not result and default:
print(default)
return default
if not optional:
while not result:
result = input(message).strip()
return result

def _get_python_bindings():
# Use PyQt5 by default. Only use PySide2 if it is available and PyQt5 isn't.
try:
Expand All @@ -238,12 +229,4 @@ def _get_python_bindings():
import PyQt5
except ImportError:
return 'PySide2'
return 'PyQt5'

def _require_existing_project():
if not exists(path('src')):
raise FbsError(
"Could not find the src/ directory. Are you in the right folder?\n"
"If yes, did you already run\n"
" fbs startproject ?"
)
return 'PyQt5'
2 changes: 1 addition & 1 deletion fbs/builtin_commands/_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def runvm(name):

def _run_docker(args, **kwargs):
try:
run(['docker'] + args, **kwargs)
return run(['docker'] + args, **kwargs)
except FileNotFoundError:
raise FbsError(
'fbs could not find Docker. Is it installed and on your PATH?'
Expand Down
17 changes: 17 additions & 0 deletions fbs/builtin_commands/_gpg/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM debian:9.6

ARG name
ARG email
ARG passphrase
ARG keylength=1024

RUN apt-get update

ADD gpg-agent.conf /root/.gnupg/gpg-agent.conf
RUN chmod -R 600 /root/.gnupg/
RUN apt-get install gnupg2 pinentry-tty -y

WORKDIR /root

ADD genkey.sh /root/genkey.sh
RUN chmod +x genkey.sh
100 changes: 100 additions & 0 deletions fbs/builtin_commands/_gpg/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from fbs import path, SETTINGS
from fbs.builtin_commands._docker import _run_docker
from fbs.builtin_commands._util import prompt_for_value, \
require_existing_project
from fbs.cmdline import command
from fbs_runtime import FbsError
from os import makedirs
from os.path import dirname, exists
from subprocess import DEVNULL, PIPE

import json
import logging
import re

_LOG = logging.getLogger(__name__)
_DOCKER_IMAGE = 'fbs:gpg_generator'
_DEST_DIR = 'src/sign/linux'
_PUBKEY_NAME = 'public-key.gpg'
_PRIVKEY_NAME = 'private-key.gpg'
_BASE_JSON = 'src/build/settings/base.json'

@command
def gengpgkey():
"""
Generate a GPG key for code signing on Linux
"""
require_existing_project()
if exists(_DEST_DIR):
raise FbsError('The %s folder already exists. Aborting.' % _DEST_DIR)
try:
email = prompt_for_value('Email address')
name = prompt_for_value('Real name', default=SETTINGS['author'])
passphrase = prompt_for_value('Key password', password=True)
except KeyboardInterrupt:
print('')
return
_LOG.info('Generating the GPG key. This can take a little...')
_init_docker()
args = ['run', '-t']
if exists('/dev/urandom'):
# Give the key generator more entropy on Posix:
args.extend(['-v', '/dev/urandom:/dev/random'])
args.extend([_DOCKER_IMAGE, '/root/genkey.sh', name, email, passphrase])
result = _run_docker(args, check=True, stdout=PIPE, universal_newlines=True)
key = _snip(
result.stdout,
"revocation certificate stored as '/root/.gnupg/openpgp-revocs.d/",
".rev'",
include_bounds=False
)
pubkey = _snip(result.stdout,
'-----BEGIN PGP PUBLIC KEY BLOCK-----\n',
'-----END PGP PUBLIC KEY BLOCK-----\n')
privkey = _snip(result.stdout,
'-----BEGIN PGP PRIVATE KEY BLOCK-----\n',
'-----END PGP PRIVATE KEY BLOCK-----\n')
makedirs(path(_DEST_DIR), exist_ok=True)
pubkey_dest = _DEST_DIR + '/' + _PUBKEY_NAME
with open(path(pubkey_dest), 'w') as f:
f.write(pubkey)
privkey_dest = _DEST_DIR + '/' + _PRIVKEY_NAME
with open(path(privkey_dest), 'w') as f:
f.write(privkey)
with open(path(_BASE_JSON)) as f:
base_contents = f.read()
new_base_contents = _extend_json(base_contents, {
'gpg_key': key, 'gpg_name': name, 'gpg_pass': passphrase
})
with open(path(_BASE_JSON), 'w') as f:
f.write(new_base_contents)
_LOG.info(
'Done. Created %s and ...%s. Also updated %s with the values you '
'entered.', pubkey_dest, _PRIVKEY_NAME, _BASE_JSON
)

def _init_docker():
_run_docker(
['build', '-t', _DOCKER_IMAGE, dirname(__file__)], stdout=DEVNULL
)

def _snip(str_, preamble, postamble, include_bounds=True):
start = str_.index(preamble)
end = str_.index(postamble, start + len(preamble))
if not include_bounds:
start += len(preamble)
if include_bounds:
end += len(postamble)
return str_[start:end]

def _extend_json(f_contents, dict_):
if not dict_:
return f_contents
start = f_contents.index('{')
end = f_contents.rindex('}', start + 1)
body = f_contents[start:end]
match = re.search('\n(\\s+)', body)
indent = match.group(1) if match else ''
append = json.dumps(dict_, indent=indent)[1:-1]
new_body = body.rstrip() + ',' + append
return f_contents[:start] + new_body + f_contents[end:]
25 changes: 25 additions & 0 deletions fbs/builtin_commands/_gpg/genkey.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/sh

set -e

tmpfile=$(mktemp)

cat >"$tmpfile" <<EOF
Key-Type: default
Key-Length: 4096
Subkey-Type: default
Subkey-Length: 4096
Name-Real: $1
Name-Email: $2
Expire-Date: 0
Passphrase: $3
%commit
EOF

gpg --batch --generate-key ${tmpfile}

rm ${tmpfile}

gpg --export -a "$1"

echo "$3\n" | gpg --batch --export-secret-key -a --pinentry-mode loopback --passphrase-fd 0 "$1"
3 changes: 3 additions & 0 deletions fbs/builtin_commands/_gpg/gpg-agent.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Let us provide the GPG passphrase via a pipe | when exporting the private key:
pinentry-program /usr/bin/pinentry-tty
allow-loopback-pinentry
27 changes: 27 additions & 0 deletions fbs/builtin_commands/_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from fbs import path
from fbs_runtime import FbsError
from getpass import getpass
from os.path import exists

def prompt_for_value(value, optional=False, default='', password=False):
message = value
if default:
message += ' [%s] ' % default
message += ': '
prompt = getpass if password else input
result = prompt(message).strip()
if not result and default:
print(default)
return default
if not optional:
while not result:
result = prompt(message).strip()
return result

def require_existing_project():
if not exists(path('src')):
raise FbsError(
"Could not find the src/ directory. Are you in the right folder?\n"
"If yes, did you already run\n"
" fbs startproject ?"
)
1 change: 1 addition & 0 deletions fbs/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def main(project_dir=None):
# Load built-in commands:
from fbs import builtin_commands
from fbs.builtin_commands import _docker
from fbs.builtin_commands import _gpg
fn, args = _parse_cmdline()
fn(*args)
except FbsError as e:
Expand Down
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ def _get_package_data(pkg_dir, data_subdir):
url='https://build-system.fman.io',
packages=find_packages(exclude=('tests', 'tests.*')),
package_data={
'fbs._defaults': _get_package_data('fbs/_defaults', 'src'),
'fbs.builtin_commands':
_get_package_data('fbs/builtin_commands', 'project_template'),
'fbs._defaults': _get_package_data('fbs/_defaults', 'src'),
'fbs.builtin_commands._gpg':
['Dockerfile', 'genkey.sh', 'gpg-agent.conf'],
'fbs.installer.mac': _get_package_data(
'fbs/installer/mac', 'yoursway-create-dmg'
)
Expand Down
Empty file.
File renamed without changes.
17 changes: 17 additions & 0 deletions tests/test_fbs/builtin_commands/test__gpg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from fbs.builtin_commands._gpg import _extend_json
from unittest import TestCase

class ExtendJsonTest(TestCase):
def test_empty(self):
json_str = '{\n\t"a": "b"}'
self.assertEqual(json_str, _extend_json(json_str, {}))
def test_single(self):
json_str = '{\n\t"a": "b"}'
self.assertEqual(
'{\n\t"a": "b",\n\t"c": "d"\n}', _extend_json(json_str, {'c': 'd'})
)
def test_spaces(self):
self.assertEqual(
'{\n "a": "b",\n "c": "d"\n}',
_extend_json('{\n "a": "b"}', {'c': 'd'})
)
File renamed without changes.

0 comments on commit 4f32d2b

Please sign in to comment.