Subject: [PATCH] openpgp: Add support for ECDSA with NIST curves.

From: Ludovic Courtès <[email-masked]>
Date: Mon, 5 Sep 2022 18:09:29 +0200

Fixes <https://issues.guix.gnu.org/57576>.
Reported by Zhu Zihao <[email-masked]>.

* guix/openpgp.scm (verify-openpgp-signature): Add case for ecdsa.
(get-signature): Likewise for PUBLIC-KEY-ECDSA.
(get-public-key): Likewise.
* tests/keys/secp384.pub, tests/keys/secp384.sec,
tests/keys/secp521.pub, tests/keys/secp521.sec: New files.
* Makefile.am (EXTRA_DIST): Add them.
* tests/openpgp.scm (%secp384-key-id, %secp384-key-fingerprint)
(%hello-signature/secp384/sha384)
(%secp521-key-id, %secp521-key-fingerprint)
(%hello-signature/secp521/sha521): New variables.
* tests/openpgp.scm ("get-openpgp-detached-signature/ascii")
("verify-openpgp-signature, good signatures")
("verify-openpgp-signature, bad signature"): Check with
the secp384 and secp521 curves.
---
 Makefile.am            |  4 +++
 guix/openpgp.scm       | 41 +++++++++++++++++++++-----
 tests/keys/secp384.pub | 11 +++++++
 tests/keys/secp384.sec | 12 ++++++++
 tests/keys/secp521.pub | 13 +++++++++
 tests/keys/secp521.sec | 14 +++++++++
 tests/openpgp.scm      | 66 +++++++++++++++++++++++++++++++++++-------
 7 files changed, 143 insertions(+), 18 deletions(-)
 create mode 100644 tests/keys/secp384.pub
 create mode 100644 tests/keys/secp384.sec
 create mode 100644 tests/keys/secp521.pub
 create mode 100644 tests/keys/secp521.sec

diff --git a/Makefile.am b/Makefile.am
index a0c4e941c1..71c3bd4a98 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -702,6 +702,10 @@ EXTRA_DIST +=						\
   tests/keys/ed25519-2.sec				\
   tests/keys/ed25519-3.pub				\
   tests/keys/ed25519-3.sec				\
+  tests/keys/secp384.pub				\
+  tests/keys/secp384.sec				\
+  tests/keys/secp521.pub				\
+  tests/keys/secp521.sec				\
   build-aux/config.rpath				\
   bootstrap						\
   doc/build.scm						\
diff --git a/guix/openpgp.scm b/guix/openpgp.scm
index 9de7feb644..b999c30474 100644
--- a/guix/openpgp.scm
+++ b/guix/openpgp.scm
@@ -1,6 +1,6 @@
 ;; -*- mode: scheme; coding: utf-8 -*-
 ;; Copyright © 2010, 2012 Göran Weinholt <[email-masked]>
-;; Copyright © 2020 Ludovic Courtès <[email-masked]>
+;; Copyright © 2020, 2022 Ludovic Courtès <[email-masked]>
 
 ;; Permission is hereby granted, free of charge, to any person obtaining a
 ;; copy of this software and associated documentation files (the "Software"),
@@ -290,7 +290,7 @@ (define PUBLIC-KEY-RSA-SIGN-ONLY 3)
 (define PUBLIC-KEY-ELGAMAL-ENCRYPT-ONLY 16)
 (define PUBLIC-KEY-DSA 17)
 (define PUBLIC-KEY-ECDH 18)                       ;RFC-6637
-(define PUBLIC-KEY-ECDSA 19)                      ;RFC-6639
+(define PUBLIC-KEY-ECDSA 19)                      ;RFC-6637
 (define PUBLIC-KEY-ELGAMAL 20)                    ;encrypt + sign (legacy)
 (define PUBLIC-KEY-EDDSA 22)                      ;"not yet assigned" says GPG
 
