1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3'''
4ntpkeygen - generate cryptographic keys for NTP clients and servers
5
6All file names are like "ntpkey_<type>_<hostname>.<filestamp>", where
7<type> is the file type, <hostname> the generating host name and
8<filestamp> the generation time in NTP seconds. The NTP programs
9expect generic names such as "ntpkey_<type>_whimsy.udel.edu" with the
10association maintained by soft links. Following is a list of file
11types.
12
13ntpkey_AES_<hostname>.<filestamp>
14AES (128-bit) keys used to compute CMAC mode authentcation
15using shared key cryptography
16
17The file can be edited by hand to support MD5 and SHA-1 for
18old digest mode authentication.
19'''
20
21from __future__ import print_function
22
23import os
24import sys
25import socket
26import time
27import getopt
28import stat
29
30try:
31    import secrets
32    def gen_key(bytes, asciified=True):
33        if asciified:
34            result = ''
35            for index in range(bytes):
36                # Start ASCII characters with 0x24 so as not to include comment-beginning #
37                result += chr(0x24 + secrets.randbelow(0x5a))
38            return result
39        else:
40            return secrets.token_hex(bytes)
41except ImportError:
42    import random
43    def gen_key(bytes, asciified=True):
44        result = ''
45        if asciified:
46            for index in range(bytes):
47                # Start ASCII characters with 0x24 so as not to include comment-beginning #
48                result += chr(random.randint(0x24, 0x7e))
49        else:
50            for index in range(bytes):
51                result += "%02x" % random.randint(0x0, 0xff)
52        return result
53
54#
55# Cryptodefines
56#
57NUMKEYS = 10    # number of keys generated of each type
58KEYSIZE = 16    # maximum key size
59
60
61def gen_keys(ident, groupname):
62    "Generate semi-random AES keys for versions of ntpd with CMAC support."
63    with fheader("AES", ident, groupname) as wp:
64        for i in range(1, NUMKEYS+1):
65            key = gen_key(KEYSIZE, True)
66            wp.write("%2d AES %s\n" % (i, key))
67        for i in range(1, NUMKEYS+1):
68            key = gen_key(KEYSIZE, False)
69            wp.write("%2d AES %s\n" % (i + NUMKEYS, key))
70
71
72#
73# Generate file header and link
74#
75def fheader(fileid,     # file name id
76            ulink,      # linkname
77            owner       # owner name
78            ):
79    "Generate file header and link"
80    try:
81        filename = "ntpkey_%s_%s.%u" % (fileid, owner, int(time.time()))
82        orig_umask = os.umask(stat.S_IWGRP | stat.S_IRWXO)
83        wp = open(filename, "w")
84        os.umask(orig_umask)
85
86        linkname = "ntp.keys"
87        if os.path.exists(linkname):
88            os.remove(linkname)    # The symlink() line below matters
89        os.symlink(filename, linkname)
90
91        sys.stderr.write("Generating new %s file and link\n" % ulink)
92        sys.stderr.write("%s->%s\n" % (linkname, filename))
93        wp.write("# %s\n# %s\n" % (filename, time.ctime()))
94        return wp
95    except IOError:
96        sys.stderr.write("Key file creation or link failed.\n")
97        raise SystemExit(1)
98
99
100if __name__ == '__main__':
101    try:
102        (options, arguments) = getopt.getopt(sys.argv[1:], "hV",
103                                             ["help", "version"])
104    except getopt.GetoptError as e:
105        print(e)
106        raise SystemExit(1)
107
108    for (switch, val) in options:
109        if switch in ("-h", "--help"):
110            print("usage: ntpkeygen")
111            raise SystemExit(0)
112        elif switch in ("-V", "--version"):
113            print("ntpkeygen ntpsec-@NTPSEC_VERSION_EXTENDED@")
114            raise SystemExit(0)
115
116    # The seed is ignored by random.SystemRandom,
117    # even though the docs do not say so.
118    gen_keys("AES", socket.gethostname())
119    raise SystemExit(0)
120
121# end
122