@@ -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 ):
@@ -288,7 +290,7 @@ def __init__(
288290
289291 {
290292 "private_key": "...-----BEGIN PRIVATE KEY-----... in PEM format",
291- "thumbprint": "An SHA-1 thumbprint such as A1B2C3D4E5F6...",
293+ "thumbprint": "An SHA-1 thumbprint such as A1B2C3D4E5F6...", # Optional since version 1.35.0
292294 "passphrase": "Needed if the private_key is encrypted (Added in version 1.6.0)",
293295 "public_certificate": "...-----BEGIN CERTIFICATE-----...", # Needed if you use Subject Name/Issuer auth. Added in version 0.5.0.
294296 }
@@ -803,6 +805,22 @@ def _build_client(self, client_credential, authority, skip_regional_client=False
803805 passphrase_bytes )
804806 if client_credential .get ("public_certificate" ) is True and x5c :
805807 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+
806824 elif (
807825 client_credential .get ("private_key" ) # PEM blob
808826 and client_credential .get ("thumbprint" )):
@@ -1828,9 +1846,9 @@ def acquire_token_by_username_password(
18281846
18291847 - A successful response would contain "access_token" key,
18301848 - an error response would contain "error" and usually "error_description".
1831-
1832- [Deprecated] This API is deprecated for public client flows and will be
1833- removed in a future release. Use a more secure flow instead.
1849+
1850+ [Deprecated] This API is deprecated for public client flows and will be
1851+ removed in a future release. Use a more secure flow instead.
18341852 Migration guide: https://aka.ms/msal-ropc-migration
18351853
18361854 """
0 commit comments