last week i put together a few emacs functions that were
of use in composing S/MIME messages with emacs's mail programs.

i just thought it may be of interest to someone.

ps. - Note the shortcomings mentioned below.
    - You need a relatively recent version of emacs since
      this emacs library uses defcustom and defgroup.
    - testing
    - These functions have been tested mainly on NTEmacs.  I
      have also tried them out on a linux version of emacs and
      good success.  hope you have the same.
-george
---------------------- cut here -------------------------
;; tinysmime.el version 0.01
;; 4/3/00

;; Here are a few user functions for easing the generation
;; of S/MIME messages using openssl's smime program from emacs
;;  
;; The user functions provided are: 
;;    tinysmime-encrypt
;;    tinysmime-sign
;;    tinysmime-decrypt
;;    tinysmime-verify
;;    tinysmime-encrypt-then-sign
;;    tinysmime-sign-then-encrypt
;;    tinysmime-decrypt-then-verify
;;    tinysmime-verify-then-decrypt

;; The last two on the list are convenience functions: they envoke 
;; tinysmime-verify and tinysmime-decrypt appropriately in turn.

;; Given a buffer in the ubiquitous email format -- message headers plus
;; blank line (or the "--text follows this line--" line) plus message
;; body -- these functions will work their and openssl's magic on the buffer.

;; If an arbitrary prefix argument is given to tinysmime-sign, 
;; tinysmime-sign-then-encrypt, or tinysmime-encrypt-then-sign, the 
;; signature generated will be opaque (ie. not detached).

;; Note: Both tinysmime-encrypt and tinysmime-sign assume the body of the 
;; message to be processed is of type text/plain -- the existing 
;; Content-Type of the message is ignored.  This is one reason 
;; tinysmime-encrypt-then-sign and tinysmime-sign-then-encrypt do not 
;; simply envoke tinysmime-sign and tinysmime-encrypt in turn, that is 
;; they are not convenience functions

;; The user functions here use the elisp function call-process-region  
;; to pass (and read) the plaintext or ciphertext input to (and output 
;; from) openssl.  This has the advantage that plaintext is not
;; written to disk, but it also has the disadvantage that openssl is 
;; prevented from prompting for the private key's passphrase.  

;; The best workaround (of wrapping the emacs executable with a script that 
;; decrypts the private key, then envokes emacs, and when emacs finishes
;; deletes the plaintext private key) still has serious shortcomings.

;; The library defines a customization group called tinysmime as follows:  
;; (At a minimum the variables in the general subgroup below should be set.)

;; 1) general:
;;       tinysmime-openssl-name, tinysmime-my-dir, tinysmime-my-cert-name,
;;       tinysmime-private-key-name
;; 2) passed to openssl whenever it is invoked (unless nil):
;;       tinysmime-rand-name
;; 3) passed to openssl only when signing (unless nil):
;;       tinysmime-sign-option-nocerts, tinysmime-sign-option-noattr,
;;       tinysmime-sign-certs-to-include-name
;; 4) passed to openssl only when verifying (unless nil):
;;       tinysmime-verify-external-certs-name, tinysmime-verify-capath-dir
;;       tinysmime-verify-cafile-name, tinysmime-verify-option-nosig
;;       tinysmime-verify-option-nochain, tinysmime-verify-option-noverify
;;       tinysmime-verify-option-nointern
;; 5) passed to openssl only when encrypting (unless nil):
;;       tinysmime-encrypt-alg
;; 6) default list of certificates to choose from when encrypting (optional)
;;       tinysmime-encrypt-cert-names

;; Installation: 
;;   1) include "(load "tinysmime")" in your .emacs file
;;   2) set the custom variables you wish preset using setq in .emacs
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defgroup tinysmime nil
  "Functions for manipulating smime messages"
  :prefix "tinysmime-"
  :group 'mail)

;; primary custom options

