1/* $OpenBSD: cctv.go,v 1.1.1.1 2023/04/23 13:43:46 tb Exp $ */ 2 3/* 4 * Copyright (c) 2023 Theo Buehler <tb@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19// cctv runs test vectors from CCTV against libcrypto. 20package main 21 22/* 23#cgo LDFLAGS: -lcrypto 24 25#include <openssl/evp.h> 26*/ 27import "C" 28 29import ( 30 "crypto/ed25519" 31 "encoding/hex" 32 "encoding/json" 33 "fmt" 34 "io/ioutil" 35 "log" 36 "os" 37 "path/filepath" 38 "unsafe" 39) 40 41const testVectorPath = "/usr/local/share/c2sp-testvectors" 42const ed25519Json = "ed25519/ed25519vectors.json" 43 44type ed25519Vectors []ed25519Vector 45 46type ed25519Vector struct { 47 Number int `json:"number"` 48 PublicKey string `json:"key"` 49 Signature string `json:"sig"` 50 Message string `json:"msg"` 51 Flags Flags `json:"flags"` 52} 53 54type Flags int 55 56const ( 57 LowOrderR Flags = 1 << iota 58 LowOrderA 59 LowOrderComponentR 60 LowOrderComponentA 61 LowOrderResidue 62 NonCanonicalA 63 NonCanonicalR 64 ReencodedK 65) 66 67func (f Flags) String() string { 68 var flags []string 69 if f&LowOrderR != 0 { 70 flags = append(flags, "low_order_R") 71 } 72 if f&LowOrderA != 0 { 73 flags = append(flags, "low_order_A") 74 } 75 if f&LowOrderComponentR != 0 { 76 flags = append(flags, "low_order_component_R") 77 } 78 if f&LowOrderComponentA != 0 { 79 flags = append(flags, "low_order_component_A") 80 } 81 if f&LowOrderResidue != 0 { 82 flags = append(flags, "low_order_residue") 83 } 84 if f&NonCanonicalA != 0 { 85 flags = append(flags, "non_canonical_A") 86 } 87 if f&NonCanonicalR != 0 { 88 flags = append(flags, "non_canonical_R") 89 } 90 if f&ReencodedK != 0 { 91 flags = append(flags, "reencoded_k") 92 } 93 return fmt.Sprintf("%v", flags) 94} 95 96func (f *Flags) UnmarshalJSON(b []byte) error { 97 var v []string 98 99 if err := json.Unmarshal(b, &v); err != nil { 100 return err 101 } 102 for _, flag := range v { 103 switch flag { 104 case "low_order_A": 105 *f |= LowOrderA 106 case "low_order_R": 107 *f |= LowOrderR 108 case "low_order_component_A": 109 *f |= LowOrderComponentA 110 case "low_order_component_R": 111 *f |= LowOrderComponentR 112 case "low_order_residue": 113 *f |= LowOrderResidue 114 case "non_canonical_A": 115 *f |= NonCanonicalA 116 case "non_canonical_R": 117 *f |= NonCanonicalR 118 case "reencoded_k": 119 *f |= ReencodedK 120 default: 121 log.Fatalf("unknown flag %q", flag) 122 } 123 } 124 125 return nil 126} 127 128func evpEd25519Verify(pubkey, msg, sig []byte) bool { 129 pkey := C.EVP_PKEY_new_raw_public_key(C.EVP_PKEY_ED25519, nil, (*C.uchar)(unsafe.Pointer(&pubkey[0])), (C.size_t)(len(pubkey))) 130 if pkey == nil { 131 log.Fatalf("EVP_PKEY_new_raw_public_key failed") 132 } 133 defer C.EVP_PKEY_free(pkey) 134 135 mdctx := C.EVP_MD_CTX_new() 136 if mdctx == nil { 137 log.Fatal("EVP_MD_CTX_new failed") 138 } 139 defer C.EVP_MD_CTX_free(mdctx) 140 141 if C.EVP_DigestVerifyInit(mdctx, nil, nil, nil, pkey) != 1 { 142 log.Fatal("EVP_DigestVerifyInit failed") 143 } 144 ret := C.EVP_DigestVerify(mdctx, (*C.uchar)(unsafe.Pointer(&sig[0])), (C.size_t)(len(sig)), (*C.uchar)(unsafe.Pointer(&msg[0])), (C.size_t)(len(msg))) 145 if ret < 0 { 146 log.Fatalf("EVP_DigestVerify errored %d", ret) 147 } 148 149 return ret == 1 150} 151 152func runEd25519Test(tv ed25519Vector) bool { 153 pubkey, err := hex.DecodeString(tv.PublicKey) 154 if err != nil { 155 log.Fatalf("Failed to decode key %q: %v", tv.PublicKey, err) 156 } 157 158 sig, err := hex.DecodeString(tv.Signature) 159 if err != nil { 160 log.Fatalf("Failed to decode Signature %q: %v", tv.Signature, err) 161 } 162 163 msg := []byte(tv.Message) 164 165 // Implementations derived from "ref10" reject `LowOrderResidue` and 166 // `NonCanonicalR` and accept everything else. 167 reject := LowOrderResidue | NonCanonicalR 168 want_verify := (tv.Flags & reject) == 0 169 170 c_verified := evpEd25519Verify(pubkey, msg, sig) 171 go_verified := ed25519.Verify(pubkey, msg, sig) 172 173 success := true 174 if c_verified != want_verify || go_verified != want_verify { 175 fmt.Printf("FAIL: Test case %d (flags: %v) - C: %t, want: %t, go: %t\n", tv.Number, tv.Flags, c_verified, want_verify, go_verified) 176 success = false 177 } 178 return success 179} 180 181func main() { 182 if _, err := os.Stat(testVectorPath); os.IsNotExist(err) { 183 fmt.Printf("package cc-testvectors is required for this regress\n") 184 fmt.Printf("SKIPPED\n") 185 os.Exit(0) 186 } 187 188 b, err := ioutil.ReadFile(filepath.Join(testVectorPath, ed25519Json)) 189 if err != nil { 190 log.Fatalf("Failed to read test vectors: %v", err) 191 } 192 193 edv := &ed25519Vectors{} 194 if err := json.Unmarshal(b, edv); err != nil { 195 log.Fatalf("Failed to unmarshal JSON: %v", err) 196 } 197 198 success := true 199 200 for _, vector := range *edv { 201 if !runEd25519Test(vector) { 202 success = false 203 } 204 } 205 206 if !success { 207 os.Exit(1) 208 } 209} 210