@@ -298,6 +298,7 @@ (define (public-key-algorithm id)
   (cond ((= id PUBLIC-KEY-RSA) 'rsa)
         ((= id PUBLIC-KEY-DSA) 'dsa)
         ((= id PUBLIC-KEY-ELGAMAL-ENCRYPT-ONLY) 'elgamal)
+        ((= id PUBLIC-KEY-ECDSA) 'ecdsa)
         ((= id PUBLIC-KEY-EDDSA) 'eddsa)
         (else id)))
 
@@ -564,10 +565,16 @@ (define (check key sig)
               ;; See "(gcrypt) Cryptographic Functions".
               (sexp->canonical-sexp
                (if (eq? key-type 'ecc)
-                   `(data
-                     (flags eddsa)
-                     (hash-algo sha512)
-                     (value ,hash))
+                   (match (openpgp-signature-public-key-algorithm sig)
+                     ('eddsa
+                      `(data
+                        (flags eddsa)
+                        (hash-algo sha512)
+                        (value ,hash)))
+                     ('ecdsa
+                      `(data
+                        (hash-algo ,(openpgp-signature-hash-algorithm sig))
+                        (value ,hash))))
                    `(data
                      (flags ,(match key-type
                                ('rsa 'pkcs1)
@@ -615,7 +622,8 @@ (define (get-sig p pkalg)
              (string->canonical-sexp
               (format #f "(sig-val (dsa (r #~a#) (s #~a#)))"
                       (->hex r) (->hex s)))))
-          ((= pkalg PUBLIC-KEY-EDDSA)
+          ((or (= pkalg PUBLIC-KEY-EDDSA)
+               (= pkalg PUBLIC-KEY-ECDSA))
            (print "EdDSA signature")
            (let ((r (get-mpi/bytevector p))
                  (s (get-mpi/bytevector p)))
@@ -630,7 +638,8 @@ (define (bytevector->hex bv)
                      str)))
 
              (string->canonical-sexp
-              (format #f "(sig-val (eddsa (r #~a#) (s #~a#)))"
+              (format #f "(sig-val (~a (r #~a#) (s #~a#)))"
+                      (public-key-algorithm pkalg)
                       (bytevector->hex r) (bytevector->hex s)))))
           (else
            (list 'unsupported-algorithm
@@ -886,6 +895,22 @@ (define curve
                       curve
                       (if (eq? curve 'Curve25519) 'djb-tweak 'eddsa)
                       (->hex q)))))
+          ((= alg PUBLIC-KEY-ECDSA)
+           (print "Public ECDSA key")
+           (let* ((len     (get-u8 p))
+                  (oid     (bytevector->uint (get-bytevector-n p len)))
+                  (q (get-mpi p)))
+             (define curve
+               ;; RFC 6637, Section 11.
+               (match oid
+                 (#x2a8648ce3d030107   "NIST P-256")
+                 (#x2b81040022         "NIST P-384")
+                 (#x2b81040023         "NIST P-521")))
+
+             (string->canonical-sexp
+              (format #f "(public-key (ecc (curve \"~a\")(q #~a#)))"
+                      curve
+                      (->hex q)))))
           (else
            (list 'unsupported-algorithm           ;FIXME: throw
                  (public-key-algorithm alg)
diff --git a/tests/keys/secp384.pub b/tests/keys/secp384.pub
new file mode 100644
index 0000000000..b90cf504e2
--- /dev/null
+++ b/tests/keys/secp384.pub
@@ -0,0 +1,11 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mG8EYxYF9RMFK4EEACIDAwTHYxcyBiiPz4ZZIkmXnVu0Yv9DHGrnbdCR6U/RT1S4
+wszaHdsSEHlPwmy3WGgTubBDOuJODf5kV/HLL7QEPsOTkIsObK+prEJO3CGpRVim
+a7nfVk2AH6D/GMkNacSXdwy0FTxleGFtcGxlQGV4YW1wbGUuY29tPoiwBBMTCQA4
+AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAFiEEzLZ9Sx8EBzgp+PzwLydGMf8+
+bFsFAmMWB+sACgkQLydGMf8+bFuD3gF/SMEDQP3Bvu0yb8KxE6j8lhOiKT186wwG
+4hBsifRdEF+UHWEa7sx74tyc4R1B01FUAYC/4QqNup4EnPzQfSE3WyVvu+ja+xui
+3vppYCpUjkHzkATsLzsN98/nkZ3q3YA8/lo=
+=vIaC
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/tests/keys/secp384.sec b/tests/keys/secp384.sec
new file mode 100644
index 0000000000..ae296dd9a1
--- /dev/null
+++ b/tests/keys/secp384.sec
@@ -0,0 +1,12 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lKQEYxYF9RMFK4EEACIDAwTHYxcyBiiPz4ZZIkmXnVu0Yv9DHGrnbdCR6U/RT1S4
+wszaHdsSEHlPwmy3WGgTubBDOuJODf5kV/HLL7QEPsOTkIsObK+prEJO3CGpRVim
+a7nfVk2AH6D/GMkNacSXdwwAAYC9iXZ9j+RWFB4rU103SCv6j68rS5Lmc7tHve9l
+B5nri/AR+OEJ61q+w6w0XO5GBBUYLrQVPGV4YW1wbGVAZXhhbXBsZS5jb20+iLAE
+ExMJADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQTMtn1LHwQHOCn4/PAv
+J0Yx/z5sWwUCYxYH6wAKCRAvJ0Yx/z5sW4PeAX9IwQNA/cG+7TJvwrETqPyWE6Ip
+PXzrDAbiEGyJ9F0QX5QdYRruzHvi3JzhHUHTUVQBgL/hCo26ngSc/NB9ITdbJW+7
+6Nr7G6Le+mlgKlSOQfOQBOwvOw33z+eRnerdgDz+Wg==
+=B1Nl
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/tests/keys/secp521.pub b/tests/keys/secp521.pub
new file mode 100644
index 0000000000..077e8e7df2
--- /dev/null
+++ b/tests/keys/secp521.pub
@@ -0,0 +1,13 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+=Nru3
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/tests/keys/secp521.sec b/tests/keys/secp521.sec
new file mode 100644
index 0000000000..663dbeaa3c
--- /dev/null
+++ b/tests/keys/secp521.sec
@@ -0,0 +1,14 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+=/mn1
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/tests/openpgp.scm b/tests/openpgp.scm
index 1f20466772..68439f7485 100644
--- a/tests/openpgp.scm
+++ b/tests/openpgp.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2020 Ludovic Courtès <[email-masked]>
+;;; Copyright © 2020, 2022 Ludovic Courtès <[email-masked]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -63,7 +63,7 @@ (define %civodul-key-id #x090B11993D9AEBB5)       ;civodul.pub
 
 #|
 Test keys in ./tests/keys.  They were generated in a container along these lines:
-  guix environment -CP --ad-hoc gnupg pinentry coreutils
+  guix shell -CP-hoc gnupg pinentry coreutils
 then, within the container:
   mkdir ~/.gnupg && chmod -R og-rwx ~/.gnupg
   gpg --batch --passphrase '' --quick-gen-key '<[email-masked]>' ed25519
@@ -75,6 +75,8 @@ (define %civodul-key-id #x090B11993D9AEBB5)       ;civodul.pub
 (define %rsa-key-id      #xAE25DA2A70DEED59)      ;rsa.pub
 (define %dsa-key-id      #x587918047BE8BD2C)      ;dsa.pub
 (define %ed25519-key-id  #x771F49CBFAAE072D)      ;ed25519.pub
+(define %secp384-key-id  #x2F274631FF3E6C5B)      ;secp384.pub
+(define %secp521-key-id  #xE84B13A0B4E7AF1B)      ;secp521.pub
 
 (define %rsa-key-fingerprint
   (base16-string->bytevector
@@ -85,6 +87,12 @@ (define %dsa-key-fingerprint
 (define %ed25519-key-fingerprint
   (base16-string->bytevector
    (string-downcase "44D31E21AF7138F9B632280A771F49CBFAAE072D")))
+(define %secp384-key-fingerprint
+  (base16-string->bytevector
+   (string-downcase "CCB67D4B1F04073829F8FCF02F274631FF3E6C5B")))
+(define %secp521-key-fingerprint
+  (base16-string->bytevector
+   (string-downcase "3BAF7E984069B671F3C31169E84B13A0B4E7AF1B")))
 
 
 ;;; The following are detached signatures created commands like:
@@ -148,6 +156,28 @@ (define %hello-signature/ed25519/sha1             ;digest-algo: sha1
 =AE4G
 -----END PGP SIGNATURE-----")
 
+(define %hello-signature/secp384/sha384           ;digest-algo: sha384
+  "\
+-----BEGIN PGP SIGNATURE-----
+
+iJUEABMJAB0WIQTMtn1LHwQHOCn4/PAvJ0Yx/z5sWwUCYxYIKAAKCRAvJ0Yx/z5s
+WxD2AX0QMeTHLJvJxRKTBP8O9kGMY9Nz0kzRBO0OJG2gYyxu9sZ+NAEQF01jAOXl
+ApL2zVkBgLUyyleJtR24LKxK73waLJb51TA29NXJJZ2fiRZ50u/lNfrFR3PYnK7/
+gvSkL3Ldzw==
+=+7h3
+-----END PGP SIGNATURE-----")
+
+(define %hello-signature/secp521/sha512
+  "\
+-----BEGIN PGP SIGNATURE-----
+
+iLcEABMKAB0WIQQ7r36YQGm2cfPDEWnoSxOgtOevGwUCYxYb+wAKCRDoSxOgtOev
+G+ByAgdwIBTnCtzo+lFuahhMMScXZZeTH055IOhTsXmptZaE3MaazTsUw3en8C9i
+EWiy/GDQKaJEZMP3dwN1+3tNTl/NUAIIiV/BFly9Ha/cYJG+p3LG24JoHVfJx04q
+LfSXejfMIvu33h8wjMA2tRQSlqdDylMWKThJgp6GH6svp+Zr4z+Smnw=
+=1zW0
+-----END PGP SIGNATURE-----")
+
 
 (test-begin "openpgp")
 
@@ -193,7 +223,9 @@ (define %hello-signature/ed25519/sha1             ;digest-algo: sha1
         `(,%rsa-key-id ,%rsa-key-fingerprint rsa sha256)
         `(,%ed25519-key-id ,%ed25519-key-fingerprint eddsa sha256)
         `(,%ed25519-key-id ,%ed25519-key-fingerprint eddsa sha512)
-        `(,%ed25519-key-id ,%ed25519-key-fingerprint eddsa sha1))
+        `(,%ed25519-key-id ,%ed25519-key-fingerprint eddsa sha1)
+        `(,%secp384-key-id ,%secp384-key-fingerprint ecdsa sha384)
+        `(,%secp521-key-id ,%secp521-key-fingerprint ecdsa sha512))
   (map (lambda (str)
          (let ((signature (get-openpgp-detached-signature/ascii
                            (open-input-string str))))
@@ -205,7 +237,9 @@ (define %hello-signature/ed25519/sha1             ;digest-algo: sha1
              %hello-signature/rsa
              %hello-signature/ed25519/sha256
              %hello-signature/ed25519/sha512
-             %hello-signature/ed25519/sha1)))
+             %hello-signature/ed25519/sha1
+             %hello-signature/secp384/sha384
+             %hello-signature/secp521/sha512)))
 
 (test-equal "verify-openpgp-signature, missing key"
   `(missing-key ,%rsa-key-fingerprint)
@@ -221,7 +255,9 @@ (define %hello-signature/ed25519/sha1             ;digest-algo: sha1
     (good-signature ,%dsa-key-id)
     (good-signature ,%ed25519-key-id)
     (good-signature ,%ed25519-key-id)
-    (good-signature ,%ed25519-key-id))
+    (good-signature ,%ed25519-key-id)
+    (good-signature ,%secp384-key-id)
+    (good-signature ,%secp521-key-id))
   (map (lambda (key signature)
          (let* ((key       (search-path %load-path key))
                 (keyring   (get-openpgp-keyring
@@ -235,18 +271,24 @@ (define %hello-signature/ed25519/sha1             ;digest-algo: sha1
        (list "tests/keys/rsa.pub" "tests/keys/dsa.pub"
              "tests/keys/ed25519.pub"
              "tests/keys/ed25519.pub"
-             "tests/keys/ed25519.pub")
+             "tests/keys/ed25519.pub"
+             "tests/keys/secp384.pub"
+             "tests/keys/secp521.pub")
        (list %hello-signature/rsa %hello-signature/dsa
              %hello-signature/ed25519/sha256
              %hello-signature/ed25519/sha512
-             %hello-signature/ed25519/sha1)))
+             %hello-signature/ed25519/sha1
+             %hello-signature/secp384/sha384
+             %hello-signature/secp521/sha512)))
 
 (test-equal "verify-openpgp-signature, bad signature"
   `((bad-signature ,%rsa-key-id)
     (bad-signature ,%dsa-key-id)
     (bad-signature ,%ed25519-key-id)
     (bad-signature ,%ed25519-key-id)
-    (bad-signature ,%ed25519-key-id))
+    (bad-signature ,%ed25519-key-id)
+    (bad-signature ,%secp384-key-id)
+    (bad-signature ,%secp521-key-id))
   (let ((keyring (fold (lambda (key keyring)
                          (let ((key (search-path %load-path key)))
                            (get-openpgp-keyring
@@ -256,7 +298,9 @@ (define %hello-signature/ed25519/sha1             ;digest-algo: sha1
                        %empty-keyring
                        '("tests/keys/rsa.pub" "tests/keys/dsa.pub"
                          "tests/keys/ed25519.pub" "tests/keys/ed25519.pub"
-                         "tests/keys/ed25519.pub"))))
+                         "tests/keys/ed25519.pub"
+                         "tests/keys/secp384.pub"
+                         "tests/keys/secp521.pub"))))
     (map (lambda (signature)
            (let ((signature (string->openpgp-packet signature)))
              (let-values (((status key)
@@ -266,6 +310,8 @@ (define %hello-signature/ed25519/sha1             ;digest-algo: sha1
          (list %hello-signature/rsa %hello-signature/dsa
                %hello-signature/ed25519/sha256
                %hello-signature/ed25519/sha512
-               %hello-signature/ed25519/sha1))))
+               %hello-signature/ed25519/sha1
+               %hello-signature/secp384/sha384
+               %hello-signature/secp521/sha512))))
 
 (test-end "openpgp")

base-commit: aae98c297214f87eb45302863adb021078c41a6f
-- 
2.37.2