Skip to content

Commit 8ca1b08

Browse files
committed
Marshal private keys in the PKCS #8 format
This format allows for different private key algorithms.
1 parent 352b652 commit 8ca1b08

File tree

3 files changed

+89
-20
lines changed

3 files changed

+89
-20
lines changed

internal/patroni/reconcile_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@ func TestReconcileInstanceCertificates(t *testing.T) {
7474
dataCert, _ := certFile(leaf.PrivateKey, leaf.Certificate)
7575
assert.Assert(t,
7676
cmp.Regexp(`^`+
77-
`-----BEGIN [^ ]+ PRIVATE KEY-----\n`+
77+
`-----BEGIN PRIVATE KEY-----\n`+
7878
`([^-]+\n)+`+
79-
`-----END [^ ]+ PRIVATE KEY-----\n`+
79+
`-----END PRIVATE KEY-----\n`+
8080
`-----BEGIN CERTIFICATE-----\n`+
8181
`([^-]+\n)+`+
8282
`-----END CERTIFICATE-----\n`+

internal/pki/encoding.go

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ const (
3131
// pemLabelECDSAKey is the textual encoding label for an elliptic curve private key
3232
// according to RFC 5915. See https://tools.ietf.org/html/rfc5915.
3333
pemLabelECDSAKey = "EC PRIVATE KEY"
34+
35+
// pemLabelPKCS8Key is the textual encoding label for a PKCS #8 private key
36+
// according to RFC 5958. See https://tools.ietf.org/html/rfc5958.
37+
pemLabelPKCS8Key = "PRIVATE KEY"
3438
)
3539

3640
var (
@@ -79,13 +83,13 @@ func (k PrivateKey) MarshalText() ([]byte, error) {
7983
k.ecdsa = new(ecdsa.PrivateKey)
8084
}
8185

82-
der, err := x509.MarshalECPrivateKey(k.ecdsa)
86+
der, err := x509.MarshalPKCS8PrivateKey(k.ecdsa)
8387
if err != nil {
8488
return nil, err
8589
}
8690

8791
return pem.EncodeToMemory(&pem.Block{
88-
Type: pemLabelECDSAKey,
92+
Type: pemLabelPKCS8Key,
8993
Bytes: der,
9094
}), nil
9195
}
@@ -94,13 +98,28 @@ func (k PrivateKey) MarshalText() ([]byte, error) {
9498
func (k *PrivateKey) UnmarshalText(data []byte) error {
9599
block, _ := pem.Decode(data)
96100

97-
if block == nil || block.Type != pemLabelECDSAKey {
101+
switch {
102+
case block == nil:
103+
fallthrough
104+
default:
98105
return fmt.Errorf("not a PEM-encoded private key")
99-
}
100106

101-
key, err := x509.ParseECPrivateKey(block.Bytes)
102-
if err == nil {
103-
k.ecdsa = key
107+
case block.Type == pemLabelECDSAKey:
108+
key, err := x509.ParseECPrivateKey(block.Bytes)
109+
if err == nil {
110+
k.ecdsa = key
111+
}
112+
return err
113+
114+
case block.Type == pemLabelPKCS8Key:
115+
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
116+
if err == nil {
117+
if ecdsa, ok := key.(*ecdsa.PrivateKey); ok {
118+
k.ecdsa = ecdsa
119+
} else {
120+
return fmt.Errorf("wrong private key algorithm: %T", key)
121+
}
122+
}
123+
return err
104124
}
105-
return err
106125
}

internal/pki/encoding_test.go

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package pki
1717

1818
import (
1919
"bytes"
20+
"errors"
2021
"os"
2122
"os/exec"
2223
"path/filepath"
@@ -118,8 +119,8 @@ func TestPrivateKeyTextMarshaling(t *testing.T) {
118119
key := root.PrivateKey
119120
txt, err := key.MarshalText()
120121
assert.NilError(t, err)
121-
assert.Assert(t, bytes.HasPrefix(txt, []byte("-----BEGIN EC PRIVATE KEY-----\n")), "got %q", txt)
122-
assert.Assert(t, bytes.HasSuffix(txt, []byte("\n-----END EC PRIVATE KEY-----\n")), "got %q", txt)
122+
assert.Assert(t, bytes.HasPrefix(txt, []byte("-----BEGIN PRIVATE KEY-----\n")), "got %q", txt)
123+
assert.Assert(t, bytes.HasSuffix(txt, []byte("\n-----END PRIVATE KEY-----\n")), "got %q", txt)
123124

124125
t.Run("RoundTrip", func(t *testing.T) {
125126
var sink PrivateKey
@@ -140,18 +141,67 @@ func TestPrivateKeyTextMarshaling(t *testing.T) {
140141
assert.DeepEqual(t, key, sink)
141142
})
142143

143-
t.Run("EncodedEmpty", func(t *testing.T) {
144-
txt := []byte("-----BEGIN EC PRIVATE KEY-----\n\n-----END EC PRIVATE KEY-----\n")
144+
t.Run("UnmarshalEllipticCurveSEC1", func(t *testing.T) {
145+
t.Run("EncodedEmpty", func(t *testing.T) {
146+
txt := []byte("-----BEGIN EC PRIVATE KEY-----\n\n-----END EC PRIVATE KEY-----\n")
145147

146-
var sink PrivateKey
147-
assert.ErrorContains(t, sink.UnmarshalText(txt), "asn1")
148+
var sink PrivateKey
149+
assert.ErrorContains(t, sink.UnmarshalText(txt), "asn1")
150+
})
151+
152+
t.Run("EncodedGarbage", func(t *testing.T) {
153+
txt := []byte("-----BEGIN EC PRIVATE KEY-----\nasdfasdf\n-----END EC PRIVATE KEY-----\n")
154+
155+
var sink PrivateKey
156+
assert.ErrorContains(t, sink.UnmarshalText(txt), "asn1")
157+
})
158+
159+
t.Run("GeneratedByOpenSSL", func(t *testing.T) {
160+
openssl := require.OpenSSL(t)
161+
162+
// The "openssl ecparam" command generates elliptic curve keys.
163+
cmd := exec.Command(openssl, "ecparam",
164+
"-genkey", "-name", "prime256v1", "-outform", "PEM", "-noout", "-text")
165+
166+
output, err := cmd.CombinedOutput()
167+
assert.NilError(t, err, "%q\n%s", cmd.Args, output)
168+
169+
var sink PrivateKey
170+
assert.NilError(t, sink.UnmarshalText(output))
171+
})
148172
})
149173

150-
t.Run("EncodedGarbage", func(t *testing.T) {
151-
txt := []byte("-----BEGIN EC PRIVATE KEY-----\nasdfasdf\n-----END EC PRIVATE KEY-----\n")
174+
t.Run("UnmarshalPKCS8", func(t *testing.T) {
175+
t.Run("EncodedEmpty", func(t *testing.T) {
176+
txt := []byte("-----BEGIN PRIVATE KEY-----\n\n-----END PRIVATE KEY-----\n")
152177

153-
var sink PrivateKey
154-
assert.ErrorContains(t, sink.UnmarshalText(txt), "asn1")
178+
var sink PrivateKey
179+
assert.ErrorContains(t, sink.UnmarshalText(txt), "asn1")
180+
})
181+
182+
t.Run("EncodedGarbage", func(t *testing.T) {
183+
txt := []byte("-----BEGIN PRIVATE KEY-----\nasdfasdf\n-----END PRIVATE KEY-----\n")
184+
185+
var sink PrivateKey
186+
assert.ErrorContains(t, sink.UnmarshalText(txt), "asn1")
187+
})
188+
189+
t.Run("WrongAlgorithm", func(t *testing.T) {
190+
openssl := require.OpenSSL(t)
191+
rsa, err := exec.Command("sh", "-ceu",
192+
`"$1" genrsa | "$1" pkcs8 -topk8 -nocrypt`,
193+
"--", openssl,
194+
).Output()
195+
196+
if exit := (*exec.ExitError)(nil); errors.As(err, &exit) {
197+
assert.NilError(t, err, "\n%s", exit.Stderr)
198+
} else {
199+
assert.NilError(t, err)
200+
}
201+
202+
var sink PrivateKey
203+
assert.ErrorContains(t, sink.UnmarshalText(rsa), "algorithm: *rsa")
204+
})
155205
})
156206

157207
t.Run("ReadByOpenSSL", func(t *testing.T) {

0 commit comments

Comments
 (0)