Skip to content

Commit a505f0c

Browse files
committed
Marshal private keys in the PKCS #8 format
This format allows for different private key algorithms.
1 parent 80e7ab8 commit a505f0c

File tree

3 files changed

+90
-21
lines changed

3 files changed

+90
-21
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: 61 additions & 11 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"
@@ -117,8 +118,8 @@ func TestPrivateKeyTextMarshaling(t *testing.T) {
117118
key := root.PrivateKey
118119
txt, err := key.MarshalText()
119120
assert.NilError(t, err)
120-
assert.Assert(t, bytes.HasPrefix(txt, []byte("-----BEGIN EC PRIVATE KEY-----\n")), "got %q", txt)
121-
assert.Assert(t, bytes.HasSuffix(txt, []byte("\n-----END EC PRIVATE KEY-----\n")), "got %q", txt)
121+
assert.Assert(t, bytes.HasPrefix(txt, []byte("-----BEGIN PRIVATE KEY-----\n")), "got %q", txt)
122+
assert.Assert(t, bytes.HasSuffix(txt, []byte("\n-----END PRIVATE KEY-----\n")), "got %q", txt)
122123

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

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

145-
var sink PrivateKey
146-
assert.ErrorContains(t, sink.UnmarshalText(txt), "asn1")
147-
})
147+
var sink PrivateKey
148+
assert.ErrorContains(t, sink.UnmarshalText(txt), "asn1")
149+
})
148150

149-
t.Run("EncodedGarbage", func(t *testing.T) {
150-
txt := []byte("-----BEGIN EC PRIVATE KEY-----\nasdfasdf\n-----END EC PRIVATE KEY-----\n")
151+
t.Run("EncodedGarbage", func(t *testing.T) {
152+
txt := []byte("-----BEGIN EC PRIVATE KEY-----\nasdfasdf\n-----END EC PRIVATE KEY-----\n")
151153

152-
var sink PrivateKey
153-
assert.ErrorContains(t, sink.UnmarshalText(txt), "asn1")
154+
var sink PrivateKey
155+
assert.ErrorContains(t, sink.UnmarshalText(txt), "asn1")
156+
})
157+
158+
t.Run("GeneratedByOpenSSL", func(t *testing.T) {
159+
openssl := require.OpenSSL(t)
160+
161+
// The "openssl ecparam" command generates elliptic curve keys.
162+
cmd := exec.Command(openssl, "ecparam",
163+
"-genkey", "-name", "prime256v1", "-outform", "PEM", "-noout", "-text")
164+
165+
output, err := cmd.CombinedOutput()
166+
assert.NilError(t, err, "%q\n%s", cmd.Args, output)
167+
168+
var sink PrivateKey
169+
assert.NilError(t, sink.UnmarshalText(output))
170+
})
171+
})
172+
173+
t.Run("UnmarshalPKCS8", func(t *testing.T) {
174+
t.Run("EncodedEmpty", func(t *testing.T) {
175+
txt := []byte("-----BEGIN PRIVATE KEY-----\n\n-----END PRIVATE KEY-----\n")
176+
177+
var sink PrivateKey
178+
assert.ErrorContains(t, sink.UnmarshalText(txt), "asn1")
179+
})
180+
181+
t.Run("EncodedGarbage", func(t *testing.T) {
182+
txt := []byte("-----BEGIN PRIVATE KEY-----\nasdfasdf\n-----END PRIVATE KEY-----\n")
183+
184+
var sink PrivateKey
185+
assert.ErrorContains(t, sink.UnmarshalText(txt), "asn1")
186+
})
187+
188+
t.Run("WrongAlgorithm", func(t *testing.T) {
189+
openssl := require.OpenSSL(t)
190+
rsa, err := exec.Command("sh", "-ceu",
191+
`"$1" genrsa | "$1" pkcs8 -topk8 -nocrypt`,
192+
"--", openssl,
193+
).Output()
194+
195+
if exit := (*exec.ExitError)(nil); errors.As(err, &exit) {
196+
assert.NilError(t, err, "\n%s", exit.Stderr)
197+
} else {
198+
assert.NilError(t, err)
199+
}
200+
201+
var sink PrivateKey
202+
assert.ErrorContains(t, sink.UnmarshalText(rsa), "algorithm: *rsa")
203+
})
154204
})
155205

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

0 commit comments

Comments
 (0)