@@ -66,7 +66,7 @@ def _str2bytes(raw):
6666 except :
6767 return raw
6868
69- def _extract_cert_and_thumbprints (private_key , cert ):
69+ def _extract_cert_and_thumbprints (cert ):
7070 # Cert concepts https://security.stackexchange.com/a/226758/125264
7171 from cryptography .hazmat .primitives import hashes , serialization
7272 cert_pem = cert .public_bytes (encoding = serialization .Encoding .PEM ).decode ()
@@ -75,7 +75,7 @@ def _extract_cert_and_thumbprints(private_key, cert):
7575 ]
7676 sha256_thumbprint = cert .fingerprint (hashes .SHA256 ()).hex ()
7777 sha1_thumbprint = cert .fingerprint (hashes .SHA1 ()).hex ()
78- return private_key , sha256_thumbprint , sha1_thumbprint , x5c
78+ return sha256_thumbprint , sha1_thumbprint , x5c
7979
8080def _parse_pfx (pfx_path , passphrase_bytes ):
8181 # Cert concepts https://security.stackexchange.com/a/226758/125264
@@ -86,7 +86,8 @@ def _parse_pfx(pfx_path, passphrase_bytes):
8686 f .read (), passphrase_bytes )
8787 if not (private_key and cert ):
8888 raise ValueError ("Your PFX file shall contain both private key and cert" )
89- return _extract_cert_and_thumbprints (private_key , cert )
89+ sha256_thumbprint , sha1_thumbprint , x5c = _extract_cert_and_thumbprints (cert )
90+ return private_key , sha256_thumbprint , sha1_thumbprint , x5c
9091
9192
9293def _load_private_key_from_pem_str (private_key_pem_str , passphrase_bytes ):
@@ -290,7 +291,9 @@ def __init__(
290291
291292 {
292293 "private_key": "...-----BEGIN PRIVATE KEY-----... in PEM format",
293- "thumbprint": "An SHA-1 thumbprint such as A1B2C3D4E5F6...", # Optional since version 1.35.0
294+ "thumbprint": "An SHA-1 thumbprint such as A1B2C3D4E5F6..."
295+ "Optional since version 1.35.0."
296+ "When absent, MSAL will calculate SHA-256 thumbprint automatically.",
294297 "passphrase": "Needed if the private_key is encrypted (Added in version 1.6.0)",
295298 "public_certificate": "...-----BEGIN CERTIFICATE-----...", # Needed if you use Subject Name/Issuer auth. Added in version 0.5.0.
296299 }
@@ -805,31 +808,30 @@ def _build_client(self, client_credential, authority, skip_regional_client=False
805808 passphrase_bytes )
806809 if client_credential .get ("public_certificate" ) is True and x5c :
807810 headers ["x5c" ] = x5c
808- elif (client_credential .get ("private_key" )
809- and client_credential .get ("public_certificate" )
810- and not client_credential .get ("thumbprint" )): #in case user does not pass thumbprint but only certificate and private key
811- if passphrase_bytes : # PEM with passphrase
812- private_key = _load_private_key_from_pem_str (
813- client_credential ['private_key' ], passphrase_bytes )
814- else : # PEM without passphrase
815- private_key = client_credential ['private_key' ]
816-
817- private_key , sha256_thumbprint , sha1_thumbprint , x5c = (
818- _extract_cert_and_thumbprints (
819- private_key ,
820- client_credential ['public_certificate' ]))
821- if x5c :
822- headers ["x5c" ] = x5c
823-
824- elif (
825- client_credential .get ("private_key" ) # PEM blob
826- and client_credential .get ("thumbprint" )):
827- sha1_thumbprint = client_credential ["thumbprint" ]
828- if passphrase_bytes :
829- private_key = _load_private_key_from_pem_str (
811+ elif client_credential .get ("private_key" ): # PEM blob
812+ private_key = ( # handles both encrypted and unencrypted
813+ _load_private_key_from_pem_str (
830814 client_credential ['private_key' ], passphrase_bytes )
831- else : # PEM without passphrase
832- private_key = client_credential ['private_key' ]
815+ if passphrase_bytes
816+ else client_credential ['private_key' ]
817+ )
818+
819+ # Determine thumbprints based on what's provided
820+ if client_credential .get ("thumbprint" ):
821+ # User provided a thumbprint - use it as SHA-1 (legacy/manual approach)
822+ sha1_thumbprint = client_credential ["thumbprint" ]
823+ sha256_thumbprint = None
824+ elif isinstance (client_credential .get ('public_certificate' ), str ):
825+ # No thumbprint provided, but we have a certificate to calculate thumbprints
826+ from cryptography import x509
827+ cert = x509 .load_pem_x509_certificate (
828+ _str2bytes (client_credential ['public_certificate' ]))
829+ sha256_thumbprint , sha1_thumbprint , headers ["x5c" ] = (
830+ _extract_cert_and_thumbprints (cert ))
831+ else :
832+ raise ValueError (
833+ "You must provide either 'thumbprint' or 'public_certificate' "
834+ "from which the thumbprint can be calculated." )
833835 else :
834836 raise ValueError (
835837 "client_credential needs to follow this format "
0 commit comments