aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfuero <fuero@users.noreply.github.com>2017-06-05 23:28:13 +0200
committerNiall Sheridan <nsheridan@gmail.com>2017-06-05 22:28:13 +0100
commit321e26fae746e661d713cedfb6642609e680cafe (patch)
tree4be9496c75aabc54325aac1b8b511223e986b8f7
parent8ee3c6473f3e2373303b9cb16ab5f059f9e6369e (diff)
Saving private keys (#61)
* enables saving private keys * renames public_file_prefix to key_file_prefix and updates its docs to better reflect the changes
-rw-r--r--README.md6
-rw-r--r--client/client.go21
-rw-r--r--client/config.go4
-rw-r--r--client/keys.go22
-rw-r--r--cmd/cashier/main.go5
-rw-r--r--vendor/github.com/mikesmitty/edkey/LICENSE21
-rw-r--r--vendor/github.com/mikesmitty/edkey/README.md32
-rw-r--r--vendor/github.com/mikesmitty/edkey/edkey.go88
-rw-r--r--vendor/vendor.json6
9 files changed, 197 insertions, 8 deletions
diff --git a/README.md b/README.md
index 04b015b..c861162 100644
--- a/README.md
+++ b/README.md
@@ -221,7 +221,7 @@ The client is configured using either a [HCL](https://github.com/hashicorp/hcl)
- `--config` Path to config file (default "~/.cashier.conf").
- `--key_size` Key size. Ignored for ed25519 keys (default 2048).
- `--key_type` Type of private key to generate - rsa, ecdsa or ed25519 (default "rsa").
-- `--public_file_prefix` Prefix for filename for public key and cert (optional, no default). The public key is put in a file with `.pub` appended to it; the public cert file in a file with `-cert.pub` appended to it.
+- `--key_file_prefix` Prefix for filename for SSH keys and cert (optional, no default). The public key is put in a file with `id_<id>.pub` appended to it; the public cert file in a file with `id_<id>-cert.pub` appended to it. The private key is stored in a file with `id_<id>` appended to it. <id> is taken from the id stored on the server.
- `--validity` Key validity (default 24h).
Running the `cashier` cli tool will open a browser window at the configured CA address.
@@ -230,13 +230,13 @@ Copy the access token. In the terminal where you ran the `cashier` cli paste the
The client will then generate a new ssh key-pair and send the public part to the server (along with the access token).
Once signed the client will install the key and signed certificate in your ssh agent. When the certificate expires it will be removed automatically from the agent.
-If you set `public_file_prefix` then the public key and public cert will be written to the files that start with `public_file_prefix` and end with `.pub` and `-cert.pub` respectively.
+If you set `key_file_prefix` then the public key and public cert will be written to the files that start with `key_file_prefix` and end with `.pub` and `-cert.pub` respectively.
In your `ssh_config` you can load these for a given host with the `IdentityFile` and `CertificateFile`. However prior to OpenSSH version 7.2p1 the latter option didn't exist.
In that case you could specify `~/.ssh/some-identity` as your `IdentityFile` and OpenSSH would look in `~/.ssh/some-identity.pub` and `~/.ssh/some-identity-cert.pub`.
Starting with 7.2p1 the two options exist in the `ssh_config` and you'll need to use the full paths to them.
-Note that like these `ssh_config` options, the `public_file_prefix` supports tilde expansion.
+Note that like these `ssh_config` options, the `key_file_prefix` supports tilde expansion.
## Configuring SSH
The ssh client needs no special configuration, just a running `ssh-agent`.
diff --git a/client/client.go b/client/client.go
index 305d0d2..8ebe29a 100644
--- a/client/client.go
+++ b/client/client.go
@@ -5,6 +5,7 @@ import (
"crypto/tls"
"encoding/base64"
"encoding/json"
+ "encoding/pem"
"fmt"
"io/ioutil"
"net/http"
@@ -33,14 +34,30 @@ func SavePublicFiles(prefix string, cert *ssh.Certificate, pub ssh.PublicKey) er
pubTxt := ssh.MarshalAuthorizedKey(pub)
certPubTxt := []byte(cert.Type() + " " + base64.StdEncoding.EncodeToString(cert.Marshal()))
- if err := ioutil.WriteFile(prefix+".pub", pubTxt, 0644); err != nil {
+ _prefix := prefix + "/id_" + cert.KeyId
+
+ if err := ioutil.WriteFile(_prefix+".pub", pubTxt, 0644); err != nil {
return err
}
- err := ioutil.WriteFile(prefix+"-cert.pub", certPubTxt, 0644)
+ err := ioutil.WriteFile(_prefix+"-cert.pub", certPubTxt, 0644)
return err
}
+// SavePrivateFiles installs the private part of the key.
+func SavePrivateFiles(prefix string, cert *ssh.Certificate, key Key) error {
+ if prefix == "" {
+ return nil
+ }
+ _prefix := prefix + "/id_" + cert.KeyId
+ pemBlock, err := pemBlockForKey(key);
+ if err != nil {
+ return err
+ }
+ err = ioutil.WriteFile(_prefix, pem.EncodeToMemory(pemBlock), 0600)
+ return err
+}
+
// InstallCert adds the private key and signed certificate to the ssh agent.
func InstallCert(a agent.Agent, cert *ssh.Certificate, key Key) error {
t := time.Unix(int64(cert.ValidBefore), 0)
diff --git a/client/config.go b/client/config.go
index 07bbb8c..eae3bfa 100644
--- a/client/config.go
+++ b/client/config.go
@@ -13,7 +13,7 @@ type Config struct {
Keysize int `mapstructure:"key_size"`
Validity string `mapstructure:"validity"`
ValidateTLSCertificate bool `mapstructure:"validate_tls_certificate"`
- PublicFilePrefix string `mapstructure:"public_file_prefix"`
+ PublicFilePrefix string `mapstructure:"key_file_prefix"`
}
func setDefaults() {
@@ -21,7 +21,7 @@ func setDefaults() {
viper.BindPFlag("key_type", pflag.Lookup("key_type"))
viper.BindPFlag("key_size", pflag.Lookup("key_size"))
viper.BindPFlag("validity", pflag.Lookup("validity"))
- viper.BindPFlag("public_file_prefix", pflag.Lookup("public_file_prefix"))
+ viper.BindPFlag("key_file_prefix", pflag.Lookup("key_file_prefix"))
viper.SetDefault("validateTLSCertificate", true)
}
diff --git a/client/keys.go b/client/keys.go
index 73983a8..b488ea2 100644
--- a/client/keys.go
+++ b/client/keys.go
@@ -6,12 +6,16 @@ import (
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
+ "crypto/x509"
+ "encoding/pem"
"fmt"
"github.com/pkg/errors"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/ssh"
+
+ "github.com/mikesmitty/edkey"
)
// Key is a private key.
@@ -32,6 +36,24 @@ var defaultOptions = options{
// A KeyOption is used to generate keys of different types and sizes.
type KeyOption func(*options)
+func pemBlockForKey(priv interface{}) (*pem.Block, error) {
+ switch k := priv.(type) {
+ case *rsa.PrivateKey:
+ return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}, nil
+ case *ecdsa.PrivateKey:
+ b, err := x509.MarshalECPrivateKey(k)
+ if err != nil {
+ return nil, err
+ }
+ return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}, nil
+ case *ed25519.PrivateKey:
+ b := edkey.MarshalED25519PrivateKey(*k)
+ return &pem.Block{Type: "OPENSSH PRIVATE KEY", Bytes: b}, nil
+ default:
+ return nil, fmt.Errorf("Unable to create PEM blck from key")
+ }
+}
+
// KeyType sets the type of key to generate.
// Valid types are: "rsa", "ecdsa", "ed25519".
// Default is "rsa"
diff --git a/cmd/cashier/main.go b/cmd/cashier/main.go
index f13b3ed..f2d3bbe 100644
--- a/cmd/cashier/main.go
+++ b/cmd/cashier/main.go
@@ -23,7 +23,7 @@ var (
keysize = pflag.Int("key_size", 0, "Size of key to generate. Ignored for ed25519 keys. (default 2048 for rsa keys, 256 for ecdsa keys)")
validity = pflag.Duration("validity", time.Hour*24, "Key lifetime. May be overridden by the CA at signing time")
keytype = pflag.String("key_type", "", "Type of private key to generate - rsa, ecdsa or ed25519. (default \"rsa\")")
- publicFilePrefix = pflag.String("public_file_prefix", "", "Prefix for filename for public key and cert (optional, no default)")
+ publicFilePrefix = pflag.String("key_file_prefix", "", "Prefix for filename for public key and cert (optional, no default)")
useGRPC = pflag.Bool("use_grpc", false, "Use grpc (experimental)")
)
@@ -72,5 +72,8 @@ func main() {
if err := client.SavePublicFiles(c.PublicFilePrefix, cert, pub); err != nil {
log.Fatalln(err)
}
+ if err := client.SavePrivateFiles(c.PublicFilePrefix, cert, priv); err != nil {
+ log.Fatalln(err)
+ }
fmt.Println("Credentials added.")
}
diff --git a/vendor/github.com/mikesmitty/edkey/LICENSE b/vendor/github.com/mikesmitty/edkey/LICENSE
new file mode 100644
index 0000000..79169f1
--- /dev/null
+++ b/vendor/github.com/mikesmitty/edkey/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Michael Smith
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/mikesmitty/edkey/README.md b/vendor/github.com/mikesmitty/edkey/README.md
new file mode 100644
index 0000000..a1690ae
--- /dev/null
+++ b/vendor/github.com/mikesmitty/edkey/README.md
@@ -0,0 +1,32 @@
+# edkey
+edkey allows you to marshal/write ED25519 private keys in the OpenSSH private key format
+
+## Example
+```go
+package main
+
+import (
+ "crypto/rand"
+ "encoding/pem"
+ "io/ioutil"
+ "github.com/mikesmitty/edkey"
+ "golang.org/x/crypto/ed25519"
+ "golang.org/x/crypto/ssh"
+)
+
+func main() {
+ // Generate a new private/public keypair for OpenSSH
+ pubKey, privKey, _ := ed25519.GenerateKey(rand.Reader)
+ publicKey, _ := ssh.NewPublicKey(pubKey)
+
+ pemKey := &pem.Block{
+ Type: "OPENSSH PRIVATE KEY",
+ Bytes: edkey.MarshalED25519PrivateKey(privKey),
+ }
+ privateKey := pem.EncodeToMemory(pemKey)
+ authorizedKey := ssh.MarshalAuthorizedKey(publicKey)
+
+ _ = ioutil.WriteFile("id_ed25519", privateKey, 0600)
+ _ = ioutil.WriteFile("id_ed25519.pub", authorizedKey, 0644)
+}
+```
diff --git a/vendor/github.com/mikesmitty/edkey/edkey.go b/vendor/github.com/mikesmitty/edkey/edkey.go
new file mode 100644
index 0000000..99aca55
--- /dev/null
+++ b/vendor/github.com/mikesmitty/edkey/edkey.go
@@ -0,0 +1,88 @@
+package edkey
+
+import (
+ "math/rand"
+
+ "golang.org/x/crypto/ed25519"
+ "golang.org/x/crypto/ssh"
+)
+
+/* Writes ed25519 private keys into the new OpenSSH private key format.
+I have no idea why this isn't implemented anywhere yet, you can do seemingly
+everything except write it to disk in the OpenSSH private key format. */
+func MarshalED25519PrivateKey(key ed25519.PrivateKey) []byte {
+ // Add our key header (followed by a null byte)
+ magic := append([]byte("openssh-key-v1"), 0)
+
+ var w struct {
+ CipherName string
+ KdfName string
+ KdfOpts string
+ NumKeys uint32
+ PubKey []byte
+ PrivKeyBlock []byte
+ }
+
+ // Fill out the private key fields
+ pk1 := struct {
+ Check1 uint32
+ Check2 uint32
+ Keytype string
+ Pub []byte
+ Priv []byte
+ Comment string
+ Pad []byte `ssh:"rest"`
+ }{}
+
+ // Set our check ints
+ ci := rand.Uint32()
+ pk1.Check1 = ci
+ pk1.Check2 = ci
+
+ // Set our key type
+ pk1.Keytype = ssh.KeyAlgoED25519
+
+ // Add the pubkey to the optionally-encrypted block
+ pk, ok := key.Public().(ed25519.PublicKey)
+ if !ok {
+ //fmt.Fprintln(os.Stderr, "ed25519.PublicKey type assertion failed on an ed25519 public key. This should never ever happen.")
+ return nil
+ }
+ pubKey := []byte(pk)
+ pk1.Pub = pubKey
+
+ // Add our private key
+ pk1.Priv = []byte(key)
+
+ // Might be useful to put something in here at some point
+ pk1.Comment = ""
+
+ // Add some padding to match the encryption block size within PrivKeyBlock (without Pad field)
+ // 8 doesn't match the documentation, but that's what ssh-keygen uses for unencrypted keys. *shrug*
+ bs := 8
+ blockLen := len(ssh.Marshal(pk1))
+ padLen := (bs - (blockLen % bs)) % bs
+ pk1.Pad = make([]byte, padLen)
+
+ // Padding is a sequence of bytes like: 1, 2, 3...
+ for i := 0; i < padLen; i++ {
+ pk1.Pad[i] = byte(i + 1)
+ }
+
+ // Generate the pubkey prefix "\0\0\0\nssh-ed25519\0\0\0 "
+ prefix := []byte{0x0, 0x0, 0x0, 0x0b}
+ prefix = append(prefix, []byte(ssh.KeyAlgoED25519)...)
+ prefix = append(prefix, []byte{0x0, 0x0, 0x0, 0x20}...)
+
+ // Only going to support unencrypted keys for now
+ w.CipherName = "none"
+ w.KdfName = "none"
+ w.KdfOpts = ""
+ w.NumKeys = 1
+ w.PubKey = append(prefix, pubKey...)
+ w.PrivKeyBlock = ssh.Marshal(pk1)
+
+ magic = append(magic, ssh.Marshal(w)...)
+
+ return magic
+}
diff --git a/vendor/vendor.json b/vendor/vendor.json
index 63bdaaf..25b227e 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -453,6 +453,12 @@
"revisionTime": "2016-04-24T11:30:07Z"
},
{
+ "checksumSHA1": "lafP5ecnlvfzv3oYf63Ibhcl8As=",
+ "path": "github.com/mikesmitty/edkey",
+ "revision": "3356ea4e686a1d47ae5d2d4c3cbc1832ce2df626",
+ "revisionTime": "2017-02-22T07:25:05Z"
+ },
+ {
"checksumSHA1": "V/quM7+em2ByJbWBLOsEwnY3j/Q=",
"path": "github.com/mitchellh/go-homedir",
"revision": "b8bc1bf767474819792c23f32d8286a45736f1c6",