diff --git a/README.md b/README.md index 6f0153c..724c178 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ Open a certificate, either PEM or DER encoded, in a buffer. M-x x509-dwim -A new buffer should display the parsed certificate. +A new buffer displays the parsed certificate. -To view certificates, CRLs, private and public keys Diffie-Hellman parameters, certificate requests, pkcs7 files and parsed ASN.1 respectively: +To view certificates, CRLs, private and public keys Diffie-Hellman parameters, elliptic curve parameters, certificate requests, pkcs7 files and parsed ASN.1 respectively: M-x x509-viewcert M-x x509-viewcrl @@ -31,7 +31,7 @@ back to `x509-viewasn1`. M-x x509-dwim -The command line for all command can be edited with C-u prefix. Example: +The command line for all of the above commands can be edited with C-u prefix. Example: C-u M-x x509-viewcert @@ -40,6 +40,10 @@ Commands operate on PEM data around point by default. If point is in is considered. If no PEM region is found, either around point or in buffer, then the buffer is assumed to be DER encoded. +There is special command, `x509-swoop`, that has different semantics than those above. It searches the whole buffer for _all_ recognized PEM regions and parses them one by one. The output of all regions are sent to the same buffer. The result buffer does not have the capabilities of a normal `x509-buffer`. There is no way to edit command line argument, toggle to `x509-asn1-mode` or move to next/previous region. `x509-swoop` intended to be used on PEM-coded certificate chain files. + + M-x x509-swoop + ### Key bindings **e** edit the current command. diff --git a/keywords.txt b/keywords.txt index b881b94..940c044 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,3 +1,4 @@ +# -*- eval: (ethan-wspace-mode -1); -*- # openssl/crypto/x509/t_x509.c Certificate: Data: @@ -154,10 +155,12 @@ DH Parameters: # encode_key2text.c EC-Parameters: +GROUP: # eck_prn.c ECPKParameters_print -A: -B: +# NOTE: There is SPC after these so that they are not matched in hex strings +A: +B: # openssl/crypto/x509v3/v3_bcons.c # X509V3_add_value_bool diff --git a/testfiles/multi.pem b/testfiles/multi.pem index 28f1027..07a1fb3 100644 --- a/testfiles/multi.pem +++ b/testfiles/multi.pem @@ -20,35 +20,31 @@ Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl MrY= -----END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEyDCCA7CgAwIBAgIQDPW9BitWAvR6uFAsI8zwZjANBgkqhkiG9w0BAQsFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH -MjAeFw0yMTAzMzAwMDAwMDBaFw0zMTAzMjkyMzU5NTlaMFkxCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEdsb2Jh -bCBHMiBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAMz3EGJPprtjb+2QUlbFbSd7ehJWivH0+dbn4Y+9lavyYEEV -cNsSAPonCrVXOFt9slGTcZUOakGUWzUb+nv6u8W+JDD+Vu/E832X4xT1FE3LpxDy -FuqrIvAxIhFhaZAmunjZlx/jfWardUSVc8is/+9dCopZQ+GssjoP80j812s3wWPc -3kbW20X+fSP9kOhRBx5Ro1/tSUZUfyyIxfQTnJcVPAPooTncaQwywa8WV0yUR0J8 -osicfebUTVSvQpmowQTCd5zWSOTOEeAqgJnwQ3DPP3Zr0UxJqyRewg2C/Uaoq2yT -zGJSQnWS+Jr6Xl6ysGHlHx+5fwmY6D36g39HaaECAwEAAaOCAYIwggF+MBIGA1Ud -EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFHSFgMBmx9833s+9KTeqAx2+7c0XMB8G -A1UdIwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485MA4GA1UdDwEB/wQEAwIBhjAd -BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdgYIKwYBBQUHAQEEajBoMCQG -CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQAYIKwYBBQUHMAKG -NGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RH -Mi5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29t -L0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDA9BgNVHSAENjA0MAsGCWCGSAGG/WwC -ATAHBgVngQwBATAIBgZngQwBAgEwCAYGZ4EMAQICMAgGBmeBDAECAzANBgkqhkiG -9w0BAQsFAAOCAQEAkPFwyyiXaZd8dP3A+iZ7U6utzWX9upwGnIrXWkOH7U1MVl+t -wcW1BSAuWdH/SvWgKtiwla3JLko716f2b4gp/DA/JIS7w7d7kwcsr4drdjPtAFVS -slme5LnQ89/nD/7d+MS5EHKBCQRfz5eeLjJ1js+aWNJXMX43AYGyZm0pGrFmCW3R -bpD0ufovARTFXFZkAdl9h6g4U5+LXUZtXMYnhIHUfoyMo5tS58aI7Dd8KvvwVVo4 -chDYABPPTHPbqjc1qCmBaZx2vN4Ye5DUys/vZwP9BFohFrH/6j/f3IL16/RZkiMN -JCqVJUzKoZHm1Lesh3Sz8W2jmdv51b2EQJ8HmA== ------END CERTIFICATE----- + +-----BEGIN JUNK----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +-----BEGIN END----- + +-----BEGIN CERTIFICATE REQUEST----- +MIICyDCCAbACAQAwPDEaMBgGA1UEAwwRQ0EgVGVzdFRvb2wgQ0EgMDExHjAcBgNV +BAoMFUNBIFRlc3RUb29sIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBANSZMZbKEIrK3B3O8T6pxctV+FT96RktoozzQoBZ+1DMSdAQZnpz +BHWPRZvjtgb/pZh9FeI5WvR1tjuaETxs+RbCrUfhSIdEa/FGt034A1RDkJRYyxgy +aflwiHcSm1ViQfB6MJVZpRqBqNwPr88URqbmjfBhCQISo7hknQYVcbrH78bWHOIE +kU1ZnB9xat1tLtjglPRdcj5iT7P2ENFrOGX1hBA0YRCkqSlNkJPNKBjl7XXhYlw8 +Pad6pxxp8K6kpl1Fgp8LkR0r9yItoS+XgNfJ2fYnaW9xe/lp4oOMuLyklHzuvOqc +a8veq00vWKj5+ecCWU+RA8uvjAzI3xuUFzkCAwEAAaBHMEUGCSqGSIb3DQEJDjE4 +MDYwCQYDVR0TBAIwADApBgNVHR8EIjAgMB6gHKAahhhodHRwOi8vY3JsLmNhdGVz +dHRvb2wuc2UwDQYJKoZIhvcNAQELBQADggEBAHeuVS8tmKZL8IQhpE6N2NBCRcCd +zX1P+ZpYf0vK05ueg/+6o7wujXxvb0g5yOXJE7/rlHdzAGTRKL7wvnpdiNLcK7fR +DSG12kpDurd83ADzDRj/FQLjOdo7Fvkihew4stajxETFmZvBvvnP9DvxyYzjD8jn +ftX+f0pYZLsU2Z+XokevXKp1MUQBGhCKwLmBiCx19tx0jvZHDqvE/jv8fhPJuXyx +EaMBwhWSmBlT7blPW7dLey6/HlZHLQDP8M/48IVrb/6a3C36juCR6aQg2ZLuFhPZ +Fhw3zp10Sj9cMWUQ2dLwpBUfQWtk9YuQpNuYlAM0xSsGrhZf6GqelWs7vEU= +-----END CERTIFICATE REQUEST----- + Nothing here to see + -----BEGIN DH PARAMETERS----- MIIBCAKCAQEAnc5+uXl2K09Nrp1oxN/KbIcIYLg8HXCu9UNW7gFknkHil7OVAKHR Km0Dc8IjqhJpDfoNKFoDo2Vd0KB9moSkDmhFmidcXO7Q8zSq0Z4BXFTO61OMukdd @@ -57,17 +53,23 @@ Njc9reFgL6Vua8HkOGkLB+EvRP1YT4v5hGGP/6A7WxRevx5EjF9VgojyDLMPN26C 3c17KY2jNV0W1GEcKEciWS61QInUDBDPYNuQzTl0LucbOpJyV3BFr6pokRBaO3bI ZYUPhjA2WSxJUeeJboJfisr+CQa9kc1dYwIBAg== -----END DH PARAMETERS----- + -----BEGIN RSA PUBLIC KEY----- MEgCQQCo9+BpMRYQ/dL3DS2CyJxRF+j6ctbT3/Qp84+KeFhnii7NT7fELilKUSnx S30WAvQCCo2yU1orfgqr41mM70MBAgMBAAE= -----END RSA PUBLIC KEY----- xxx static const char g_ffdhe2048_sz[] = - "-----BEGIN DH PARAMETERS-----\n" - "MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz\n" - "+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a\n" - "87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7\n" - "YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi\n" - "7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD\n" - "ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==\n" - "-----END DH PARAMETERS-----\n"; + "-----BEGIN EC PARAMETERS-----\n" + "MIIB5gIBATBMBgcqhkjOPQEBAkEA2FXXx8/oQxG4Uhuic2YH2xXZqYDgYUyH/ogp\n" + "HnuCWS9grRn1v8AWCpEdwrloaH63oYQN/sKQAVfRf8k1J7nKpzCByARAAgK0Agsl\n" + "YcJvokRDUCnNloLbfHEya0rbeZNf6DPwc9T4mcU7/H1Hs9hjxC9bE/N2njtLpaR2\n" + "bykyE9Or5GOkngRAMyIQgOcnU06PyEY9CZG3b+i0hEOp9bHoVJnm6uhjwY/97aH8\n" + "mG/Pkr4N2B8ZzkaOmz5A4Ujejx4WR39TRlS0ywNCAAHG1Hq3nS8FBhpmZJGvWHmv\n" + "Bjj0RCCU9MtcFZU5Ncfralhq3kjrzbTVSmVU9m/QEHni+qDIawyMX4ktm7mcpJo8\n" + "BIGBBEJk6lNnM17uHfxPPFe6975BDgLoQW7cNbuVxF1ozxkuB8g75+iqTi+GerH9\n" + "RbsFraw2RPbwPH8fQzTLniHXHkEBVvoisBzZqI/Y4DzmDhQexeMcEk0QUw+NxA+k\n" + "wLYyb+gB9VH7G0RqnZuraqk5Y0+1wNbFiu3w0lBOSpdEGrJJAkEA2FXXx8/oQxG4\n" + "Uhuic2YH2xXZqYDgYUyH/ogpHnuCWS8oKmc+nL5CRANkM1N1byv/8WsUio+aJDqR\n" + "MY2SccEWYQIBAQ==\n" + "-----END EC PARAMETERS-----\n"; diff --git a/x509-mode-tests.el b/x509-mode-tests.el index 07be3a0..0f29797 100644 --- a/x509-mode-tests.el +++ b/x509-mode-tests.el @@ -176,38 +176,45 @@ When `x509-warn-near-expire-days' is nil." (ert-deftest x509--pem-region () "Find region delimited by BEGIN/END." (with-temp-buffer - ;; 54 - ;; v + ;; 22 53 + ;; v v (insert - "-----BEGIN TYPE----- -----END BOGUS-----END TYPE----- -----END TYPE-----") + "-----BEGIN PKCS7----- -----END XX-----END PKCS7----- -----END PKCS7-----") (goto-char (point-min)) (let ((region (x509--pem-region))) (should region) (should (equal 1 (car region))) - (should (equal 54 (cdr region)))) + (should (equal 53 (cdr region)))) (goto-char 22) (let ((region (x509--pem-region))) (should region) (should (equal 1 (car region))) - (should (equal 54 (cdr region)))))) + (should (equal 53 (cdr region)))))) (ert-deftest x509--pem-region-negative () "Behave when there is no region." (with-temp-buffer - (insert "-----BEGIN TYPE----- -----END BOGUS-----") + (insert "-----BEGIN PKCS7----- -----END XZY-----") (goto-char (point-min)) (should-not (x509--pem-region))) (with-temp-buffer - (insert "-----END TYPE-----") + (insert "-----END PKCS7-----") (goto-char (point-min)) (should-not (x509--pem-region)))) +(ert-deftest x509--pem-region-known-types () + (dolist (type x509--known-pki-types) + (with-temp-buffer + (insert (format "-----BEGIN %s----- -----END %s-----" type type)) + (goto-char (point-min)) + (should (x509--pem-region))))) + (ert-deftest x509--pem-region-type () "Get type of region, e.g. \"CERTIFICATE\"." (with-temp-buffer - (insert "-----BEGIN my type----- -----END my type-----") + (insert "-----BEGIN PKCS7----- -----END PKCS7-----") (goto-char (point-min)) - (should (equal "my type" (x509--pem-region-type)))) + (should (equal "PKCS7" (x509--pem-region-type)))) (with-temp-buffer (insert "-----END TYPE-----") (goto-char (point-min)) @@ -216,20 +223,25 @@ When `x509-warn-near-expire-days' is nil." (ert-deftest x509--pem-region-next/prev () (with-temp-buffer (insert - "-----BEGIN my type-----\n-----END my type-----\n" - "-----BEGIN my type-----\n-----END my type-----\n") + ;; This is the initial valid region + "-----BEGIN PKCS7-----\n-----END PKCS7-----\n" + ;; This region with unknown type should be ignored. + "-----BEGIN xxxxx-----\n-----END xxxxx-----\n" + ;; This is the next valid region + "-----BEGIN PKCS7-----\n-----END PKCS7-----\n") ;;^ point after next (goto-char (point-min)) - (let ((first-region (x509--pem-region))) + (let ((first-region (x509--pem-region)) + (expected-next-point 85)) (should first-region) (should (= 1 (car first-region))) (let ((next-region (x509--pem-region-next/prev (current-buffer) 'next))) (should next-region) - (should (= 47 (car next-region))) - (should (= 47 (point))) + (should (= expected-next-point (car next-region))) + (should (= expected-next-point (point))) ;; Next again should leave point unchanged (should-not (x509--pem-region-next/prev (current-buffer) 'next)) - (should (= 47 (point)))) + (should (= expected-next-point (point)))) ;; Prev 1 (let ((prev-region (x509--pem-region-next/prev (current-buffer) 'prev))) (should prev-region) @@ -245,10 +257,10 @@ When `x509-warn-near-expire-days' is nil." (with-temp-buffer (let ((q-pem-data (concat - "const char* pem = \"-----BEGIN XX-----\"\n" + "const char* pem = \"-----BEGIN PKCS7-----\"\n" " \"data\"\n" - " \"-----END XX-----\";\n")) - (clean-pem-data "-----BEGIN XX-----\ndata\n-----END XX-----")) + " \"-----END PKCS7-----\";\n")) + (clean-pem-data "-----BEGIN PKCS7-----\ndata\n-----END PKCS7-----")) (insert q-pem-data) (goto-char 20) (let ((buf (x509--generate-input-buffer))) @@ -377,14 +389,20 @@ When `x509-warn-near-expire-days' is nil." (expand-file-name file-name "testfiles")) (defun view-test-helper - (test-files view-command expected-mode expected-strings) + (test-files + view-command expected-mode expected-strings &optional anti-strings) "Run VIEW-COMMAND on all TEST-FILES. Check that buffer has EXPECTED-MODE and contains EXPECTED-STRINGS. +Check that buffer does _not_ contain ANTI-STRINGS. Repeat with `x509-dwim' which should produce the same result." (let ((regexes (if (listp expected-strings) expected-strings (list expected-strings))) + (anti-regexes + (if (listp anti-strings) + anti-strings + (list anti-strings))) (files (if (listp test-files) test-files @@ -401,7 +419,9 @@ Repeat with `x509-dwim' which should produce the same result." (point-min) (point-max)))) (should (derived-mode-p expected-mode)) (dolist (regex regexes) - (should (string-match-p regex content))))) + (should (string-match-p regex content))) + (dolist (regex anti-regexes) + (should-not (string-match-p regex content))))) (kill-buffer view-buffer)))))))) (ert-deftest x509-viewcert () @@ -410,14 +430,15 @@ Repeat with `x509-dwim' which should produce the same result." '("CA/pki/crt/jobbflykt.crt" "CA/pki/crt/jobbflykt.cer") 'x509-viewcert 'x509-mode - "Certificate:")) + "Certificate:" + "Warning")) (ert-deftest x509-viewreq () "View cert request." (view-test-helper '("CA/ca/request/jobbflykt.pem" "CA/ca/request/jobbflykt_req.der") - 'x509-viewreq 'x509-mode "Certificate Request:")) + 'x509-viewreq 'x509-mode "Certificate Request:" "Warning")) (ert-deftest x509-viewcrl () "View CRL." @@ -672,11 +693,11 @@ SEQUENCE 30 0C (goto-char (point-min)) (let ((view-buffer (x509-dwim))) (should view-buffer) - (check-content-helper view-buffer "1d:09:fa:e5") + (check-content-helper view-buffer "Certificate:") (with-current-buffer view-buffer (setq view-buffer (x509-dwim-next))) (should view-buffer) - (check-content-helper view-buffer "23:cc:f0:66") + (check-content-helper view-buffer "Certificate Request:") (with-current-buffer view-buffer (setq view-buffer (x509-dwim-next))) (should view-buffer) @@ -688,11 +709,11 @@ SEQUENCE 30 0C (with-current-buffer view-buffer (setq view-buffer (x509-dwim-next))) (should view-buffer) - (check-content-helper view-buffer "GROUP: ffdhe2048") + (check-content-helper view-buffer "EC-Parameters: (512 bit)") ;; At end, next should fail (with-current-buffer view-buffer (should-not (x509-dwim-next))) - (check-content-helper view-buffer "GROUP: ffdhe2048") + (check-content-helper view-buffer "EC-Parameters: (512 bit)") ;; Go backward (with-current-buffer view-buffer (setq view-buffer (x509-dwim-prev))) @@ -705,15 +726,15 @@ SEQUENCE 30 0C (with-current-buffer view-buffer (setq view-buffer (x509-dwim-prev))) (should view-buffer) - (check-content-helper view-buffer "23:cc:f0:66") + (check-content-helper view-buffer "Certificate Request:") (with-current-buffer view-buffer (setq view-buffer (x509-dwim-prev))) (should view-buffer) - (check-content-helper view-buffer "1d:09:fa:e5") + (check-content-helper view-buffer "Certificate:") ;; At beginning, prev should fail (with-current-buffer view-buffer (should-not (x509-dwim-prev))) - (check-content-helper view-buffer "1d:09:fa:e5") + (check-content-helper view-buffer "Certificate:") ;; Kill it (with-current-buffer view-buffer (x509-mode-kill-buffer))))) @@ -725,21 +746,46 @@ SEQUENCE 30 0C (goto-char (point-min)) (let ((view-buffer (x509-viewasn1))) (should view-buffer) - (check-content-helper view-buffer "1D09FAE5") + (check-content-helper view-buffer ":033AF1E6A711A9A0BB2864B11D09FAE5") ;; Go next and verify we are still in asn1 mode looking at the next ;; section. (with-current-buffer view-buffer (setq view-buffer (x509-dwim-next))) (should view-buffer) - (check-content-helper view-buffer "CCF066") + (check-content-helper view-buffer ":Extension Request") ;; Go back again (with-current-buffer view-buffer (setq view-buffer (x509-dwim-prev))) (should view-buffer) - (check-content-helper view-buffer "1D09FAE5") + (check-content-helper view-buffer ":033AF1E6A711A9A0BB2864B11D09FAE5") ;; Kill it (with-current-buffer view-buffer (x509-mode-kill-buffer))))) +(ert-deftest x509-swoop () + "Multiple dwim in all regions in buffer." + (let ((x509-swoop-separator "7iyefiaeo7bf"))) + (with-temp-buffer + (insert-file-contents-literally (find-testfile "multi.pem")) + ;; Goto some random point in src buffer and check that it's unchanged + ;; after swooping. + (let ((src-buffer (current-buffer))) + (goto-char 1322) + (let ((swoop-buffer (x509-swoop))) + (should swoop-buffer) + (with-current-buffer swoop-buffer + (check-content-helper swoop-buffer "Certificate:") + (check-content-helper swoop-buffer "Certificate Request:") + (check-content-helper swoop-buffer "DH Parameters:") + (check-content-helper swoop-buffer "Public-Key:") + (check-content-helper swoop-buffer "EC-Parameters: (512 bit)") + (check-content-helper swoop-buffer x509-swoop-separator) + (x509-mode-kill-buffer))) + (with-current-buffer src-buffer + (should (= (point) 1322))))) + (with-temp-buffer + (insert "-----BEGIN nothing----- -----END nothing-----") + (should-not (x509-swoop)))) + (provide 'x509-mode-tests) ;;; x509-mode-tests.el ends here diff --git a/x509-mode.el b/x509-mode.el index 53d35ca..e97a269 100644 --- a/x509-mode.el +++ b/x509-mode.el @@ -198,6 +198,12 @@ Example: "Face for highlighting ASN.1 value in hexl buffer in `x509-asn1-mode'." :group 'x509-faces) +(defun x509--normalize-buffer-end (buffer) + "Trim empty lines and ensure BUFFER ends in a newline." + (with-current-buffer buffer + (let ((delete-trailing-lines t)) + (delete-trailing-whitespace (point-min) (point-max))))) + (defun x509--match-date (cmp bound) "Return non-nil if it can find a date that CMP to current time. Intended to search for dates in form \"Jun 11 00:00:01 2014 GMT\" @@ -259,6 +265,11 @@ Used with `(format x509-query-oid-url-format oid)'" :type 'string :group 'x509) +(defcustom x509-swoop-separator " " + "A string that separates items in a buffer created by `x509-swoop'." + :type 'string + :group 'x509) + (defun x509--match-date-near-now (bound) "Return non-nil it can find a date that is \"near\" in the future. @@ -330,16 +341,16 @@ For simple cases, COMPOSE-URL-FN returns its argument unchanged." Skip blank lines and comment lines. Return list." ;; Try to guess path to filename. It may not be in the current directory ;; when compiling. - (let ((path (cond ((bound-and-true-p byte-compile-current-file) - (expand-file-name filename - (file-name-directory - byte-compile-current-file))) - ((bound-and-true-p load-file-name) - (expand-file-name filename - (file-name-directory - load-file-name))) - (t - filename)))) + (let ((path + (cond + ((bound-and-true-p byte-compile-current-file) + (expand-file-name filename + (file-name-directory + byte-compile-current-file))) + ((bound-and-true-p load-file-name) + (expand-file-name filename (file-name-directory load-file-name))) + (t + filename)))) (with-temp-buffer (insert-file-contents path) (cl-remove-if @@ -479,8 +490,29 @@ Return string \"PEM\" or \"DER\"." "PEM" "DER")))) +(defconst x509--known-pki-types + '("CERTIFICATE" + "TRUSTED CERTIFICATE" + "CERTIFICATE REQUEST" + "DH PARAMETERS" + "EC PARAMETERS" + "PKCS7" + "ENCRYPTED PRIVATE KEY" + "PRIVATE KEY" + "RSA PRIVATE KEY" + "PUBLIC KEY" + "RSA PUBLIC KEY" + "X509 CRL") + "All the PEM region types known by `x509-mode'. +Other types will not be recognized as valid regions.") + +(defun x509--known-region-type-p (type) + "Return non-nil if TYPE in a supported PKI type." + (member type x509--known-pki-types)) + (defun x509--pem-region () "Determine if point is in region delimited by \"-----BEGIN\" \"-----END\". +Only consider regions whose type in known by `x509--known-region-type-p'. Return (begin . end) or nil" (save-excursion (save-match-data @@ -490,6 +522,7 @@ Return (begin . end) or nil" (let ((begin (match-beginning 0)) (type (match-string-no-properties 1))) (if (and (search-forward (concat "-----END " type "-----") nil t) + (x509--known-region-type-p type) ;; Ensure point is between begin and end. (< here (match-end 0))) (cons begin (match-end 0))))))))) @@ -642,9 +675,11 @@ Return output buffer." ;; Since OpenSSL 3, there is a warning when reading input from stdin and ;; not from a file specified by an -in parameter. Delete that warning ;; line from the output. "Warning: Reading certificate from stdin since - ;; no -in or -new option is given" + ;; no -in or -new option is given". Also seen: + ;; "Warning: Will read cert request from stdin since no -in option is + ;; given" (goto-char (point-min)) - (if (looking-at-p "Warning: Reading ") + (if (looking-at-p "Warning: .*stdin") (delete-line)) (set-buffer-modified-p nil) (setq buffer-read-only t)) @@ -928,17 +963,23 @@ If no next/prev region, leave point unchanged." (if-let* ((current-region (x509--pem-region)) (current-begin (funcall region-point-fn current-region)) (current-point (point))) - (progn + (let (new-region) (goto-char current-begin) ;; Look for next/prev region - (if (funcall search-fn "-----BEGIN" nil t) - (progn - (goto-char (match-beginning 0)) - (x509--pem-region)) - ;; Restore point since we didn't fins any new region - (goto-char current-point) - ;; Return nil - nil)))))) + (while (and (not new-region) (funcall search-fn "-----BEGIN" nil t)) + (goto-char (match-beginning 0)) + (setq new-region (x509--pem-region)) + ;; If this BEGIN was bogus. Move a bit and look for another + (if (not new-region) + ;; Only need to move if searching forward so as not to hit the + ;; same match again. + (if is-next + (forward-char 1)))) + (if (not new-region) + ;; Restore point since we didn't find any new region + (goto-char current-point)) + ;; Return new region. Maybe nil + new-region))))) (defun x509--dwim-next/prev (direction) "Look for a PEM region before of after the current one. @@ -1014,18 +1055,20 @@ if the buffer contains data of certain type." Look at -----BEGIN header for known object types. Then test different openssl commands until one succeeds. Call -`x509-viewasn1' as a last resort." +`x509-viewasn1' as a last resort. +Return the output buffer." (interactive) (pcase (x509--pem-region-type) ((or "CERTIFICATE" "TRUSTED CERTIFICATE") (call-interactively #'x509-viewcert)) ("CERTIFICATE REQUEST" (call-interactively #'x509-viewreq)) ("DH PARAMETERS" (call-interactively #'x509-viewdh)) - ("DC PARAMETERS" (call-interactively #'x509-viewec)) + ("EC PARAMETERS" (call-interactively #'x509-viewec)) ("PKCS7" (call-interactively #'x509-viewpkcs7)) ((or "ENCRYPTED PRIVATE KEY" "PRIVATE KEY" "RSA PRIVATE KEY") (call-interactively #'x509-viewkey)) - ("PUBLIC KEY" (call-interactively #'x509-viewpublickey)) + ((or "PUBLIC KEY" "RSA PUBLIC KEY") + (call-interactively #'x509-viewpublickey)) ("X509 CRL" (call-interactively #'x509-viewcrl)) (_ (cond @@ -1048,6 +1091,62 @@ different openssl commands until one succeeds. Call (t (call-interactively #'x509-viewasn1)))))) +;; --------------------------------------------------------------------------- +;;;###autoload +(defun x509-swoop () + "Find all known BEGIN/END PEM regions i buffer and call `x509-dwim'. +For each region, the result is sent to the same `x509-mode' buffer. +Some functions does not work in a swooped buffer, like next/prev or +toggling to and from `x509-asn1-mode'. The buffer is for static viewing only. + +Return view buffer on success." + (interactive) + (let* ((src-buffer (current-buffer)) + (orig-point (point)) + (swoop-buffer + (generate-new-buffer + (generate-new-buffer-name + (format "*x-%s*" (buffer-name src-buffer)))))) + (goto-char (point-min)) + (while (re-search-forward "-----BEGIN" nil t) + (goto-char (match-beginning 0)) + (when-let* ((tmp-output-buffer (x509-dwim))) + (with-current-buffer tmp-output-buffer + ;; Only consider x509-mode output. We don't want asn1. + (if (eq major-mode 'x509-mode) + (let ((new-content + (buffer-substring-no-properties (point-min) (point-max)))) + (with-current-buffer swoop-buffer + (if (> (buffer-size (current-buffer)) 0) + (insert x509-swoop-separator "\n")) + (insert new-content) + (x509--normalize-buffer-end swoop-buffer) + (goto-char (point-max)))))) + (kill-buffer tmp-output-buffer)) + ;; switch back to src-buffer + (switch-to-buffer src-buffer) + ;; Go forward and look for next region + (forward-char 1)) + ;; Restore point in src-buffer + (goto-char orig-point) + ;; If there is data in the output buffer, switch to it. + (if (> (buffer-size swoop-buffer) 0) + (progn + (switch-to-buffer swoop-buffer) + (x509-mode) + ;; Un-bind keys that doesn't work in s swoop buffer. + (dolist (key '("t" "e" "n" "p")) + (define-key x509-mode-map (kbd key) nil)) + (goto-char (point-min)) + (set-buffer-modified-p nil) + (setq buffer-read-only t) + ;; Return buffer + swoop-buffer) + (kill-buffer swoop-buffer) + (message "No BEGIN/END regions found.") + ;; Return nil + nil))) + ;; ---------------------------------------------------------------------------- ;; asn1-mode