[Git][NTPsec/ntpsec][master] keygone is intended to be a flexible replacement for ntpkeygen ...

James Browning gitlab at mg.gitlab.com
Wed Sep 23 12:22:39 UTC 2020



James Browning pushed to branch master at NTPsec / ntpsec


Commits:
526ac37c by James Browning at 2020-09-23T12:22:29+00:00
keygone is intended to be a flexible replacement for ntpkeygen ...

- - - - -


4 changed files:

- + contrib/keygone-body.adoc
- + contrib/keygone-man.adoc
- + contrib/keygone.adoc
- contrib/keygone.py


Changes:

=====================================
contrib/keygone-body.adoc
=====================================
@@ -0,0 +1,131 @@
+// This is the body of the manual page for keygone.
+// It's included in two places: once for the docs/ HTML
+// tree, and once to make an individual man page.
+
+== Synopsis
+[verse]
++keygone+ [+-hlctvx+] [+-ad+ 'ALGO' ...] [+-f+ 'FILE'] [+-s+ 'LINK'] \
+          [+-n+ 'NUMBER'] [+-i+ 'INITIAL'] [+-g+ 'GAP'] 
+
+== Description
+
+This program generates keys that can be used in NTP's symmetric
+key cryptography.
+
+The program produces a file containing groups of pseudo-random
+printable ASCII strings suitable for NTPsec symmetric authentication.
+The groups are 'NUMBER' entries long, their numbers seperated by
+'GAP' starting at 'INITIAL'. The keys may either be in hexadecimal
+(lowercase base 16) or printable ASCII (base 95ish).
+
+The keys file must be distributed and stored using secure means
+beyond the scope of NTP itself. The keys can also be used as
+passwords for the link:ntpq.html[+ntpq+] utility program.
+
+[[cmd]]
+== Command Line Options
+
++-h+, +--help+::
+  show this help message and exit
+
++-L+, +--list+::
+  List known algorithms
+
++-d+ DELETE [DELETE ...], +--delete+ DELETE [DELETE ...]::
+  delete algorithm (repeatable) or "everything"
+
++-a+ ADD [ADD ...], +--add+ ADD [ADD ...]::
+  delete algorithm (repeatable) or "everything"
+
++-f+ FILE, +--file+ FILE::
+  Output to a file
+
++-s+ LINK, +--link+ LINK::
+  create a symlink (requires file)
+
++-c+, +--console+::
+  also print keys to the console
+
++-n+ NUMBER, +--number+ NUMBER::
+  number of keys per group (default 10)
+
++-i+ INITIAL, +--initial+ INITIAL::
+  number of initial key (default 1)
+
++-g+ GAP, +--gap+ GAP::
+  gap between subsequent groups (default 0)
+
++-t+, +--text+::
+  generate text keys (base-95 default)
+
++-x+, +--hex+::
+  generate hexadecimal keys (lowercase base-16)
+
+
++-V+, +--version+::
+  Print the version string and exit. (unimplemented)
+
+[[run]]
+== Running the program
+
+The simplest way to run the +keygone+ program is logged in directly as
+root. The recommended procedure is to change to the keys directory,
+usually +/var/lib/ntp/+, then run the program.  Then chown the output
+file to ntp:ntp. (typically 123:123) It should be mode 400.
+
+[[access]]
+== Key file access and location
+
+File names are suggested to begin with the prefix _ntpkey_ and end
+with the postfix _hostname.filestamp_, where _hostname_ is the owner
+name, usually the string returned by the Unix gethostname() routine,
+and _filestamp_ is the NTP seconds when the file was generated, in
+decimal digits.
+
++keygone+ can also makes a soft link from +ntp.keys+ to the generated
+file.  +ntp.keys+ is the normal file used in +{ntpconf}+.
+
+[[random]]
+== Random Seed File
+
+All key generation schemes must have means to randomize the
+entropy seed used to initialize the internal pseudo-random
+number generator used by the library routines.
+
+It is important to understand that entropy must be evolved for each
+generation, for otherwise the random number sequence would be
+predictable. Various means dependent on external events, such as
+keystroke intervals can be used to do this and some systems have
+built-in entropy sources.
+
+This implementation uses Python's secrets module..
+
+[[crypto]]
+== Cryptographic Data Files
+
+Unlike NTP Classic, this implementation can generate many key types.
+
+Since the file contains private shared keys, it should be visible
+only to root or ntp.
+
+In order to use a shared key, the line to be used must also be setup
+on the target server.
+
+This file is also used to authenticate remote configuration
+commands used by the {ntpqman} utility.
+
+Comments may appear in the file and are preceded with the +#+
+character.
+
+Following any headers the keys are entered one per line in the
+format:
+
+[options="header"]
+|====================================================================
+|Field	| Meaning
+|keyno	| Positive integer in the range 1-65,535
+|type	| Type of key (md5, sha224, aes-128 etc).
+|key	| the actual key, printable ASCII or hex
+|====================================================================
+
+// end


