pvl/login/ssl.py
changeset 438 d45fc43c6073
parent 437 5100b359906c
child 439 6a8ea0d363c1
equal deleted inserted replaced
437:5100b359906c 438:d45fc43c6073
     1 # encoding: utf-8
       
     2 
       
     3 import base64
       
     4 import datetime
       
     5 import hashlib
       
     6 import os
       
     7 import os.path
       
     8 import string
       
     9 
       
    10 import pvl.invoke
       
    11 
       
    12 import logging; log = logging.getLogger('pvl.login.ssl')
       
    13 
       
    14 class Error (Exception) :
       
    15     pass
       
    16 
       
    17 class UsersCA (object) :
       
    18     OPENSSL = '/usr/bin/openssl'
       
    19 
       
    20     SIGN_DAYS = 1
       
    21 
       
    22     VALID_USER = set(string.letters + string.digits + '-.')
       
    23     
       
    24     O = u"Päivölän Kansanopisto"
       
    25     OU = u"People"
       
    26     DC = ('paivola', 'fi')
       
    27 
       
    28     def __init__ (self, ca, users) :
       
    29         self.ca = ca
       
    30         self.users = users
       
    31 
       
    32         self.ca_config = os.path.join(ca, 'openssl.cnf')
       
    33 
       
    34     def sign_spkac (self, out, spkac, days=SIGN_DAYS) :
       
    35         """
       
    36             Sign given request file (path).
       
    37 
       
    38             Creates the given output file (path). Empty file on errors..
       
    39         """
       
    40 
       
    41         pvl.invoke.invoke(self.OPENSSL, ('ca',
       
    42                 '-config', self.ca_config,
       
    43                 '-spkac', spkac,
       
    44                 '-out', out,
       
    45                 '-policy', 'policy_user',
       
    46                 '-days', str(days),
       
    47                 '-utf8',
       
    48             ),
       
    49             setenv={
       
    50                 'CA':   self.ca,
       
    51             },
       
    52         )
       
    53 
       
    54     def generate_dn (self, uid, cn=None) :
       
    55         """
       
    56             Generate OpenSSL (rdn, value) pairs for given user.
       
    57         """
       
    58 
       
    59         if self.O :
       
    60             yield 'O', self.O
       
    61 
       
    62         elif self.DC :
       
    63             for index, dc in enumerate(self.DC, 1) :
       
    64                 yield '{index}.DC'.format(index=index), dc
       
    65         
       
    66         yield 'OU', self.OU
       
    67          
       
    68         yield 'UID', uid 
       
    69         
       
    70         if cn :
       
    71             yield 'CN', cn
       
    72 
       
    73     def write_spkac (self, path, spkac, dn) :
       
    74         """
       
    75             Write out a spkac file to the given path, containing the given base64-encoded spkac and DN.
       
    76         """
       
    77 
       
    78         # roundtrip the spkac for consistent formatting
       
    79         spkac = base64.b64encode(base64.b64decode(spkac))
       
    80 
       
    81         file = open(path, 'w')
       
    82 
       
    83         file.write('SPKAC=')
       
    84         file.write(spkac)
       
    85         file.write('\n')
       
    86 
       
    87         for rdn, value in dn :
       
    88             file.write(u'{rdn}={value}\n'.format(rdn=rdn, value=value).encode('utf-8'))
       
    89         
       
    90         file.close()
       
    91 
       
    92     def sign_user (self, user, spkac, userinfo=None) :
       
    93         """
       
    94             Sign given spkac string (base64-encoded) for given user.
       
    95 
       
    96             Returns a name for the signed cert.
       
    97         """
       
    98 
       
    99         if not set(user).issubset(self.VALID_USER) :
       
   100             raise Error("Invalid username: {user}".format(user=user))
       
   101 
       
   102         dir = os.path.join(self.users, user)
       
   103 
       
   104         if not os.path.exists(dir) :
       
   105             os.mkdir(dir)
       
   106 
       
   107         name = hashlib.sha1(user + spkac).hexdigest()
       
   108         spkac_file = os.path.join(dir, name) + '.spkac'
       
   109         cert_file = os.path.join(dir, name)
       
   110         tmp_file = os.path.join(dir, name) + '.tmp'
       
   111         
       
   112         # the req to sign
       
   113         if os.path.exists(spkac_file) :
       
   114             log.warning("spkac already exists: %s", spkac_file)
       
   115         else :
       
   116             log.info("%s: write spkac: %s", user, spkac_file)
       
   117             self.write_spkac(os.path.join(dir, name) + '.spkac', spkac, self.generate_dn(user, userinfo))
       
   118         
       
   119         # sign it
       
   120         if os.path.exists(cert_file) :
       
   121             log.warning("cert already exists: %s", cert_file)
       
   122             return name
       
   123         
       
   124         if os.path.exists(tmp_file) :
       
   125             log.warning("cleaning out previous tmp file: %s", tmp_file)
       
   126             os.unlink(tmp_file)
       
   127 
       
   128         log.info("%s: sign cert: %s", user, cert_file)
       
   129         self.sign_spkac(tmp_file, spkac_file)
       
   130 
       
   131         log.debug("%s: rename %s -> %s", user, tmp_file, cert_file)
       
   132         os.rename(tmp_file, cert_file)
       
   133 
       
   134         return name
       
   135 
       
   136     def open_cert (self, user, name) :
       
   137         """
       
   138             Return an opened cert file by username / cert name.
       
   139         """
       
   140 
       
   141         if not set(user).issubset(self.VALID_USER) :
       
   142             raise Error("Invalid username: {user}".format(user=user))
       
   143 
       
   144         path = os.path.join(self.users, user, name)
       
   145 
       
   146         if not os.path.exists(path) :
       
   147             raise Error("No cert found on server")
       
   148 
       
   149         return open(path)