-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add PKCS8 marshaling to PKI #3518
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,17 @@ key and issuing cert will be appended to the | |
certificate pem. Defaults to "pem".`, | ||
} | ||
|
||
fields["private_key_format"] = &framework.FieldSchema{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add this param to the docs page as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
Type: framework.TypeString, | ||
Default: "der", | ||
Description: `Format for the returned private key. | ||
Generally the default will be controlled by the "format" | ||
parameter as either base64-encoded DER or PEM-encoded DER. | ||
However, this can be set to "pkcs8" to have the returned | ||
private key contain base64-encoded pkcs8 or PEM-encoded | ||
pkcs8 instead. Defaults to "der".`, | ||
} | ||
|
||
fields["ip_sans"] = &framework.FieldSchema{ | ||
Type: framework.TypeString, | ||
Description: `The requested IP SANs, if any, in a | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -106,6 +106,13 @@ func (b *backend) pathGenerateIntermediate( | |
} | ||
} | ||
|
||
if data.Get("private_key_format").(string) == "pkcs8" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fine as-is, but noticed that the other params were being retrieved with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I went down that path originally but there lay madness. The problem is that it's not called by all functions that can generate certs and what is then done with the generation params depends on the path. After going down that path and then one other, I realized the easiest way by far was to just transform the output once it's already generated. |
||
err = convertRespToPKCS8(resp) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
cb := &certutil.CertBundle{} | ||
cb.PrivateKey = csrb.PrivateKey | ||
cb.PrivateKeyType = csrb.PrivateKeyType | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// Copyright 2011 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package certutil | ||
|
||
import ( | ||
"crypto/ecdsa" | ||
"crypto/elliptic" | ||
"crypto/rsa" | ||
"crypto/x509" | ||
"crypto/x509/pkix" | ||
"encoding/asn1" | ||
"errors" | ||
"fmt" | ||
) | ||
|
||
var ( | ||
oidNamedCurveP224 = asn1.ObjectIdentifier{1, 3, 132, 0, 33} | ||
oidNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7} | ||
oidNamedCurveP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34} | ||
oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35} | ||
|
||
oidPublicKeyRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} | ||
oidPublicKeyDSA = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 1} | ||
oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1} | ||
) | ||
|
||
// pkcs8 reflects an ASN.1, PKCS#8 PrivateKey. See | ||
// ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-8/pkcs-8v1_2.asn | ||
// and RFC 5208. | ||
type pkcs8 struct { | ||
Version int | ||
Algo pkix.AlgorithmIdentifier | ||
PrivateKey []byte | ||
// optional attributes omitted. | ||
} | ||
|
||
type ecPrivateKey struct { | ||
Version int | ||
PrivateKey []byte | ||
NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"` | ||
PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"` | ||
} | ||
|
||
// MarshalPKCS8PrivateKey converts a private key to PKCS#8 encoded form. | ||
// The following key types are supported: *rsa.PrivateKey, *ecdsa.PublicKey. | ||
// Unsupported key types result in an error. | ||
// | ||
// See RFC 5208. | ||
func MarshalPKCS8PrivateKey(key interface{}) ([]byte, error) { | ||
var privKey pkcs8 | ||
|
||
switch k := key.(type) { | ||
case *rsa.PrivateKey: | ||
privKey.Algo = pkix.AlgorithmIdentifier{ | ||
Algorithm: oidPublicKeyRSA, | ||
Parameters: asn1.NullRawValue, | ||
} | ||
privKey.PrivateKey = x509.MarshalPKCS1PrivateKey(k) | ||
|
||
case *ecdsa.PrivateKey: | ||
oid, ok := oidFromNamedCurve(k.Curve) | ||
if !ok { | ||
return nil, errors.New("x509: unknown curve while marshalling to PKCS#8") | ||
} | ||
|
||
oidBytes, err := asn1.Marshal(oid) | ||
if err != nil { | ||
return nil, errors.New("x509: failed to marshal curve OID: " + err.Error()) | ||
} | ||
|
||
privKey.Algo = pkix.AlgorithmIdentifier{ | ||
Algorithm: oidPublicKeyECDSA, | ||
Parameters: asn1.RawValue{ | ||
FullBytes: oidBytes, | ||
}, | ||
} | ||
|
||
if privKey.PrivateKey, err = marshalECPrivateKeyWithOID(k, nil); err != nil { | ||
return nil, errors.New("x509: failed to marshal EC private key while building PKCS#8: " + err.Error()) | ||
} | ||
|
||
default: | ||
return nil, fmt.Errorf("x509: unknown key type while marshalling PKCS#8: %T", key) | ||
} | ||
|
||
return asn1.Marshal(privKey) | ||
} | ||
|
||
func oidFromNamedCurve(curve elliptic.Curve) (asn1.ObjectIdentifier, bool) { | ||
switch curve { | ||
case elliptic.P224(): | ||
return oidNamedCurveP224, true | ||
case elliptic.P256(): | ||
return oidNamedCurveP256, true | ||
case elliptic.P384(): | ||
return oidNamedCurveP384, true | ||
case elliptic.P521(): | ||
return oidNamedCurveP521, true | ||
} | ||
|
||
return nil, false | ||
} | ||
|
||
// marshalECPrivateKey marshals an EC private key into ASN.1, DER format and | ||
// sets the curve ID to the given OID, or omits it if OID is nil. | ||
func marshalECPrivateKeyWithOID(key *ecdsa.PrivateKey, oid asn1.ObjectIdentifier) ([]byte, error) { | ||
privateKeyBytes := key.D.Bytes() | ||
paddedPrivateKey := make([]byte, (key.Curve.Params().N.BitLen()+7)/8) | ||
copy(paddedPrivateKey[len(paddedPrivateKey)-len(privateKeyBytes):], privateKeyBytes) | ||
|
||
return asn1.Marshal(ecPrivateKey{ | ||
Version: 1, | ||
PrivateKey: paddedPrivateKey, | ||
NamedCurveOID: oid, | ||
PublicKey: asn1.BitString{Bytes: elliptic.Marshal(key.Curve, key.X, key.Y)}, | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// Copyright 2011 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package certutil | ||
|
||
import ( | ||
"bytes" | ||
"crypto/ecdsa" | ||
"crypto/elliptic" | ||
"crypto/rsa" | ||
"crypto/x509" | ||
"encoding/hex" | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
// Generated using: | ||
// openssl genrsa 1024 | openssl pkcs8 -topk8 -nocrypt | ||
var pkcs8RSAPrivateKeyHex = `30820278020100300d06092a864886f70d0101010500048202623082025e02010002818100cfb1b5bf9685ffa97b4f99df4ff122b70e59ac9b992f3bc2b3dde17d53c1a34928719b02e8fd17839499bfbd515bd6ef99c7a1c47a239718fe36bfd824c0d96060084b5f67f0273443007a24dfaf5634f7772c9346e10eb294c2306671a5a5e719ae24b4de467291bc571014b0e02dec04534d66a9bb171d644b66b091780e8d020301000102818100b595778383c4afdbab95d2bfed12b3f93bb0a73a7ad952f44d7185fd9ec6c34de8f03a48770f2009c8580bcd275e9632714e9a5e3f32f29dc55474b2329ff0ebc08b3ffcb35bc96e6516b483df80a4a59cceb71918cbabf91564e64a39d7e35dce21cb3031824fdbc845dba6458852ec16af5dddf51a8397a8797ae0337b1439024100ea0eb1b914158c70db39031dd8904d6f18f408c85fbbc592d7d20dee7986969efbda081fdf8bc40e1b1336d6b638110c836bfdc3f314560d2e49cd4fbde1e20b024100e32a4e793b574c9c4a94c8803db5152141e72d03de64e54ef2c8ed104988ca780cd11397bc359630d01b97ebd87067c5451ba777cf045ca23f5912f1031308c702406dfcdbbd5a57c9f85abc4edf9e9e29153507b07ce0a7ef6f52e60dcfebe1b8341babd8b789a837485da6c8d55b29bbb142ace3c24a1f5b54b454d01b51e2ad03024100bd6a2b60dee01e1b3bfcef6a2f09ed027c273cdbbaf6ba55a80f6dcc64e4509ee560f84b4f3e076bd03b11e42fe71a3fdd2dffe7e0902c8584f8cad877cdc945024100aa512fa4ada69881f1d8bb8ad6614f192b83200aef5edf4811313d5ef30a86cbd0a90f7b025c71ea06ec6b34db6306c86b1040670fd8654ad7291d066d06d031` | ||
|
||
// Generated using: | ||
// openssl ecparam -genkey -name secp224r1 | openssl pkcs8 -topk8 -nocrypt | ||
var pkcs8P224PrivateKeyHex = `3078020100301006072a8648ce3d020106052b810400210461305f020101041cca3d72b3e88fed2684576dad9b80a9180363a5424986900e3abcab3fa13c033a0004f8f2a6372872a4e61263ed893afb919576a4cacfecd6c081a2cbc76873cf4ba8530703c6042b3a00e2205087e87d2435d2e339e25702fae1` | ||
|
||
// Generated using: | ||
// openssl ecparam -genkey -name secp256r1 | openssl pkcs8 -topk8 -nocrypt | ||
var pkcs8P256PrivateKeyHex = `308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b0201010420dad6b2f49ca774c36d8ae9517e935226f667c929498f0343d2424d0b9b591b43a14403420004b9c9b90095476afe7b860d8bd43568cab7bcb2eed7b8bf2fa0ce1762dd20b04193f859d2d782b1e4cbfd48492f1f533113a6804903f292258513837f07fda735` | ||
|
||
// Generated using: | ||
// openssl ecparam -genkey -name secp384r1 | openssl pkcs8 -topk8 -nocrypt | ||
var pkcs8P384PrivateKeyHex = `3081b6020100301006072a8648ce3d020106052b8104002204819e30819b02010104309bf832f6aaaeacb78ce47ffb15e6fd0fd48683ae79df6eca39bfb8e33829ac94aa29d08911568684c2264a08a4ceb679a164036200049070ad4ed993c7770d700e9f6dc2baa83f63dd165b5507f98e8ff29b5d2e78ccbe05c8ddc955dbf0f7497e8222cfa49314fe4e269459f8e880147f70d785e530f2939e4bf9f838325bb1a80ad4cf59272ae0e5efe9a9dc33d874492596304bd3` | ||
|
||
// Generated using: | ||
// openssl ecparam -genkey -name secp521r1 | openssl pkcs8 -topk8 -nocrypt | ||
// | ||
// Note that OpenSSL will truncate the private key if it can (i.e. it emits it | ||
// like an integer, even though it's an OCTET STRING field). Thus if you | ||
// regenerate this you may, randomly, find that it's a byte shorter than | ||
// expected and the Go test will fail to recreate it exactly. | ||
var pkcs8P521PrivateKeyHex = `3081ee020100301006072a8648ce3d020106052b810400230481d63081d3020101044200cfe0b87113a205cf291bb9a8cd1a74ac6c7b2ebb8199aaa9a5010d8b8012276fa3c22ac913369fa61beec2a3b8b4516bc049bde4fb3b745ac11b56ab23ac52e361a1818903818600040138f75acdd03fbafa4f047a8e4b272ba9d555c667962b76f6f232911a5786a0964e5edea6bd21a6f8725720958de049c6e3e6661c1c91b227cebee916c0319ed6ca003db0a3206d372229baf9dd25d868bf81140a518114803ce40c1855074d68c4e9dab9e65efba7064c703b400f1767f217dac82715ac1f6d88c74baf47a7971de4ea` | ||
|
||
func TestPKCS8(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
keyHex string | ||
keyType reflect.Type | ||
curve elliptic.Curve | ||
}{ | ||
{ | ||
name: "RSA private key", | ||
keyHex: pkcs8RSAPrivateKeyHex, | ||
keyType: reflect.TypeOf(&rsa.PrivateKey{}), | ||
}, | ||
{ | ||
name: "P-224 private key", | ||
keyHex: pkcs8P224PrivateKeyHex, | ||
keyType: reflect.TypeOf(&ecdsa.PrivateKey{}), | ||
curve: elliptic.P224(), | ||
}, | ||
{ | ||
name: "P-256 private key", | ||
keyHex: pkcs8P256PrivateKeyHex, | ||
keyType: reflect.TypeOf(&ecdsa.PrivateKey{}), | ||
curve: elliptic.P256(), | ||
}, | ||
{ | ||
name: "P-384 private key", | ||
keyHex: pkcs8P384PrivateKeyHex, | ||
keyType: reflect.TypeOf(&ecdsa.PrivateKey{}), | ||
curve: elliptic.P384(), | ||
}, | ||
{ | ||
name: "P-521 private key", | ||
keyHex: pkcs8P521PrivateKeyHex, | ||
keyType: reflect.TypeOf(&ecdsa.PrivateKey{}), | ||
curve: elliptic.P521(), | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
derBytes, err := hex.DecodeString(test.keyHex) | ||
if err != nil { | ||
t.Errorf("%s: failed to decode hex: %s", test.name, err) | ||
continue | ||
} | ||
privKey, err := x509.ParsePKCS8PrivateKey(derBytes) | ||
if err != nil { | ||
t.Errorf("%s: failed to decode PKCS#8: %s", test.name, err) | ||
continue | ||
} | ||
if reflect.TypeOf(privKey) != test.keyType { | ||
t.Errorf("%s: decoded PKCS#8 returned unexpected key type: %T", test.name, privKey) | ||
continue | ||
} | ||
if ecKey, isEC := privKey.(*ecdsa.PrivateKey); isEC && ecKey.Curve != test.curve { | ||
t.Errorf("%s: decoded PKCS#8 returned unexpected curve %#v", test.name, ecKey.Curve) | ||
continue | ||
} | ||
reserialised, err := MarshalPKCS8PrivateKey(privKey) | ||
if err != nil { | ||
t.Errorf("%s: failed to marshal into PKCS#8: %s", test.name, err) | ||
continue | ||
} | ||
if !bytes.Equal(derBytes, reserialised) { | ||
t.Errorf("%s: marshalled PKCS#8 didn't match original: got %x, want %x", test.name, reserialised, derBytes) | ||
continue | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we not use
%q
here since its not taking any variable as input? Or was there a reason for doing it this way?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did it this way instead of
\"
. Just felt nicer than escapes.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see. 👍