=====================================
contrib/keygone-man.adoc
=====================================
@@ -0,0 +1,20 @@
+= keygone(8)
+:man version: @NTPSEC_VERSION@
+include::../docs/include-man.ad[]
+
+keygone - create and manage NTP host keys
+
+include::keygone-body.adoc[]
+
+== EXIT STATUS
+
+One of the following exit values will be returned:
+
+0 (EXIT_SUCCESS)::
+  Successful program execution.
+
+1 (EXIT_FAILURE)::
+  The operation failed or the command syntax was not valid.
+
+// end
+


=====================================
contrib/keygone.adoc
=====================================
@@ -0,0 +1,52 @@
+= keygone - generate public and private keys
+include::include-html.ad[]
+
+[cols="10%,90%",frame="none",grid="none",style="verse"]
+|==============================
+|image:pic/alice23.gif[]|
+{millshome}pictures.html[from 'Alice's Adventures in Wonderland', Lewis Carroll]
+
+Alice holds the key.
+
+|==============================
+
+== Manual Pages
+
+include::includes/manual.adoc[]
+
+== Table of Contents
+
+* link:#_synopsis[Synopsis]
+* link:#_description[Description]
+* link:#cmd[Command Line Options]
+* link:#run[Running the program]
+* link:#access[Key file access and location]
+* link:#random[Random Seed File]
+* link:#crypto[Cryptographic Data Files]
+
+'''''
+
+include::keygone-body.adoc[]
+
+// The end of "Cryptographic Data Files" runs into this following text.
+
+image:pic/sx5.gif[]
+
+Figure 1. Typical Symmetric Key File
+
+Figure 1 shows a typical symmetric keys file used by the reference
+implementation. Each line of the file contains three fields, first
+keyno an integer between 1 and 65535, inclusive, representing the
+key identifier used in the `server` configuration commands. Next
+is the key type for the message digest algorithm, which can be any
+message digest algorithm supported by the OpenSSL library.
+
+For details, see {ntpkeysman}.
+
++keygone+ just makes a sample file with good random keys.  You can
+edit it by hand to change the keyno or keytype and/or copy lines to
+other key files.
+
+'''''
+
+include::includes/footer.adoc[]


=====================================
contrib/keygone.py
=====================================
@@ -11,53 +11,204 @@ key_number starts as the first key number and is incremented.
 max_bytes is the implementation-specific maximum number of entropy bytes
 allowed per key.
 
-types_lengths_list is a list of lists. the inner lists store the output
+the variables list_* are lists of lists. the inner lists store the output
 length of the algorithm (block size?) and the standard form of the
-algorithms name (in lowercase). Commented out algorithms are believed
-broken, non-commented ones are not. This list believed accurate as of
-the 6th of May 2020.
+algorithms name (in lowercase). *_bad list algorithms are believed
+broken, *_good ones are not. This list believed accurate as of the
+6th of May 2020.
 '''
+import argparse
+import os
 import secrets
+import stat
+import sys
 
-group_iterator = range(10)
-key_number = 100
-max_bytes = 32
-
-types_lengths_list = [
-    # [16, 'md5'],
-     [16, 'aes-128'],
-     [16, 'aria-128'],
-     [16, 'camellia-128'],
-     [16, 'sm4'],
-
-    # [20, 'sha1'],
-    # [20, 'rmd160'],
-
-     [24, 'aes-192'],
-     [24, 'aria-192'],
-     [24, 'camellia-192'],
-
-     [28, 'sha224'],
-     [28, 'sha3-224'],
-
-     [32, 'blake2s256'],
-     [32, 'sha256'],
-     [32, 'sha3-256'],
-     [32, 'aes-256'],
-     [32, 'aria-256'],
-     [32, 'camellia-256'],
-
-     [48, 'sha384'],
-     [48, 'sha3-384'],
-
-     [64, 'blake2b512'],
-     [64, 'sha512'],
-     [64, 'sha3-512']]
-
-for type_length_tuple in types_lengths_list:
-    length = min(type_length_tuple[0], max_bytes)
-    for _ in group_iterator:
-        print("%3d %12s\t%s" %
-        (key_number, type_length_tuple[1],
-         secrets.token_hex(length)))
-        key_number += 1
+list_md_bad = [
+    [16, 'md5'],
+
+    [20, 'sha1'],
+    [20, 'rmd160']
+]
+list_md_good = [
+    [16, 'sm4'],
+
+    [28, 'sha224'],
+    [28, 'sha3-224'],
+
+    [32, 'blake2s256'],
+    [32, 'sha256'],
+    [32, 'sha3-256'],
+
+    [48, 'sha384'],
+    [48, 'sha3-384'],
+
+    [64, 'blake2b512'],
+    [64, 'sha512'],
+    [64, 'sha3-512']
+]
+list_cmac_bad = [
+]
+list_cmac_good = [
+    [16, 'aes-128'],
+    [16, 'aria-128'],
+    [16, 'camellia-128'],
+
+    [24, 'aes-192'],
+    [24, 'aria-192'],
+    [24, 'camellia-192'],
+
+    [32, 'aes-256'],
+    [32, 'aria-256'],
+    [32, 'camellia-256'],
+]
+
+
+class KeyGone():
+    'Set up, generate and output keys for NTPsec.'
+
+    def __init__(self, start: int, gap: int):
+        'Set up key generation for NTPsec.'
+        self.gap = min(0, gap)
+        self.index = start
+        self.backing = {}
+
+    def __str__(self):
+        'Return a string containing the generated keys.'
+        _ = ''
+        for row in self.backing:
+            _ += '%d\t%s\t%s\n' % (row, *self.backing[row])
+        return _
+
+    def to_file(self, oname: str):
+        'Write the current keys to a file.'
+        with open(oname, 'w') as of:
+            of.write(str(self))
+
+    def do_link(self, oname: str, link: str):
+        'Write the current keys to a file and link to it elsewhere.'
+        orig_umask = os.umask(stat.S_IWGRP | stat.S_IRWXO)
+        os.umask(orig_umask)
+        self.to_file(oname)
+        if os.path.exists(link):
+            os.remove(link)    # The symlink() line below matters
+        os.symlink(oname, link)
+
+    def add(self, algor: str, keys: int, length: int, hexed: bool = False):
+        'Generate a slew of new keys according to specs.'
+        for _ in range(keys):
+            self.backing[self.index] = [algor, self.gen_key(length, hexed)]
+            self.index += 1
+        self.index += self.gap
+
+    def gen_key(self, length: int, hexed: bool) -> str:
+        'Generate a single key.'
+        if hexed:
+            return secrets.token_hex(length)
+        result = ''
+        for _ in range(length):
+            result += chr(0x21 + secrets.randbelow(0x5d))
+        return result
+
+
+def list_algos():
+    'List the available algorithms by buckets.'
+    chunks = []
+    iterable = (('bad CMAC algos:', list_cmac_bad),
+                ('good CMAC algos:', list_cmac_good),
+                ('bad digest algos:', list_md_bad),
+                ('good digest algos:', list_md_good)
+                )
+    for label, things in iterable:
+        _ = label + '\n'
+        for thing in things:
+            _ += '%3dbit\t%s\n' % (thing[0] * 8, thing[1])
+        chunks.append(_)
+    sys.stderr.write('\n'.join(chunks))
+    sys.exit(0)
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description='NTPsec key generator.')
+    parser.add_argument('-L', '--list', action='store_true',
+                        default=False, help='List known algorithms')
+    parser.add_argument('-d', '--delete', nargs='+',
+                        help='delete algorithm (repeatable) or "everything"')
+    parser.add_argument('-a', '--add', nargs='+',
+                        help='delete algorithm (repeatable) or "everything"')
+    parser.add_argument('-f', '--file', help='Output to a file')
+    parser.add_argument(
+        '-s', '--link', help='create a symlink (requires file)')
+    parser.add_argument('-c', '--console', action='store_true',
+                        default=False, help='output to console')
+    parser.add_argument('-n', '--number', type=int,
+                        default=10, help='number of keys per group')
+    parser.add_argument('-i', '--initial', type=int,
+                        default=1, help='number of initial key')
+    parser.add_argument('-g', '--gap', type=int, default=0,
+                        help='gap between subsequent groups')
+    parser.add_argument('-t', '--text', dest='fmt', action='store_false',
+                        default=False, help='generate text keys (default)')
+    parser.add_argument('-x', '--hex', dest='fmt',
+                        action='store_true', help='generate hexadecimal keys')
+    args = parser.parse_args()
+    if args.list:
+        list_algos()
+    kg = KeyGone(args.initial, args.gap)
+    set_cur = set(map(lambda x: x[1], list_cmac_good + list_md_good))
+    set_all = set(map(lambda x: x[1], list_cmac_bad + list_md_bad))
+    set_all.update(set_cur)
+    if isinstance(args.delete, str):
+        args.delete = [args.delete]
+    elif args.delete is None:
+        args.delete = []
+    for iteM in list(args.delete):
+        item = iteM.lower()
+        if item in set_cur:
+            set_cur.remove(item)
+        elif item == 'everything':
+            set_cur = set()
+        else:
+            sys.stderr.write('"%s" not in algos' % item)
+    if isinstance(args.add, str):
+        args.add = [args.add]
+    elif args.add is None:
+        args.add = []
+    for iteM in list(args.add):
+        item = iteM.lower()
+        if item in set_cur:
+            sys.stderr.write('"%s" already in algos' % item)
+        if item in set_all:
+            set_cur.add(item)
+        elif item == 'everything':
+            set_cur = set_all.copy()
+        else:
+            sys.stderr.write('"%s" not in available algos' % item)
+    algos = list_cmac_good + list_md_good + list_cmac_bad + list_md_bad
+    fail = False
+    if args.initial < 1:
+        fail = True
+        sys.stderr.write('"initial" key number must be a positive integer')
+    if args.number < 1:
+        fail = True
+        sys.stderr.write('"number" of keys per group must be a positive integer')
+    if args.gap < 0:
+        fail = True
+        sys.stderr.write('the "gap" between groups must be non-negative integer')
+    if len(algos) < 1:
+        fail = True
+        sys.stderr.write('At least one algorithm must be specified')
+    if args.initial + (args.gap + args.number) * len(algos) - args.gap > 65535:
+        fail = True
+        sys.stderr.write('End number must be less than 65,536')
+    if fail:
+        sys.exit(1)
+    for algo in algos:
+        if algo[1] in set_cur:
+            kg.add(algo[1], args.number, algo[0], args.fmt)
+    if args.file is not None:
+        if args.link is not None:
+            kg.do_link(args.file, args.link)
+        else:
+            kg.to_file(args.file)
+    if args.console:
+        print(str(kg))



View it on GitLab: https://gitlab.com/NTPsec/ntpsec/-/commit/526ac37cc297d1ab7e3952fffb5874cc0befea9b

-- 
View it on GitLab: https://gitlab.com/NTPsec/ntpsec/-/commit/526ac37cc297d1ab7e3952fffb5874cc0befea9b
You're receiving this email because of your account on gitlab.com.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.ntpsec.org/pipermail/vc/attachments/20200923/00f519ae/attachment-0001.htm>


More information about the vc mailing list