Statistics
| Branch: | Tag: | Revision:

root / lib / utils / x509.py @ 2c9fa1ff

History | View | Annotate | Download (9.8 kB)

1 c50645c0 Michael Hanselmann
#
2 c50645c0 Michael Hanselmann
#
3 c50645c0 Michael Hanselmann
4 f97a7ada Iustin Pop
# Copyright (C) 2006, 2007, 2010, 2011, 2012 Google Inc.
5 c50645c0 Michael Hanselmann
#
6 c50645c0 Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 c50645c0 Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 c50645c0 Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 c50645c0 Michael Hanselmann
# (at your option) any later version.
10 c50645c0 Michael Hanselmann
#
11 c50645c0 Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 c50645c0 Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 c50645c0 Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 c50645c0 Michael Hanselmann
# General Public License for more details.
15 c50645c0 Michael Hanselmann
#
16 c50645c0 Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 c50645c0 Michael Hanselmann
# along with this program; if not, write to the Free Software
18 c50645c0 Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 c50645c0 Michael Hanselmann
# 02110-1301, USA.
20 c50645c0 Michael Hanselmann
21 c50645c0 Michael Hanselmann
"""Utility functions for X509.
22 c50645c0 Michael Hanselmann

23 c50645c0 Michael Hanselmann
"""
24 c50645c0 Michael Hanselmann
25 c50645c0 Michael Hanselmann
import time
26 c50645c0 Michael Hanselmann
import OpenSSL
27 c50645c0 Michael Hanselmann
import re
28 c50645c0 Michael Hanselmann
import datetime
29 c50645c0 Michael Hanselmann
import calendar
30 c50645c0 Michael Hanselmann
31 c50645c0 Michael Hanselmann
from ganeti import errors
32 c50645c0 Michael Hanselmann
from ganeti import constants
33 c50645c0 Michael Hanselmann
34 c50645c0 Michael Hanselmann
from ganeti.utils import text as utils_text
35 c50645c0 Michael Hanselmann
from ganeti.utils import io as utils_io
36 c50645c0 Michael Hanselmann
from ganeti.utils import hash as utils_hash
37 c50645c0 Michael Hanselmann
38 c50645c0 Michael Hanselmann
39 c50645c0 Michael Hanselmann
HEX_CHAR_RE = r"[a-zA-Z0-9]"
40 c50645c0 Michael Hanselmann
VALID_X509_SIGNATURE_SALT = re.compile("^%s+$" % HEX_CHAR_RE, re.S)
41 c50645c0 Michael Hanselmann
X509_SIGNATURE = re.compile(r"^%s:\s*(?P<salt>%s+)/(?P<sign>%s+)$" %
42 c50645c0 Michael Hanselmann
                            (re.escape(constants.X509_CERT_SIGNATURE_HEADER),
43 c50645c0 Michael Hanselmann
                             HEX_CHAR_RE, HEX_CHAR_RE),
44 c50645c0 Michael Hanselmann
                            re.S | re.I)
45 c50645c0 Michael Hanselmann
46 c50645c0 Michael Hanselmann
# Certificate verification results
47 c50645c0 Michael Hanselmann
(CERT_WARNING,
48 c50645c0 Michael Hanselmann
 CERT_ERROR) = range(1, 3)
49 c50645c0 Michael Hanselmann
50 c50645c0 Michael Hanselmann
#: ASN1 time regexp
51 c50645c0 Michael Hanselmann
_ASN1_TIME_REGEX = re.compile(r"^(\d+)([-+]\d\d)(\d\d)$")
52 c50645c0 Michael Hanselmann
53 c50645c0 Michael Hanselmann
54 c50645c0 Michael Hanselmann
def _ParseAsn1Generalizedtime(value):
55 c50645c0 Michael Hanselmann
  """Parses an ASN1 GENERALIZEDTIME timestamp as used by pyOpenSSL.
56 c50645c0 Michael Hanselmann

57 c50645c0 Michael Hanselmann
  @type value: string
58 c50645c0 Michael Hanselmann
  @param value: ASN1 GENERALIZEDTIME timestamp
59 c50645c0 Michael Hanselmann
  @return: Seconds since the Epoch (1970-01-01 00:00:00 UTC)
60 c50645c0 Michael Hanselmann

61 c50645c0 Michael Hanselmann
  """
