@@ -66,25 +66,27 @@ def _str2bytes(raw):
6666 except :
6767 return raw
6868
69+ def _extract_cert_and_thumbprints (private_key , cert ):
70+ # Cert concepts https://security.stackexchange.com/a/226758/125264
71+ from cryptography .hazmat .primitives import hashes , serialization
72+ cert_pem = cert .public_bytes (encoding = serialization .Encoding .PEM ).decode ()
73+ x5c = [
74+ '\n ' .join (cert_pem .splitlines ()[1 :- 1 ])
75+ ]
76+ sha256_thumbprint = cert .fingerprint (hashes .SHA256 ()).hex ()
77+ sha1_thumbprint = cert .fingerprint (hashes .SHA1 ()).hex ()
78+ return private_key , sha256_thumbprint , sha1_thumbprint , x5c
6979
7080def _parse_pfx (pfx_path , passphrase_bytes ):
7181 # Cert concepts https://security.stackexchange.com/a/226758/125264
72- from cryptography .hazmat .primitives import hashes , serialization
7382 from cryptography .hazmat .primitives .serialization import pkcs12
7483 with open (pfx_path , 'rb' ) as f :
7584 private_key , cert , _ = pkcs12 .load_key_and_certificates ( # cryptography 2.5+
7685 # https://cryptography.io/en/latest/hazmat/primitives/asymmetric/serialization/#cryptography.hazmat.primitives.serialization.pkcs12.load_key_and_certificates
7786 f .read (), passphrase_bytes )
7887 if not (private_key and cert ):
7988 raise ValueError ("Your PFX file shall contain both private key and cert" )
80- cert_pem = cert .public_bytes (encoding = serialization .Encoding .PEM ).decode () # cryptography 1.0+
81- x5c = [
82- '\n ' .join (cert_pem .splitlines ()[1 :- 1 ]) # Strip the "--- header ---" and "--- footer ---"
83- ]
84- sha256_thumbprint = cert .fingerprint (hashes .SHA256 ()).hex () # cryptography 0.7+
85- sha1_thumbprint = cert .fingerprint (hashes .SHA1 ()).hex () # cryptography 0.7+
86- # https://cryptography.io/en/latest/x509/reference/#x-509-certificate-object
87- return private_key , sha256_thumbprint , sha1_thumbprint , x5c
89+ return _extract_cert_and_thumbprints (private_key , cert )
8890
8991
9092def _load_private_key_from_pem_str (private_key_pem_str , passphrase_bytes ):
@@ -306,7 +308,7 @@ def __init__(
306308
307309 {
308310 "private_key": "...-----BEGIN PRIVATE KEY-----... in PEM format",
309- "thumbprint": "A1B2C3D4E5F6...",
311+ "thumbprint": "A1B2C3D4E5F6...", (Optinal, if not provided, MSAL will calculate it. Added in version 1.34.0)
310312 "public_certificate": "...-----BEGIN CERTIFICATE-----...",
311313 "passphrase": "Passphrase if the private_key is encrypted (Optional. Added in version 1.6.0)",
312314 }
@@ -815,6 +817,15 @@ def _build_client(self, client_credential, authority, skip_regional_client=False
815817 passphrase_bytes )
816818 if client_credential .get ("public_certificate" ) is True and x5c :
817819 headers ["x5c" ] = x5c
820+ elif (client_credential .get ("private_key" )
821+ and client_credential .get ("public_certificate" )
822+ and not client_credential .get ("thumbprint" )): # in case user does not pass thumbprint but only certificate and private key
823+ private_key , sha256_thumbprint , sha1_thumbprint , x5c = (
824+ _extract_cert_and_thumbprints (
825+ client_credential ['private_key' ],
826+ client_credential ['public_certificate' ]))
827+ if x5c :
828+ headers ["x5c" ] = x5c
818829 elif (
819830 client_credential .get ("private_key" ) # PEM blob
820831 and client_credential .get ("thumbprint" )):
0 commit comments