diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index 3179e97463eb32847356107850cc471988a397c8..93d97cc5ba6ac5381bf101c701b901bb841f9592 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -33,6 +33,7 @@ import re
 import platform
 import logging
 import copy
+import OpenSSL
 
 from ganeti import ssh
 from ganeti import utils
@@ -848,6 +849,13 @@ def _FindFaultyInstanceDisks(cfg, rpc, instance, node_name, prereq):
   return faulty
 
 
+def _FormatTimestamp(secs):
+  """Formats a Unix timestamp with the local timezone.
+
+  """
+  return time.strftime("%F %T %Z", time.gmtime(secs))
+
+
 class LUPostInitCluster(LogicalUnit):
   """Logical unit for running hooks after cluster initialization.
 
@@ -939,6 +947,66 @@ class LUDestroyCluster(LogicalUnit):
     return master
 
 
+def _VerifyCertificateInner(filename, expired, not_before, not_after, now,
+                            warn_days=constants.SSL_CERT_EXPIRATION_WARN,
+                            error_days=constants.SSL_CERT_EXPIRATION_ERROR):
+  """Verifies certificate details for LUVerifyCluster.
+
+  """
+  if expired:
+    msg = "Certificate %s is expired" % filename
+
+    if not_before is not None and not_after is not None:
+      msg += (" (valid from %s to %s)" %
+              (_FormatTimestamp(not_before),
+               _FormatTimestamp(not_after)))
+    elif not_before is not None:
+      msg += " (valid from %s)" % _FormatTimestamp(not_before)
+    elif not_after is not None:
+      msg += " (valid until %s)" % _FormatTimestamp(not_after)
+
+    return (LUVerifyCluster.ETYPE_ERROR, msg)
+
+  elif not_before is not None and not_before > now:
+    return (LUVerifyCluster.ETYPE_WARNING,
+            "Certificate %s not yet valid (valid from %s)" %
+            (filename, _FormatTimestamp(not_before)))
+
+  elif not_after is not None:
+    remaining_days = int((not_after - now) / (24 * 3600))
+
+    msg = ("Certificate %s expires in %d days" % (filename, remaining_days))
+
+    if remaining_days <= error_days:
+      return (LUVerifyCluster.ETYPE_ERROR, msg)
+
+    if remaining_days <= warn_days:
+      return (LUVerifyCluster.ETYPE_WARNING, msg)
+
+  return (None, None)
+
+
+def _VerifyCertificate(filename):
+  """Verifies a certificate for LUVerifyCluster.
+
+  @type filename: string
+  @param filename: Path to PEM file
+
+  """
+  try:
+    cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
+                                           utils.ReadFile(filename))
+  except Exception, err: # pylint: disable-msg=W0703
+    return (LUVerifyCluster.ETYPE_ERROR,
+            "Failed to load X509 certificate %s: %s" % (filename, err))
+
+  # Depending on the pyOpenSSL version, this can just return (None, None)
+  (not_before, not_after) = utils.GetX509CertValidity(cert)
+
+  return _VerifyCertificateInner(filename, cert.has_expired(),
+                                 not_before, not_after, time.time())
+
+
 class LUVerifyCluster(LogicalUnit):
   """Verifies the cluster status.
 
@@ -953,6 +1021,7 @@ class LUVerifyCluster(LogicalUnit):
   TINSTANCE = "instance"
 
   ECLUSTERCFG = (TCLUSTER, "ECLUSTERCFG")
+  ECLUSTERCERT = (TCLUSTER, "ECLUSTERCERT")
   EINSTANCEBADNODE = (TINSTANCE, "EINSTANCEBADNODE")
   EINSTANCEDOWN = (TINSTANCE, "EINSTANCEDOWN")
   EINSTANCELAYOUT = (TINSTANCE, "EINSTANCELAYOUT")
@@ -1315,6 +1384,11 @@ class LUVerifyCluster(LogicalUnit):
     for msg in self.cfg.VerifyConfig():
       _ErrorIf(True, self.ECLUSTERCFG, None, msg)
 
+    # Check the cluster certificates
+    for cert_filename in constants.ALL_CERT_FILES:
+      (errcode, msg) = _VerifyCertificate(cert_filename)
+      _ErrorIf(errcode, self.ECLUSTERCERT, None, msg, code=errcode)
+
     vg_name = self.cfg.GetVGName()
     hypervisors = self.cfg.GetClusterInfo().enabled_hypervisors
     nodelist = utils.NiceSort(self.cfg.GetNodeList())
diff --git a/lib/constants.py b/lib/constants.py
index 2977c647097340752943654c864d6a3a55abd583..51897258eb8a7f6f22a4d9b6194508e01f33baf9 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -582,6 +582,10 @@ NV_DRBDLIST = "drbd-list"
 NV_NODESETUP = "nodesetup"
 NV_TIME = "time"
 
+# SSL certificate check constants (in days)
+SSL_CERT_EXPIRATION_WARN = 30
+SSL_CERT_EXPIRATION_ERROR = 7
+
 # Allocator framework constants
 IALLOCATOR_VERSION = 2
 IALLOCATOR_DIR_IN = "in"
