* epg-package-info.el.in (epg-bug-report-address): New constant.
[elisp/epg.git] / epg.el
diff --git a/epg.el b/epg.el
index 3e9bab0..ea0d4a1 100644 (file)
--- a/epg.el
+++ b/epg.el
@@ -33,6 +33,7 @@
 (defvar epg-user-id-alist nil
   "An alist mapping from key ID to user ID.")
 
+(defvar epg-last-status nil)
 (defvar epg-read-point nil)
 (defvar epg-process-filter-running nil)
 (defvar epg-pending-status-list nil)
@@ -329,7 +330,7 @@ This function is for internal use only."
 (defun epg-context-set-passphrase-callback (context passphrase-callback
                                                    &optional handback)
   "Set the function used to query passphrase.
-If optional argument HANDBACK is specified, it is passed to CALLBACK."
+If optional argument HANDBACK is specified, it is passed to PASSPHRASE-CALLBACK."
   (unless (eq (car-safe context) 'epg-context)
     (signal 'wrong-type-argument (list 'epg-context-p context)))
   (aset (cdr context) 7 (if handback
@@ -339,7 +340,7 @@ If optional argument HANDBACK is specified, it is passed to CALLBACK."
 (defun epg-context-set-progress-callback (context progress-callback
                                                  &optional handback)
   "Set the function which handles progress update.
-If optional argument HANDBACK is specified, it is passed to CALLBACK."
+If optional argument HANDBACK is specified, it is passed to PROGRESS-CALLBACK."
   (unless (eq (car-safe context) 'epg-context)
     (signal 'wrong-type-argument (list 'epg-context-p context)))
   (aset (cdr context) 8 (if handback
@@ -948,8 +949,9 @@ This function is for internal use only."
 
 (defun epg-signature-to-string (signature)
   "Convert SIGNATURE to a human readable string."
-  (let ((user-id (cdr (assoc (epg-signature-key-id signature)
-                            epg-user-id-alist))))
+  (let* ((user-id (cdr (assoc (epg-signature-key-id signature)
+                             epg-user-id-alist)))
+        (pubkey-algorithm (epg-signature-pubkey-algorithm signature)))
     (concat
      (cond ((eq (epg-signature-status signature) 'good)
            "Good signature from ")
@@ -972,6 +974,15 @@ This function is for internal use only."
        "")
      (if (epg-signature-validity signature)
         (format " (trust %s)"  (epg-signature-validity signature))
+       "")
+     (if (epg-signature-creation-time signature)
+        (format-time-string " created at %Y-%m-%dT%T%z"
+                            (epg-signature-creation-time signature))
+       "")
+     (if pubkey-algorithm
+        (concat " using "
+                (or (cdr (assq pubkey-algorithm epg-pubkey-algorithm-alist))
+                    (format "(unknown algorithm %d)" pubkey-algorithm)))
        ""))))
 
 (defun epg-verify-result-to-string (verify-result)
@@ -1050,7 +1061,9 @@ This function is for internal use only."
   (let* ((args (append (list "--no-tty"
                             "--status-fd" "1"
                             "--yes")
-                      (if (string-match ":" (or (getenv "GPG_AGENT_INFO") ""))
+                      (if (and (not (eq (epg-context-protocol context) 'CMS))
+                               (string-match ":" (or (getenv "GPG_AGENT_INFO")
+                                                     "")))
                           '("--use-agent"))
                       (if (and (not (eq (epg-context-protocol context) 'CMS))
                                (epg-context-progress-callback context))
@@ -1065,6 +1078,7 @@ This function is for internal use only."
                           (list "--output" (epg-context-output-file context)))
                       args))
         (coding-system-for-write 'binary)
+        (coding-system-for-read 'binary)
         process-connection-type
         (orig-mode (default-file-modes))
         (buffer (generate-new-buffer " *epg*"))
@@ -1081,6 +1095,10 @@ This function is for internal use only."
                           epg-gpg-program)
                          (mapconcat #'identity args " ")))))
     (with-current-buffer buffer
+      (if (fboundp 'set-buffer-multibyte)
+         (set-buffer-multibyte nil))
+      (make-local-variable 'epg-last-status)
+      (setq epg-last-status nil)
       (make-local-variable 'epg-read-point)
       (setq epg-read-point (point-min))
       (make-local-variable 'epg-process-filter-running)
@@ -1133,7 +1151,8 @@ This function is for internal use only."
                            (setq epg-pending-status-list nil))
                        (if (and symbol
                                 (fboundp symbol))
-                           (funcall symbol epg-context string))))
+                           (funcall symbol epg-context string))
+                       (setq epg-last-status (cons status string))))
                  (forward-line)
                  (setq epg-read-point (point))))
            (setq epg-process-filter-running nil))))))
@@ -1174,11 +1193,21 @@ This function is for internal use only."
           (file-exists-p (epg-context-output-file context)))
       (delete-file (epg-context-output-file context))))
 
+(eval-and-compile
+  (if (fboundp 'decode-coding-string)
+      (defalias 'epg--decode-coding-string 'decode-coding-string)
+    (defalias 'epg--decode-coding-string 'identity)))
+
 (defun epg--status-USERID_HINT (context string)
   (if (string-match "\\`\\([^ ]+\\) \\(.*\\)" string)
       (let* ((key-id (match-string 1 string))
             (user-id (match-string 2 string))
             (entry (assoc key-id epg-user-id-alist)))
+       (condition-case nil
+           (setq user-id (epg--decode-coding-string
+                          (epg--decode-percent-escape user-id)
+                          'utf-8))
+         (error))
        (if entry
            (setcdr entry user-id)
          (setq epg-user-id-alist (cons (cons key-id user-id)
@@ -1194,6 +1223,17 @@ This function is for internal use only."
 (defun epg--status-NEED_PASSPHRASE_PIN (context string)
   (setq epg-key-id 'PIN))
 
+(eval-and-compile
+  (if (fboundp 'clear-string)
+      (defalias 'epg--clear-string 'clear-string)
+    (defun epg--clear-string (string)
+      (fillarray string 0))))
+
+(eval-and-compile
+  (if (fboundp 'encode-coding-string)
+      (defalias 'epg--encode-coding-string 'encode-coding-string)
+    (defalias 'epg--encode-coding-string 'identity)))
+
 (defun epg--status-GET_HIDDEN (context string)
   (when (and epg-key-id
             (string-match "\\`passphrase\\." string))
@@ -1222,7 +1262,7 @@ This function is for internal use only."
                  (if epg-passphrase-coding-system
                      (progn
                        (setq encoded-passphrase-with-new-line
-                             (encode-coding-string
+                             (epg--encode-coding-string
                               passphrase-with-new-line
                               (coding-system-change-eol-conversion
                                epg-passphrase-coding-system 'unix)))
@@ -1246,13 +1286,30 @@ This function is for internal use only."
        (if encoded-passphrase-with-new-line
            (epg--clear-string encoded-passphrase-with-new-line))))))
 
+(defun epg--prompt-GET_BOOL (context string)
+  (let ((entry (assoc string epg-prompt-alist)))
+    (y-or-n-p (if entry (cdr entry) (concat string "? ")))))
+
+(defun epg--prompt-GET_BOOL-untrusted_key.override (context string)
+  (y-or-n-p (if (and (equal (car epg-last-status) "USERID_HINT")
+                    (string-match "\\`\\([^ ]+\\) \\(.*\\)"
+                                  (cdr epg-last-status)))
+               (let* ((key-id (match-string 1 (cdr epg-last-status)))
+                      (user-id (match-string 2 (cdr epg-last-status)))
+                      (entry (assoc key-id epg-user-id-alist)))
+                 (if entry
+                     (setq user-id (cdr entry)))
+                 (format "Untrusted key %s %s.  Use anyway? " key-id user-id))
+             "Use untrusted key anyway? ")))
+
 (defun epg--status-GET_BOOL (context string)
-  (let ((entry (assoc string epg-prompt-alist))
-       inhibit-quit)
+  (let (inhibit-quit)
     (condition-case nil
-      (if (y-or-n-p (if entry (cdr entry) (concat string "? ")))
-         (process-send-string (epg-context-process context) "y\n")
-       (process-send-string (epg-context-process context) "n\n"))
+       (if (funcall (or (intern-soft (concat "epg--prompt-GET_BOOL-" string))
+                        #'epg--prompt-GET_BOOL)
+                    context string)
+           (process-send-string (epg-context-process context) "y\n")
+         (process-send-string (epg-context-process context) "n\n"))
       (quit
        (epg-context-set-result-for
        context 'error
@@ -1287,10 +1344,13 @@ This function is for internal use only."
         'verify
         (cons (epg-make-signature status key-id)
               (epg-context-result-for context 'verify)))
-       (if (eq (epg-context-protocol context) 'CMS)
-           (condition-case nil
+       (condition-case nil
+           (if (eq (epg-context-protocol context) 'CMS)
                (setq user-id (epg-dn-from-string user-id))
-             (error)))
+             (setq user-id (epg--decode-coding-string
+                            (epg--decode-percent-escape user-id)
+                            'utf-8)))
+         (error))
        (if entry
            (setcdr entry user-id)
          (setq epg-user-id-alist
@@ -1368,9 +1428,10 @@ This function is for internal use only."
       (epg-signature-set-creation-time
        signature
        (epg--time-from-seconds (match-string 2 string)))
-      (epg-signature-set-expiration-time
-       signature
-       (epg--time-from-seconds (match-string 3 string)))
+      (unless (equal (match-string 3 string) "0")
+       (epg-signature-set-expiration-time
+        signature
+        (epg--time-from-seconds (match-string 3 string))))
       (epg-signature-set-version
        signature
        (string-to-number (match-string 4 string)))
@@ -1452,6 +1513,15 @@ This function is for internal use only."
               (if (consp (epg-context-progress-callback context))
                   (cdr (epg-context-progress-callback context))))))
 
+(defun epg--status-ENC_TO (context string)
+  (if (string-match "\\`\\([0-9A-Za-z]+\\) \\([0-9]+\\) \\([0-9]+\\)" string)
+      (epg-context-set-result-for
+       context 'encrypted-to
+       (cons (list (match-string 1 string)
+                  (string-to-number (match-string 2 string))
+                  (string-to-number (match-string 3 string)))
+            (epg-context-result-for context 'encrypted-to)))))
+
 (defun epg--status-DECRYPTION_FAILED (context string)
   (epg-context-set-result-for context 'decryption-failed t))
 
@@ -1548,6 +1618,11 @@ This function is for internal use only."
       (let* ((key-id (match-string 1 string))
             (user-id (match-string 2 string))
             (entry (assoc key-id epg-user-id-alist)))
+       (condition-case nil
+           (setq user-id (epg--decode-coding-string
+                          (epg--decode-percent-escape user-id)
+                          'utf-8))
+         (error))
        (if entry
            (setcdr entry user-id)
          (setq epg-user-id-alist (cons (cons key-id user-id)
@@ -1618,18 +1693,25 @@ This function is for internal use only."
 (defun epg--list-keys-1 (context name mode)
   (let ((args (append (if epg-gpg-home-directory
                          (list "--homedir" epg-gpg-home-directory))
-                     (list "--with-colons" "--no-greeting" "--batch"
-                           "--with-fingerprint"
-                           "--with-fingerprint"
-                           (if (memq mode '(t secret))
-                               "--list-secret-keys"
-                             (if (memq mode '(nil public))
-                                 "--list-keys"
-                               "--list-sigs")))
+                     '("--with-colons" "--no-greeting" "--batch"
+                       "--with-fingerprint" "--with-fingerprint")
                      (unless (eq (epg-context-protocol context) 'CMS)
-                       '("--fixed-list-mode"))
-                     (if name (list name))))
+                       '("--fixed-list-mode"))))
+       (list-keys-option (if (memq mode '(t secret))
+                             "--list-secret-keys"
+                           (if (memq mode '(nil public))
+                               "--list-keys"
+                             "--list-sigs")))
+       (coding-system-for-read 'binary)
        keys string field index)
+    (if name
+       (progn
+         (unless (listp name)
+           (setq name (list name)))
+         (while name
+           (setq args (append args (list list-keys-option (car name)))
+                 name (cdr name))))
+      (setq args (append args (list list-keys-option))))
     (with-temp-buffer
       (apply #'call-process
             (if (eq (epg-context-protocol context) 'CMS)
@@ -1661,7 +1743,8 @@ This function is for internal use only."
    (string-to-number (aref line 2))
    (aref line 4)
    (epg--time-from-seconds (aref line 5))
-   (epg--time-from-seconds (aref line 6))))
+   (if (aref line 6)
+       (epg--time-from-seconds (aref line 6)))))
 
 ;;;###autoload
 (defun epg-list-keys (context &optional name mode)
@@ -1669,9 +1752,10 @@ This function is for internal use only."
 If MODE is nil or 'public, only public keyring should be searched.
 If MODE is t or 'secret, only secret keyring should be searched. 
 Otherwise, only public keyring should be searched and the key
-signatures should be included."
+signatures should be included.
+NAME is either a string or a list of strings."
   (let ((lines (epg--list-keys-1 context name mode))
-       keys cert pointer pointer-1)
+       keys cert pointer pointer-1 index string)
     (while lines
       (cond
        ((member (aref (car lines) 0) '("pub" "sec" "crt" "crs"))
@@ -1691,6 +1775,19 @@ signatures should be included."
         (cons (epg--make-sub-key-1 (car lines))
               (epg-key-sub-key-list (car keys)))))
        ((equal (aref (car lines) 0) "uid")
+       ;; Decode the UID name as a backslash escaped UTF-8 string,
+       ;; generated by GnuPG/GpgSM.
+       (setq string (copy-sequence (aref (car lines) 9))
+             index 0)
+       (while (string-match "\"" string index)
+         (setq string (replace-match "\\\"" t t string)
+               index (1+ (match-end 0))))
+       (condition-case nil
+           (setq string (epg--decode-coding-string
+                         (car (read-from-string (concat "\"" string "\"")))
+                         'utf-8))
+         (error
+          (setq string (aref (car lines) 9))))
        (epg-key-set-user-id-list
         (car keys)
         (cons (epg-make-user-id
@@ -1699,9 +1796,9 @@ signatures should be included."
                               epg-key-validity-alist)))
                (if cert
                    (condition-case nil
-                       (epg-dn-from-string (aref (car lines) 9))
-                     (error (aref (car lines) 9)))
-                 (aref (car lines) 9)))
+                       (epg-dn-from-string string)
+                     (error string))
+                 string))
               (epg-key-user-id-list (car keys)))))
        ((equal (aref (car lines) 0) "fpr")
        (epg-sub-key-set-fingerprint (car (epg-key-sub-key-list (car keys)))
@@ -1741,64 +1838,60 @@ signatures should be included."
       (setq pointer (cdr pointer)))
     keys))
 
-(if (fboundp 'make-temp-file)
-    (defalias 'epg--make-temp-file 'make-temp-file)
-  (defvar temporary-file-directory)
-  ;; stolen from poe.el.
-  (defun epg--make-temp-file (prefix)
-    "Create a temporary file.
+(eval-and-compile
+  (if (fboundp 'make-temp-file)
+      (defalias 'epg--make-temp-file 'make-temp-file)
+    (defvar temporary-file-directory)
+    ;; stolen from poe.el.
+    (defun epg--make-temp-file (prefix)
+      "Create a temporary file.
 The returned file name (created by appending some random characters at the end
 of PREFIX, and expanding against `temporary-file-directory' if necessary),
 is guaranteed to point to a newly created empty file.
 You can then use `write-region' to write new data into the file."
-    (let (tempdir tempfile)
-      (setq prefix (expand-file-name prefix
-                                    (if (featurep 'xemacs)
-                                        (temp-directory)
-                                      temporary-file-directory)))
-      (unwind-protect
-         (let (file)
-           ;; First, create a temporary directory.
-           (while (condition-case ()
-                      (progn
-                        (setq tempdir (make-temp-name
-                                       (concat
-                                        (file-name-directory prefix)
-                                        "DIR")))
-                        ;; return nil or signal an error.
-                        (make-directory tempdir))
-                    ;; let's try again.
-                    (file-already-exists t)))
-           (set-file-modes tempdir 448)
-           ;; Second, create a temporary file in the tempdir.
-           ;; There *is* a race condition between `make-temp-name'
-           ;; and `write-region', but we don't care it since we are
-           ;; in a private directory now.
-           (setq tempfile (make-temp-name (concat tempdir "/EMU")))
-           (write-region "" nil tempfile nil 'silent)
-           (set-file-modes tempfile 384)
-           ;; Finally, make a hard-link from the tempfile.
-           (while (condition-case ()
-                      (progn
-                        (setq file (make-temp-name prefix))
-                        ;; return nil or signal an error.
-                        (add-name-to-file tempfile file))
-                    ;; let's try again.
-                    (file-already-exists t)))
-           file)
-       ;; Cleanup the tempfile.
-       (and tempfile
-            (file-exists-p tempfile)
-            (delete-file tempfile))
-       ;; Cleanup the tempdir.
-       (and tempdir
-            (file-directory-p tempdir)
-            (delete-directory tempdir))))))
-
-(if (fboundp 'clear-string)
-    (defalias 'epg--clear-string 'clear-string)
-  (defun epg--clear-string (string)
-    (fillarray string 0)))
+      (let (tempdir tempfile)
+       (setq prefix (expand-file-name prefix
+                                      (if (featurep 'xemacs)
+                                          (temp-directory)
+                                        temporary-file-directory)))
+       (unwind-protect
+           (let (file)
+             ;; First, create a temporary directory.
+             (while (condition-case ()
+                        (progn
+                          (setq tempdir (make-temp-name
+                                         (concat
+                                          (file-name-directory prefix)
+                                          "DIR")))
+                          ;; return nil or signal an error.
+                          (make-directory tempdir))
+                      ;; let's try again.
+                      (file-already-exists t)))
+             (set-file-modes tempdir 448)
+             ;; Second, create a temporary file in the tempdir.
+             ;; There *is* a race condition between `make-temp-name'
+             ;; and `write-region', but we don't care it since we are
+             ;; in a private directory now.
+             (setq tempfile (make-temp-name (concat tempdir "/EMU")))
+             (write-region "" nil tempfile nil 'silent)
+             (set-file-modes tempfile 384)
+             ;; Finally, make a hard-link from the tempfile.
+             (while (condition-case ()
+                        (progn
+                          (setq file (make-temp-name prefix))
+                          ;; return nil or signal an error.
+                          (add-name-to-file tempfile file))
+                      ;; let's try again.
+                      (file-already-exists t)))
+             file)
+         ;; Cleanup the tempfile.
+         (and tempfile
+              (file-exists-p tempfile)
+              (delete-file tempfile))
+         ;; Cleanup the tempdir.
+         (and tempdir
+              (file-directory-p tempdir)
+              (delete-directory tempdir)))))))
 
 (defun epg--args-from-sig-notations (notations)
   (apply #'nconc
@@ -2076,20 +2169,37 @@ Otherwise, it makes a cleartext signature."
 If optional 3rd argument MODE is t or 'detached, it makes a detached signature.
 If it is nil or 'normal, it makes a normal signature.
 Otherwise, it makes a cleartext signature."
-  (unwind-protect
-      (progn
-       (epg-context-set-output-file context
-                                    (epg--make-temp-file "epg-output"))
-       (epg-start-sign context (epg-make-data-from-string plain) mode)
-       (epg-wait-for-completion context)
-       (unless (epg-context-result-for context 'sign)
-         (if (epg-context-result-for context 'error)
-             (error "Sign failed: %S"
-                    (epg-context-result-for context 'error))
-           (error "Sign failed")))
-       (epg-read-output context))
-    (epg-delete-output-file context)
-    (epg-reset context)))
+  (let ((input-file
+        (unless (or (eq (epg-context-protocol context) 'CMS)
+                    (condition-case nil
+                        (progn
+                          (epg-check-configuration (epg-configuration))
+                          t)
+                      (error)))
+          (epg--make-temp-file "epg-input")))
+       (coding-system-for-write 'binary))
+    (unwind-protect
+       (progn
+         (epg-context-set-output-file context
+                                      (epg--make-temp-file "epg-output"))
+         (if input-file
+             (write-region plain nil input-file nil 'quiet))
+         (epg-start-sign context
+                         (if input-file
+                             (epg-make-data-from-file input-file)
+                           (epg-make-data-from-string plain))
+                         mode)
+         (epg-wait-for-completion context)
+         (unless (epg-context-result-for context 'sign)
+           (if (epg-context-result-for context 'error)
+               (error "Sign failed: %S"
+                      (epg-context-result-for context 'error))
+             (error "Sign failed")))
+         (epg-read-output context))
+      (epg-delete-output-file context)
+      (if input-file
+         (delete-file input-file))
+      (epg-reset context))))
 
 ;;;###autoload
 (defun epg-start-encrypt (context plain recipients
@@ -2177,25 +2287,42 @@ If RECIPIENTS is nil, it performs symmetric encryption."
                                   &optional sign always-trust)
   "Encrypt a string PLAIN.
 If RECIPIENTS is nil, it performs symmetric encryption."
-  (unwind-protect
-      (progn
-       (epg-context-set-output-file context
-                                    (epg--make-temp-file "epg-output"))
-       (epg-start-encrypt context (epg-make-data-from-string plain)
-                          recipients sign always-trust)
-       (epg-wait-for-completion context)
-       (if (and sign
-                (not (epg-context-result-for context 'sign)))
-           (if (epg-context-result-for context 'error)
-               (error "Sign failed: %S"
-                      (epg-context-result-for context 'error))
-             (error "Sign failed")))
-       (if (epg-context-result-for context 'error)
-           (error "Encrypt failed: %S"
-                  (epg-context-result-for context 'error)))
-       (epg-read-output context))
-    (epg-delete-output-file context)
-    (epg-reset context)))
+  (let ((input-file
+        (unless (or (not sign)
+                    (eq (epg-context-protocol context) 'CMS)
+                    (condition-case nil
+                        (progn
+                          (epg-check-configuration (epg-configuration))
+                          t)
+                      (error)))
+          (epg--make-temp-file "epg-input")))
+       (coding-system-for-write 'binary))
+    (unwind-protect
+       (progn
+         (epg-context-set-output-file context
+                                      (epg--make-temp-file "epg-output"))
+         (if input-file
+             (write-region plain nil input-file nil 'quiet))
+         (epg-start-encrypt context
+                            (if input-file
+                                (epg-make-data-from-file input-file)
+                              (epg-make-data-from-string plain))
+                            recipients sign always-trust)
+         (epg-wait-for-completion context)
+         (if (and sign
+                  (not (epg-context-result-for context 'sign)))
+             (if (epg-context-result-for context 'error)
+                 (error "Sign failed: %S"
+                        (epg-context-result-for context 'error))
+               (error "Sign failed")))
+         (if (epg-context-result-for context 'error)
+             (error "Encrypt failed: %S"
+                    (epg-context-result-for context 'error)))
+         (epg-read-output context))
+      (epg-delete-output-file context)
+      (if input-file
+         (delete-file input-file))
+      (epg-reset context))))
 
 ;;;###autoload
 (defun epg-start-export-keys (context keys)
@@ -2220,7 +2347,7 @@ If you are unsure, use synchronous version of this function
   "Extract public KEYS."
   (unwind-protect
       (progn
-       (if keys
+       (if file
            (epg-context-set-output-file context file)
          (epg-context-set-output-file context
                                       (epg--make-temp-file "epg-output")))
@@ -2344,13 +2471,13 @@ If you are unsure, use synchronous version of this function
          (if entry
              (if (setq entry (assq (cdr entry)
                                    epg-delete-problem-reason-alist))
-                 (error "Delete keys failed: %s" (cdr entry)))
-           (error "Delete keys failed" (cdr entry)))))
+                 (error "Delete keys failed: %s" (cdr entry))
+               (error "Delete keys failed")))))
     (epg-reset context)))
 
 ;;;###autoload
 (defun epg-start-sign-keys (context keys &optional local)
-  "Initiate an sign keys operation.
+  "Initiate a sign keys operation.
 
 If you use this function, you will need to wait for the completion of
 `epg-gpg-program' by using `epg-wait-for-completion' and call
@@ -2430,27 +2557,42 @@ PARAMETERS is a string which tells how to create the key."
                   (epg-context-result-for context 'error))))
     (epg-reset context)))
 
+(defun epg--decode-percent-escape (string)
+  (let ((index 0))
+    (while (string-match "%\\(\\(%\\)\\|\\([0-9A-Fa-f][0-9A-Fa-f]\\)\\)"
+                        string index)
+      (if (match-beginning 2)
+         (setq string (replace-match "%" t t string)
+               index (1- (match-end 0)))
+       (setq string (replace-match
+                     (string (string-to-number (match-string 3 string) 16))
+                     t t string)
+             index (- (match-end 0) 2))))
+    string))
+
 (defun epg--decode-hexstring (string)
   (let ((index 0))
     (while (eq index (string-match "[0-9A-Fa-f][0-9A-Fa-f]" string index))
-      (setq string (replace-match "\\x\\&" t nil string)
-           index (+ index 4)))
-    (car (read-from-string (concat "\"" string "\"")))))
+      (setq string (replace-match (string (string-to-number
+                                          (match-string 0 string) 16))
+                                 t t string)
+           index (1- (match-end 0))))
+    string))
 
 (defun epg--decode-quotedstring (string)
   (let ((index 0))
     (while (string-match "\\\\\\(\\([,=+<>#;\\\"]\\)\\|\
-\\([0-9A-Fa-f][0-9A-Fa-f]\\)\\|\\(.\\)\\)"
+\\([0-9A-Fa-f][0-9A-Fa-f]\\)\\)"
                         string index)
       (if (match-beginning 2)
          (setq string (replace-match "\\2" t nil string)
-               index (1+ index))
+               index (1- (match-end 0)))
        (if (match-beginning 3)
-           (setq string (replace-match "\\x\\3" t nil string)
-                 index (+ index 4))
-         (setq string (replace-match "\\\\\\\\\\4" t nil string)
-               index (+ index 3)))))
-    (car (read-from-string (concat "\"" string "\"")))))
+           (setq string (replace-match (string (string-to-number
+                                                (match-string 0 string) 16))
+                                       t t string)
+                 index (- (match-end 0) 2)))))
+    string))
 
 (defun epg-dn-from-string (string)
   "Parse STRING as LADPv3 Distinguished Names (RFC2253).