@@ -1720,13 +1720,16 @@ public function ssl_verify( $args = [], $assoc_args = [], $www_or_non_www = fals
17201720 }
17211721
17221722 /**
1723- * Prints the DNS TXT record(s) required for DNS-based SSL challenge for a site.
1723+ * Shows SSL info and DNS challenge records for a site.
17241724 *
17251725 * ## OPTIONS
17261726 *
17271727 * [<site-name>]
17281728 * : Name of website.
17291729 *
1730+ * [--get-dns-records]
1731+ * : Show DNS challenge records (if using DNS-01 challenge).
1732+ *
17301733 * [--format=<format>]
17311734 * : Render output in a particular format.
17321735 * ---
@@ -1736,19 +1739,19 @@ public function ssl_verify( $args = [], $assoc_args = [], $www_or_non_www = fals
17361739 * - csv
17371740 * - yaml
17381741 * - json
1739- * - count
1740- * - text
17411742 * ---
17421743 *
17431744 * ## EXAMPLES
17441745 *
1746+ * # Show SSL info for a site
1747+ * $ ee site ssl-info example.com
1748+ *
17451749 * # Show DNS challenge info for a site
1746- * $ ee site ssl-dns-info example.com
1747- * $ ee site ssl-dns-info example.com --format=json
1750+ * $ ee site ssl-info example.com --get-dns-records
17481751 *
1749- * @subcommand ssl-dns- info
1752+ * @subcommand ssl-info
17501753 */
1751- public function ssl_dns_info ( $ args , $ assoc_args ) {
1754+ public function ssl_info ( $ args , $ assoc_args ) {
17521755 $ args = auto_site_name ( $ args , 'site ' , __FUNCTION__ );
17531756 $ this ->site_data = get_site_info ( $ args , false , true , false );
17541757
@@ -1758,54 +1761,150 @@ public function ssl_dns_info( $args, $assoc_args ) {
17581761 $ domains = $ this ->get_cert_domains ( $ site_url , $ wildcard );
17591762 $ domains = array_unique ( array_merge ( $ domains , $ alias_domains ) );
17601763
1761- $ preferred_challenge = get_preferred_ssl_challenge ( $ domains ) ;
1762- $ is_dns = $ wildcard || $ preferred_challenge === ' dns ' ;
1764+ $ output = [] ;
1765+ $ warnings = [] ;
17631766
1764- if ( ! $ is_dns ) {
1765- \EE ::log ( 'This site does not use DNS-based (DNS-01) SSL challenge. ' );
1767+ // If --get-dns-records is passed, show DNS challenge info (old behavior)
1768+ if ( \EE \Utils \get_flag_value ( $ assoc_args , 'get-dns-records ' , false ) ) {
1769+ $ preferred_challenge = get_preferred_ssl_challenge ( $ domains );
1770+ $ is_dns = $ wildcard || $ preferred_challenge === 'dns ' ;
1771+
1772+ if ( ! $ is_dns ) {
1773+ $ warnings [] = 'This site does not use DNS-based (DNS-01) SSL challenge. ' ;
1774+ } else {
1775+ $ client = new \EE \Site \Type \Site_Letsencrypt ();
1776+ $ rows = [];
1777+ foreach ( $ domains as $ domain ) {
1778+ if ( $ client ->hasDomainAuthorizationChallenge ( $ domain ) ) {
1779+ $ challenge = $ client ->loadDomainAuthorizationChallenge ( $ domain );
1780+ if ( method_exists ( $ challenge , 'toArray ' ) ) {
1781+ $ data = $ challenge ->toArray ();
1782+ $ record_name = isset ( $ data ['dnsRecordName ' ] ) ? $ data ['dnsRecordName ' ] : '_acme-challenge. ' . $ domain ;
1783+ if ( isset ( $ data ['dnsRecordValue ' ] ) ) {
1784+ $ record_value = $ data ['dnsRecordValue ' ];
1785+ } elseif ( isset ( $ data ['payload ' ] ) ) {
1786+ $ keyAuthorization = $ data ['payload ' ];
1787+ $ digest = rtrim ( strtr ( base64_encode ( hash ( 'sha256 ' , $ keyAuthorization , true ) ), '+/ ' , '-_ ' ), '= ' );
1788+ $ record_value = $ digest ;
1789+ } else {
1790+ $ record_value = '' ;
1791+ }
1792+ $ rows [] = [
1793+ 'domain ' => $ domain ,
1794+ 'record_name ' => $ record_name ,
1795+ 'record_value ' => $ record_value ,
1796+ ];
1797+ } else {
1798+ $ warnings [] = "Could not extract DNS challenge for $ domain. " ;
1799+ }
1800+ } else {
1801+ $ warnings [] = "No pending DNS challenge found for $ domain. (Try running 'ee site ssl-verify $ site_url' if you are setting up SSL) " ;
1802+ }
1803+ }
1804+ $ output ['dns_challenges ' ] = $ rows ;
1805+ }
1806+ $ output ['warnings ' ] = $ warnings ;
1807+ $ formatter = new \EE \Formatter ( $ assoc_args , array_keys ( $ output ) );
1808+ $ formatter ->display_items ( [ $ output ] );
17661809
17671810 return ;
17681811 }
17691812
1770- $ format = \EE \Utils \get_flag_value ( $ assoc_args , 'format ' , 'table ' );
1771- $ client = new \EE \Site \Type \Site_Letsencrypt ();
1772- $ rows = [];
1773- foreach ( $ domains as $ domain ) {
1774- if ( $ client ->hasDomainAuthorizationChallenge ( $ domain ) ) {
1775- $ challenge = $ client ->loadDomainAuthorizationChallenge ( $ domain );
1776- if ( method_exists ( $ challenge , 'toArray ' ) ) {
1777- $ data = $ challenge ->toArray ();
1778- $ record_name = isset ( $ data ['dnsRecordName ' ] ) ? $ data ['dnsRecordName ' ] : '_acme-challenge. ' . $ domain ;
1779- if ( isset ( $ data ['dnsRecordValue ' ] ) ) {
1780- $ record_value = $ data ['dnsRecordValue ' ];
1781- } elseif ( isset ( $ data ['payload ' ] ) ) {
1782- // Compute digest for DNS-01 TXT value
1783- $ keyAuthorization = $ data ['payload ' ];
1784- $ digest = rtrim ( strtr ( base64_encode ( hash ( 'sha256 ' , $ keyAuthorization , true ) ), '+/ ' , '-_ ' ), '= ' );
1785- $ record_value = $ digest ;
1786- } else {
1787- $ record_value = '' ;
1813+ // Otherwise, show SSL status and cert details
1814+ $ ssl_type = $ this ->site_data ->site_ssl ;
1815+ $ output ['ssl_type ' ] = $ ssl_type ? $ ssl_type : 'off ' ;
1816+
1817+ if ( ! $ ssl_type || $ ssl_type === 'off ' ) {
1818+ $ output ['status ' ] = 'SSL is not enabled for this site. ' ;
1819+ $ output ['warnings ' ] = $ warnings ;
1820+ $ formatter = new \EE \Formatter ( $ assoc_args , array_keys ( $ output ) );
1821+ $ formatter ->display_items ( [ $ output ] );
1822+
1823+ return ;
1824+ }
1825+
1826+ // Determine which cert to show (le, self, inherit, custom)
1827+ $ cert_site_name = $ site_url ;
1828+ if ( $ ssl_type === 'inherit ' ) {
1829+ $ cert_site_name = implode ( '. ' , array_slice ( explode ( '. ' , $ site_url ), 1 ) );
1830+ }
1831+
1832+ $ certs_dir = EE_ROOT_DIR . '/services/nginx-proxy/certs/ ' ;
1833+ $ crt_file = $ certs_dir . $ cert_site_name . '.crt ' ;
1834+
1835+ if ( ! file_exists ( $ crt_file ) ) {
1836+ $ warnings [] = "Certificate file not found for $ cert_site_name ( $ crt_file) " ;
1837+ $ output ['status ' ] = 'Certificate file not found / yet to be issued. ' ;
1838+ $ output ['warnings ' ] = $ warnings ;
1839+ $ formatter = new \EE \Formatter ( $ assoc_args , array_keys ( $ output ) );
1840+ $ formatter ->display_items ( [ $ output ] );
1841+
1842+ return ;
1843+ }
1844+
1845+ try {
1846+ $ certificate = new \AcmePhp \Ssl \Certificate ( file_get_contents ( $ crt_file ) );
1847+ $ certificateParser = new \AcmePhp \Ssl \Parser \CertificateParser ();
1848+ $ parsedCertificate = $ certificateParser ->parse ( $ certificate );
1849+
1850+ $ issuer = $ parsedCertificate ->getIssuer ();
1851+ $ subject = $ parsedCertificate ->getSubject ();
1852+ $ validFrom = $ parsedCertificate ->getValidFrom ()->format ( 'Y-m-d H:i:s ' );
1853+ $ validTo = $ parsedCertificate ->getValidTo ()->format ( 'Y-m-d H:i:s ' );
1854+ $ serial = $ parsedCertificate ->getSerialNumber ();
1855+
1856+ // Use openssl_x509_parse for CN fields, as in migration
1857+ $ crt_pem = file_get_contents ( $ crt_file );
1858+ if ( function_exists ( 'openssl_x509_parse ' ) ) {
1859+ $ cert_data = openssl_x509_parse ( $ crt_pem );
1860+ $ subjectCN = isset ( $ cert_data ['subject ' ]['CN ' ] ) ? $ cert_data ['subject ' ]['CN ' ] : '' ;
1861+ $ issuer_full = isset ( $ cert_data ['issuer ' ] ) ? $ cert_data ['issuer ' ] : [];
1862+ $ le_found = false ;
1863+ foreach ( $ issuer_full as $ field => $ value ) {
1864+ if ( stripos ( $ value , "Let's Encrypt " ) !== false ) {
1865+ $ le_found = true ;
1866+ break ;
17881867 }
1789- $ rows [] = [
1790- 'domain ' => $ domain ,
1791- 'record_name ' => $ record_name ,
1792- 'record_value ' => $ record_value ,
1793- ];
1868+ }
1869+ if ( $ le_found ) {
1870+ $ issuerCN = "Let's Encrypt " ;
17941871 } else {
1795- \ EE :: warning ( " Could not extract DNS challenge for $ domain . " );
1872+ $ issuerCN = isset ( $ issuer_full [ ' CN ' ] ) ? $ issuer_full [ ' CN ' ] : implode ( ' , ' , $ issuer_full );
17961873 }
17971874 } else {
1798- \EE ::warning ( "No pending DNS challenge found for $ domain. (Try running 'ee site ssl-verify $ site_url' if you are setting up SSL) " );
1799- }
1800- }
1801- if ( $ rows ) {
1802- $ formatter = new \EE \Formatter ( $ assoc_args , [ 'domain ' , 'record_name ' , 'record_value ' ] );
1803- $ formatter ->display_items ( $ rows );
1804- } else {
1805- \EE ::log ( 'No DNS challenge records found for this site. ' );
1875+ if ( is_object ( $ subject ) && method_exists ( $ subject , 'getField ' ) ) {
1876+ $ subjectCN = $ subject ->getField ( 'CN ' );
1877+ } else {
1878+ $ subjectCN = is_string ( $ subject ) ? $ subject : json_encode ( $ subject );
1879+ $ warnings [] = 'Could not parse subject CN: unexpected type. ' ;
1880+ }
1881+ if ( is_object ( $ issuer ) && method_exists ( $ issuer , 'getField ' ) ) {
1882+ $ issuerCN = $ issuer ->getField ( 'CN ' );
1883+ } else {
1884+ $ issuerCN = is_string ( $ issuer ) ? $ issuer : json_encode ( $ issuer );
1885+ $ warnings [] = 'Could not parse issuer CN: unexpected type. ' ;
1886+ }
1887+ $ warnings [] = 'openssl_x509_parse() not available in PHP. Used fallback parser. ' ;
1888+ }
1889+ $ san = $ parsedCertificate ->getSubjectAlternativeNames ();
1890+
1891+ $ output ['cert_file ' ] = $ crt_file ;
1892+ $ output ['issued_to_CN ' ] = $ subjectCN ;
1893+ $ output ['issued_by_CN ' ] = $ issuerCN ;
1894+ $ output ['valid_from ' ] = $ validFrom ;
1895+ $ output ['valid_till ' ] = $ validTo ;
1896+ $ output ['serial_number ' ] = $ serial ;
1897+ $ output ['SANs ' ] = implode ( ', ' , $ san );
1898+ $ output ['status ' ] = 'SSL certificate details loaded. ' ;
1899+ } catch ( \Exception $ e ) {
1900+ $ warnings [] = 'Could not parse certificate: ' . $ e ->getMessage ();
1901+ $ output ['status ' ] = 'Could not parse certificate. ' ;
18061902 }
1807- }
1903+ $ output [ ' warnings ' ] = $ warnings ;
18081904
1905+ $ formatter = new \EE \Formatter ( $ assoc_args , array_keys ( $ output ) );
1906+ $ formatter ->display_items ( [ $ output ] );
1907+ }
18091908
18101909 /**
18111910 * Renews letsencrypt ssl certificates.
0 commit comments