diff --git a/test/ganeti.cmdlib_unittest.py b/test/ganeti.cmdlib_unittest.py
index 726b941b3e0705055167636ab8f1d1f4073a4c8d..8af61680f9f9934b88502afdbab37ad8a8b40906 100755
--- a/test/ganeti.cmdlib_unittest.py
+++ b/test/ganeti.cmdlib_unittest.py
@@ -25,7 +25,8 @@
 import os
 import unittest
 import time
-import Queue
+import tempfile
+import shutil
 
 from ganeti import cmdlib
 from ganeti import errors
@@ -33,5 +34,75 @@ from ganeti import errors
 import testutils
 
 
-if __name__ == '__main__':
+class TestCertVerification(testutils.GanetiTestCase):
+  def setUp(self):
+    testutils.GanetiTestCase.setUp(self)
+
+    self.tmpdir = tempfile.mkdtemp()
+
+  def tearDown(self):
+    shutil.rmtree(self.tmpdir)
+
+  def testVerifyCertificate(self):
+    cmdlib._VerifyCertificate(self._TestDataFilename("cert1.pem"))
+
+    nonexist_filename = os.path.join(self.tmpdir, "does-not-exist")
+
+    (errcode, msg) = cmdlib._VerifyCertificate(nonexist_filename)
+    self.assertEqual(errcode, cmdlib.LUVerifyCluster.ETYPE_ERROR)
+
+    # Try to load non-certificate file
+    invalid_cert = self._TestDataFilename("bdev-net1.txt")
+    (errcode, msg) = cmdlib._VerifyCertificate(invalid_cert)
+    self.assertEqual(errcode, cmdlib.LUVerifyCluster.ETYPE_ERROR)
+
+
+class TestVerifyCertificateInner(unittest.TestCase):
+  FAKEFILE = "/tmp/fake/cert/file.pem"
+
+  def test(self):
+    vci = cmdlib._VerifyCertificateInner
+
+    # Valid
+    self.assertEqual(vci(self.FAKEFILE, False, 1263916313, 1298476313,
+                         1266940313, warn_days=30, error_days=7),
+                     (None, None))
+
+    # Not yet valid
+    (errcode, msg) = vci(self.FAKEFILE, False, 1266507600, 1267544400,
+                         1266075600, warn_days=30, error_days=7)
+    self.assertEqual(errcode, cmdlib.LUVerifyCluster.ETYPE_WARNING)
+
+    # Expiring soon
+    (errcode, msg) = vci(self.FAKEFILE, False, 1266507600, 1267544400,
+                         1266939600, warn_days=30, error_days=7)
+    self.assertEqual(errcode, cmdlib.LUVerifyCluster.ETYPE_ERROR)
+
+    (errcode, msg) = vci(self.FAKEFILE, False, 1266507600, 1267544400,
+                         1266939600, warn_days=30, error_days=1)
+    self.assertEqual(errcode, cmdlib.LUVerifyCluster.ETYPE_WARNING)
+
+    (errcode, msg) = vci(self.FAKEFILE, False, 1266507600, None,
+                         1266939600, warn_days=30, error_days=7)
+    self.assertEqual(errcode, None)
+
+    # Expired
+    (errcode, msg) = vci(self.FAKEFILE, True, 1266507600, 1267544400,
+                         1266939600, warn_days=30, error_days=7)
+    self.assertEqual(errcode, cmdlib.LUVerifyCluster.ETYPE_ERROR)
+
+    (errcode, msg) = vci(self.FAKEFILE, True, None, 1267544400,
+                         1266939600, warn_days=30, error_days=7)
+    self.assertEqual(errcode, cmdlib.LUVerifyCluster.ETYPE_ERROR)
+
+    (errcode, msg) = vci(self.FAKEFILE, True, 1266507600, None,
+                         1266939600, warn_days=30, error_days=7)
+    self.assertEqual(errcode, cmdlib.LUVerifyCluster.ETYPE_ERROR)
+
+    (errcode, msg) = vci(self.FAKEFILE, True, None, None,
+                         1266939600, warn_days=30, error_days=7)
+    self.assertEqual(errcode, cmdlib.LUVerifyCluster.ETYPE_ERROR)
+
+
+if __name__ == "__main__":
   testutils.GanetiTestProgram()
diff --git a/test/ganeti.constants_unittest.py b/test/ganeti.constants_unittest.py
index 7f1f0cba7f781b42f3f1f59d35c591980f06771a..f21c99df68544ff8f71fe4544dde94b4038a5932 100755
--- a/test/ganeti.constants_unittest.py
+++ b/test/ganeti.constants_unittest.py
@@ -64,6 +64,10 @@ class TestConstants(unittest.TestCase):
     self.failUnless(constants.NODE_MAX_CLOCK_SKEW <
                     (0.8 * constants.CONFD_MAX_CLOCK_SKEW))
 
+  def testSslCertExpiration(self):
+    self.failUnless(constants.SSL_CERT_EXPIRATION_ERROR <
+                    constants.SSL_CERT_EXPIRATION_WARN)
+
 
 class TestParameterNames(unittest.TestCase):
   """HV/BE parameter tests"""