62 c50645c0 Michael Hanselmann
  m = _ASN1_TIME_REGEX.match(value)
63 c50645c0 Michael Hanselmann
  if m:
64 c50645c0 Michael Hanselmann
    # We have an offset
65 c50645c0 Michael Hanselmann
    asn1time = m.group(1)
66 c50645c0 Michael Hanselmann
    hours = int(m.group(2))
67 c50645c0 Michael Hanselmann
    minutes = int(m.group(3))
68 c50645c0 Michael Hanselmann
    utcoffset = (60 * hours) + minutes
69 c50645c0 Michael Hanselmann
  else:
70 c50645c0 Michael Hanselmann
    if not value.endswith("Z"):
71 c50645c0 Michael Hanselmann
      raise ValueError("Missing timezone")
72 c50645c0 Michael Hanselmann
    asn1time = value[:-1]
73 c50645c0 Michael Hanselmann
    utcoffset = 0
74 c50645c0 Michael Hanselmann
75 c50645c0 Michael Hanselmann
  parsed = time.strptime(asn1time, "%Y%m%d%H%M%S")
76 c50645c0 Michael Hanselmann
77 c50645c0 Michael Hanselmann
  tt = datetime.datetime(*(parsed[:7])) - datetime.timedelta(minutes=utcoffset)
78 c50645c0 Michael Hanselmann
79 c50645c0 Michael Hanselmann
  return calendar.timegm(tt.utctimetuple())
80 c50645c0 Michael Hanselmann
81 c50645c0 Michael Hanselmann
82 c50645c0 Michael Hanselmann
def GetX509CertValidity(cert):
83 c50645c0 Michael Hanselmann
  """Returns the validity period of the certificate.
84 c50645c0 Michael Hanselmann

85 c50645c0 Michael Hanselmann
  @type cert: OpenSSL.crypto.X509
86 c50645c0 Michael Hanselmann
  @param cert: X509 certificate object
87 c50645c0 Michael Hanselmann

88 c50645c0 Michael Hanselmann
  """
89 c50645c0 Michael Hanselmann
  # The get_notBefore and get_notAfter functions are only supported in
90 c50645c0 Michael Hanselmann
  # pyOpenSSL 0.7 and above.
91 c50645c0 Michael Hanselmann
  try:
92 c50645c0 Michael Hanselmann
    get_notbefore_fn = cert.get_notBefore
93 c50645c0 Michael Hanselmann
  except AttributeError:
94 c50645c0 Michael Hanselmann
    not_before = None
95 c50645c0 Michael Hanselmann
  else:
96 c50645c0 Michael Hanselmann
    not_before_asn1 = get_notbefore_fn()
97 c50645c0 Michael Hanselmann
98 c50645c0 Michael Hanselmann
    if not_before_asn1 is None:
99 c50645c0 Michael Hanselmann
      not_before = None
100 c50645c0 Michael Hanselmann
    else:
101 c50645c0 Michael Hanselmann
      not_before = _ParseAsn1Generalizedtime(not_before_asn1)
102 c50645c0 Michael Hanselmann
103 c50645c0 Michael Hanselmann
  try:
104 c50645c0 Michael Hanselmann
    get_notafter_fn = cert.get_notAfter
105 c50645c0 Michael Hanselmann
  except AttributeError:
106 c50645c0 Michael Hanselmann
    not_after = None
107 c50645c0 Michael Hanselmann
  else:
108 c50645c0 Michael Hanselmann
    not_after_asn1 = get_notafter_fn()
109 c50645c0 Michael Hanselmann
110 c50645c0 Michael Hanselmann
    if not_after_asn1 is None:
111 c50645c0 Michael Hanselmann
      not_after = None
112 c50645c0 Michael Hanselmann
    else:
