Run the Wycheproof ChaCha20-Poly1305 test vectors against libcrypto.
authortb <tb@openbsd.org>
Mon, 20 Aug 2018 17:06:18 +0000 (17:06 +0000)
committertb <tb@openbsd.org>
Mon, 20 Aug 2018 17:06:18 +0000 (17:06 +0000)
We currently only support nonces of length 12, so skip a few tests.

With input from jsing

regress/lib/libcrypto/wycheproof/wycheproof.go

index d145723..c00a869 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: wycheproof.go,v 1.5 2018/08/10 16:22:58 jsing Exp $ */
+/* $OpenBSD: wycheproof.go,v 1.6 2018/08/20 17:06:18 tb Exp $ */
 /*
  * Copyright (c) 2018 Joel Sing <jsing@openbsd.org>
  *
@@ -25,6 +25,7 @@ package main
 #include <openssl/curve25519.h>
 #include <openssl/ec.h>
 #include <openssl/ecdsa.h>
+#include <openssl/evp.h>
 #include <openssl/objects.h>
 #include <openssl/rsa.h>
 */
@@ -48,6 +49,27 @@ import (
 
 const testVectorPath = "/usr/local/share/wycheproof/testvectors"
 
+type wycheproofTestGroupChaCha20Poly1305 struct {
+       IVSize  int                               `json:"ivSize"`
+       KeySize int                               `json:"keySize"`
+       TagSize int                               `json:"tagSize"`
+       Type    string                            `json:"type"`
+       Tests   []*wycheproofTestChaCha20Poly1305 `json:"tests"`
+}
+
+type wycheproofTestChaCha20Poly1305 struct {
+       TCID    int      `json:"tcId"`
+       Comment string   `json:"comment"`
+       Key     string   `json:"key"`
+       IV      string   `json:"iv"`
+       AAD     string   `json:"aad"`
+       Msg     string   `json:"msg"`
+       CT      string   `json:"ct"`
+       Tag     string   `json:"tag"`
+       Result  string   `json:"result"`
+       Flags   []string `json:"flags"`
+}
+
 type wycheproofECDSAKey struct {
        Curve        string `json:"curve"`
        KeySize      int    `json:"keySize"`
@@ -169,6 +191,117 @@ func hashFromString(hs string) (hash.Hash, error) {
        }
 }
 
+func runChaCha20Poly1305Test(iv_len int, key_len int, tag_len int, wt *wycheproofTestChaCha20Poly1305) bool {
+       aead := C.EVP_aead_chacha20_poly1305()
+       if aead == nil {
+               log.Fatal("EVP_aead_chacha20_poly1305 failed")
+       }
+
+       key, err := hex.DecodeString(wt.Key)
+       if err != nil {
+               log.Fatalf("Failed to decode key %q: %v", wt.Key, err)
+       }
+       if key_len != len(key) {
+               log.Fatalf("Key length mismatch: got %d, want %d", len(key), key_len)
+       }
+
+       var ctx C.EVP_AEAD_CTX
+       if C.EVP_AEAD_CTX_init((*C.EVP_AEAD_CTX)(unsafe.Pointer(&ctx)), aead, (*C.uchar)(unsafe.Pointer(&key[0])), C.size_t(key_len), C.size_t(tag_len), nil) != 1 {
+               log.Fatalf("Failed to initialize AEAD context")
+       }
+
+       iv, err := hex.DecodeString(wt.IV)
+       if err != nil {
+               log.Fatalf("Failed to decode key %q: %v", wt.IV, err)
+       }
+       if iv_len != len(iv) {
+               log.Fatalf("IV length mismatch: got %d, want %d", len(iv), iv_len)
+       }
+       aad, err := hex.DecodeString(wt.AAD)
+       if err != nil {
+               log.Fatalf("Failed to decode AAD %q: %v", wt.AAD, err)
+       }
+       msg, err := hex.DecodeString(wt.Msg)
+       if err != nil {
+               log.Fatalf("Failed to decode msg %q: %v", wt.Msg, err)
+       }
+
+       ivLen, aadLen, msgLen := len(iv), len(aad), len(msg)
+       if ivLen == 0 {
+               iv = append(iv, 0)
+       }
+       if aadLen == 0 {
+               aad = append(aad, 0)
+       }
+       if msgLen == 0 {
+               msg = append(msg, 0)
+       }
+
+       maxOutLen := msgLen + tag_len
+       out := make([]byte, maxOutLen)
+       var outLen C.size_t
+
+       ret := C.EVP_AEAD_CTX_seal((*C.EVP_AEAD_CTX)(unsafe.Pointer(&ctx)), (*C.uint8_t)(unsafe.Pointer(&out[0])), (*C.size_t)(unsafe.Pointer(&outLen)), C.size_t(maxOutLen), (*C.uint8_t)(unsafe.Pointer(&iv[0])), C.size_t(ivLen), (*C.uint8_t)(unsafe.Pointer(&msg[0])), C.size_t(msgLen), (*C.uint8_t)(unsafe.Pointer(&aad[0])), C.size_t(aadLen))
+
+       C.EVP_AEAD_CTX_cleanup((*C.EVP_AEAD_CTX)(unsafe.Pointer(&ctx)))
+
+       if ret != 1 && wt.Result == "invalid" {
+               fmt.Printf("INFO: Test case %d (%q) - EVP_AEAD_CTX_seal() = %d, want %v\n", wt.TCID, wt.Comment, int(ret), wt.Result)
+               return true
+       }
+
+       if (outLen != C.size_t(maxOutLen)) {
+               fmt.Printf("FAIL: Test case %d (%q) - ChaCha output length mismatch: got %d, want %d", wt.TCID, wt.Comment, outLen, maxOutLen)
+               return false
+       }
+       
+       outCt := out[0:msgLen]
+       outTag := out[msgLen: maxOutLen]
+
+       ct, err := hex.DecodeString(wt.CT)
+       if err != nil {
+               log.Fatalf("Failed to decode ct %q: %v", wt.CT, err)
+       }
+       if len(ct) != msgLen {
+               fmt.Printf("FAIL: Test case %d (%q) - msg length %d doesn't match ct length %d", wt.TCID, wt.Comment, msgLen, len(ct))
+               return false
+       }
+       tag, err := hex.DecodeString(wt.Tag)
+       if err != nil {
+               log.Fatalf("Failed to decode tag %q: %v", wt.Tag, err)
+       }
+       if len(tag) != tag_len {
+               fmt.Printf("FAIL: Test case %d (%q) tag length: got %d, want %d", wt.TCID, wt.Comment, len(tag), tag_len)
+               return false
+       }
+
+       success := false
+       if (bytes.Equal(outCt, ct) && bytes.Equal(outTag, tag)) || wt.Result == "invalid" {
+               success = true
+       } else {
+               fmt.Printf("FAIL: Test case %d (%q) - EVP_AEAD_CTX_seal() = %d, ct match: %t, tag match: %t; want %v\n", wt.TCID, wt.Comment, int(ret), bytes.Equal(outCt, ct), bytes.Equal(outTag, tag), wt.Result)
+       }
+
+       return success
+}
+
+func runChaCha20Poly1305TestGroup(wtg *wycheproofTestGroupChaCha20Poly1305) bool {
+       // We currently only support nonces of length 12 (96 bits)
+       if wtg.IVSize != 96 {
+               return true
+       }
+
+       fmt.Printf("Running ChaCha20-Poly1305 test group %v with IV size %d, key size %d, tag size %d...\n", wtg.Type, wtg.IVSize, wtg.KeySize, wtg.TagSize)
+
+       success := true
+       for _, wt := range wtg.Tests {
+               if !runChaCha20Poly1305Test(wtg.IVSize / 8, wtg.KeySize / 8, wtg.TagSize / 8, wt) {
+                       success = false
+               }
+       }
+       return success
+}
+
 func runECDSATest(ecKey *C.EC_KEY, nid int, h hash.Hash, wt *wycheproofTestECDSA) bool {
        msg, err := hex.DecodeString(wt.Msg)
        if err != nil {
@@ -390,6 +523,8 @@ func runTestVectors(path string) bool {
 
        var wtg interface{}
        switch wtv.Algorithm {
+       case "CHACHA20-POLY1305":
+               wtg = &wycheproofTestGroupChaCha20Poly1305{}
        case "ECDSA":
                wtg = &wycheproofTestGroupECDSA{}
        case "RSASig":
@@ -406,6 +541,10 @@ func runTestVectors(path string) bool {
                        log.Fatalf("Failed to unmarshal test groups JSON: %v", err)
                }
                switch wtv.Algorithm {
+               case "CHACHA20-POLY1305":
+                       if !runChaCha20Poly1305TestGroup(wtg.(*wycheproofTestGroupChaCha20Poly1305)) {
+                               success = false
+                       }
                case "ECDSA":
                        if !runECDSATestGroup(wtg.(*wycheproofTestGroupECDSA)) {
                                success = false
@@ -437,6 +576,7 @@ func main() {
                name    string
                pattern string
        }{
+               {"ChaCha20-Poly1305", "chacha20_poly1305_test.json"},
                {"ECDSA", "ecdsa_[^w]*test.json"}, // Skip ecdsa_webcrypto_test.json for now.
                {"RSA signature", "rsa_signature_*test.json"},
                {"X25519", "x25519_*test.json"},