#!/usr/bin/env python3
# ==========================================================================
#         ____            _                     _____           _
#        / ___| _   _ ___| |_ ___ _ __ ___     |_   _|__   ___ | |___
#        \___ \| | | / __| __/ _ \ '_ ` _ \ _____| |/ _ \ / _ \| / __|
#         ___) | |_| \__ \ ||  __/ | | | | |_____| | (_) | (_) | \__ \
#        |____/ \__, |___/\__\___|_| |_| |_|     |_|\___/ \___/|_|___/
#               |___/
#                             --- System-Tools ---
#                  https://www.nntb.no/~dreibh/system-tools/
# ==========================================================================
#
# X.509 CA and Certificate Test Setup Script
# Copyright (C) 2015-2025 by Thomas Dreibholz
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Contact: thomas.dreibholz@gmail.com

import getopt
import sys

from CertificateHelper import *


# ###### Print usage and exit ###############################################
def usage(exitCode : int = 0) -> None:
   sys.stderr.write('Usage: ' + sys.argv[0] + ' ssl_directory [-s|--server|-c|--client|-u|--user] [-x|--revoke-if-existing|-i|--ignore-if-existing] [-r|--revoke] [-S|--san|--subjectAltName subjectAltName|LOCAL|LOOKUP] [-K|--keylen-ca length] [-k|--keylen-cert length] name ...\n')
   sys.exit(exitCode)


# ###### Main program #######################################################

caKeyLength   : int = DefaultCAKeyLength
certKeyLength : int = DefaultCertKeyLength

# ====== Handle command-line parameters =====================================
certificatesToCreate : list[dict[str,str]] = []
subjectAltName       : str | None          = None
certType             : CertificateType     = CertificateType.Server
revokeIfExisting     : bool                = True
revoke               : bool                = False

# ====== Handle arguments ===================================================
try:
   options, args = getopt.gnu_getopt(
      sys.argv[1:],
      'scuxirS:K:k:',
      [ 'server',
        'client',
        'user',
        'revoke-if-existing',
        'ignore-if-existing',
        'revoke',
        'san=',
        'keylen-ca=',
        'keylen-cert='
      ])
   for option, optarg in options:
      if option in ( '-s', '--server' ):
         certType = CertificateType.Server
      elif option in ( '-c', '--client' ):
         certType = CertificateType.Client
      elif option in ( '-u', '--user' ):
         certType = CertificateType.User
      elif option in ( '-r', '--revoke' ):
         revoke = True
      elif option in ( '-x', '--revoke-if-existing' ):
         revokeIfExisting = True
      elif option in ( '-i', '--ignore-if-existing' ):
         revokeIfExisting = False
      elif option in ( '-S', '--san', '--subjectAltName' ):
         subjectAltName = optarg
      elif option in ( '-K', '--keylen-ca' ):
         caKeyLength = int(optarg)
      elif option in ( '-k', '--keylen-cert' ):
         certKeyLength = int(optarg)
except getopt.GetoptError as error:
   sys.stderr.write('ERROR: ' + str(error) + '\n')
   usage(1)
if len(args) < 2:
   usage(1)

mainDirectory : str = args[0]
for i in range(1, len(args)):
   ( name,  san ) = prepareSubjectAltName(certType, args[i], subjectAltName)
   certificatesToCreate.append({ 'name'             : name,
                                 'type'             : certType,
                                 'subjectAltName'   : san,
                                 'revokeIfExisting' : revokeIfExisting })
# print(certificatesToCreate)


# ====== Create CA hierarchy, if not existing ===============================

# ===========================================================================
# Hierarchy to be created:
# TestLevel1
#    - TestLevel2
#       - TestIntermediate1
#          - TestIntermediate2
#             - TestLeaf
#                - Servers ...
#                - Clients ...
#                - Users ...
# ===========================================================================


# ====== Create Test CAs ====================================================
globalCRLFileName = "TestGlobal.crl"