113 c50645c0 Michael Hanselmann
      not_after = _ParseAsn1Generalizedtime(not_after_asn1)
114 c50645c0 Michael Hanselmann
115 c50645c0 Michael Hanselmann
  return (not_before, not_after)
116 c50645c0 Michael Hanselmann
117 c50645c0 Michael Hanselmann
118 c50645c0 Michael Hanselmann
def _VerifyCertificateInner(expired, not_before, not_after, now,
119 c50645c0 Michael Hanselmann
                            warn_days, error_days):
120 c50645c0 Michael Hanselmann
  """Verifies certificate validity.
121 c50645c0 Michael Hanselmann

122 c50645c0 Michael Hanselmann
  @type expired: bool
123 c50645c0 Michael Hanselmann
  @param expired: Whether pyOpenSSL considers the certificate as expired
124 c50645c0 Michael Hanselmann
  @type not_before: number or None
125 c50645c0 Michael Hanselmann
  @param not_before: Unix timestamp before which certificate is not valid
126 c50645c0 Michael Hanselmann
  @type not_after: number or None
127 c50645c0 Michael Hanselmann
  @param not_after: Unix timestamp after which certificate is invalid
128 c50645c0 Michael Hanselmann
  @type now: number
129 c50645c0 Michael Hanselmann
  @param now: Current time as Unix timestamp
130 c50645c0 Michael Hanselmann
  @type warn_days: number or None
131 c50645c0 Michael Hanselmann
  @param warn_days: How many days before expiration a warning should be reported
132 c50645c0 Michael Hanselmann
  @type error_days: number or None
133 c50645c0 Michael Hanselmann
  @param error_days: How many days before expiration an error should be reported
134 c50645c0 Michael Hanselmann

135 c50645c0 Michael Hanselmann
  """
136 c50645c0 Michael Hanselmann
  if expired:
137 c50645c0 Michael Hanselmann
    msg = "Certificate is expired"
138 c50645c0 Michael Hanselmann
139 c50645c0 Michael Hanselmann
    if not_before is not None and not_after is not None:
140 c50645c0 Michael Hanselmann
      msg += (" (valid from %s to %s)" %
141 c50645c0 Michael Hanselmann
              (utils_text.FormatTime(not_before),
142 c50645c0 Michael Hanselmann
               utils_text.FormatTime(not_after)))
143 c50645c0 Michael Hanselmann
    elif not_before is not None:
144 c50645c0 Michael Hanselmann
      msg += " (valid from %s)" % utils_text.FormatTime(not_before)
145 c50645c0 Michael Hanselmann
    elif not_after is not None:
146 c50645c0 Michael Hanselmann
      msg += " (valid until %s)" % utils_text.FormatTime(not_after)
147 c50645c0 Michael Hanselmann
148 c50645c0 Michael Hanselmann
    return (CERT_ERROR, msg)
149 c50645c0 Michael Hanselmann
150 c50645c0 Michael Hanselmann
  elif not_before is not None and not_before > now:
151 c50645c0 Michael Hanselmann
    return (CERT_WARNING,
152 c50645c0 Michael Hanselmann
            "Certificate not yet valid (valid from %s)" %
153 c50645c0 Michael Hanselmann
            utils_text.FormatTime(not_before))
154 c50645c0 Michael Hanselmann
155 c50645c0 Michael Hanselmann
  elif not_after is not None:
156 c50645c0 Michael Hanselmann
    remaining_days = int((not_after - now) / (24 * 3600))
157 c50645c0 Michael Hanselmann
158 c50645c0 Michael Hanselmann
    msg = "Certificate expires in about %d days" % remaining_days
159 c50645c0 Michael Hanselmann
160 c50645c0 Michael Hanselmann
    if error_days is not None and remaining_days <= error_days:
161 c50645c0 Michael Hanselmann
      return (CERT_ERROR, msg)
162 c50645c0 Michael Hanselmann
163 c50645c0 Michael Hanselmann
    if warn_days is not None and remaining_days <= warn_days:
164 c50645c0 Michael Hanselmann
      return (CERT_WARNING, msg)
