4fe563bc2d49e55f880d23737403d00846d5b315
[leap_pycommon.git] / src / leap / common / certs.py
1 # -*- coding: utf-8 -*-
2 # certs.py
3 # Copyright (C) 2013 LEAP
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 """
19 Implements cert checks and helpers
20 """
21
22 import os
23 import time
24 import logging
25
26 from OpenSSL import crypto
27 from dateutil.parser import parse as dateparse
28
29 from leap.common.check import leap_assert
30
31 logger = logging.getLogger(__name__)
32
33
34 def get_cert_from_string(string):
35     """
36     Returns the x509 from the contents of this string
37
38     :param string: certificate contents as downloaded
39     :type string: str
40
41     :return: x509 or None
42     """
43     leap_assert(string, "We need something to load")
44
45     x509 = None
46     try:
47         x509 = crypto.load_certificate(crypto.FILETYPE_PEM, string)
48     except Exception as e:
49         logger.error("Something went wrong while loading the certificate: %r"
50                      % (e,))
51     return x509
52
53
54 def get_privatekey_from_string(string):
55     """
56     Returns the private key from the contents of this string
57
58     :param string: private key contents as downloaded
59     :type string: str
60
61     :return: private key or None
62     """
63     leap_assert(string, "We need something to load")
64
65     pkey = None
66     try:
67         pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, string)
68     except Exception as e:
69         logger.error("Something went wrong while loading the certificate: %r"
70                      % (e,))
71     return pkey
72
73
74 def get_digest(cert_data, method):
75     """
76     Returns the digest for the cert_data using the method specified
77
78     :param cert_data: certificate data in string form
79     :type cert_data: str
80     :param method: method to be used for digest
81     :type method: str
82
83     :rtype: str
84     """
85     x509 = get_cert_from_string(cert_data)
86     digest = x509.digest(method).replace(":", "").lower()
87
88     return digest
89
90
91 def can_load_cert_and_pkey(string):
92     """
93     Loads certificate and private key from a buffer, returns True if
94     everything went well, False otherwise
95
96     :param string: buffer containing the cert and private key
97     :type string: str or any kind of buffer
98
99     :rtype: bool
100     """
101     can_load = True
102
103     try:
104         cert = get_cert_from_string(string)
105         key = get_privatekey_from_string(string)
106
107         leap_assert(cert, 'The certificate could not be loaded')
108         leap_assert(key, 'The private key could not be loaded')
109     except Exception as e:
110         can_load = False
111         logger.error("Something went wrong while trying to load "
112                      "the certificate: %r" % (e,))
113
114     return can_load
115
116
117 def is_valid_pemfile(cert):
118     """
119     Checks that the passed string is a valid pem certificate
120
121     :param cert: String containing pem content
122     :type cert: str
123
124     :rtype: bool
125     """
126     leap_assert(cert, "We need a cert to load")
127
128     return can_load_cert_and_pkey(cert)
129
130
131 def get_cert_time_boundaries(certfile):
132     """
133     Returns the time boundaries for the certificate saved in certfile
134
135     :param certfile: path to certificate
136     :type certfile: str
137
138     :rtype: tuple (from, to)
139     """
140     cert = get_cert_from_string(certfile)
141     leap_assert(cert, 'There was a problem loading the certificate')
142
143     fromts, tots = (cert.get_notBefore(), cert.get_notAfter())
144     from_, to_ = map(
145         lambda ts: time.gmtime(time.mktime(dateparse(ts).timetuple())),
146         (fromts, tots))
147     return from_, to_
148
149
150 def should_redownload(certfile, now=time.gmtime):
151     """
152     Returns True if any of the checks don't pass, False otherwise
153
154     :param certfile: path to certificate
155     :type certfile: str
156     :param now: current date function, ONLY USED FOR TESTING
157
158     :rtype: bool
159     """
160     exists = os.path.isfile(certfile)
161
162     if not exists:
163         return True
164
165     certdata = None
166     try:
167         with open(certfile, "r") as f:
168             certdata = f.read()
169             if not is_valid_pemfile(certdata):
170                 return True
171     except:
172         return True
173
174     valid_from, valid_to = get_cert_time_boundaries(certdata)
175
176     if not (valid_from < now() < valid_to):
177         return True
178
179     return False