summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrian C. (anrxc) <anrxc@sysphere.org>2021-01-17 05:27:56 +0100
committerAdrian C. (anrxc) <anrxc@sysphere.org>2021-01-17 05:27:56 +0100
commitd099ec6ab71ea8732908c8f14c14653ac02efd58 (patch)
treed6109f53cbeab62795671b1dc84c9c29088ec28a
downloadfreebsd-pkgsign-d099ec6ab71ea8732908c8f14c14653ac02efd58.tar.xz
Initial commitHEADmaster
-rwxr-xr-xpkgsign194
1 files changed, 194 insertions, 0 deletions
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<SOCK>[^;]+).*SSH_AGENT_PID=(?P<PID>\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")