# Create Test Root Level-1 CA certificate (self-signed):
TestLevel1 = CA(mainDirectory, 'TestLevel1',
                parentCA  = None,
                subject   = '/C=NO/ST=Oslo/L=Oslo/O=Simula Research Laboratory/streetAddress=Kristian Augusts gate 23/postalCode=0164/CN=Test Level-1 CA Certificate',
                certType  = CertificateType.RootCA,
                keyLength = caKeyLength, globalCRLFileName = globalCRLFileName)

# Create Test Root Level-2 CA certificate (signed by Test Root Level-1):
TestLevel2 = CA(mainDirectory, 'TestLevel2',
                parentCA  = TestLevel1,
                subject   = '/C=NO/ST=Oslo/L=Oslo/O=Simula Research Laboratory/streetAddress=Kristian Augusts gate 23/postalCode=0164/CN=Test Level-2 CA Certificate',
                certType  = CertificateType.IntermediateCA,
                keyLength = caKeyLength, globalCRLFileName = globalCRLFileName)

# Create Test Intermediate CA certificate (signed by Test Root Level-2):
TestIntermediate1 = CA(mainDirectory, 'TestIntermediate1',
                       parentCA  = TestLevel2,
                       subject   = '/C=NO/ST=Oslo/L=Oslo/O=Simula Metropolitan Centre for Digital Engineering (SimulaMet)/streetAddress=Stensberggata 27/postalCode=0170/CN=*',
                       certType  = CertificateType.IntermediateCA,
                       keyLength = caKeyLength, globalCRLFileName = globalCRLFileName)

# Create Test Intermediate CA certificate (signed by Test Root Level-2):
TestIntermediate2 = CA(mainDirectory, 'TestIntermediate2',
                       parentCA  = TestIntermediate1,
                       subject   = '/C=NO/ST=Oslo/L=Oslo/O=Simula Metropolitan Centre for Digital Engineering (SimulaMet)/OU=Centre for Resilient Networks and Applications (CRNA)/streetAddress=Stensberggata 27/postalCode=0170/CN=*',
                       certType  = CertificateType.IntermediateCA,
                       keyLength = caKeyLength, globalCRLFileName = globalCRLFileName)

# Create Test Leaf CA certificate (signed by Test Intermediate):
TestLeaf = CA(mainDirectory, 'TestLeaf',
              parentCA  = TestIntermediate2,
              subject   = '/C=NO/ST=Oslo/L=Oslo/O=Simula Metropolitan Centre for Digital Engineering (SimulaMet)/OU=SimulaMet Interoperability Lab (SMIL)/streetAddress=Stensberggata 27/postalCode=0170/CN=*/',
              certType  = CertificateType.LeafCA,
              keyLength = caKeyLength, globalCRLFileName = globalCRLFileName)


# ====== Create Test Servers ================================================
defaultSubjectWithoutCN = '/C=NO/ST=Oslo/L=Oslo/O=Simula Metropolitan Centre for Digital Engineering (SimulaMet)/OU=SimulaMet Interoperability Lab (SMIL)/streetAddress=Stensberggata 27/postalCode=0170'

certificates = { }
for certificateToCreate in certificatesToCreate:
   print(certificateToCreate)

   name = certificateToCreate['name']
   if certificateToCreate['type'] != CertificateType.User:
      subjectWithoutCN = defaultSubjectWithoutCN
   else:
      subjectWithoutCN = defaultSubjectWithoutCN + prepareUserSubject(name)
   certificates[name] = Certificate(mainDirectory, name, TestLeaf,
                                    subjectWithoutCN, certificateToCreate['subjectAltName'],
                                    certificateToCreate['type'],
                                    keyLength        = certKeyLength,
                                    revokeIfExisting = revokeIfExisting)

   if revoke:
      certificates[name].revoke()


# ====== Finally, force regeneration of the CRL to ensure it is up-to-date ==
TestLevel1.generateCRL()
