Noded: Consider certificate chain in callback
authorHelga Velroyen <helgav@google.com>
Wed, 24 Jun 2015 12:19:17 +0000 (14:19 +0200)
committerHelga Velroyen <helgav@google.com>
Mon, 6 Jul 2015 10:46:52 +0000 (12:46 +0200)
This patch significantly changes the callback that is
called upon receiving an incoming SSL connection. Since
this callback is called not only with the certificate
that the client sends, but also (in some implementations)
with the entire certificate chain of the client
certificate.

In our case, the certficate chain contains
the client certificate and the server certificate as
the one that signed the client certificate. This means
that we have to accept the server certificate, but only
if we receive it with the 'depth' greater than 0, meaning
that this is part of the chain and not the actual
certificate. If the depth value is 0, we can be sure
to have received the actual certficate and match it
against the list of master candidate certificates as
before.

Signed-off-by: Helga Velroyen <helgav@google.com>
Reviewed-by: Klaus Aehlig <aehlig@google.com>

lib/server/noded.py

index a7fdf49..581ef27 100644 (file)
@@ -1262,22 +1262,49 @@ def SSLVerifyPeer(conn, cert, errnum, errdepth, ok):
   @param conn: the OpenSSL connection object
   @type cert: C{OpenSSL.X509}
   @param cert: the peer's SSL certificate
+  @type errdepth: integer
+  @param errdepth: number of the step in the certificate chain starting at 0
+                   for the actual client certificate.
 
   """
   # some parameters are unused, but this is the API
   # pylint: disable=W0613
-  sstore = ssconf.SimpleStore()
-  try:
-    candidate_certs = sstore.GetMasterCandidatesCertMap()
-  except errors.ConfigurationError:
-    logging.info("No candidate certificates found. Switching to "
-                 "bootstrap/update mode.")
-    candidate_certs = None
-  if not candidate_certs:
-    candidate_certs = {
-      constants.CRYPTO_BOOTSTRAP: utils.GetCertificateDigest(
-        cert_filename=pathutils.NODED_CERT_FILE)}
-  return cert.digest("sha1") in candidate_certs.values()
+
+  # If we receive a certificate from the certificate chain that is higher
+  # than the lowest element of the chain, we have to check it against the
+  # server certificate.
+  if errdepth > 0:
+    server_digest = utils.GetCertificateDigest(
+        cert_filename=pathutils.NODED_CERT_FILE)
+    match = cert.digest("sha1") == server_digest
+    if not match:
+      logging.debug("Received certificate from the certificate chain, which"
+                    " does not match the server certficate. Digest of the"
+                    " received certificate: %s. Digest of the server"
+                    " certificate: %s.", cert.digest("sha1"), server_digest)
+    return match
+  elif errdepth == 0:
+    sstore = ssconf.SimpleStore()
+    try:
+      candidate_certs = sstore.GetMasterCandidatesCertMap()
+    except errors.ConfigurationError:
+      logging.info("No candidate certificates found. Switching to "
+                   "bootstrap/update mode.")
+      candidate_certs = None
+    if not candidate_certs:
+      candidate_certs = {
+        constants.CRYPTO_BOOTSTRAP: utils.GetCertificateDigest(
+          cert_filename=pathutils.NODED_CERT_FILE)}
+    match = cert.digest("sha1") in candidate_certs.values()
+    if not match:
+      logging.debug("Received certificate which is not a certificate of a"
+                    " master candidate. Certificate digest: %s. List of master"
+                    " candidate certificate digests: %s.", cert.digest("sha1"),
+                    str(candidate_certs))
+    return match
+  else:
+    logging.error("Invalid errdepth value: %s.", errdepth)
+    return False
   # pylint: enable=W0613