4fb5a4ddec0610f26005e721b874632a4cca18c3
[sfa.git] / util / sec / certgen2.py
1 #!/usr/bin/env python
2
3 """
4 Certificate generation module.
5 """
6
7 from OpenSSL     import crypto
8 from os          import chmod, remove
9 from os.path     import join
10
11 from myap.tools  import execute_cmd
12 from myap.config import config
13
14 TYPE_RSA = crypto.TYPE_RSA
15 TYPE_DSA = crypto.TYPE_DSA
16
17 YEAR = 60*60*24*365
18
19 X509Attr    = ( 'C', 'ST', 'L', 'O', 'OU', 'CN', 'emailAddress' )
20 X509WinAttr = { 'C'            : 'C',
21                 'ST'           : 'S',
22                 'L'            : 'L',
23                 'O'            : 'O',
24                 'OU'           : 'OU',
25                 'CN'           : 'CN',
26                 'emailAddress' : 'E' }
27
28 def createKeyPair(type, bits):
29     """
30     Create a public/private key pair.
31
32     Arguments: type - Key type, must be one of TYPE_RSA and TYPE_DSA
33                bits - Number of bits to use in the key
34
35     Returns:   The public/private key pair in a PKey object
36     """
37
38     pkey = crypto.PKey()
39     pkey.generate_key(type, bits)
40
41     return pkey
42
43 def createCertRequest(pkey, subject, digest='md5'):
44     """
45     Create a certificate request.
46
47     Arguments: pkey    - The key to associate with the request
48                subject - A dictionary with the subject of the request, possible
49                          key,value pairs are:
50                            C            - Country name
51                            ST           - State or province name
52                            L            - Locality name
53                            O            - Organization name
54                            OU           - Organizational unit name
55                            CN           - Common name
56                            emailAddress - E-mail address
57                digest  - Digestion method to use for signing, default is md5
58
59     Returns:   The certificate request in an X509Req object
60     """
61
62     req  = crypto.X509Req()
63     subj = req.get_subject()
64
65     # Storing attributes in the correct order
66     for attr in X509Attr:
67         if subject.has_key(attr):
68             setattr(subj, attr, subject[attr])
69
70     req.set_pubkey(pkey)
71     req.sign(pkey, digest)
72
73     return req
74
75 def createCertificate(req, (issuerKey, issuerCert), serial, (notBefore, notAfter), extensions=[], digest='md5'):
76     """
77     Generate a certificate given a certificate request.
78
79     Arguments: req        - Certificate reqeust to use
80                issuerCert - The certificate of the issuer
81                issuerKey  - The private key of the issuer
82                serial     - Serial number for the certificate
83                notBefore  - Timestamp (relative to now) when the certificate
84                             starts being valid
85                notAfter   - Timestamp (relative to now) when the certificate
86                             stops being valid
87                digest     - Digest method to use for signing, default is md5
88                isca       - The certificate is a CA
89
90     Returns:   The signed certificate in an X509 object
91     """
92
93     cert = crypto.X509()
94     cert.set_version(2)
95
96     if extensions:
97         X509Extensions = []
98         for name, critical, value in extensions:
99             X509Extensions.append(crypto.X509Extension(name, critical, value))
100
101         cert.add_extensions(X509Extensions)
102
103     cert.set_serial_number(serial)
104     cert.gmtime_adj_notBefore(notBefore)
105     cert.gmtime_adj_notAfter(notAfter)
106     cert.set_issuer(issuerCert.get_subject())
107     cert.set_subject(req.get_subject())
108     cert.set_pubkey(req.get_pubkey())
109     cert.sign(issuerKey, digest)
110
111     return cert
112
113 def createSignedCertificate(subject, serial, extensions=[], type=TYPE_RSA, bits=2048, cipher='DES-EDE3-CBC', passphrase='', years=5, capkey='', cacert='', capassphrase=''):
114     """
115     Generate a Signed Certificate.
116
117     Arguments: subject      - The subject of the request, see createCertRequest()
118                type         - (optional) Key type, see createKeyPair()
119                bits         - (optional) Number of bits to use in the key
120                cipher       - (optional) if encrypted PEM format, the cipher to use, see dump_privatekey()
121                passphrase   - (optional) if encrypted PEM format, the passphrase to use, see dump_privatekey()
122                years        - (optional) Number of years to use for validity, see X509()
123                capkey       - (optional) CA's private key (PEM formated string)
124                cacert       - (optional) CA's certificate (PEM formated string)
125                capassphrase - (optional) if CA private key is in encrypted PEM format,
126                               the passphrase to use, see load_privatekey()
127                
128     Returns:   Two PEM formated strings containing private key and signed certificate
129     """
130
131     pkey = createKeyPair(type, bits)
132     req  = createCertRequest(pkey, subject)
133
134     if capkey and cacert:
135         # Certificate will be signed by an Autority
136         capkey = crypto.load_privatekey(crypto.FILETYPE_PEM,  capkey, capassphrase)
137         cacert = crypto.load_certificate(crypto.FILETYPE_PEM, cacert)
138     else:
139         # Self signed certificate
140         capkey = pkey
141         cacert = req
142
143     cert = createCertificate(req, (capkey, cacert), serial, (0, years*YEAR), extensions=extensions)
144
145     if passphrase:
146         pkey = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey, cipher, passphrase)
147     else:
148         pkey = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)
149
150     cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
151
152     return (pkey, cert)
153
154 def getSubject(cert, for_win=False):
155
156     cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
157     subj = cert.get_subject()
158
159     subject = ''
160     for attr in X509Attr:
161         value = getattr(subj, attr)
162         if value:
163             if for_win:
164                 # Converting Attr to Windows Attr
165                 attr = X509WinAttr[attr]
166             subject += '%s=%s, ' % (attr, value)
167
168     subject = subject[:-2]
169
170     return subject
171
172 def convertPemToDer(pkey, cert, cipher='DES-EDE3-CBC', passphrase=''):
173     """
174     Convert two PEM formated strings (pkey, cert) onto two DER formated strings.
175
176     Arguments: pkey       - private key (PEM formated string)
177                cert       - certificate (PEM formated string)
178                cipher     - (optional) if encrypted PEM format, the cipher to use, see dump_privatekey()
179                passphrase - (optional) if encrypted PEM format, the passphrase to use, see dump_privatekey()
180
181     Returns:   Two DER formated strings containing private key and signed certificate
182     """
183
184     pkey = crypto.load_privatekey(crypto.FILETYPE_PEM,  pkey, passphrase)
185     cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
186     
187     if passphrase:
188         pkey = crypto.dump_privatekey(crypto.FILETYPE_ASN1, pkey, cipher, passphrase)
189     else:
190         pkey = crypto.dump_privatekey(crypto.FILETYPE_ASN1, pkey)
191
192     cert = crypto.dump_certificate(crypto.FILETYPE_ASN1, cert)
193
194     return (pkey, cert)
195
196 def convertDerToPem(pkey, cert, cipher='DES-EDE3-CBC', passphrase=''):
197     """
198     Convert two DER formated strings (pkey, cert) onto two PEM formated strings.
199
200     Arguments: pkey       - private key (DER formated string)
201                cert       - certificate (DER formated string)
202                cipher     - (optional) if encrypted DER format, the cipher to use, see dump_privatekey()
203                passphrase - (optional) if encrypted DER format, the passphrase to use, see dump_privatekey()
204
205     Returns:   Two PEM formated strings containing private key and signed certificate
206     """
207
208     pkey = crypto.load_privatekey(crypto.FILETYPE_ASN1,  pkey, passphrase)
209     cert = crypto.load_certificate(crypto.FILETYPE_ASN1, cert)
210     
211     if passphrase:
212         pkey = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey, cipher, passphrase)
213     else:
214         pkey = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)
215
216     cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
217
218     return (pkey, cert)
219
220 def dump_pkcs12(pkey, cert, cacert='', passphrase=''):
221     """
222
223     """
224
225     # Storing all the params to a temp file
226     temp_file = '/tmp/pkcs12'
227
228     pkcs12 = pkey + cert + cacert
229
230     f = open(temp_file, 'w')
231     f.write(pkcs12)
232     f.close()
233     chmod(temp_file, 0600)
234
235     # Create pkcs12 file with openssl
236     cmd = 'openssl pkcs12 -export -in %s -passout pass:"%s"' % (temp_file, passphrase)
237
238     exitcode, pkcs12 = execute_cmd(cmd)
239     remove(temp_file)
240     
241     if exitcode:
242         return pkcs12
243
244     return None
245
246 def newCertificate(db, ip):
247
248     serial = db.get_last_serial_cert()
249     ca     = db.get_certificate(serial=0)
250
251     # Creating the certificate for the ip
252     serial       += 1
253     subject       = config['SSL_DEFAULT']
254     subject['CN'] = ip
255     extensions    = (('nsCertType',       False, 'client'),
256                      ('keyUsage',         False, 'dataEncipherment'),
257                      ('extendedKeyUsage', False, 'clientAuth,1.3.6.1.4.1.311.10.3.4.1'))
258     pkey, cert = createSignedCertificate(subject, serial, extensions=extensions, bits=1024, capkey=ca['private_key'], cacert=ca['certificate'], capassphrase=config['SSL_CA_PASS'])
259
260     # Adding it to the database
261     db_cert = {}
262     db_cert['serial']      = serial
263     db_cert['private_key'] = pkey
264     db_cert['certificate'] = cert
265
266     return db.set_certificate(db_cert)
267
268 def getPKCS12(db, reservation_id):
269
270     ca          = db.get_certificate(serial=0)
271     reservation = db.get_reservation(reservation_id=reservation_id)
272     certificate = db.get_certificate(certificate_id=reservation['certificate_id'])
273
274     pkcs12 = dump_pkcs12(certificate['private_key'], certificate['certificate'], ca['certificate'])
275
276     return pkcs12
277     
278 def revokeCertificate(db, certificate_id):
279
280     return True
281
282 def init(db):
283
284     db.delete_certificates()
285
286     # Creating the CA
287     serial     = 0
288     subject    = config['SSL_DEFAULT']
289     extensions = (('basicConstraints', True,  'CA:true'),
290                   ('nsCertType',       False, 'objCA,sslCA,objsign,client,server'),
291                   ('keyUsage',         False, 'keyCertSign,cRLSign,nonRepudiation,keyEncipherment,dataEncipherment'),
292                   ('extendedKeyUsage', False, 'clientAuth,serverAuth,1.3.6.1.4.1.311.10.3.4.1')) # 1.3.6.1.4.1.311.10.3.4.1 is for VPN
293
294     capkey, cacert = createSignedCertificate(subject, serial, extensions=extensions, passphrase=config['SSL_CA_PASS'])
295
296     # Adding it to the database
297     db_cert = {}
298     db_cert['serial']      = serial
299     db_cert['private_key'] = capkey
300     db_cert['certificate'] = cacert
301     if not db.set_certificate(db_cert):
302         return False
303
304     # Creating the certificate for the WebServer
305     serial       += 1
306     subject['CN'] = config['SERVER_NAME']
307     extensions    = (('nsCertType',       False, 'server'),
308                      ('keyUsage',         False, 'keyEncipherment'),
309                      ('extendedKeyUsage', False, 'serverAuth'))
310
311     pkey, cert = createSignedCertificate(subject, serial, extensions=extensions, bits=1024, capkey=capkey, cacert=cacert, capassphrase=config['SSL_CA_PASS'])
312
313     # Adding it to the database
314     db_cert = {}
315     db_cert['serial']      = serial
316     db_cert['private_key'] = pkey
317     db_cert['certificate'] = cert
318     if not db.set_certificate(db_cert):
319         return False
320
321     try:
322         f = open(config['MYAP_HTTPD_PKEY'], 'w')
323         f.write(pkey)
324         f.close()
325         chmod(config['MYAP_HTTPD_PKEY'], 0600)
326     
327         f = open(config['MYAP_HTTPD_CERT'], 'w')
328         f.write(cert)
329         f.close()
330
331     except:
332         return False
333
334     # Creating the certificate for Racoon
335     serial       += 1
336     subject['CN'] = 'Racoon Server'
337     extensions    = (('nsCertType',       False, 'client,server'),
338                      ('keyUsage',         False, 'keyEncipherment'),
339                      ('extendedKeyUsage', False, 'clientAuth,serverAuth,1.3.6.1.4.1.311.10.3.4.1'))
340
341     pkey, cert = createSignedCertificate(subject, serial, extensions=extensions, bits=1024, capkey=capkey, cacert=cacert, capassphrase=config['SSL_CA_PASS'])
342
343     # Adding it to the database
344     db_cert = {}
345     db_cert['serial']      = serial
346     db_cert['private_key'] = pkey
347     db_cert['certificate'] = cert
348     if not db.set_certificate(db_cert):
349         return False
350
351     try:
352         pkey_file = join(config['SSL_TOP_DIR'], config['RACOON_PKEY'])
353         cert_file = join(config['SSL_TOP_DIR'], config['RACOON_CERT'])
354
355         f = open(pkey_file, 'w')
356         f.write(pkey)
357         f.close()
358         chmod(pkey_file, 0600)
359     
360         f = open(cert_file, 'w')
361         f.write(cert)
362         f.close()
363
364     except:
365         return False
366
367     return True
368