165 c50645c0 Michael Hanselmann
166 c50645c0 Michael Hanselmann
  return (None, None)
167 c50645c0 Michael Hanselmann
168 c50645c0 Michael Hanselmann
169 c50645c0 Michael Hanselmann
def VerifyX509Certificate(cert, warn_days, error_days):
170 a3d32770 Iustin Pop
  """Verifies a certificate for LUClusterVerify.
171 c50645c0 Michael Hanselmann

172 c50645c0 Michael Hanselmann
  @type cert: OpenSSL.crypto.X509
173 c50645c0 Michael Hanselmann
  @param cert: X509 certificate object
174 c50645c0 Michael Hanselmann
  @type warn_days: number or None
175 c50645c0 Michael Hanselmann
  @param warn_days: How many days before expiration a warning should be reported
176 c50645c0 Michael Hanselmann
  @type error_days: number or None
177 c50645c0 Michael Hanselmann
  @param error_days: How many days before expiration an error should be reported
178 c50645c0 Michael Hanselmann

179 c50645c0 Michael Hanselmann
  """
180 c50645c0 Michael Hanselmann
  # Depending on the pyOpenSSL version, this can just return (None, None)
181 c50645c0 Michael Hanselmann
  (not_before, not_after) = GetX509CertValidity(cert)
182 c50645c0 Michael Hanselmann
183 f97a7ada Iustin Pop
  now = time.time() + constants.NODE_MAX_CLOCK_SKEW
184 f97a7ada Iustin Pop
185 c50645c0 Michael Hanselmann
  return _VerifyCertificateInner(cert.has_expired(), not_before, not_after,
186 f97a7ada Iustin Pop
                                 now, warn_days, error_days)
187 c50645c0 Michael Hanselmann
188 c50645c0 Michael Hanselmann
189 c50645c0 Michael Hanselmann
def SignX509Certificate(cert, key, salt):
190 c50645c0 Michael Hanselmann
  """Sign a X509 certificate.
191 c50645c0 Michael Hanselmann

192 c50645c0 Michael Hanselmann
  An RFC822-like signature header is added in front of the certificate.
193 c50645c0 Michael Hanselmann

194 c50645c0 Michael Hanselmann
  @type cert: OpenSSL.crypto.X509
195 c50645c0 Michael Hanselmann
  @param cert: X509 certificate object
196 c50645c0 Michael Hanselmann
  @type key: string
197 c50645c0 Michael Hanselmann
  @param key: Key for HMAC
198 c50645c0 Michael Hanselmann
  @type salt: string
199 c50645c0 Michael Hanselmann
  @param salt: Salt for HMAC
200 c50645c0 Michael Hanselmann
  @rtype: string
201 c50645c0 Michael Hanselmann
  @return: Serialized and signed certificate in PEM format
202 c50645c0 Michael Hanselmann

203 c50645c0 Michael Hanselmann
  """
204 c50645c0 Michael Hanselmann
  if not VALID_X509_SIGNATURE_SALT.match(salt):
205 c50645c0 Michael Hanselmann
    raise errors.GenericError("Invalid salt: %r" % salt)
206 c50645c0 Michael Hanselmann
207 c50645c0 Michael Hanselmann
  # Dumping as PEM here ensures the certificate is in a sane format
208 c50645c0 Michael Hanselmann
  cert_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
209 c50645c0 Michael Hanselmann
210 c50645c0 Michael Hanselmann
  return ("%s: %s/%s\n\n%s" %
211 c50645c0 Michael Hanselmann
          (constants.X509_CERT_SIGNATURE_HEADER, salt,
212 c50645c0 Michael Hanselmann
           utils_hash.Sha1Hmac(key, cert_pem, salt=salt),
213 c50645c0 Michael Hanselmann
           cert_pem))
214 c50645c0 Michael Hanselmann
215 c50645c0 Michael Hanselmann
216 c50645c0 Michael Hanselmann
def _ExtractX509CertificateSignature(cert_pem):
217 c50645c0 Michael Hanselmann
  """Helper function to extract signature from X509 certificate.
218 c50645c0 Michael Hanselmann

219 c50645c0 Michael Hanselmann
  """