(defcustom tinysmime-openssl-name "openssl"
  "*Name of openssl executable (version 0.9.5 or greater).
   Provide a fully qualified filename if executable is not in PATH
   since filename will not be expanded."
  :type 'string
  :group 'tinysmime)

(defcustom tinysmime-my-dir ""
  "*Directory of your certificates and private keys"
  :type 'string
  :group 'tinysmime)

(defcustom tinysmime-my-cert-name ""
  "*Name of your x509 certificate"
  :type 'string
  :group 'tinysmime)

(defcustom tinysmime-my-private-key-name ""
  "*Name of your RSA private key"
  :type 'string
  :group 'tinysmime)

;; extended custom options (general)

(defcustom tinysmime-rand-name nil
  "*Name of file(s) to help seed the random number generator.
   Use the appropriate delimiter mentioned by the openssl smime 
   documentation if supplying more than one file." 
  :type '(choice string (const nil))
  :group 'tinysmime)

;; extended custom options (for signing)

(defcustom tinysmime-sign-option-nocerts nil
  "*Enable the -nocerts option while signing -- causing 
   the signer's certificate to not be included."
  :type 'boolean
  :group 'tinysmime)

(defcustom tinysmime-sign-option-noattr nil
  "*Enable the -noattr option while signing -- causing
   signing attributes to not be set."
  :type 'boolean
  :group 'tinysmime)

(defcustom tinysmime-sign-certs-to-include-name nil
  "*Name of file that has the certificate(s), beyond
   the signer's certificate, which are to be included with 
   a message while signing"
  :type '(choice string (const nil))
  :group 'tinysmime)

;; extended custom options (for verifying)

(defcustom tinysmime-verify-option-nointern nil
  "*Enable the -nointern option while verifying -- causing the 
   certificates included in the message not to be used, but 
   rather certificates named by tinysmime-verify-external-certs-name."   
  :type 'boolean
  :group 'tinysmime)

(defcustom tinysmime-verify-option-noverify t
  "*Enable the -noverify option while verifying -- causing
   the certificates themselves not to be verified."
  :type 'boolean
  :group 'tinysmime)

(defcustom tinysmime-verify-option-nochain nil
  "*Enable the -nochain option while verifying -- causes
   the certificate verification not to be chained."
  :type 'boolean
  :group 'tinysmime)

(defcustom tinysmime-verify-option-nosig nil
  "*Enable the -nosig option while verifying -- causing
   the message signature not to be verified."
  :type 'boolean
  :group 'tinysmime)

(defcustom tinysmime-verify-cafile-name nil
  "*Name of file with trusted CA certificates"
  :type '(choice string (const nil))
  :group 'tinysmime)

(defcustom tinysmime-verify-capath-dir nil
  "*Name of directory with hash of trusted CA certificates"
  :type '(choice string (const nil))
  :group 'tinysmime)

(defcustom tinysmime-verify-external-certs-name nil
  "*Name of file that has the certificate(s) for 
  verifying the signature(s) on the message.  See the 
  tinysmime-verify-option-nointern variable."
  :type '(choice string (const nil))
  :group 'tinysmime)

;; extended custom options (for encrypting)

(defcustom tinysmime-encrypt-alg nil
  "*Select desired encryption algorithm"
  :type '(choice (const nil)
                 (const "-des")
                 (const "-des3")
                 (const "-rc2-40")
                 (const "-rc2-64")
                 (const "-rc2-128"))
  :group 'tinysmime)

(defcustom tinysmime-encrypt-cert-names nil
  "*List of certificates, which will be provided as possible
   completions when queried for the certificate to use to
   encrypt your message."
  :type '(repeat string)
  :group 'tinysmime)

;; low-level non-interactive routines

(defvar tinysmime-history tinysmime-encrypt-cert-names)

(defun tinysmime-cert-prompt ()
  (read-from-minibuffer "Certificate: " nil nil nil 
                        'tinysmime-history))

(defun tinysmime-message (a)
  (and a 
       (princ (concat (car a) " ") t) 
       (tinysmime-message (cdr a))))

(defun tinysmime-expand (userfile)
  (expand-file-name 
      (substitute-in-file-name userfile)
      (substitute-in-file-name tinysmime-my-dir)))

(defun tinysmime-wash-headers (min)
  (goto-char (point-min))
  (while (re-search-forward "^Content-" min t)
         (setq min (+ min 2))
         (replace-match "X-Content-"))
  (goto-char (point-min))
  (while (re-search-forward "^MIME-" min t)
         (setq min (+ min 2))
         (replace-match "X-MIME-"))
  min)

(defun tinysmime-encode (arg-list &optional keep)
  "setup buffer for encoding then call openssl"
  (goto-char (point-min))
  (let ((delim (re-search-forward "^--text follows this line--" nil t)))
       (cond (delim
              (beginning-of-line)
              (kill-line)))
       (goto-char (point-min))
       (re-search-forward "^\r?$" nil t)
       (beginning-of-line)
       (if keep (next-line 1) (kill-line 1))
       (let ((min (tinysmime-wash-headers (point))))
            (goto-char min)
            (tinysmime-message arg-list)
            (apply 'call-process-region
                   min (point-max) 
                   (substitute-in-file-name tinysmime-openssl-name)
                   t t nil arg-list)
            (goto-char min)
            (if (re-search-forward 
                 "Loading 'screen' into random state -" (+ min 40) t)
                (replace-match ""))
            (goto-char (point-max))
            (if (re-search-backward "done" (- (point-max) 10) t)
                (replace-match ""))
            (cond (delim
                   (goto-char (point-min))
                   (re-search-forward "^\r?$" nil t) 
                   (beginning-of-line)
                   (kill-line)
                   (insert "--text follows this line--\n")))
            (goto-char min))))

(defun tinysmime-decode (arg-list)
  "setup buffer for decoding then call openssl"
  (goto-char (point-min))
  (let ((delim (re-search-forward "^--text follows this line--" nil t)))
       (cond (delim
              (beginning-of-line)
              (kill-line)))
       (goto-char (point-min))
       (re-search-forward "^\r?$" nil t)
       (let ((min (point)))
            (copy-region-as-kill (point-min) min)
            (yank)
            (setq min (tinysmime-wash-headers min))
            (goto-char min)
            (tinysmime-message arg-list)
            (apply 'call-process-region
                   min (point-max) 
                   (substitute-in-file-name tinysmime-openssl-name)
                   t t nil arg-list)
            (goto-char (point-max))
            (if (re-search-backward "Verification Successful" nil t)
                (replace-match ""))
            (cond (delim
                 (goto-char (point-min))
                 (re-search-forward "^\r?$" nil t) 
                 (beginning-of-line)
                 (kill-line)
                 (insert "--text follows this line--\n")))
            (goto-char min))))

(defun tinysmime-params-verify ()
  (append (list "smime" "-verify")
          (if tinysmime-rand-name 
              (list "-rand" (tinysmime-expand tinysmime-rand-name)))
          (if tinysmime-verify-option-nosig '("-nosig"))
          (if tinysmime-verify-option-noverify '("-noverify"))
          (if tinysmime-verify-option-nochain '("-nochain"))
          (if tinysmime-verify-option-nointern '("-nointern"))
          (if tinysmime-verify-external-certs-name
              (list "-certfile" 
                    (tinysmime-expand tinysmime-verify-external-certs-name)))
          (if tinysmime-verify-capath-dir 
              (list "-CApath" (expand-file-name (substitute-in-file-name 
                                                tinysmime-verify-capath-dir))))
          (if tinysmime-verify-cafile-name 
              (list "-CAfile" 
                    (tinysmime-expand tinysmime-verify-cafile-name)))))

(defun tinysmime-params-decrypt ()
  (append (list "smime" "-decrypt"
                "-inkey" (tinysmime-expand tinysmime-my-private-key-name)
                "-recip" (tinysmime-expand tinysmime-my-cert-name))
          (if tinysmime-rand-name 
              (list "-rand" (tinysmime-expand tinysmime-rand-name)))))

(defun tinysmime-params-encrypt (&optional notext)
  (append (list "smime" "-encrypt")
          (if tinysmime-rand-name 
              (list "-rand" (tinysmime-expand tinysmime-rand-name)))
          (if tinysmime-encrypt-alg (list tinysmime-encrypt-alg))
          (if (not notext) '("-text"))
          (list (tinysmime-expand (tinysmime-cert-prompt)))))

(defun tinysmime-params-sign (nodetach &optional notext)
  (append (list "smime" "-sign"         
                "-signer" (tinysmime-expand tinysmime-my-cert-name)
                "-inkey" (tinysmime-expand tinysmime-my-private-key-name))
          (if tinysmime-rand-name (list "-rand" tinysmime-rand-name))
          (if tinysmime-sign-option-nocerts '("-nocerts"))
          (if tinysmime-sign-option-noattr '("-noattr"))
          (if tinysmime-sign-certs-to-include-name
              (list "-certfile" 
                    (tinysmime-expand tinysmime-sign-certs-to-include-name)))
          (if (not notext) '("-text"))
          (if nodetach '("-nodetach"))))

;; interactive commands (encrypting/signing)

(defun tinysmime-encrypt ()
  "s/mime encrypt the current buffer"
  (interactive)
  (tinysmime-encode (tinysmime-params-encrypt)))

(defun tinysmime-sign (&optional nodetach)
  "s/mime sign the current buffer"
  (interactive "P")
  (tinysmime-encode (tinysmime-params-sign nodetach)))

(defun tinysmime-sign-then-encrypt (&optional nodetach)
  "s/mime sign and then encrypt the current buffer"
  (interactive "P")
  (tinysmime-encode (tinysmime-params-sign nodetach) t)
  (tinysmime-encode (tinysmime-params-encrypt t)))

(defun tinysmime-encrypt-then-sign (&optional nodetach)
  "s/mime encrypt and then sign the current buffer"
  (interactive "P")
  (tinysmime-encode (tinysmime-params-encrypt) t)
  (tinysmime-encode (tinysmime-params-sign nodetach t)))

;; interactive commands (decrypting/verifying)

(defun tinysmime-decrypt ()
  "s/mime decrypt the current buffer"
  (interactive)
  (tinysmime-decode (tinysmime-params-decrypt)))

(defun tinysmime-verify ()
  "s/mime verify the current buffer"
  (interactive)
  (tinysmime-decode (tinysmime-params-verify)))

;; interactive commands (convenience routines)

(defun tinysmime-verify-then-decrypt ()
  "s/mime verify and then decrypt the current buffer"
  (interactive)
  (tinysmime-decode (tinysmime-params-verify))
  (tinysmime-decode (tinysmime-params-decrypt)))

(defun tinysmime-decrypt-then-verify ()
  "s/mime decrypt and then verify the current buffer"
  (interactive)
  (tinysmime-decode (tinysmime-params-decrypt))
  (tinysmime-decode (tinysmime-params-verify)))



smime.p7s

Reply via email to