Merge branch 'geni-v3' into pep8
authorThierry Parmentelat <thierry.parmentelat@inria.fr>
Fri, 13 Jan 2017 12:14:17 +0000 (13:14 +0100)
committerThierry Parmentelat <thierry.parmentelat@inria.fr>
Fri, 13 Jan 2017 12:14:17 +0000 (13:14 +0100)
* geni-v3:
  more, and more legible, debug messages in the cert verification area
  sfax509 will run openssl x509 on all parts of a gid
  ignore html and pdf files when doing stuff like make tags
  bugfix in sfi when running the discover subcommand

# Conflicts:
# sfa/trust/certificate.py
# sfa/trust/gid.py

Makefile
clientbin/sfadump.py
clientbin/sfax509.py [new file with mode: 0755]
debian/sfa-client.install
sfa.spec
sfa/client/sfi.py
sfa/trust/certificate.py
sfa/trust/credential.py
sfa/trust/gid.py

index e6aa5a9..02bab1b 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -126,7 +126,7 @@ files:
        @find . -type f | egrep -v '^\./\.|/\.git/|/\.svn/|TAGS|AA-|~$$|egg-info|\.(py[co]|doc|html|pdf|png|svg|out|bak|dg|pickle)$$' 
 
 git-files:
-       @git ls-files | grep -v '\.doc$$'
+       @git ls-files | egrep -v '\.(doc|html|pdf)$$'
 
 tags:  
        $(MAKE) git-files | xargs etags
index 4a9316d..c87d193 100755 (executable)
@@ -1,5 +1,4 @@
 #! /usr/bin/env python
-from __future__ import with_statement
 
 import sys
 import os
diff --git a/clientbin/sfax509.py b/clientbin/sfax509.py
new file mode 100755 (executable)
index 0000000..0d64808
--- /dev/null
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+
+# something like openssl x509
+# but when used on a gid file we show all the parts
+
+import os
+import argparse
+
+begin = "-----BEGIN CERTIFICATE-----\n"
+end   = "-----END CERTIFICATE-----"
+
+default_openssl_options = "-noout -text"
+
+tmpfilename = "/tmp/sfax509.pem"
+
+def openssl_x509_string(string, openssl_options):
+
+    if not string.startswith(begin):
+        string = begin + string
+    if not string.endswith(end):
+        string = string + end
+    with open(tmpfilename, "w") as f:
+        f.write(string)
+
+    command = "openssl x509 -in {} {}".format(tmpfilename, openssl_options)
+    os.system(command)
+
+# typically on .gids
+def openssl_x509_gid(filename, openssl_options):
+    with open(filename) as f:
+        pem = f.read()
+
+    # remove begins altogether
+    pem = pem.replace(begin, "")
+    # split along end - last item in list is '\n'
+    parts = pem.split(end)[:-1]
+
+    for part in parts:
+        print("==============================")
+        openssl_x509_string(part, openssl_options)
+    
+
+example = 'sfax509.py -x "-noout -dates" foo.gid'
+        
+def main():
+    parser = argparse.ArgumentParser(usage="example: {}".format(example))
+    parser.add_argument("gids", nargs='+')
+    parser.add_argument("-x", "--openssl-option", action='store',
+                        default=default_openssl_options, dest='openssl_options',
+                        help = "options passed to openssl x509 instead of {}"
+                        .format(default_openssl_options))
+    args = parser.parse_args()
+
+    for gid in args.gids:
+        openssl_x509_gid(gid, openssl_options=args.openssl_options)
+
+if __name__ == '__main__':
+    main()
index ced96ba..26951b5 100644 (file)
@@ -6,3 +6,4 @@ usr/bin/setRecord.py*
 usr/bin/sfascan.py*
 #usr/bin/sfascan
 usr/bin/sfadump.py*
+usr/bin/sfax509.py*
index 047de43..3742f01 100644 (file)
--- a/sfa.spec
+++ b/sfa.spec
@@ -187,6 +187,7 @@ rm -rf $RPM_BUILD_ROOT
 %{_bindir}/sfascan.py*
 %{_bindir}/sfascan
 %{_bindir}/sfadump.py*
+%{_bindir}/sfax509.py*
 
 %files plc
 %defattr(-,root,root)