220 c50645c0 Michael Hanselmann
  # Extract signature from original PEM data
221 c50645c0 Michael Hanselmann
  for line in cert_pem.splitlines():
222 c50645c0 Michael Hanselmann
    if line.startswith("---"):
223 c50645c0 Michael Hanselmann
      break
224 c50645c0 Michael Hanselmann
225 c50645c0 Michael Hanselmann
    m = X509_SIGNATURE.match(line.strip())
226 c50645c0 Michael Hanselmann
    if m:
227 c50645c0 Michael Hanselmann
      return (m.group("salt"), m.group("sign"))
228 c50645c0 Michael Hanselmann
229 c50645c0 Michael Hanselmann
  raise errors.GenericError("X509 certificate signature is missing")
230 c50645c0 Michael Hanselmann
231 c50645c0 Michael Hanselmann
232 c50645c0 Michael Hanselmann
def LoadSignedX509Certificate(cert_pem, key):
233 c50645c0 Michael Hanselmann
  """Verifies a signed X509 certificate.
234 c50645c0 Michael Hanselmann

235 c50645c0 Michael Hanselmann
  @type cert_pem: string
236 c50645c0 Michael Hanselmann
  @param cert_pem: Certificate in PEM format and with signature header
237 c50645c0 Michael Hanselmann
  @type key: string
238 c50645c0 Michael Hanselmann
  @param key: Key for HMAC
239 c50645c0 Michael Hanselmann
  @rtype: tuple; (OpenSSL.crypto.X509, string)
240 c50645c0 Michael Hanselmann
  @return: X509 certificate object and salt
241 c50645c0 Michael Hanselmann

242 c50645c0 Michael Hanselmann
  """
243 c50645c0 Michael Hanselmann
  (salt, signature) = _ExtractX509CertificateSignature(cert_pem)
244 c50645c0 Michael Hanselmann
245 6b96df59 Michael Hanselmann
  # Load and dump certificate to ensure it's in a sane format
246 6b96df59 Michael Hanselmann
  (cert, sane_pem) = ExtractX509Certificate(cert_pem)
247 c50645c0 Michael Hanselmann
248 c50645c0 Michael Hanselmann
  if not utils_hash.VerifySha1Hmac(key, sane_pem, signature, salt=salt):
249 c50645c0 Michael Hanselmann
    raise errors.GenericError("X509 certificate signature is invalid")
250 c50645c0 Michael Hanselmann
251 c50645c0 Michael Hanselmann
  return (cert, salt)
252 c50645c0 Michael Hanselmann
253 c50645c0 Michael Hanselmann
254 c50645c0 Michael Hanselmann
def GenerateSelfSignedX509Cert(common_name, validity):
255 c50645c0 Michael Hanselmann
  """Generates a self-signed X509 certificate.
256 c50645c0 Michael Hanselmann

257 c50645c0 Michael Hanselmann
  @type common_name: string
258 c50645c0 Michael Hanselmann
  @param common_name: commonName value
259 c50645c0 Michael Hanselmann
  @type validity: int
260 c50645c0 Michael Hanselmann
  @param validity: Validity for certificate in seconds
261 b6267745 Andrea Spadaccini
  @return: a tuple of strings containing the PEM-encoded private key and
262 b6267745 Andrea Spadaccini
           certificate
263 c50645c0 Michael Hanselmann

264 c50645c0 Michael Hanselmann
  """
265 c50645c0 Michael Hanselmann
  # Create private and public key
266 c50645c0 Michael Hanselmann
  key = OpenSSL.crypto.PKey()
267 c50645c0 Michael Hanselmann
  key.generate_key(OpenSSL.crypto.TYPE_RSA, constants.RSA_KEY_BITS)
268 c50645c0 Michael Hanselmann
269 c50645c0 Michael Hanselmann
  # Create self-signed certificate
270 c50645c0 Michael Hanselmann
  cert = OpenSSL.crypto.X509()
