signature helpers

This commit is contained in:
François TERROT 2021-04-25 18:03:48 +02:00
parent ecc1931f84
commit 3caf2dfdef
Signed by: fterrot
GPG Key ID: 7D96B088C27AC869
2 changed files with 333 additions and 0 deletions

278
bin/gpgsign.py Normal file
View File

@ -0,0 +1,278 @@
#!/usr/bin/env python3
"""
gpgsign.py - Helper to "GPG" sign Mylyn Gitea Release files
Limitations:
Too many limitations to be listed here.
Usage:
1. Set GPG configuration in signature.conf file
2. run gpgsign.py
Caveats:
Must be run AFTER jarsign.sh to avoid gpg signature modification
Generated files are similar to maven-gpg-plugin ones (default configuration)
"""
import logging
import subprocess
from pathlib import Path, PurePath
from dataclasses import dataclass
from typing import Tuple
from enum import Enum
import configparser
# pylint: disable=logging-fstring-interpolation, line-too-long
__PROG__ = "gpgsign"
WORKSPACEDIR = Path(__file__).parent.parent # <workspace>/bin/gpgsign.py
ASC_DIRECTORY = "gpg"
class GPGLockMode(Enum):
ONCE = "once"
MULTIPLE = "multiple"
NEVER = "never"
@dataclass
class GPGConfiguration:
keyname: str
passphrase: str = ""
verbose:bool = False
skip: bool = False
interactive : bool = True
executable : Path = None
use_agent: bool = False
home_dir: str = ""
secret_keyring : str = ""
public_keyring : str = ""
lock_mode : GPGLockMode = None # gpg lock mode, could be : None -> lock mode not used, "once", "multiple", "never" => SyntaxError
args = "" # list, of additional arguments as list or tuple or comma separated as string
@dataclass
class Configuration:
gpg: GPGConfiguration
class GPGSigner:
"""Compute signature.
"""
SIGNATURE_EXTENSION : str = ".asc"
def __init__(self, gpg:GPGConfiguration , output_dir: Path ):
self.output_dir = output_dir
self.gpg = gpg
self.interactive = False
def generate_signature_for_file(self, file: Path, signature: Path) -> int:
"""Generate a detached signature file for provided file.
Args:
file: Path of file to be signed
signature: Path of file where the signature will be written.
Returns:
return code of signature application
"""
# Configure command line
cmd = ['gpg']
if self.gpg.executable:
cmd = [str(self.gpg.executable)]
if self.gpg.args:
if isinstance(self.gpg.args, (list, tuple)):
cmd += self.gpg.args
else:
cmd += self.gpg.args.split(",")
if self.gpg.verbose:
cmd.append("-v")
if self.gpg.home_dir:
cmd += [ "--homedir", self.gpg.home_dir]
cmd.append(f"--{'' if self.gpg.use_agent else 'no-'}use-agent")
# required for subprocess input:
# 1. to disable input when self.gpg.passphrase is "" (run is waiting None)
# 2. run is waiting bytes for input
in_passphrase=None
if self.gpg.passphrase:
cmd += ["--batch", "--passphrase-fd", "0"]
in_passphrase = bytes(self.gpg.passphrase,"UTF-8")
if self.gpg.keyname:
cmd += ["--local-user", self.gpg.keyname]
cmd.append("--armor")
cmd.append("--detach-sign")
if not self.gpg.interactive:
cmd.append("--no-tty") #
if self.gpg.secret_keyring or self.gpg.public_keyring:
cmd.append("--no-default-keyring")
if self.gpg.secret_keyring:
cmd += ["--secret-keyring", self.gpg.secret_keyring]
if self.gpg.public_keyring:
cmd +=[ "--keyring", self.gpg.public_keyring]
if self.gpg.lock_mode:
cmd.append(f"--lock-{self.gpg.lock_mode}")
cmd += ["--output", str(signature)]
cmd.append(str(file))
logging.debug(f"GPGSigner> {cmd}")
# Execute command line
completed_process = subprocess.run(cmd, input=in_passphrase)
logging.debug(str(completed_process))
return completed_process.returncode
def generate_signature_for_artifact (self, file: Path) -> int:
"""Create a detached signature file for the provided file
managing valide signature file destination.
"""
# Setup file and directory for signature
signature = file.with_suffix ( file.suffix + GPGSigner.SIGNATURE_EXTENSION)
if self.output_dir:
# ugly but working way to get common parts
for i,part in enumerate(signature.parts):
try:
if part == self.output_dir.parts[i]:
continue
except IndexError: # output_dir is smaller than signature
pass
break
common_dir = Path(*signature.parts[:i])
if common_dir:
signature = self.output_dir / signature.relative_to(common_dir)
# Create destination directory
logging.debug(f"GPGSigner> artifact:{file}")
logging.debug(f"GPGSigner> signature:{signature}")
if not signature.parent.is_dir():
signature.parent.mkdir(parents=True, exist_ok=True)
if signature.exists():
signature.unlink()
# Generate signature file
return self.generate_signature_for_file(file, signature)
def get_passphrase(self):
return ""
class Project:
""" Project as list of artifacts """
ARTIFACTS_EXTENSIONS = ('.jar', '.xml', '.md')
DEFAULT_EXCLUDES = ('.asc', ".gpg", ".sig", ".git", ".externalToolBuilders")
def __init__(self, projectdir):
self.projectdir = projectdir
logging.debug(f"Project> {projectdir}")
@property
def name(self):
return self.projectdir.name
def get_attached_artifacts(self, iter_in_dir :Path = None) -> Path:
"""
Yields:
artifact
"""
iter_in_dir = self.projectdir if iter_in_dir is None else iter_in_dir
for e in iter_in_dir.iterdir():
if e.is_dir() :
logging.debug(f"Project.get_artifacts> entering in {str(e)}")
yield from self.get_attached_artifacts(e)
if e.suffix.lower() in Project.DEFAULT_EXCLUDES:
logging.debug(f"Project.get_artifacts> excluding {e}")
continue
if e.suffix.lower() in Project.ARTIFACTS_EXTENSIONS or e.name == 'LICENSE':
logging.debug(f"Project.get_artifacts> {e}")
yield e
class GPGSignAttached:
"""Sign artifacts attached to the project."""
def __init__(self, project: Project, config: Configuration):
self.project = project
self.config = config
def execute(self):
if self.config.gpg.skip:
logging.warning(f"GPGSign> Skipping GPG Signature for {self.project.name}")
return
logging.info(f"GPGSign> Signing {self.project.name} artifacts")
signer = GPGSigner(config.gpg, self.project.projectdir / ASC_DIRECTORY)
for artifact in self.project.get_attached_artifacts():
signer.generate_signature_for_artifact(artifact)
if __name__ == "__main__":
# pylint: disable=invalid-name
import argparse
import sys
import os
# backup working directory
OLDPWD = os.path.curdir
# Configure the command line arguments parser
parser = argparse.ArgumentParser(prog=__PROG__,
description=__PROG__ + " Sign (Java + GPG) updatesite artifacts",
prefix_chars="-+")
parser.add_argument("-c", "--config-file",
help="Configuration file (default <workspacedir>/signature.conf",
dest="config_file",
default=str((WORKSPACEDIR / "signature.conf").absolute()),
required=False)
parser.add_argument("-x",
help="Active 'debug' level for logging",
dest='debug',
action='store_true',
default=False,
required=False)
# Parse the command line
args = parser.parse_args()
# Configuration file
config_ini = configparser.ConfigParser()
config_ini.read(args.config_file)
config = Configuration(
gpg=GPGConfiguration(
skip = config_ini.getboolean('GPG','skip', fallback=True),
verbose = config_ini.getboolean('GPG','verbose', fallback=False),
keyname = config_ini.get('GPG', 'keyname', fallback=None),
passphrase= config_ini.get('GPG','passphrase', fallback=None),
)
)
# Configure logging
# see https://docs.python.org/2/howto/logging.html
logging.basicConfig(
format='%(asctime)s:%(levelname)s:%(message)s' if args.debug else '%(levelname)s:%(message)s',
level=logging.DEBUG if args.debug else logging.INFO)
siteupdate_project = Project(WORKSPACEDIR / "io.gitea.mylyn.updatesite")
GPGSignAttached(siteupdate_project, config).execute()
sys.exit(0)
# vim: tabstop=4:shiftwidth=4:expandtab

