From d099ec6ab71ea8732908c8f14c14653ac02efd58 Mon Sep 17 00:00:00 2001 From: "Adrian C. (anrxc)" Date: Sun, 17 Jan 2021 05:27:56 +0100 Subject: Initial commit --- pkgsign | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100755 pkgsign diff --git a/pkgsign b/pkgsign new file mode 100755 index 0000000..c908ab2 --- /dev/null +++ b/pkgsign @@ -0,0 +1,194 @@ +#!/usr/bin/env python3.7 +# -*- mode:python; coding:utf-8 -*- +# +# NAME +# pkgsign -- tool for use with FreeBSD pkg-repo(8) that utilizes +# ssh-agent for private key management when signing +# repositories +# +# LICENSE +# Copyright (c) 2011 lars at oddbit dot com +# Copyright (c) 2021 anrxc at sysphere dot org +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# SYNOPSIS +# /usr/sbin/pkg repo /path/to/repository signing_command: pkgsign [FINGERPRINT] +# /usr/sbin/pkg repo /path/to/repository signing_command: ssh signing-server pkgsign [FINGERPRINT] +# +# FILES +# $HOME/.ssh/ssh-agent.info +# $HOME/.gnupg/gpg-agent.info +# /usr/local/etc/ssl/public/[FINGERPRINT].pub +# + +import paramiko.agent +from sys import stdin +from sys import stdout +from os import environ +from binascii import hexlify +from sys import argv as sysargv +from struct import unpack as strunpack + + +try: + # For obtaining fingerprints from an agent + _KDUMP = sysargv[1] == "--dump" and 1 + # For experimenting with keys and signatures + _DEBUG = sysargv[1] == "--debug" and 1 +except IndexError: + _KDUMP = 0 + _DEBUG = 0 + +try: + if _KDUMP < 1: + if _DEBUG > 0: + _PKGID = stdin.readline().strip() + _KEYID = sysargv[2] + else: + _PKGID = stdin.readline().strip() + _KEYID = sysargv[1] +except IndexError: + raise SystemExit("ERROR: key fingerprint missing from command line, aborting") + + +if "SSH_AUTH_SOCK" not in environ: + import re + ## gpg-agent untested, at some point in 2013 SSH support was broken + ## for months and if such a thing is allowed to happen then best to + ## rely on ssh-agent + #agent_info = open("%s/.gnupg/gpg-agent.info" % environ["HOME"], "r") + agent_info = open("%s/.ssh/ssh-agent.info" % environ["HOME"], "r") + agent_format = re.compile("SSH_AUTH_SOCK=(?P[^;]+).*SSH_AGENT_PID=(?P\d+)", re.MULTILINE | re.DOTALL) + + match = agent_format.search(agent_info.read()) + agent_info.close() + + localenv = match.groupdict() + environ["SSH_AGENT_PID"] = localenv["PID"] + environ["SSH_AUTH_SOCK"] = localenv["SOCK"] + + +if _DEBUG > 0: + print("INFO: SSH_AGENT_PID is %s " % environ["SSH_AGENT_PID"]) + print("INFO: SSH_AUTH_SOCK is %s " % environ["SSH_AUTH_SOCK"]) + + +agent = paramiko.agent.Agent() +agent_keys = agent.get_keys() + +if len(agent_keys) == 0: + if _DEBUG > 0: + print("ERROR: no key(s) found in ssh-agent, aborting") + raise SystemExit(1) + + +if _KDUMP > 0: + for key in agent_keys: + print("INFO: found ssh-agent key %s" % hexlify(key.get_fingerprint()).decode()) + raise SystemExit(0) + + +for key in agent_keys: + if _DEBUG > 0: + print("INFO: found ssh-agent key %s" % hexlify(key.get_fingerprint()).decode()) + + if _KEYID.encode() == hexlify(key.get_fingerprint()): + if _DEBUG > 0: + print("INFO: key match found, signing with %s" % _KEYID) + + # RSA sign flags: 0 (sha1), 2 (sha256), 4 (sha512) + # - for widespread use upstream must accept the flags support patch + try: + raw_sig = key.sign_ssh_data(_PKGID, 2) + except TypeError: + raise SystemExit("ERROR: agent.py missing flags support, see patch at the bottom") + + # Strip fields with the algorithm name and length of the signature + sig_parts = [] + while raw_sig: + len = strunpack('>I', raw_sig[:4])[0] + bits = raw_sig[4:len+4] + sig_parts.append(bits) + raw_sig = raw_sig[len+4:] + sig = sig_parts[1] + + # To convert key.get_base64() to pkcs8 would be more code than this + # entire thing. To use ssh-keygen instead we need a temporary file as + # it can't read it from stdin when performing a conversion. In the end + # it is much simpler to just read it from a pregenerated file. + pub_key = open("/usr/local/etc/ssl/public/%s.pub" % _KEYID, "r") + + # Print data in order that pkg-repo(8) is expecting + print("SIGNATURE") + # - flush to ensure order and prevent pkg-repo(8) segfault + stdout.flush() + # - write the signature raw bytes that pkg-repo(8) is expecting + stdout.buffer.write(sig) + stdout.flush() + print() + stdout.flush() + print("CERT") + stdout.flush() + print(pub_key.read().strip()) + stdout.flush() + print("END") + stdout.flush() + pub_key.close() + + # For validating signatures against/with openssl pkeyutl + # - generate: echo -n "[HASH]" | openssl dgst -sign [PRIVATEKEY] -sha256 -binary >signature-cmp + if _DEBUG > 0: + # - validate: echo -n "[HASH]" | openssl sha256 -binary | \ + # openssl pkeyutl -verify -sigfile signature-[HASH] -pubin \ + # -inkey [PUBLICKEY] -pkeyopt digest:sha256 + open("signature-%s" % _PKGID, "wb").write(sig) + + raise SystemExit(0) + else: + if _DEBUG > 0: + print("WARN: key mismatch, continuing search...") + continue + + +if _DEBUG > 0: + raise SystemExit("ERROR: no matching key(s) found for signing, aborting") +else: + raise SystemExit(1) + + +# Subject: [PATCH] paramiko agent RSA sign flags support +# +#--- paramiko/agent.py 2021-01-15 23:03:50.387801224 +0100 +#+++ paramiko/agent.py 2021-01-15 23:04:34.667800388 +0100 +#@@ -407,12 +407,12 @@ +# def get_name(self): +# return self.name +# +#- def sign_ssh_data(self, data): +#+ def sign_ssh_data(self, data, flags=0): +# msg = Message() +# msg.add_byte(cSSH2_AGENTC_SIGN_REQUEST) +# msg.add_string(self.blob) +# msg.add_string(data) +#- msg.add_int(0) +#+ msg.add_int(flags) +# ptype, result = self.agent._send_message(msg) +# if ptype != SSH2_AGENT_SIGN_RESPONSE: +# raise SSHException("key cannot be used for signing") -- cgit v1.2.3