@@ -869,6 +869,98 @@ def load_pubkey(pk, get_encoding=False):
869869 raise PubDeserializationError ("Could not load DER-encoded public key." ) from err
870870
871871
872+ def order_certs_naively (bundle , allow_orphans = True , require_leaf = True ):
873+ """
874+ Deterministically order certificates in a bundle using a naive algorithm.
875+ This is not a chain building algorithm! It just selects the longest chain
876+ of direct certification, preferring leaves by default, and appends all
877+ orphans ordered by their fingerprints, if orphans are allowed.
878+
879+ bundle
880+ A set of cryptography.x509.Certificate objects to order.
881+
882+ allow_orphans
883+ Do not require all certificates to build a single chain. Defaults to true.
884+
885+ require_leaf
886+ Require that a path begins with a certificate that itself has not
887+ been used to issue another certificate in the bundle. Defaults to true.
888+ """
889+ if len (bundle ) < 2 :
890+ return list (bundle )
891+
892+ def _directly_issued_by (subject , issuer ):
893+ if subject .issuer != issuer .subject :
894+ return False
895+ try :
896+ subject .verify_directly_issued_by (issuer )
897+ except (InvalidSignature , TypeError , ValueError ):
898+ return False
899+ return True
900+
901+ def _fp (cert ):
902+ return cert .fingerprint (hashes .SHA256 ())
903+
904+ ordered_bundle = tuple (sorted (bundle , key = _fp ))
905+ issuers = {
906+ cert : [
907+ candidate
908+ for candidate in ordered_bundle
909+ if _directly_issued_by (cert , candidate )
910+ ]
911+ for cert in ordered_bundle
912+ }
913+ if require_leaf :
914+ # ensure we treat self-signed root certificates that have not issued another certificate in this bundle as a leaf
915+ cert_issuers = {
916+ issuer
917+ for subject , candidates in issuers .items ()
918+ for issuer in candidates
919+ if issuer != subject
920+ }
921+ leaves = {cert for cert in ordered_bundle if cert not in cert_issuers }
922+ if not leaves :
923+ # This would be unusual, but possible when e.g. two certificates signed each other
924+ raise ValueError (
925+ "Certificate bundle did not contain a single leaf certificate"
926+ )
927+ else :
928+ leaves = {}
929+
930+ def _paths_from (
931+ cert ,
932+ seen ,
933+ ):
934+ candidates = [issuer for issuer in issuers [cert ] if issuer not in seen ]
935+ if not candidates :
936+ return [[cert ]]
937+ return [
938+ [cert , * tail ]
939+ for issuer in candidates
940+ for tail in _paths_from (issuer , seen | {issuer })
941+ ]
942+
943+ paths = [
944+ path for cert in ordered_bundle for path in _paths_from (cert , frozenset ({cert }))
945+ ]
946+
947+ # Longest path first; fingerprints provide a stable tie-breaker.
948+ selected = min (
949+ paths ,
950+ key = lambda path : (
951+ - int (path [0 ] in leaves ),
952+ - len (path ),
953+ tuple (_fp (cert ) for cert in path ),
954+ ),
955+ )
956+ orphans = [cert for cert in ordered_bundle if cert not in selected ]
957+ if not allow_orphans and orphans :
958+ raise ValueError (
959+ "Certificate bundle did not contain a singular chain comprising all certificates"
960+ )
961+ return [* selected , * orphans ]
962+
963+
872964def load_cert (cert , passphrase = None , load_chain = False , get_encoding = False ):
873965 """
874966 Return a certificate instance from
@@ -910,12 +1002,13 @@ def load_cert(cert, passphrase=None, load_chain=False, get_encoding=False):
9101002 ) from err
9111003 else :
9121004 try :
913- loaded = pkcs7 .load_pem_pkcs7_certificates (pems [0 ])
1005+ chain = order_certs_naively (pkcs7 .load_pem_pkcs7_certificates (pems [0 ]))
1006+ loaded = chain .pop (0 ) # the first cert is sure to be a leaf
9141007 if load_chain :
915- return loaded . pop ( 0 ), loaded
1008+ return loaded , chain
9161009 if get_encoding :
917- return loaded . pop ( 0 ) , "pkcs7_pem" , loaded , None
918- return loaded . pop ( 0 )
1010+ return loaded , "pkcs7_pem" , chain , None
1011+ return loaded
9191012 except ValueError as err :
9201013 raise CertDeserializationError (
9211014 "Could not load PEM-encoded PKCS#7 blob"
@@ -952,14 +1045,20 @@ def load_cert(cert, passphrase=None, load_chain=False, get_encoding=False):
9521045 # PKCS7
9531046 try :
9541047 # v37+
955- loaded = pkcs7 .load_der_pkcs7_certificates (cert )
956- if load_chain :
957- return loaded .pop (0 ), loaded
958- if get_encoding :
959- return loaded .pop (0 ), "pkcs7_der" , loaded , None
960- return loaded [0 ]
1048+ bundle = pkcs7 .load_der_pkcs7_certificates (cert )
9611049 except ValueError :
9621050 pass
1051+ else :
1052+ try :
1053+ chain = order_certs_naively (bundle )
1054+ except ValueError as err :
1055+ raise CertDeserializationError (str (err )) from err
1056+ loaded = chain .pop (0 ) # the first cert is sure to be a leaf
1057+ if load_chain :
1058+ return loaded , chain
1059+ if get_encoding :
1060+ return loaded , "pkcs7_der" , chain , None
1061+ return loaded
9631062 # nothing worked
9641063 raise CertDeserializationError (
9651064 "Could not deserialize binary data, neither as DER nor PKCS#7, PKCS#12."
0 commit comments