FreedomBox/actions/auth-pubtkt
Sunil Mohan Adapa a0d880b62c
sso: Update usage of OpenSSL crypt signing API
Avoid the deprecation warning:

DeprecationWarning: str for data is no longer accepted, use bytes
    sig = crypto.sign(pkey, data, 'sha512')

Tests:

- Login to web interface, access Syncthing web interface. The login should work.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
2021-09-26 12:29:45 -04:00

127 lines
4.1 KiB
Python
Executable File

#!/usr/bin/python3
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Module with utilities to generate a auth_pubtkt ticket and
sign it with the FreedomBox server's private key.
"""
import argparse
import base64
import datetime
import os
from OpenSSL import crypto
KEYS_DIRECTORY = '/etc/apache2/auth-pubtkt-keys'
def parse_arguments():
""" Return parsed command line arguments as dictionary. """
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
subparsers.add_parser(
'create-key-pair', help='create a key pair for the apache server '
'to sign auth_pubtkt tickets')
gen_tkt = subparsers.add_parser('generate-ticket',
help='generate auth_pubtkt ticket')
gen_tkt.add_argument('--uid', help='username of the user')
gen_tkt.add_argument('--private-key-file',
help='path of the private key file of the server')
gen_tkt.add_argument('--tokens',
help='tokens, usually containing the user groups')
subparsers.required = True
return parser.parse_args()
def subcommand_create_key_pair(_):
"""Create public/private key pair for signing the auth_pubtkt
tickets.
"""
private_key_file = os.path.join(KEYS_DIRECTORY, 'privkey.pem')
public_key_file = os.path.join(KEYS_DIRECTORY, 'pubkey.pem')
os.path.exists(KEYS_DIRECTORY) or os.mkdir(KEYS_DIRECTORY)
if not all([
os.path.exists(key_file)
for key_file in [public_key_file, private_key_file]
]):
pkey = crypto.PKey()
pkey.generate_key(crypto.TYPE_RSA, 4096)
with open(private_key_file, 'w') as priv_key_file:
priv_key = crypto.dump_privatekey(crypto.FILETYPE_PEM,
pkey).decode()
priv_key_file.write(priv_key)
with open(public_key_file, 'w') as pub_key_file:
pub_key = crypto.dump_publickey(crypto.FILETYPE_PEM, pkey).decode()
pub_key_file.write(pub_key)
for fil in [public_key_file, private_key_file]:
os.chmod(fil, 0o440)
def create_ticket(pkey, uid, validuntil, ip=None, tokens=None, udata=None,
graceperiod=None, extra_fields=None):
"""Create and return a signed mod_auth_pubtkt ticket."""
fields = [
f'uid={uid}',
f'validuntil={int(validuntil)}',
ip and f'cip={ip}',
tokens and f'tokens={tokens}',
graceperiod and f'graceperiod={int(graceperiod)}',
udata and f'udata={udata}',
extra_fields
and ';'.join(['{}={}'.format(k, v) for k, v in extra_fields]),
]
data = ';'.join(filter(None, fields))
signature = 'sig={}'.format(sign(pkey, data))
return ';'.join([data, signature])
def sign(pkey, data):
"""Calculates and returns ticket's signature."""
sig = crypto.sign(pkey, data.encode(), 'sha512')
return base64.b64encode(sig).decode()
def subcommand_generate_ticket(arguments):
"""Generate a mod_auth_pubtkt ticket using login credentials."""
uid = arguments.uid
private_key_file = arguments.private_key_file
tokens = arguments.tokens
with open(private_key_file, 'r') as fil:
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, fil.read().encode())
valid_until = minutes_from_now(12 * 60)
grace_period = minutes_from_now(11 * 60)
print(
create_ticket(pkey, uid, valid_until, tokens=tokens,
graceperiod=grace_period))
def minutes_from_now(minutes):
"""Return a timestamp at the given number of minutes from now."""
return seconds_from_now(minutes * 60)
def seconds_from_now(seconds):
"""Return a timestamp at the given number of seconds from now."""
return (datetime.datetime.now() +
datetime.timedelta(0, seconds)).timestamp()
def main():
"""Parse arguments and perform all duties."""
arguments = parse_arguments()
subcommand = arguments.subcommand.replace('-', '_')
subcommand_method = globals()['subcommand_' + subcommand]
subcommand_method(arguments)
if __name__ == '__main__':
main()