aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Nordberg <linus@nordberg.se>2021-12-08 09:55:37 +0100
committerLinus Nordberg <linus@nordberg.se>2021-12-08 09:55:37 +0100
commit7576a1ebd03e1d7e68bd1701b8bff8159230fe19 (patch)
tree34a6b53a432b842c5aa93fdb97cb17971515280e
parentb06f07550957ba8ba4ff237332f16147a29a6dd2 (diff)
add tooling for signing
There's tools for key generation and conversion and there's tools for signing and verifying a tree leaf. Note that the leaf signing tools use the yet to be decided about SSH signing format, with message (ie signers checksum) being hashed with SHA-512 to match SSH tooling (ssh-keygen -Y).
-rw-r--r--tools/libsigntools.py118
-rwxr-xr-xtools/sigsum-gensigkey.py21
-rwxr-xr-xtools/sigsum-sign-leaf.py61
-rwxr-xr-xtools/sigsum-verify-leaf.py32
-rwxr-xr-xtools/sshkey2nacl.py20
5 files changed, 252 insertions, 0 deletions
diff --git a/tools/libsigntools.py b/tools/libsigntools.py
new file mode 100644
index 0000000..a3f0bcc
--- /dev/null
+++ b/tools/libsigntools.py
@@ -0,0 +1,118 @@
+import sys
+import struct
+from hashlib import sha256, sha512
+
+def checksum_stdin(hashalg='sha256'):
+ if hashalg == 'sha256':
+ d = sha256()
+ elif hashalg == 'sha512':
+ d = sha512()
+ else:
+ return None
+
+ while True:
+ buf = sys.stdin.buffer.read()
+ if not buf:
+ break
+ d.update(buf)
+
+ return d.digest()
+
+def ssh_to_sign(namespace, hashalg, checksum):
+ if hashalg == 'sha256':
+ hashlen = 32
+ elif hashalg == 'sha512':
+ hashlen = 64
+ else:
+ return None
+ s = struct.pack('!6sI{}sII6sI{}s'.format(len(namespace), hashlen),
+ b'SSHSIG',
+ len(namespace), bytes(namespace, 'ascii'),
+ 0,
+ 6, bytes(hashalg, 'ascii'),
+ hashlen, checksum)
+ return s
+
+# Adapted from https://stackoverflow.com/questions/65684414/how-to-use-ssh-keygen-ed25519-keys-for-encryption-in-python
+# Author: LJHW
+from base64 import b64decode
+from nacl.encoding import RawEncoder
+from nacl.signing import SigningKey, VerifyKey
+class C25519:
+ # Adapted from https://gist.github.com/R-VdP/b7ac0106a4fd395ee1c37bfe6f552a36 sealing.py
+ # Author: Ramses https://github.com/R-VdP
+ __key_length = 32
+ __private_key_signature = b'\x00\x00\x00\x40'
+ __public_key_signature = b'\x00\x00\x00\x20'
+
+ @classmethod
+ def __bytes_after(cls, signature, length, bytestr):
+ start = bytestr.find(signature) + len(signature)
+ return bytestr[start:start+length]
+
+ @classmethod
+ def __extract_signing_key(cls, private_data):
+ openssh_bytes = b64decode(private_data)
+ private_bytes = cls.__bytes_after(
+ cls.__private_key_signature,
+ cls.__key_length,
+ openssh_bytes
+ )
+ signing_key = SigningKey(seed=private_bytes, encoder=RawEncoder)
+ return signing_key
+
+ @classmethod
+ def __extract_verify_key(cls, public_data):
+ openssh_bytes = b64decode(public_data)
+ public_bytes = cls.__bytes_after(
+ cls.__public_key_signature,
+ cls.__key_length,
+ openssh_bytes
+ )
+ verify_key = VerifyKey(key=public_bytes, encoder=RawEncoder)
+ return verify_key
+
+ @classmethod
+ def __private_data_from_file(cls, file_name):
+ with open(file_name, 'r') as file:
+ contents = file.read()
+ contents = contents.split('\n')
+ private_data = ''
+ for line in contents:
+ if 'PRIVATE KEY' in line:
+ continue
+ if not line:
+ continue
+ private_data += line
+ return private_data
+
+ @classmethod
+ def __public_data_from_file(cls, file_name):
+ with open(file_name, 'r') as file:
+ contents = file.read()
+ contents = contents.split(' ')
+ # assert contents[0] == 'ssh-ed25519'
+ public_data = contents[1].strip(' ')
+ return public_data
+
+ @classmethod
+ def signingKey(cls, private_ed25519_file):
+ private_data = cls.__private_data_from_file(private_ed25519_file)
+ signing_key = cls.__extract_signing_key(private_data)
+ return signing_key
+
+ @classmethod
+ def verifyKey(cls, public_ed25519_file):
+ public_data = cls.__public_data_from_file(public_ed25519_file)
+ verify_key = cls.__extract_verify_key(public_data)
+ return verify_key
+
+ @classmethod
+ def privateKey(cls, private_ed25519_file):
+ signing_key = cls.signingKey(private_ed25519_file)
+ return signing_key.to_curve25519_private_key()
+
+ @classmethod
+ def publicKey(cls, public_ed25519_file):
+ verify_key = cls.verifyKey(public_ed25519_file)
+ return verify_key.to_curve25519_public_key()
diff --git a/tools/sigsum-gensigkey.py b/tools/sigsum-gensigkey.py
new file mode 100755
index 0000000..3c74108
--- /dev/null
+++ b/tools/sigsum-gensigkey.py
@@ -0,0 +1,21 @@
+#! /usr/bin/env python3
+
+import sys
+import os
+from stat import *
+from nacl.encoding import HexEncoder
+from nacl.signing import SigningKey
+
+def generate_and_store_sigkey(fn):
+ signing_key = SigningKey.generate()
+ verify_key = signing_key.verify_key
+ with open(fn, 'w') as f:
+ os.chmod(f.fileno(), S_IRUSR)
+ f.write(signing_key.encode(HexEncoder).decode('ascii') + '\n')
+ print(verify_key.encode(HexEncoder).decode('ascii'))
+
+def main():
+ generate_and_store_sigkey(sys.argv[1])
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/sigsum-sign-leaf.py b/tools/sigsum-sign-leaf.py
new file mode 100755
index 0000000..19d7f5c
--- /dev/null
+++ b/tools/sigsum-sign-leaf.py
@@ -0,0 +1,61 @@
+#! /usr/bin/env python3
+
+# Input: skeyfile shard_hint [checksum]
+# Output: tree_leaf signature
+# Example: echo foo | ./sigsum-sign-leaf.py nacl.sk 1633039200
+# be70f92465c27bf412008f26fa953d06899c53fa9867f40d9c0a1d657b188c9631699954728c719cf6b3819c1343c6e9e454cd9d519a9bf96dad3cf4cd959c0a
+
+import struct, sys, binascii
+from base64 import b64encode
+from nacl.signing import VerifyKey, SigningKey
+from nacl.encoding import HexEncoder
+from libsigntools import checksum_stdin, ssh_to_sign
+
+alg = 'sha512'
+
+def ssh_blob(vk, sig, namespace):
+ vkdata = struct.pack('!I11sI32s',
+ 11, bytes('ssh-ed25519', 'ascii'),
+ 32, vk.encode())
+ assert(len(vkdata) == 51)
+
+ assert(len(sig) == 64)
+ sigdata = struct.pack('!I11sI64s',
+ 11, bytes('ssh-ed25519', 'ascii'),
+ 64, sig)
+ assert(len(sigdata) == 83)
+
+ s = "-----BEGIN SSH SIGNATURE-----\n"
+ b = b64encode(struct.pack('!6sII51sI{}sII6sI83s'.format(len(namespace)),
+ b'SSHSIG',
+ 1,
+ 51, vkdata,
+ len(namespace), bytes(namespace, 'ascii'),
+ 0,
+ 6, bytes(alg, 'ascii'),
+ 83, sigdata)).decode('ascii')
+ while len(b) > 0:
+ s += b[:72] + '\n'
+ b = b[72:]
+ s += "-----END SSH SIGNATURE-----\n"
+ return s
+
+def main():
+ keyfile = sys.argv[1]
+ shard_hint = int(sys.argv[2])
+ if len(sys.argv) > 3:
+ checksum = bytes.fromhex(sys.argv[3])
+ else:
+ checksum = checksum_stdin(hashalg=alg)
+
+ with open(keyfile, 'r') as f:
+ signing_key = SigningKey(f.readline().strip(), encoder=HexEncoder)
+ namespace = 'tree_leaf:v0:{}@sigsum.org'.format(shard_hint)
+ signature = signing_key.sign(ssh_to_sign(namespace, alg, checksum)).signature
+
+ print(binascii.hexlify(signature).decode('ascii'))
+ if False:
+ print(ssh_blob(signing_key.verify_key, signature, namespace))
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/sigsum-verify-leaf.py b/tools/sigsum-verify-leaf.py
new file mode 100755
index 0000000..d8a15fa
--- /dev/null
+++ b/tools/sigsum-verify-leaf.py
@@ -0,0 +1,32 @@
+#! /usr/bin/env python3
+
+# Input: vkeyfile shard_hint signature [checksum]
+# Example: echo foo | ./sigsum-verify-leaf.py nacl.vk 0 $(echo foo | ./sigsum-sign-leaf.py nacl.sk 0)
+# OK
+
+import sys
+from nacl.signing import VerifyKey
+from nacl.encoding import HexEncoder
+from libsigntools import checksum_stdin, ssh_to_sign
+
+alg = 'sha512'
+
+def main():
+ keyfile = sys.argv[1]
+ shard_hint = int(sys.argv[2])
+ sig = bytes.fromhex(sys.argv[3])
+
+ with open(keyfile, 'r') as f:
+ vkey = VerifyKey(f.readline().strip(), encoder=HexEncoder)
+ if len(sys.argv) > 4:
+ checksum = bytes.fromhex(sys.argv[4])
+ else:
+ checksum = checksum_stdin(hashalg=alg)
+
+ namespace = 'tree_leaf:v0:{}@sigsum.org'.format(shard_hint)
+ data = ssh_to_sign(namespace, alg, checksum)
+ vkey.verify(data, signature=sig)
+ print("OK")
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/sshkey2nacl.py b/tools/sshkey2nacl.py
new file mode 100755
index 0000000..c109a3c
--- /dev/null
+++ b/tools/sshkey2nacl.py
@@ -0,0 +1,20 @@
+#! /usr/bin/env python3
+
+import sys
+from libsigntools import C25519
+from nacl.encoding import HexEncoder
+
+def main():
+ input_fn = sys.argv[1]
+ output_fn = sys.argv[2]
+
+ sk = C25519.signingKey(input_fn)
+ with open('{}.sk'.format(output_fn), 'w') as f:
+ f.write(sk.encode(HexEncoder).decode('ascii'))
+
+ vk = C25519.verifyKey('{}.pub'.format(input_fn))
+ with open('{}.vk'.format(output_fn), 'w') as f:
+ f.write(vk.encode(HexEncoder).decode('ascii'))
+
+if __name__ == '__main__':
+ main()