(2.9) Make NiceSort treat integers well
[ganeti-local] / lib / utils / x509.py
index 71ba25d..8cc7af7 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+# Copyright (C) 2006, 2007, 2010, 2011, 2012 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -27,9 +27,12 @@ import OpenSSL
 import re
 import datetime
 import calendar
+import errno
+import logging
 
 from ganeti import errors
 from ganeti import constants
+from ganeti import pathutils
 
 from ganeti.utils import text as utils_text
 from ganeti.utils import io as utils_io
@@ -180,8 +183,10 @@ def VerifyX509Certificate(cert, warn_days, error_days):
   # Depending on the pyOpenSSL version, this can just return (None, None)
   (not_before, not_after) = GetX509CertValidity(cert)
 
+  now = time.time() + constants.NODE_MAX_CLOCK_SKEW
+
   return _VerifyCertificateInner(cert.has_expired(), not_before, not_after,
-                                 time.time(), warn_days, error_days)
+                                 now, warn_days, error_days)
 
 
 def SignX509Certificate(cert, key, salt):
@@ -240,11 +245,8 @@ def LoadSignedX509Certificate(cert_pem, key):
   """
   (salt, signature) = _ExtractX509CertificateSignature(cert_pem)
 
-  # Load certificate
-  cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_pem)
-
-  # Dump again to ensure it's in a sane format
-  sane_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
+  # Load and dump certificate to ensure it's in a sane format
+  (cert, sane_pem) = ExtractX509Certificate(cert_pem)
 
   if not utils_hash.VerifySha1Hmac(key, sane_pem, signature, salt=salt):
     raise errors.GenericError("X509 certificate signature is invalid")
@@ -259,6 +261,8 @@ def GenerateSelfSignedX509Cert(common_name, validity):
   @param common_name: commonName value
   @type validity: int
   @param validity: Validity for certificate in seconds
+  @return: a tuple of strings containing the PEM-encoded private key and
+           certificate
 
   """
   # Create private and public key
@@ -292,6 +296,8 @@ def GenerateSelfSignedSslCert(filename, common_name=constants.X509_CERT_CN,
   @param common_name: commonName value
   @type validity: int
   @param validity: validity of certificate in number of days
+  @return: a tuple of strings containing the PEM-encoded private key and
+           certificate
 
   """
   # TODO: Investigate using the cluster name instead of X505_CERT_CN for
@@ -301,3 +307,92 @@ def GenerateSelfSignedSslCert(filename, common_name=constants.X509_CERT_CN,
                                                    validity * 24 * 60 * 60)
 
   utils_io.WriteFile(filename, mode=0400, data=key_pem + cert_pem)
+  return (key_pem, cert_pem)
+
+
+def ExtractX509Certificate(pem):
+  """Extracts the certificate from a PEM-formatted string.
+
+  @type pem: string
+  @rtype: tuple; (OpenSSL.X509 object, string)
+  @return: Certificate object and PEM-formatted certificate
+
+  """
+  cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, pem)
+
+  return (cert,
+          OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert))
+
+
+def PrepareX509CertKeyCheck(cert, key):
+  """Get function for verifying certificate with a certain private key.
+
+  @type key: OpenSSL.crypto.PKey
+  @param key: Private key object
+  @type cert: OpenSSL.crypto.X509
+  @param cert: X509 certificate object
+  @rtype: callable
+  @return: Callable doing the actual check; will raise C{OpenSSL.SSL.Error} if
+    certificate is not signed by given private key
+
+  """
+  ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
+  ctx.use_privatekey(key)
+  ctx.use_certificate(cert)
+
+  return ctx.check_privatekey
+
+
+def CheckNodeCertificate(cert, _noded_cert_file=pathutils.NODED_CERT_FILE):
+  """Checks the local node daemon certificate against given certificate.
+
+  Both certificates must be signed with the same key (as stored in the local
+  L{pathutils.NODED_CERT_FILE} file). No error is raised if no local
+  certificate can be found.
+
+  @type cert: OpenSSL.crypto.X509
+  @param cert: X509 certificate object
+  @raise errors.X509CertError: When an error related to X509 occurred
+  @raise errors.GenericError: When the verification failed
+
+  """
+  try:
+    noded_pem = utils_io.ReadFile(_noded_cert_file)
+  except EnvironmentError, err:
+    if err.errno != errno.ENOENT:
+      raise
+
+    logging.debug("Node certificate file '%s' was not found", _noded_cert_file)
+    return
+
+  try:
+    noded_cert = \
+      OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, noded_pem)
+  except Exception, err:
+    raise errors.X509CertError(_noded_cert_file,
+                               "Unable to load certificate: %s" % err)
+
+  try:
+    noded_key = \
+      OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, noded_pem)
+  except Exception, err:
+    raise errors.X509CertError(_noded_cert_file,
+                               "Unable to load private key: %s" % err)
+
+  # Check consistency of server.pem file
+  check_fn = PrepareX509CertKeyCheck(noded_cert, noded_key)
+  try:
+    check_fn()
+  except OpenSSL.SSL.Error:
+    # This should never happen as it would mean the certificate in server.pem
+    # is out of sync with the private key stored in the same file
+    raise errors.X509CertError(_noded_cert_file,
+                               "Certificate does not match with private key")
+
+  # Check with supplied certificate with local key
+  check_fn = PrepareX509CertKeyCheck(cert, noded_key)
+  try:
+    check_fn()
+  except OpenSSL.SSL.Error:
+    raise errors.GenericError("Given cluster certificate does not match"
+                              " local key")