Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 40 additions & 6 deletions crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ import (
"io"
"math/big"
"net"
"time"

"strings"
"time"

"github.com/google/uuid"
bcrypt_lib "golang.org/x/crypto/bcrypt"
Expand Down Expand Up @@ -198,17 +197,17 @@ func generatePrivateKey(typ string) string {
return fmt.Sprintf("failed to generate private key: %s", err)
}

return string(pem.EncodeToMemory(pemBlockForKey(priv)))
return string(pem.EncodeToMemory(pemBlockForPrivKey(priv)))
}

// DSAKeyFormat stores the format for DSA keys.
// Used by pemBlockForKey
// Used by pemBlockForPrivKey
type DSAKeyFormat struct {
Version int
P, Q, G, Y, X *big.Int
}

func pemBlockForKey(priv interface{}) *pem.Block {
func pemBlockForPrivKey(priv interface{}) *pem.Block {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
Expand All @@ -232,6 +231,14 @@ func pemBlockForKey(priv interface{}) *pem.Block {
}
}

func pemBlockForPubKey(pub interface{}) (*pem.Block, error) {
b, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
return nil, err
}
return &pem.Block{Type: "PUBLIC KEY", Bytes: b}, nil
}

func parsePrivateKeyPEM(pemBlock string) (crypto.PrivateKey, error) {
block, _ := pem.Decode([]byte(pemBlock))
if block == nil {
Expand Down Expand Up @@ -293,6 +300,33 @@ func getPublicKey(priv crypto.PrivateKey) (crypto.PublicKey, error) {
}
}

func derivePublicKey(privPEM string) (string, error) {
priv, err := parsePrivateKeyPEM(privPEM)
if err != nil {
return "", fmt.Errorf("error parsing private key: %s", err)
}

pub, err := getPublicKey(priv)
if err != nil {
return "", fmt.Errorf("error getting public key: %s", err)
}

pubBlock, err := pemBlockForPubKey(pub)
if err != nil {
return "", fmt.Errorf("error getting public key PEM block: %s", err)
}

pubBuffer := bytes.Buffer{}
if err := pem.Encode(
&pubBuffer,
pubBlock,
); err != nil {
return "", fmt.Errorf("error pem-encoding public key: %s", err)
}

return pubBuffer.String(), nil
}

type certificate struct {
Cert string
Key string
Expand Down Expand Up @@ -534,7 +568,7 @@ func getCertAndKey(
keyBuffer := bytes.Buffer{}
if err := pem.Encode(
&keyBuffer,
pemBlockForKey(signeeKey),
pemBlockForPrivKey(signeeKey),
); err != nil {
return "", "", fmt.Errorf("error pem-encoding key: %s", err)
}
Expand Down
107 changes: 107 additions & 0 deletions crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,113 @@ func TestGenPrivateKey(t *testing.T) {
}
}

func TestDerivePublicKey(t *testing.T) {
tpl := `{{genPrivateKey "rsa" | derivePublicKey}}`
out, err := runRaw(tpl, nil)
if err != nil {
t.Error(err)
}
if !strings.Contains(out, "PUBLIC KEY") {
t.Error("Expected PUBLIC KEY", out)
}
tpl = `{{genPrivateKey "dsa" | derivePublicKey}}`
out, err = runRaw(tpl, nil)
// x509.MarshalPKIXPublicKey() does not support DSA keys
if err == nil || !strings.Contains(err.Error(), "x509: unsupported public key type") {
t.Error("Expected error to contain 'x509: unsupported public key type'", err)
}
tpl = `{{genPrivateKey "ecdsa" | derivePublicKey}}`
out, err = runRaw(tpl, nil)
if err != nil {
t.Error(err)
}
if !strings.Contains(out, "PUBLIC KEY") {
t.Error("Expected PUBLIC KEY", out)
}
tpl = `{{genPrivateKey "ed25519" | derivePublicKey}}`
out, err = runRaw(tpl, nil)
if err != nil {
t.Error(err)
}
if !strings.Contains(out, "PUBLIC KEY") {
t.Error("Expected PUBLIC KEY", out)
}
testPrivateKey := `-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----`
expectedPublicKey := `-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjrVUJpXK13XN7o+B1OdP
rOWt8xpDld3q9GX5f7HlxF98KDBrLzxLcI4nFE1XriEZSCitG7cSC9jvoiU4yA59
t+fIcmU5fLAwBmkNmWnTPD2YkH7GauenL2+LaGQ/6oc3/KqhHACQ7Sj+tzOwkMiv
hw7MMdrP1NEXsYQw1ht/o38EcdRf+G9w4d/YU7aKIxM7XX3evDFpada7RhBsMXoO
tHA/mE4KuztIFZ6e2McB+4fVNPsYN/k9f5ta/iCBdOkG1WdZvDj7KZfLUWno6emD
3oE6I1crrXeuz/tabjHuQoWhxCV2OZStDMdxFg1rAjZBsq9325kO3N6PiH+pyHrd
kRZvbQBiFjlJBa+/YMJzU3dDjpCxVBGlIXuT4/22JhdPBxvGwRx9ZLKup2qbfkCx
tquWMCQN+7SE3mNXxrGxBfMsFtCgVQvkVuDaGhiYQ98DgmR/sXSZ/0okWWIockoX
WOrnMrXzvhMkF4zsd1CqhF6ctN4SYADCiR2VmeN2brzB3JZHh97DHWDlkWmDALSy
Izka65Tg7xaEYvAluaKAkbjMYBP9t9rSE3Z5w4BkwTypAnyJkOd4NwzGon3OhEjD
zHXuCEHwBPZgEcNFbYS1kezDB7QkuzbyTZvDhY+jOnJZuD6JxDLox20LBEGB1dFH
cn2WwZpyAtzWCqPvtFqKpA0CAwEAAQ==
-----END PUBLIC KEY-----
`
tpl = `{{derivePublicKey .}}`
out, err = runRaw(tpl, testPrivateKey)
if err != nil {
t.Error(err)
}
if out != expectedPublicKey {
t.Error("Got incorrect public key", out)
}
}

func TestRandBytes(t *testing.T) {
tpl := `{{randBytes 12}}`
out, err := runRaw(tpl, nil)
Expand Down
10 changes: 10 additions & 0 deletions docs/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ It takes one of the values for its first param:
- `rsa`: Generate an RSA 4096 key
- `ed25519`: Generate an Ed25519 key

## derivePublicKey

The `derivePublicKey` function takes a PEM-encoded private key and returns the
corresponding public key in PEM format.

Supported private key types are:
- ECDSA: elliptic curve DSA key (P256)
- RSA: RSA 4096 key
- Ed25519: Ed25519 key

## buildCustomCert

The `buildCustomCert` function allows customizing the certificate.
Expand Down
1 change: 1 addition & 0 deletions functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ var genericMap = map[string]interface{}{
"bcrypt": bcrypt,
"htpasswd": htpasswd,
"genPrivateKey": generatePrivateKey,
"derivePublicKey": derivePublicKey,
"derivePassword": derivePassword,
"buildCustomCert": buildCustomCertificate,
"genCA": generateCertificateAuthority,
Expand Down