47df361761
The original sprig-library implementation of genCA and genSignedCert may accept only CN parameter which isn't enough for K8s admin certificate. That certification must have O=system:masters, e.g. /CN=admin/O=system:masters This PR introduces the set of functions that insted of cn accept subj argument that may have a form compatible with openssl -subj parameter. If the first symbol isn't '/' subj behaves as cn argument. The set of new functions that accept subj arg is: genCAEx genCAWithKeyEx genSignedCertEx genSignedCertWithKeyEx Since the implementaion required to copy some non-exported helper functions from sprig, the decision was made to separate all go-template extension functions into a separate package: extlib. This package can be reused in other go-applications, it's just necessary to use GenericFuncMap function to get function-map. Change-Id: I0ffddee2e597323803bf5f1b54f315ded424b7be
221 lines
5.3 KiB
Go
221 lines
5.3 KiB
Go
/*
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package extlib
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"crypto"
|
|
"crypto/dsa"
|
|
"crypto/ecdsa"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"encoding/asn1"
|
|
"encoding/pem"
|
|
"math/big"
|
|
"net"
|
|
)
|
|
|
|
// that pieces of code were copied from
|
|
// https://github.com/Masterminds/sprig/blob/868e7517d046cb7540e10345b09c0d70da584c8e/crypto.go#L405
|
|
type certificate struct {
|
|
Cert string
|
|
Key string
|
|
}
|
|
|
|
// DSAKeyFormat stores the format for DSA keys.
|
|
// Used by pemBlockForKey
|
|
type DSAKeyFormat struct {
|
|
Version int
|
|
P, Q, G, Y, X *big.Int
|
|
}
|
|
|
|
func getCertAndKey(
|
|
template *x509.Certificate,
|
|
signeeKey crypto.PrivateKey,
|
|
parent *x509.Certificate,
|
|
signingKey crypto.PrivateKey,
|
|
) (string, string, error) {
|
|
signeePubKey, err := getPublicKey(signeeKey)
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("error retrieving public key from signee key: %s", err)
|
|
}
|
|
derBytes, err := x509.CreateCertificate(
|
|
rand.Reader,
|
|
template,
|
|
parent,
|
|
signeePubKey,
|
|
signingKey,
|
|
)
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("error creating certificate: %s", err)
|
|
}
|
|
|
|
certBuffer := bytes.Buffer{}
|
|
if err := pem.Encode(
|
|
&certBuffer,
|
|
&pem.Block{Type: "CERTIFICATE", Bytes: derBytes},
|
|
); err != nil {
|
|
return "", "", fmt.Errorf("error pem-encoding certificate: %s", err)
|
|
}
|
|
|
|
keyBuffer := bytes.Buffer{}
|
|
if err := pem.Encode(
|
|
&keyBuffer,
|
|
pemBlockForKey(signeeKey),
|
|
); err != nil {
|
|
return "", "", fmt.Errorf("error pem-encoding key: %s", err)
|
|
}
|
|
|
|
return certBuffer.String(), keyBuffer.String(), nil
|
|
}
|
|
|
|
func pemBlockForKey(priv interface{}) *pem.Block {
|
|
switch k := priv.(type) {
|
|
case *rsa.PrivateKey:
|
|
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
|
|
case *dsa.PrivateKey:
|
|
val := DSAKeyFormat{
|
|
P: k.P, Q: k.Q, G: k.G,
|
|
Y: k.Y, X: k.X,
|
|
}
|
|
bytes, err := asn1.Marshal(val)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return &pem.Block{Type: "DSA PRIVATE KEY", Bytes: bytes}
|
|
case *ecdsa.PrivateKey:
|
|
b, err := x509.MarshalECPrivateKey(k)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
|
|
default:
|
|
// attempt PKCS#8 format for all other keys
|
|
b, err := x509.MarshalPKCS8PrivateKey(k)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return &pem.Block{Type: "PRIVATE KEY", Bytes: b}
|
|
}
|
|
}
|
|
|
|
func parsePrivateKeyPEM(pemBlock string) (crypto.PrivateKey, error) {
|
|
block, _ := pem.Decode([]byte(pemBlock))
|
|
if block == nil {
|
|
return nil, errors.New("no PEM data in input")
|
|
}
|
|
|
|
if block.Type == "PRIVATE KEY" {
|
|
priv, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decoding PEM as PKCS#8: %s", err)
|
|
}
|
|
return priv, nil
|
|
} else if !strings.HasSuffix(block.Type, " PRIVATE KEY") {
|
|
return nil, fmt.Errorf("no private key data in PEM block of type %s", block.Type)
|
|
}
|
|
|
|
switch block.Type[:len(block.Type)-12] { // strip " PRIVATE KEY"
|
|
case "RSA":
|
|
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing RSA private key from PEM: %s", err)
|
|
}
|
|
return priv, nil
|
|
case "EC":
|
|
priv, err := x509.ParseECPrivateKey(block.Bytes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing EC private key from PEM: %s", err)
|
|
}
|
|
return priv, nil
|
|
case "DSA":
|
|
var k DSAKeyFormat
|
|
_, err := asn1.Unmarshal(block.Bytes, &k)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing DSA private key from PEM: %s", err)
|
|
}
|
|
priv := &dsa.PrivateKey{
|
|
PublicKey: dsa.PublicKey{
|
|
Parameters: dsa.Parameters{
|
|
P: k.P, Q: k.Q, G: k.G,
|
|
},
|
|
Y: k.Y,
|
|
},
|
|
X: k.X,
|
|
}
|
|
return priv, nil
|
|
default:
|
|
return nil, fmt.Errorf("invalid private key type %s", block.Type)
|
|
}
|
|
}
|
|
|
|
func getPublicKey(priv crypto.PrivateKey) (crypto.PublicKey, error) {
|
|
switch k := priv.(type) {
|
|
case interface{ Public() crypto.PublicKey }:
|
|
return k.Public(), nil
|
|
case *dsa.PrivateKey:
|
|
return &k.PublicKey, nil
|
|
default:
|
|
return nil, fmt.Errorf("unable to get public key for type %T", priv)
|
|
}
|
|
}
|
|
|
|
func getNetIPs(ips []interface{}) ([]net.IP, error) {
|
|
if ips == nil {
|
|
return []net.IP{}, nil
|
|
}
|
|
var ipStr string
|
|
var ok bool
|
|
var netIP net.IP
|
|
netIPs := make([]net.IP, len(ips))
|
|
for i, ip := range ips {
|
|
ipStr, ok = ip.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("error parsing ip: %v is not a string", ip)
|
|
}
|
|
netIP = net.ParseIP(ipStr)
|
|
if netIP == nil {
|
|
return nil, fmt.Errorf("error parsing ip: %s", ipStr)
|
|
}
|
|
netIPs[i] = netIP
|
|
}
|
|
return netIPs, nil
|
|
}
|
|
|
|
func getAlternateDNSStrs(alternateDNS []interface{}) ([]string, error) {
|
|
if alternateDNS == nil {
|
|
return []string{}, nil
|
|
}
|
|
var dnsStr string
|
|
var ok bool
|
|
alternateDNSStrs := make([]string, len(alternateDNS))
|
|
for i, dns := range alternateDNS {
|
|
dnsStr, ok = dns.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf(
|
|
"error processing alternate dns name: %v is not a string",
|
|
dns,
|
|
)
|
|
}
|
|
alternateDNSStrs[i] = dnsStr
|
|
}
|
|
return alternateDNSStrs, nil
|
|
}
|