271 c50645c0 Michael Hanselmann
  if common_name:
272 c50645c0 Michael Hanselmann
    cert.get_subject().CN = common_name
273 c50645c0 Michael Hanselmann
  cert.set_serial_number(1)
274 c50645c0 Michael Hanselmann
  cert.gmtime_adj_notBefore(0)
275 c50645c0 Michael Hanselmann
  cert.gmtime_adj_notAfter(validity)
276 c50645c0 Michael Hanselmann
  cert.set_issuer(cert.get_subject())
277 c50645c0 Michael Hanselmann
  cert.set_pubkey(key)
278 c50645c0 Michael Hanselmann
  cert.sign(key, constants.X509_CERT_SIGN_DIGEST)
279 c50645c0 Michael Hanselmann
280 c50645c0 Michael Hanselmann
  key_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)
281 c50645c0 Michael Hanselmann
  cert_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
282 c50645c0 Michael Hanselmann
283 c50645c0 Michael Hanselmann
  return (key_pem, cert_pem)
284 c50645c0 Michael Hanselmann
285 c50645c0 Michael Hanselmann
286 c50645c0 Michael Hanselmann
def GenerateSelfSignedSslCert(filename, common_name=constants.X509_CERT_CN,
287 c50645c0 Michael Hanselmann
                              validity=constants.X509_CERT_DEFAULT_VALIDITY):
288 c50645c0 Michael Hanselmann
  """Legacy function to generate self-signed X509 certificate.
289 c50645c0 Michael Hanselmann

290 c50645c0 Michael Hanselmann
  @type filename: str
291 c50645c0 Michael Hanselmann
  @param filename: path to write certificate to
292 c50645c0 Michael Hanselmann
  @type common_name: string
293 c50645c0 Michael Hanselmann
  @param common_name: commonName value
294 c50645c0 Michael Hanselmann
  @type validity: int
295 c50645c0 Michael Hanselmann
  @param validity: validity of certificate in number of days
296 b6267745 Andrea Spadaccini
  @return: a tuple of strings containing the PEM-encoded private key and
297 b6267745 Andrea Spadaccini
           certificate
298 c50645c0 Michael Hanselmann

299 c50645c0 Michael Hanselmann
  """
300 c50645c0 Michael Hanselmann
  # TODO: Investigate using the cluster name instead of X505_CERT_CN for
301 c50645c0 Michael Hanselmann
  # common_name, as cluster-renames are very seldom, and it'd be nice if RAPI
302 c50645c0 Michael Hanselmann
  # and node daemon certificates have the proper Subject/Issuer.
303 c50645c0 Michael Hanselmann
  (key_pem, cert_pem) = GenerateSelfSignedX509Cert(common_name,
304 c50645c0 Michael Hanselmann
                                                   validity * 24 * 60 * 60)
305 c50645c0 Michael Hanselmann
306 c50645c0 Michael Hanselmann
  utils_io.WriteFile(filename, mode=0400, data=key_pem + cert_pem)
307 b6267745 Andrea Spadaccini
  return (key_pem, cert_pem)
308 6b96df59 Michael Hanselmann
309 6b96df59 Michael Hanselmann
310 6b96df59 Michael Hanselmann
def ExtractX509Certificate(pem):
311 6b96df59 Michael Hanselmann
  """Extracts the certificate from a PEM-formatted string.
312 6b96df59 Michael Hanselmann

313 6b96df59 Michael Hanselmann
  @type pem: string
314 6b96df59 Michael Hanselmann
  @rtype: tuple; (OpenSSL.X509 object, string)
315 6b96df59 Michael Hanselmann
  @return: Certificate object and PEM-formatted certificate
316 6b96df59 Michael Hanselmann

317 6b96df59 Michael Hanselmann
  """
318 6b96df59 Michael Hanselmann
  cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, pem)
319 6b96df59 Michael Hanselmann
320 6b96df59 Michael Hanselmann
  return (cert,
321 6b96df59 Michael Hanselmann
          OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert))