Run the wycheproof ECDSA test vectors against libcrypto.
authorjsing <jsing@openbsd.org>
Fri, 10 Aug 2018 16:22:58 +0000 (16:22 +0000)
committerjsing <jsing@openbsd.org>
Fri, 10 Aug 2018 16:22:58 +0000 (16:22 +0000)
Skip the ecdsa_webcrypto_test.json vectors for the time being, as these
likely need some extra glue.

regress/lib/libcrypto/wycheproof/wycheproof.go

index 427dc47..d145723 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: wycheproof.go,v 1.4 2018/08/10 16:18:55 jsing Exp $ */
+/* $OpenBSD: wycheproof.go,v 1.5 2018/08/10 16:22:58 jsing Exp $ */
 /*
  * Copyright (c) 2018 Joel Sing <jsing@openbsd.org>
  *
@@ -23,6 +23,8 @@ package main
 
 #include <openssl/bn.h>
 #include <openssl/curve25519.h>
+#include <openssl/ec.h>
+#include <openssl/ecdsa.h>
 #include <openssl/objects.h>
 #include <openssl/rsa.h>
 */
@@ -46,6 +48,33 @@ import (
 
 const testVectorPath = "/usr/local/share/wycheproof/testvectors"
 
+type wycheproofECDSAKey struct {
+       Curve        string `json:"curve"`
+       KeySize      int    `json:"keySize"`
+       Type         string `json:"type"`
+       Uncompressed string `json:"uncompressed"`
+       WX           string `json:"wx"`
+       WY           string `json:"wy"`
+}
+
+type wycheproofTestECDSA struct {
+       TCID    int      `json:"tcId"`
+       Comment string   `json:"comment"`
+       Msg     string   `json:"msg"`
+       Sig     string   `json:"sig"`
+       Result  string   `json:"result"`
+       Flags   []string `json:"flags"`
+}
+
+type wycheproofTestGroupECDSA struct {
+       Key    *wycheproofECDSAKey    `json:"key"`
+       KeyDER string                 `json:"keyDer"`
+       KeyPEM string                 `json:"keyPem"`
+       SHA    string                 `json:"sha"`
+       Type   string                 `json:"type"`
+       Tests  []*wycheproofTestECDSA `json:"tests"`
+}
+
 type wycheproofTestRSA struct {
        TCID    int      `json:"tcId"`
        Comment string   `json:"comment"`
@@ -94,11 +123,25 @@ type wycheproofTestVectors struct {
 }
 
 var nids = map[string]int{
-       "SHA-1":   C.NID_sha1,
-       "SHA-224": C.NID_sha224,
-       "SHA-256": C.NID_sha256,
-       "SHA-384": C.NID_sha384,
-       "SHA-512": C.NID_sha512,
+       "brainpoolP224r1": C.NID_brainpoolP224r1,
+       "brainpoolP256r1": C.NID_brainpoolP256r1,
+       "brainpoolP320r1": C.NID_brainpoolP320r1,
+       "brainpoolP384r1": C.NID_brainpoolP384r1,
+       "brainpoolP512r1": C.NID_brainpoolP512r1,
+       "brainpoolP224t1": C.NID_brainpoolP224t1,
+       "brainpoolP256t1": C.NID_brainpoolP256t1,
+       "brainpoolP320t1": C.NID_brainpoolP320t1,
+       "brainpoolP384t1": C.NID_brainpoolP384t1,
+       "brainpoolP512t1": C.NID_brainpoolP512t1,
+       "secp224r1":       C.NID_secp224r1,
+       "secp256k1":       C.NID_secp256k1,
+       "secp384r1":       C.NID_secp384r1,
+       "secp521r1":       C.NID_secp521r1,
+       "SHA-1":           C.NID_sha1,
+       "SHA-224":         C.NID_sha224,
+       "SHA-256":         C.NID_sha256,
+       "SHA-384":         C.NID_sha384,
+       "SHA-512":         C.NID_sha512,
 }
 
 func nidFromString(ns string) (int, error) {
@@ -126,6 +169,96 @@ func hashFromString(hs string) (hash.Hash, error) {
        }
 }
 
+func runECDSATest(ecKey *C.EC_KEY, nid int, h hash.Hash, wt *wycheproofTestECDSA) 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.ECDSA_verify(0, (*C.uchar)(unsafe.Pointer(&msg[0])), C.int(msgLen),
+               (*C.uchar)(unsafe.Pointer(&sig[0])), C.int(sigLen), ecKey)
+
+       // XXX audit acceptable cases...
+       success := true
+       if (ret == 1) != (wt.Result == "valid") && wt.Result != "acceptable" {
+               fmt.Printf("FAIL: Test case %d (%q) - ECDSA_verify() = %d, want %v\n", wt.TCID, wt.Comment, int(ret), wt.Result)
+               success = false
+       }
+       return success
+}
+
+func runECDSATestGroup(wtg *wycheproofTestGroupECDSA) bool {
+       // No secp256r1 support.
+       if wtg.Key.Curve == "secp256r1" {
+               return true
+       }
+
+       fmt.Printf("Running ECDSA test group %v with curve %v, key size %d and %v...\n", wtg.Type, wtg.Key.Curve, wtg.Key.KeySize, wtg.SHA)
+
+       nid, err := nidFromString(wtg.Key.Curve)
+       if err != nil {
+               log.Fatalf("Failed to get nid for curve: %v", err)
+       }
+       ecKey := C.EC_KEY_new_by_curve_name(C.int(nid))
+       if ecKey == nil {
+               log.Fatal("EC_KEY_new_by_curve_name failed")
+       }
+       defer C.EC_KEY_free(ecKey)
+
+       var bnX *C.BIGNUM
+       wx := C.CString(wtg.Key.WX)
+       if C.BN_hex2bn(&bnX, wx) == 0 {
+               log.Fatal("Failed to decode WX")
+       }
+       C.free(unsafe.Pointer(wx))
+       defer C.BN_free(bnX)
+
+       var bnY *C.BIGNUM
+       wy := C.CString(wtg.Key.WY)
+       if C.BN_hex2bn(&bnY, wy) == 0 {
+               log.Fatal("Failed to decode WY")
+       }
+       C.free(unsafe.Pointer(wy))
+       defer C.BN_free(bnY)
+
+       if C.EC_KEY_set_public_key_affine_coordinates(ecKey, bnX, bnY) != 1 {
+               log.Fatal("Failed to set EC public key")
+       }
+
+       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)
+       }
+
+       success := true
+       for _, wt := range wtg.Tests {
+               if !runECDSATest(ecKey, nid, h, wt) {
+                       success = false
+               }
+       }
+       return success
+}
+
 func runRSATest(rsa *C.RSA, nid int, h hash.Hash, wt *wycheproofTestRSA) bool {
        msg, err := hex.DecodeString(wt.Msg)
        if err != nil {
@@ -257,6 +390,8 @@ func runTestVectors(path string) bool {
 
        var wtg interface{}
        switch wtv.Algorithm {
+       case "ECDSA":
+               wtg = &wycheproofTestGroupECDSA{}
        case "RSASig":
                wtg = &wycheproofTestGroupRSA{}
        case "X25519":
@@ -271,6 +406,10 @@ func runTestVectors(path string) bool {
                        log.Fatalf("Failed to unmarshal test groups JSON: %v", err)
                }
                switch wtv.Algorithm {
+               case "ECDSA":
+                       if !runECDSATestGroup(wtg.(*wycheproofTestGroupECDSA)) {
+                               success = false
+                       }
                case "RSASig":
                        if !runRSATestGroup(wtg.(*wycheproofTestGroupRSA)) {
                                success = false
@@ -293,11 +432,12 @@ func main() {
                os.Exit(0)
        }
 
-       // TODO: AES, Chacha20Poly1305, DSA, ECDH, ECDSA, RSA-PSS.
+       // AES, Chacha20Poly1305, DSA, ECDH
        tests := []struct {
                name    string
                pattern string
        }{
+               {"ECDSA", "ecdsa_[^w]*test.json"}, // Skip ecdsa_webcrypto_test.json for now.
                {"RSA signature", "rsa_signature_*test.json"},
                {"X25519", "x25519_*test.json"},
        }