Statistics
| Branch: | Tag: | Revision:

root / lib / utils / x509.py @ 0c28bee1

History | View | Annotate | Download (9.5 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 c50645c0 Michael Hanselmann
  # Load certificate
246 c50645c0 Michael Hanselmann
  cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_pem)
247 c50645c0 Michael Hanselmann
248 c50645c0 Michael Hanselmann
  # Dump again to ensure it's in a sane format
249 c50645c0 Michael Hanselmann
  sane_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
250 c50645c0 Michael Hanselmann
251 c50645c0 Michael Hanselmann
  if not utils_hash.VerifySha1Hmac(key, sane_pem, signature, salt=salt):
252 c50645c0 Michael Hanselmann
    raise errors.GenericError("X509 certificate signature is invalid")
253 c50645c0 Michael Hanselmann
254 c50645c0 Michael Hanselmann
  return (cert, salt)
255 c50645c0 Michael Hanselmann
256 c50645c0 Michael Hanselmann
257 c50645c0 Michael Hanselmann
def GenerateSelfSignedX509Cert(common_name, validity):
258 c50645c0 Michael Hanselmann
  """Generates a self-signed X509 certificate.
259 c50645c0 Michael Hanselmann

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

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

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

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