index c32669c..b94dcf6 100644 (file)
@@ -1261,7 +1261,7 @@ use this if you mean an authority instead""")
 
         server = self.sliceapi()
         # set creds
-        creds = [self.my_credential]
+        creds = [self.my_credential_string]
         if options.delegate:
             creds.append(self.delegate_cred(
                 cred, get_authority(self.authority)))
index 55d46d3..9e0f82b 100644 (file)
@@ -572,6 +572,17 @@ class Certificate:
         message += "]"
         return message
 
+    def pretty_chain(self):
+        message = "{}".format(self.x509.get_subject())
+        parent = self.parent
+        while parent:
+            message += " -> {}".format(parent.x509.get_subject())
+            parent = parent.parent
+        return message
+
+    def pretty_name(self):
+        return self.get_filename() or self.pretty_chain()
+
     ##
     # Get the public key of the certificate.
     #
@@ -623,7 +634,6 @@ class Certificate:
     # @param value string containing value of the extension
 
     def add_extension(self, name, critical, value):
-        import M2Crypto
         oldExtVal = None
         try:
             oldExtVal = self.get_extension(name)
@@ -720,9 +730,17 @@ class Certificate:
         m2x509 = M2Crypto.X509.load_cert_string(self.save_to_string())
         m2pubkey = pubkey.get_m2_pubkey()
         # verify it
-        # verify returns -1 or 0 on failure depending on how serious the
-        # error conditions are
-        return m2x509.verify(m2pubkey) == 1
+        # https://www.openssl.org/docs/man1.1.0/crypto/X509_verify.html
+        # verify returns
+        # 1  if it checks out
+        # 0  if if does not
+        # -1 if it could not be checked 'for some reason'
+        m2result = m2x509.verify(m2pubkey)
+        result = m2result == 1
+        if debug_verify_chain:
+            logger.debug("Certificate.verify: <- {} (m2={}) ({} x {})"
+                         .format(result, m2result, self.pretty_cert(), m2pubkey))
+        return result
 
         # XXX alternatively, if openssl has been patched, do the much simpler:
         # try:
@@ -745,6 +763,7 @@ class Certificate:
     # @param cert certificate object
 
     def is_signed_by_cert(self, cert):
+        logger.debug("Certificate.is_signed_by_cert -> invoking verify")
         k = cert.get_pubkey()
         result = self.verify(k)
         return result
@@ -789,6 +808,7 @@ class Certificate:
         # the public key contained in it's parent. The chain is recursed
         # until a certificate is found that is signed by a trusted root.
 
+        logger.debug("Certificate.verify_chain {}".format(self.pretty_name()))
         # verify expiration time
         if self.x509.has_expired():
             if debug_verify_chain:
@@ -797,13 +817,15 @@ class Certificate:
             raise CertExpired(self.pretty_cert(), "client cert")
 
         # if this cert is signed by a trusted_cert, then we are set
-        for trusted_cert in trusted_certs:
+        for i, trusted_cert in enumerate(trusted_certs, 1):
+            logger.debug("Certificate.verify_chain - trying trusted #{} : {}"
+                         .format(i, trusted_cert.pretty_name()))
             if self.is_signed_by_cert(trusted_cert):
                 # verify expiration of trusted_cert ?
                 if not trusted_cert.x509.has_expired():
                     if debug_verify_chain:
                         logger.debug("verify_chain: YES. Cert {} signed by trusted cert {}"
-                                     .format(self.pretty_cert(), trusted_cert.pretty_cert()))
+                                     .format(self.pretty_name(), trusted_cert.pretty_name()))
                     return trusted_cert
                 else:
                     if debug_verify_chain:
@@ -811,28 +833,31 @@ class Certificate:
                                      "but that signer is expired..."
                                      .format(self.pretty_cert(), trusted_cert.pretty_cert()))
                     raise CertExpired("{} signer trusted_cert {}"
-                                      .format(self.pretty_cert(), trusted_cert.pretty_cert()))
+                                      .format(self.pretty_name(), trusted_cert.pretty_name()))
+            else:
+                logger.debug("verify_chain: not a direct descendant of a trusted root".
+                             format(self.pretty_name(), trusted_cert))
 
         # if there is no parent, then no way to verify the chain
         if not self.parent:
             if debug_verify_chain:
                 logger.debug("verify_chain: NO. {} has no parent "
                              "and issuer {} is not in {} trusted roots"
-                             .format(self.pretty_cert(), self.get_issuer(), len(trusted_certs)))
+                             .format(self.pretty_name(), self.get_issuer(), len(trusted_certs)))
             raise CertMissingParent("{}: Issuer {} is not one of the {} trusted roots, "
                                     "and cert has no parent."
-                                    .format(self.pretty_cert(), self.get_issuer(), len(trusted_certs)))
+                                    .format(self.pretty_name(), self.get_issuer(), len(trusted_certs)))
 
         # if it wasn't signed by the parent...
         if not self.is_signed_by_cert(self.parent):
             if debug_verify_chain:
-                logger.debug("verify_chain: NO. {} is not signed by parent {}, but by {}"
-                             .format(self.pretty_cert(),
-                                     self.parent.pretty_cert(),
-                                     self.get_issuer()))
+                logger.debug("verify_chain: NO. {} is not signed by parent {}"
+                             .format(self.pretty_name(),
+                                     self.parent.pretty_name()))
+                self.save_to_file("/tmp/xxx-capture.pem", save_parents=True)
             raise CertNotSignedByParent("{}: Parent {}, issuer {}"
-                                        .format(self.pretty_cert(),
-                                                self.parent.pretty_cert(),
+                                        .format(self.pretty_name(),
+                                                self.parent.pretty_name(),
                                                 self.get_issuer()))
 
         # Confirm that the parent is a CA. Only CAs can be trusted as
@@ -843,14 +868,14 @@ class Certificate:
         # extension and hope there are no other basicConstraints
         if not self.parent.isCA and not (self.parent.get_extension('basicConstraints') == 'CA:TRUE'):
             logger.warn("verify_chain: cert {}'s parent {} is not a CA"
-                        .format(self.pretty_cert(), self.parent.pretty_cert()))
+                        .format(self.pretty_name(), self.parent.pretty_name()))
             raise CertNotSignedByParent("{}: Parent {} not a CA"
-                                        .format(self.pretty_cert(), self.parent.pretty_cert()))
+                                        .format(self.pretty_name(), self.parent.pretty_name()))
 
         # if the parent isn't verified...
         if debug_verify_chain:
             logger.debug("verify_chain: .. {}, -> verifying parent {}"
-                         .format(self.pretty_cert(), self.parent.pretty_cert()))
+                         .format(self.pretty_name(),self.parent.pretty_name()))
         self.parent.verify_chain(trusted_certs)
 
         return
index 54fe3fc..ecdd3bd 100644 (file)
@@ -882,11 +882,16 @@ class Credential(object):
         # If caller explicitly passed in None that means skip cert chain validation.
         # - Strange and not typical
         if trusted_certs is not None:
-            # Verify the gids of this cred and of its parents
+            # Verify the caller and object gids of this cred and of its parents
             for cur_cred in self.get_credential_list():
-                cur_cred.get_gid_object().verify_chain(trusted_cert_objects)
-                cur_cred.get_gid_caller().verify_chain(trusted_cert_objects)
-
+                # check both the caller and the subject 
+                for gid in cur_cred.get_gid_object(), cur_cred.get_gid_caller():
+                    logger.debug("Credential.verify: verifying chain {}"
+                                 .format(gid.pretty_cert()))
+                    logger.debug("Credential.verify: against trusted {}"
+                                 .format(" ".join(trusted_certs)))
+                    gid.verify_chain(trusted_cert_objects)
+                        
         refs = []
         refs.append("Sig_{}".format(self.get_refid()))
 
index b490060..5d7ce57 100644 (file)
@@ -232,6 +232,7 @@ class GID(Certificate):
     # planetlab.us.arizona cannot sign a GID for planetlab.us.princeton.foo.
 
     def verify_chain(self, trusted_certs=None):
+        logger.debug("GID.verify_chain with {} trusted certs".format(len(trusted_certs)))
         # do the normal certificate verification stuff
         trusted_root = Certificate.verify_chain(self, trusted_certs)