Skip to content

Commit 6cd9ebd

Browse files
committed
Cleaner implementation and precise docs on SHA256
1 parent bbeb494 commit 6cd9ebd

File tree

2 files changed

+33
-28
lines changed

2 files changed

+33
-28
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,6 @@ msal_cache.bin
6262

6363
.env
6464
.perf.baseline
65+
66+
*.pfx
67+
.vscode/settings.json

msal/application.py

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -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

8080
def _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

9293
def _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

Comments
 (0)