Description
Go version
go version go1.24.0 windows/amd64
Output of go env
in your module/workspace:
set AR=ar
set CC=gcc
set CGO_CFLAGS=-O2 -g
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-O2 -g
set CGO_ENABLED=1
set CGO_FFLAGS=-O2 -g
set CGO_LDFLAGS=-O2 -g
set CXX=g++
set GCCGO=gccgo
set GO111MODULE=on
set GOAMD64=v1
set GOARCH=amd64
set GOAUTH=netrc
set GOBIN=
set GOCACHE=C:\Users\justi\AppData\Local\go-build
set GOCACHEPROG=
set GODEBUG=
set GOENV=C:\Users\justi\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFIPS140=off
set GOFLAGS=
set GOGCCFLAGS=-m64 -mthreads -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=E:/go-tmp/go-build2302615705=/tmp/go-build -gno-record-gcc-switches
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMOD=C:\Dev\Projects\certutil\go.mod
set GOMODCACHE=C:\Users\justi\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\justi\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:/Program Files/Go
set GOSUMDB=sum.golang.org
set GOTELEMETRY=local
set GOTELEMETRYDIR=C:\Users\justi\AppData\Roaming\go\telemetry
set GOTMPDIR=E:/go-tmp/
set GOTOOLCHAIN=auto
set GOTOOLDIR=C:\Program Files\Go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.24.0
set GOWORK=
set PKG_CONFIG=pkg-config
What did you do?
https://go.dev/play/p/r0_VyaLBFeL
package main
import (
"crypto/ecdh"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/stretchr/testify/assert"
"testing"
)
var ecdhTestCurves = []struct {
Name string
Curve ecdh.Curve
}{
{"ECDH X25519", ecdh.X25519()}, // PASS
{"ECDH P256", ecdh.P256()}, // FAIL => encode+decode returns ECDSA instead of ECDH
{"ECDH P384", ecdh.P384()}, // FAIL => encode+decode returns ECDSA instead of ECDH
{"ECDH P521", ecdh.P521()}, // FAIL => encode+decode returns ECDSA instead of ECDH
}
var ecdsaTestCurves = []struct {
Name string
Curve elliptic.Curve
}{
{"ECDSA P224", elliptic.P224()}, // PASS
{"ECDSA P256", elliptic.P256()}, // PASS
{"ECDSA P384", elliptic.P384()}, // PASS
{"ECDSA P521", elliptic.P521()}, // PASS
}
func TestEncodeDecodeECDH(t *testing.T) {
for _, curve := range ecdhTestCurves {
t.Run(curve.Name, func(t *testing.T) {
original, err := curve.Curve.GenerateKey(rand.Reader)
if err != nil {
t.Errorf("generate failed: %v", err)
}
assert.IsType(t, &ecdh.PrivateKey{}, original)
decoded, err := pkcs8EncodeDecode(t, original)
if err != nil {
t.Errorf("generate failed: %v", err)
}
assert.IsType(t, &ecdh.PrivateKey{}, decoded)
})
}
}
func TestEncodeDecodeECDSA(t *testing.T) {
for _, curve := range ecdsaTestCurves {
t.Run(curve.Name, func(t *testing.T) {
original, err := ecdsa.GenerateKey(curve.Curve, rand.Reader)
if err != nil {
t.Errorf("generate failed: %v", err)
}
assert.IsType(t, &ecdsa.PrivateKey{}, original)
decoded, err := pkcs8EncodeDecode(t, original)
if err != nil {
t.Errorf("generate failed: %v", err)
}
assert.IsType(t, &ecdsa.PrivateKey{}, decoded)
})
}
}
func pkcs8EncodeDecode(t *testing.T, key any) (any, error) {
encodedBytes, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return nil, fmt.Errorf("encode failed: %w", err)
}
pemBytes := pem.EncodeToMemory(&pem.Block{Bytes: encodedBytes, Type: "PRIVATE KEY"})
t.Logf("PKCS#8 PEM of private Key :\n%s", string(pemBytes))
decodedKey, err := x509.ParsePKCS8PrivateKey(encodedBytes)
if err != nil {
return nil, fmt.Errorf("decode failed: %w", err)
}
return decodedKey, nil
}
What did you see happen?
Output from go test
=== RUN TestEncodeDecodeECDH
=== RUN TestEncodeDecodeECDH/ECDH_P256
main_test.go:78: PKCS#8 PEM of private Key :
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgo4RiM5ZuvJpvXUgV
+UNmMNueNE/QWXrR+XPDwHg8cn6hRANCAAQ+qZlrVShfxIMl2oi/Ppl0/cXqlWDF
FQ0a76zezlbzkmGmrAneJFtdnjvlL8eRpiMW5jUXLWu/Hh3NZ9I9GnC4
-----END PRIVATE KEY-----
main_test.go:48:
Error Trace: C:/Dev/Projects/certutil/main_test.go:48
Error: Object expected to be of type *ecdh.PrivateKey, but was *ecdsa.PrivateKey
Test: TestEncodeDecodeECDH/ECDH_P256
--- FAIL: TestEncodeDecodeECDH/ECDH_P256 (0.00s)
=== RUN TestEncodeDecodeECDH/ECDH_P384
main_test.go:78: PKCS#8 PEM of private Key :
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCFp1MYtEJ/ukwhBIwA
E/MFg5IcPM9+RMW+uOQ1xbxgPFOrXebZ9S5PIgGsuW9d/KyhZANiAARi1hwRy81s
GycYcTU7Fox4k5YfRSjKdgiv6n31FPc25YjUJ29f7FaJcWz66KoZUu6oOtLBIsqH
UQXu0FH6BC/vjhtPgNEbspp1Vb/Fnv4SCeEN72+Qx8N/+EwNYiySUFs=
-----END PRIVATE KEY-----
main_test.go:48:
Error Trace: C:/Dev/Projects/certutil/main_test.go:48
Error: Object expected to be of type *ecdh.PrivateKey, but was *ecdsa.PrivateKey
Test: TestEncodeDecodeECDH/ECDH_P384
--- FAIL: TestEncodeDecodeECDH/ECDH_P384 (0.00s)
=== RUN TestEncodeDecodeECDH/ECDH_P521
main_test.go:78: PKCS#8 PEM of private Key :
-----BEGIN PRIVATE KEY-----
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIA6dsRuU6LciA5EaSa
+iPx+7d6aqLTVkuvK6vxqnwVkkm5VVWzBq0F/uzVr6YQZsgqwiTNH2PtaMNKdcKV
quz3EWyhgYkDgYYABACq7RCto7P19th0QmWZIsPHPMtbC+6jyQYwyOLGLY23GE+x
Oko4eGdXFoo9J9NrNaFP60aiYeTW6eqTGYTanSFG7wCDiRwLSbTKDjQxmaPODdQJ
V5F+pfSiJpPU+bLnhGhhM87UmaXkjB4/c+X2BI5Qig3dGKljtvfCKkGhmlVZGm0N
9Q==
-----END PRIVATE KEY-----
main_test.go:48:
Error Trace: C:/Dev/Projects/certutil/main_test.go:48
Error: Object expected to be of type *ecdh.PrivateKey, but was *ecdsa.PrivateKey
Test: TestEncodeDecodeECDH/ECDH_P521
--- FAIL: TestEncodeDecodeECDH/ECDH_P521 (0.00s)
=== RUN TestEncodeDecodeECDH/ECDH_X25519
main_test.go:78: PKCS#8 PEM of private Key :
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VuBCIEINPytT8SXA0LU6sFf1Tq5lmZ1y+ILmJWLM39wrpQ1C69
-----END PRIVATE KEY-----
--- PASS: TestEncodeDecodeECDH/ECDH_X25519 (0.00s)
--- FAIL: TestEncodeDecodeECDH (0.01s)
=== RUN TestEncodeDecodeECDSA
=== RUN TestEncodeDecodeECDSA/ECDSA_P224
main_test.go:78: PKCS#8 PEM of private Key :
-----BEGIN PRIVATE KEY-----
MHgCAQAwEAYHKoZIzj0CAQYFK4EEACEEYTBfAgEBBBysCNNRbmymBwxp8STsHWWD
5feP2R6mkINTRCa0oTwDOgAE7XfvGvo3XxXKri/t+9OMxO0Tikq6kJqQXMd5jriA
H4rfB4AKG5a4nizu00LksaBLewm+JRV4Blg=
-----END PRIVATE KEY-----
--- PASS: TestEncodeDecodeECDSA/ECDSA_P224 (0.00s)
=== RUN TestEncodeDecodeECDSA/ECDSA_P256
main_test.go:78: PKCS#8 PEM of private Key :
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgmSI7x4hO8iJ/beIo
KILJKF2b98JM7JW9Y3fGAeERkUehRANCAAT5yOjGMQ+hxiXM+2H20wplGph8ot4s
oStxC5eYo9ykqR4odPBNkubdA7zgLf6EQhO/XCVoC37tT50gaBqNX2uJ
-----END PRIVATE KEY-----
--- PASS: TestEncodeDecodeECDSA/ECDSA_P256 (0.00s)
=== RUN TestEncodeDecodeECDSA/ECDSA_P384
main_test.go:78: PKCS#8 PEM of private Key :
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAaFk2jNn/rK5DTqEai
hPkBuscpawBtNnOixaBaC53BG/1lUdhvRMR3WobuDWuYrWChZANiAAQYqmyJwnX3
iHfAOtietZum8oo6PZ0Ebp0b0rFqfr7ow3kj8WfTUNCC42BsRMuugRw8XDUgCWQ1
yfqqxgZskzfLfN/vrjmiq5FGTkSzkSKZV2tkqHf5v2udF6suPt5hNtI=
-----END PRIVATE KEY-----
--- PASS: TestEncodeDecodeECDSA/ECDSA_P384 (0.00s)
=== RUN TestEncodeDecodeECDSA/ECDSA_P521
main_test.go:78: PKCS#8 PEM of private Key :
-----BEGIN PRIVATE KEY-----
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIALlt69BM7wwuMKug1
3cwXBYOPYskyoNe9UyIOHJqZd+DkitbhFcpho5aeNhoFMLt3ULzNrv51LVGlpgoN
7pqX8HWhgYkDgYYABAEiSP7EXI5+8Ej4H/IflmyWTUnoDrArSldaGoI3GxQ4m4OJ
yOBk6F2CbSXyHpGKXq5QtkJggNDuLL8XZ/hTsc1cNAHLfH6OYDgsQ+e4q4UmwK3n
GKesededu41Gw+FU2L82ACt6AK8iOuqKoOeKSfOAaOldTQodpCvFFCenDtc7VMrX
4A==
-----END PRIVATE KEY-----
--- PASS: TestEncodeDecodeECDSA/ECDSA_P521 (0.00s)
--- PASS: TestEncodeDecodeECDSA (0.00s)
FAIL
Process finished with the exit code 1
What did you expect to see?
I was expecting all of the tests to pass. Each test passes an ECDH or ECDSA private key through x509.MarshalPKCS8PrivateKey
and then x509.ParsePKCS8PrivateKey
. The decoded private key is expected to be the same type as the original.
All 4 ECDSA tests pass. Only 1 ECDH test passes (i.e. X25519). The other 3 ECDH tests fail (i.e. P256, P384, P521). They pass in an ECDH private key, but the coded key is ECDSA instead of expected ECDH.
I log the PKCS#8 private keys as PEM in the tests. I copied the ECDH P256 PEM into an ASN.1 decoder (e.g. https://lapo.it/asn1js/). The expected OID is ECDH P256 (i.e. 1.3.132.1.12). Actual OID is ECDSA P256 (i.e. 1.2.840.10045.3.1.7), so it appears to be marshalled with the wrong OID. Here is a screenshot of the PEM from the ECDH P256 test, showing unexpected ECDSA OID.
I debugged into x509.MarshalPKCS8PrivateKey. For the ECDH P256 test, it picks the wrong OID. Here is a link to Go x509.go source where ECDH marshall picks its OID (i.e. 1.2.840.10045.3.1.7).
Lines 530 to 535 in fba83cd
I think it is a marshalling bug. The ECDH and ECDSA OIDs are meant to be distinct. However, it looks like they OIDs are mixed together in the code.