Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (9.2 kB)

1 c50645c0 Michael Hanselmann
#
2 c50645c0 Michael Hanselmann
#
3 c50645c0 Michael Hanselmann
4 c50645c0 Michael Hanselmann
# Copyright (C) 2006, 2007, 2010, 2011 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 c50645c0 Michael Hanselmann
  return _VerifyCertificateInner(cert.has_expired(), not_before, not_after,
184 c50645c0 Michael Hanselmann
                                 time.time(), warn_days, error_days)
185 c50645c0 Michael Hanselmann
186 c50645c0 Michael Hanselmann
187 c50645c0 Michael Hanselmann
def SignX509Certificate(cert, key, salt):
188 c50645c0 Michael Hanselmann
  """Sign a X509 certificate.
189 c50645c0 Michael Hanselmann

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

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

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

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

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

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

258 c50645c0 Michael Hanselmann
  @type common_name: string
259 c50645c0 Michael Hanselmann
  @param common_name: commonName value
260 c50645c0 Michael Hanselmann
  @type validity: int
261 c50645c0 Michael Hanselmann
  @param validity: Validity for certificate in seconds
262 c50645c0 Michael Hanselmann

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

289 c50645c0 Michael Hanselmann
  @type filename: str
290 c50645c0 Michael Hanselmann
  @param filename: path to write certificate to
291 c50645c0 Michael Hanselmann
  @type common_name: string
292 c50645c0 Michael Hanselmann
  @param common_name: commonName value
293 c50645c0 Michael Hanselmann
  @type validity: int
294 c50645c0 Michael Hanselmann
  @param validity: validity of certificate in number of days
295 c50645c0 Michael Hanselmann

296 c50645c0 Michael Hanselmann
  """
297 c50645c0 Michael Hanselmann
  # TODO: Investigate using the cluster name instead of X505_CERT_CN for
298 c50645c0 Michael Hanselmann
  # common_name, as cluster-renames are very seldom, and it'd be nice if RAPI
299 c50645c0 Michael Hanselmann
  # and node daemon certificates have the proper Subject/Issuer.
300 c50645c0 Michael Hanselmann
  (key_pem, cert_pem) = GenerateSelfSignedX509Cert(common_name,
301 c50645c0 Michael Hanselmann
                                                   validity * 24 * 60 * 60)
302 c50645c0 Michael Hanselmann
303 c50645c0 Michael Hanselmann
  utils_io.WriteFile(filename, mode=0400, data=key_pem + cert_pem)