55
bin/jarsign.sh Normal file
View File

@ -0,0 +1,55 @@
#!/usr/bin/env bash
#
# Helper script to sign Mylyn Gitea eclipse plugin
#
# Usage:
# 1. Configure the .sign.conf file
# 2. jarsign.sh [-x]
#
# Inspired from https://nirmalsasidharan.wordpress.com/2010/09/04/signing_eclipse_plugins/
#
# set debug
case "x$1" in "x-x" ) shift ; set -x ;; esac
#
# Sign all "not already signed" jar in a directory
# usage: jarsign_dir <directory>
#
jarsign_dir() {
for jarfile in ${1}/*.jar ; do
[ -e "$jarfile" ] || continue
unset result
case ${result:-$(jarsigner -verify -verbose ${opt_keystore} ${opt_storepass} ${opt_alias} ${jarfile} ${KEYALIAS} | egrep 'jar is unsigned|jar verified')} in
'jar is unsigned.' )
jarsigner ${opt_keystore} ${opt_storepass} -verbose ${opt_alias} ${jarfile} ${KEYALIAS} ;;
'jar verified.' )
echo "$jarfile already signed"
;;
* )
echo "***ERROR Raised by ${jarfile} signature verification..."
echo $result
;;
esac
done
}
WORKSPACEDIR=$(realpath "$(dirname $0)/.." )
SIGNPARAMS=${WORKSPACEDIR}/.sign.conf
if [ -x $SIGNPARAMS ] ; then
source $SIGNPARAMS
else
echo "Missing configuration file" && exit 1
fi
if (( $JARSIGN == 1)) ; then
[ -f "${KEYSTORE}" ] && opt_keystore="-keystore ${KEYSTORE}"
[ -n "${STOREPASS}" ] && opt_storepass="-storepass ${STOREPASS}"
jarsign_dir ${WORKSPACEDIR}/io.gitea.mylyn.updatesite
jarsign_dir ${WORKSPACEDIR}/io.gitea.mylyn.updatesite/features
jarsign_dir ${WORKSPACEDIR}/io.gitea.mylyn.updatesite/plugins
fi