Provide a harness that runs test vectors from Project Wycheproof against
authorjsing <jsing@openbsd.org>
Wed, 25 Jul 2018 18:04:09 +0000 (18:04 +0000)
committerjsing <jsing@openbsd.org>
Wed, 25 Jul 2018 18:04:09 +0000 (18:04 +0000)
libcrypto. Initially this just covers RSA signatures, but can be extended
to cover other cryptographic algorithms.

This regress requires the go and wycheproof-testvector packages to be
installed, with the regress being skipped otherwise.

Discussed with beck@ and tb@

regress/lib/libcrypto/Makefile
regress/lib/libcrypto/wycheproof/Makefile [new file with mode: 0644]
regress/lib/libcrypto/wycheproof/wycheproof.go [new file with mode: 0644]

index b357b3b..38e3304 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: Makefile,v 1.31 2018/05/15 15:11:15 tb Exp $
+#      $OpenBSD: Makefile,v 1.32 2018/07/25 18:04:09 jsing Exp $
 
 SUBDIR= \
        aead \
@@ -44,6 +44,7 @@ SUBDIR= \
        sha256 \
        sha512 \
        utf8 \
+       wycheproof \
        x509
 
 install:
diff --git a/regress/lib/libcrypto/wycheproof/Makefile b/regress/lib/libcrypto/wycheproof/Makefile
new file mode 100644 (file)
index 0000000..e7338cb
--- /dev/null
@@ -0,0 +1,18 @@
+# $OpenBSD: Makefile,v 1.1 2018/07/25 18:04:09 jsing Exp $
+
+GO_VERSION != sh -c "(go version) 2>/dev/null || true"
+
+.if empty(GO_VERSION)
+regress:
+       @echo package go is required for this regress
+       @echo SKIPPED
+.endif
+
+CLEANFILES+=wycheproof
+REGRESS_TARGETS=regress-wycheproof
+
+regress-wycheproof:
+       go build -o wycheproof ${.CURDIR}/wycheproof.go
+       ./wycheproof
+
+.include <bsd.regress.mk>
diff --git a/regress/lib/libcrypto/wycheproof/wycheproof.go b/regress/lib/libcrypto/wycheproof/wycheproof.go
new file mode 100644 (file)
index 0000000..ee53195
--- /dev/null
@@ -0,0 +1,233 @@
+/* $OpenBSD: wycheproof.go,v 1.1 2018/07/25 18:04:09 jsing Exp $ */
+/*
+ * Copyright (c) 2018 Joel Sing <jsing@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+// Wycheproof runs test vectors from Project Wycheproof against libcrypto.
+package main
+
+/*
+#cgo LDFLAGS: -lcrypto
+
+#include <openssl/bn.h>
+#include <openssl/objects.h>
+#include <openssl/rsa.h>
+*/
+import "C"
+
+import (
+       "crypto/sha1"
+       "crypto/sha256"
+       "crypto/sha512"
+       "encoding/hex"
+       "encoding/json"
+       "fmt"
+       "hash"
+       "io/ioutil"
+       "log"
+       "os"
+       "path/filepath"
+       "unsafe"
+)
+
+const testVectorPath = "/usr/local/share/wycheproof/testvectors"
+
+type wycheproofTest struct {
+       TCID    int      `json:"tcId"`
+       Comment string   `json:"comment"`
+       Msg     string   `json:"msg"`
+       Sig     string   `json:"sig"`
+       Padding string   `json:"padding"`
+       Result  string   `json:"result"`
+       Flags   []string `json:"flags"`
+}
+
+type wycheproofTestGroup struct {
+       E       string            `json:"e"`
+       KeyASN  string            `json:"keyAsn"`
+       KeyDER  string            `json:"keyDer"`
+       KeyPEM  string            `json:"keyPem"`
+       KeySize int               `json:"keysize"`
+       N       string            `json:"n"`
+       SHA     string            `json:"sha"`
+       Type    string            `json:"type"`
+       Tests   []*wycheproofTest `json:"tests"`
+}
+
+type wycheproofTestVectors struct {
+       Algorithm        string            `json:"algorithm"`
+       GeneratorVersion string            `json:"generatorVersion"`
+       Notes            map[string]string `json:"notes"`
+       NumberOfTests    int               `json:"numberOfTests"`
+       // Header
+       TestGroups []*wycheproofTestGroup `json:"testGroups"`
+}
+
+func nidFromString(ns string) (int, error) {
+       switch ns {
+       case "SHA-1":
+               return C.NID_sha1, nil
+       case "SHA-224":
+               return C.NID_sha224, nil
+       case "SHA-256":
+               return C.NID_sha256, nil
+       case "SHA-384":
+               return C.NID_sha384, nil
+       case "SHA-512":
+               return C.NID_sha512, nil
+       default:
+               return -1, fmt.Errorf("unknown NID %q", ns)
+       }
+}
+
+func hashFromString(hs string) (hash.Hash, error) {
+       switch hs {
+       case "SHA-1":
+               return sha1.New(), nil
+       case "SHA-224":
+               return sha256.New224(), nil
+       case "SHA-256":
+               return sha256.New(), nil
+       case "SHA-384":
+               return sha512.New384(), nil
+       case "SHA-512":
+               return sha512.New(), nil
+       default:
+               return nil, fmt.Errorf("unknown hash %q", hs)
+       }
+}
+
+func runRSATest(rsa *C.RSA, nid int, h hash.Hash, wt *wycheproofTest) bool {
+       msg, err := hex.DecodeString(wt.Msg)
+       if err != nil {
+               log.Fatalf("Failed to decode message %q: %v", wt.Msg, err)
+       }
+
+       h.Reset()
+       h.Write(msg)
+       msg = h.Sum(nil)
+
+       sig, err := hex.DecodeString(wt.Sig)
+       if err != nil {
+               log.Fatalf("Failed to decode signature %q: %v", wt.Sig, err)
+       }
+
+       msgLen, sigLen := len(msg), len(sig)
+       if msgLen == 0 {
+               msg = append(msg, 0)
+       }
+       if sigLen == 0 {
+               sig = append(sig, 0)
+       }
+
+       ret := C.RSA_verify(C.int(nid), (*C.uchar)(unsafe.Pointer(&msg[0])), C.uint(msgLen),
+               (*C.uchar)(unsafe.Pointer(&sig[0])), C.uint(sigLen), rsa)
+
+       // XXX audit acceptable cases...
+       succeeded := true
+       if (ret == 1) != (wt.Result == "valid") && wt.Result != "acceptable" {
+               fmt.Printf("FAIL: Test case %d - RSA_verify() = %d, want %v\n", wt.TCID, int(ret), wt.Result)
+               succeeded = false
+       }
+       return succeeded
+}
+
+func runRSATestGroup(wtg *wycheproofTestGroup) bool {
+       fmt.Printf("Running RSA test group %v with key size %d and %v...\n", wtg.Type, wtg.KeySize, wtg.SHA)
+
+       rsa := C.RSA_new()
+       if rsa == nil {
+               log.Fatal("RSA_new failed")
+       }
+       defer C.RSA_free(rsa)
+
+       e := C.CString(wtg.E)
+       if C.BN_hex2bn(&rsa.e, e) == 0 {
+               log.Fatalf("Failed to set RSA e")
+       }
+       C.free(unsafe.Pointer(e))
+
+       n := C.CString(wtg.N)
+       if C.BN_hex2bn(&rsa.n, n) == 0 {
+               log.Fatalf("Failed to set RSA n")
+       }
+       C.free(unsafe.Pointer(n))
+
+       nid, err := nidFromString(wtg.SHA)
+       if err != nil {
+               log.Fatalf("Failed to get MD NID: %v", err)
+       }
+       h, err := hashFromString(wtg.SHA)
+       if err != nil {
+               log.Fatalf("Failed to get hash: %v", err)
+       }
+
+       succeeded := true
+       for _, wt := range wtg.Tests {
+               if !runRSATest(rsa, nid, h, wt) {
+                       succeeded = false
+               }
+       }
+       return succeeded
+}
+
+func runRSATestVectors(path string) bool {
+       b, err := ioutil.ReadFile(path)
+       if err != nil {
+               log.Fatalf("Failed to read test vectors: %v", err)
+       }
+       wtv := &wycheproofTestVectors{}
+       if err := json.Unmarshal(b, wtv); err != nil {
+               log.Fatalf("Failed to unmarshal JSON: %v", err)
+       }
+       fmt.Printf("Loaded Wycheproof test vectors for %v with %d tests\n", wtv.Algorithm, wtv.NumberOfTests)
+
+       succeeded := true
+       for _, wtg := range wtv.TestGroups {
+               if !runRSATestGroup(wtg) {
+                       succeeded = false
+               }
+       }
+       return succeeded
+}
+
+func main() {
+       if _, err := os.Stat(testVectorPath); os.IsNotExist(err) {
+               fmt.Printf("package wycheproof-testvectors is required for this regress\n")
+               fmt.Printf("SKIPPING\n")
+               os.Exit(0)
+       }
+
+       tvs, err := filepath.Glob(filepath.Join(testVectorPath, "*.json"))
+       if err != nil || len(tvs) == 0 {
+               log.Fatalf("Failed to find test vectors at %q\n", testVectorPath)
+       }
+
+       succeeded := true
+
+       tvs, err = filepath.Glob(filepath.Join(testVectorPath, "rsa_signature_*test.json"))
+       if err != nil {
+               log.Fatalf("Failed to find RSA test vectors: %v", err)
+       }
+       for _, tv := range tvs {
+               if !runRSATestVectors(tv) {
+                       succeeded = false
+               }
+       }
+
+       if !succeeded {
+               os.Exit(1)
+       }
+}