Source file src/crypto/ecdh/ecdh_wycheproof_test.go

     1  // Copyright 2026 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  package ecdh_test
     5  
     6  import (
     7  	"bytes"
     8  	"crypto/ecdh"
     9  	"crypto/ecdsa"
    10  	"crypto/internal/cryptotest/wycheproof"
    11  	"crypto/x509"
    12  	"fmt"
    13  	"testing"
    14  )
    15  
    16  func TestSecpECDHWycheproof(t *testing.T) {
    17  	flagsShouldPass := map[string]bool{
    18  		// We don't support compressed points or public keys.
    19  		"CompressedPoint":  false,
    20  		"CompressedPublic": false,
    21  	}
    22  
    23  	curveToCurve := map[string]ecdh.Curve{
    24  		"secp256r1": ecdh.P256(),
    25  		"secp384r1": ecdh.P384(),
    26  		"secp521r1": ecdh.P521(),
    27  	}
    28  
    29  	curveToKeySize := map[string]int{
    30  		"secp256r1": 32,
    31  		"secp384r1": 48,
    32  		"secp521r1": 66,
    33  	}
    34  
    35  	for _, file := range []string{
    36  		"ecdh_secp256r1_ecpoint_test.json",
    37  		"ecdh_secp384r1_ecpoint_test.json",
    38  		"ecdh_secp521r1_ecpoint_test.json",
    39  	} {
    40  		var testdata wycheproof.EcdhEcpointTestSchemaV1Json
    41  		wycheproof.LoadVectorFile(t, file, &testdata)
    42  
    43  		for _, tg := range testdata.TestGroups {
    44  			if _, ok := curveToCurve[tg.Curve]; !ok {
    45  				continue
    46  			}
    47  			curve := curveToCurve[tg.Curve]
    48  			keySize := curveToKeySize[tg.Curve]
    49  
    50  			for _, tv := range tg.Tests {
    51  				testName := wycheproof.TestName(file, tv)
    52  				tv := ecdhWycheproofTV{
    53  					tcID:    tv.TcId,
    54  					comment: tv.Comment,
    55  					flags:   tv.Flags,
    56  					result:  tv.Result,
    57  					public:  tv.Public,
    58  					private: tv.Private,
    59  					shared:  tv.Shared,
    60  				}
    61  				t.Run(testName, func(t *testing.T) {
    62  					t.Parallel()
    63  					runECDHWycheproofTest(t, curve, keySize, flagsShouldPass, tv, curve.NewPublicKey)
    64  				})
    65  			}
    66  		}
    67  	}
    68  }
    69  
    70  func TestSecpECDHSPKIWycheproof(t *testing.T) {
    71  	flagsShouldPass := map[string]bool{
    72  		"CompressedPublic":         false,
    73  		"CompressedPoint":          false,
    74  		"UnnamedCurve":             false,
    75  		"WrongOrder":               false,
    76  		"UnusedParam":              false,
    77  		"ModifiedGenerator":        false,
    78  		"ModifiedCofactor":         false,
    79  		"ModifiedCurveParameter":   false,
    80  		"NoCofactor":               false,
    81  		"Modified curve parameter": false,
    82  		"InvalidAsn":               false,
    83  	}
    84  
    85  	parseSPKIPub := func(p []byte) (*ecdh.PublicKey, error) {
    86  		pubKeyAny, err := x509.ParsePKIXPublicKey(p)
    87  		if err != nil {
    88  			return nil, err
    89  		}
    90  		ecdsaPub, ok := pubKeyAny.(*ecdsa.PublicKey)
    91  		if !ok {
    92  			return nil, fmt.Errorf("unexpected key type %T", pubKeyAny)
    93  		}
    94  		return ecdsaPub.ECDH()
    95  	}
    96  
    97  	curveToCurve := map[string]ecdh.Curve{
    98  		"secp256r1": ecdh.P256(),
    99  		"secp384r1": ecdh.P384(),
   100  		"secp521r1": ecdh.P521(),
   101  	}
   102  
   103  	curveToKeySize := map[string]int{
   104  		"secp256r1": 32,
   105  		"secp384r1": 48,
   106  		"secp521r1": 66,
   107  	}
   108  
   109  	for _, file := range []string{
   110  		"ecdh_secp256r1_test.json",
   111  		"ecdh_secp384r1_test.json",
   112  		"ecdh_secp521r1_test.json",
   113  	} {
   114  		var testdata wycheproof.EcdhTestSchemaV1Json
   115  		wycheproof.LoadVectorFile(t, file, &testdata)
   116  
   117  		for _, tg := range testdata.TestGroups {
   118  			if _, ok := curveToCurve[tg.Curve]; !ok {
   119  				continue
   120  			}
   121  			curve := curveToCurve[tg.Curve]
   122  			keySize := curveToKeySize[tg.Curve]
   123  
   124  			for _, tv := range tg.Tests {
   125  				testName := wycheproof.TestName(file, tv)
   126  				tv := ecdhWycheproofTV{
   127  					tcID:    tv.TcId,
   128  					comment: tv.Comment,
   129  					flags:   tv.Flags,
   130  					result:  tv.Result,
   131  					public:  tv.Public,
   132  					private: tv.Private,
   133  					shared:  tv.Shared,
   134  				}
   135  				t.Run(testName, func(t *testing.T) {
   136  					t.Parallel()
   137  					runECDHWycheproofTest(t, curve, keySize, flagsShouldPass, tv, parseSPKIPub)
   138  				})
   139  			}
   140  		}
   141  	}
   142  }
   143  
   144  func TestX25519ECDHWycheproof(t *testing.T) {
   145  	flagsShouldPass := map[string]bool{
   146  		"Twist":                  true,
   147  		"SmallPublicKey":         false,
   148  		"LowOrderPublic":         false,
   149  		"ZeroSharedSecret":       false,
   150  		"NonCanonicalPublic":     true,
   151  		"SpecialPublicKey":       true,
   152  		"EdgeCaseMultiplication": true,
   153  		"EdgeCaseShared":         true,
   154  		"Ktv":                    true,
   155  	}
   156  
   157  	file := "x25519_test.json"
   158  	var testdata wycheproof.XdhCompSchemaV1Json
   159  	wycheproof.LoadVectorFile(t, file, &testdata)
   160  
   161  	for _, tg := range testdata.TestGroups {
   162  		if tg.Curve != "curve25519" {
   163  			continue
   164  		}
   165  
   166  		for _, tv := range tg.Tests {
   167  			testName := wycheproof.TestName(file, tv)
   168  			tv := ecdhWycheproofTV{
   169  				tcID:    tv.TcId,
   170  				comment: tv.Comment,
   171  				flags:   tv.Flags,
   172  				result:  tv.Result,
   173  				public:  tv.Public,
   174  				private: tv.Private,
   175  				shared:  tv.Shared,
   176  			}
   177  			t.Run(testName, func(t *testing.T) {
   178  				t.Parallel()
   179  				runECDHWycheproofTest(t, ecdh.X25519(), 32, flagsShouldPass, tv, ecdh.X25519().NewPublicKey)
   180  			})
   181  		}
   182  	}
   183  }
   184  
   185  // ecdhWycheproofTV is a representation common to the three different schemas
   186  // we process in this test: wycheproof.XdhCompSchemaV1Json,
   187  // wycheproof.EcdhTestSchemaV1Json and wycheproof.EcdhEcpointTestSchemaV1Json
   188  type ecdhWycheproofTV struct {
   189  	tcID    int
   190  	comment string
   191  	flags   []string
   192  	result  wycheproof.Result
   193  	public  string
   194  	private string
   195  	shared  string
   196  }
   197  
   198  // runECDHWycheproofTest runs test logic common to the three ECDH test schemas
   199  // we process in this file.
   200  func runECDHWycheproofTest(
   201  	t *testing.T,
   202  	curve ecdh.Curve,
   203  	expectedKeySize int,
   204  	flagsShouldPass map[string]bool,
   205  	tv ecdhWycheproofTV,
   206  	parsePub func([]byte) (*ecdh.PublicKey, error)) {
   207  	t.Helper()
   208  
   209  	shouldPass := wycheproof.ShouldPass(t, tv.result, tv.flags, flagsShouldPass)
   210  
   211  	pub, err := parsePub(wycheproof.MustDecodeHex(tv.public))
   212  	if err != nil {
   213  		if shouldPass {
   214  			t.Errorf("parsePub: %v", err)
   215  		}
   216  		return
   217  	}
   218  
   219  	privBytes := wycheproof.MustDecodeHex(tv.private)
   220  	priv, err := curve.NewPrivateKey(privBytes)
   221  	if err != nil {
   222  		if shouldPass && len(privBytes) == expectedKeySize {
   223  			t.Errorf("NewPrivateKey: %v", err)
   224  		}
   225  		return
   226  	}
   227  
   228  	x, err := priv.ECDH(pub)
   229  	if err != nil {
   230  		if shouldPass {
   231  			t.Fatalf("ECDH: %v", err)
   232  		}
   233  		return
   234  	}
   235  
   236  	shared := wycheproof.MustDecodeHex(tv.shared)
   237  	if shouldPass {
   238  		if !bytes.Equal(shared, x) {
   239  			t.Errorf("ECDH = %x, want %x", x, shared)
   240  		}
   241  	} else if tv.result == "invalid" {
   242  		// For invalid inputs, a correct ECDH result is a test failure.
   243  		if bytes.Equal(shared, x) {
   244  			t.Errorf("ECDH = %x, want anything else", x)
   245  		}
   246  	}
   247  }
   248  

View as plain text