Skip to content
Snippets Groups Projects
Commit 321e26fa authored by fuero's avatar fuero Committed by Niall Sheridan
Browse files

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
parent 8ee3c647
No related branches found
No related tags found
No related merge requests found
...@@ -221,7 +221,7 @@ The client is configured using either a [HCL](https://github.com/hashicorp/hcl) ...@@ -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"). - `--config` Path to config file (default "~/.cashier.conf").
- `--key_size` Key size. Ignored for ed25519 keys (default 2048). - `--key_size` Key size. Ignored for ed25519 keys (default 2048).
- `--key_type` Type of private key to generate - rsa, ecdsa or ed25519 (default "rsa"). - `--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). - `--validity` Key validity (default 24h).
Running the `cashier` cli tool will open a browser window at the configured CA address. 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 ...@@ -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). 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. 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 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`. 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. 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 ## Configuring SSH
The ssh client needs no special configuration, just a running `ssh-agent`. The ssh client needs no special configuration, just a running `ssh-agent`.
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"crypto/tls" "crypto/tls"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"encoding/pem"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
...@@ -33,14 +34,30 @@ func SavePublicFiles(prefix string, cert *ssh.Certificate, pub ssh.PublicKey) er ...@@ -33,14 +34,30 @@ func SavePublicFiles(prefix string, cert *ssh.Certificate, pub ssh.PublicKey) er
pubTxt := ssh.MarshalAuthorizedKey(pub) pubTxt := ssh.MarshalAuthorizedKey(pub)
certPubTxt := []byte(cert.Type() + " " + base64.StdEncoding.EncodeToString(cert.Marshal())) 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 return err
} }
err := ioutil.WriteFile(prefix+"-cert.pub", certPubTxt, 0644) err := ioutil.WriteFile(_prefix+"-cert.pub", certPubTxt, 0644)
return err 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. // InstallCert adds the private key and signed certificate to the ssh agent.
func InstallCert(a agent.Agent, cert *ssh.Certificate, key Key) error { func InstallCert(a agent.Agent, cert *ssh.Certificate, key Key) error {
t := time.Unix(int64(cert.ValidBefore), 0) t := time.Unix(int64(cert.ValidBefore), 0)
......
...@@ -13,7 +13,7 @@ type Config struct { ...@@ -13,7 +13,7 @@ type Config struct {
Keysize int `mapstructure:"key_size"` Keysize int `mapstructure:"key_size"`
Validity string `mapstructure:"validity"` Validity string `mapstructure:"validity"`
ValidateTLSCertificate bool `mapstructure:"validate_tls_certificate"` ValidateTLSCertificate bool `mapstructure:"validate_tls_certificate"`
PublicFilePrefix string `mapstructure:"public_file_prefix"` PublicFilePrefix string `mapstructure:"key_file_prefix"`
} }
func setDefaults() { func setDefaults() {
...@@ -21,7 +21,7 @@ func setDefaults() { ...@@ -21,7 +21,7 @@ func setDefaults() {
viper.BindPFlag("key_type", pflag.Lookup("key_type")) viper.BindPFlag("key_type", pflag.Lookup("key_type"))
viper.BindPFlag("key_size", pflag.Lookup("key_size")) viper.BindPFlag("key_size", pflag.Lookup("key_size"))
viper.BindPFlag("validity", pflag.Lookup("validity")) 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) viper.SetDefault("validateTLSCertificate", true)
} }
......
...@@ -6,12 +6,16 @@ import ( ...@@ -6,12 +6,16 @@ import (
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt" "fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/crypto/ed25519" "golang.org/x/crypto/ed25519"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"github.com/mikesmitty/edkey"
) )
// Key is a private key. // Key is a private key.
...@@ -32,6 +36,24 @@ var defaultOptions = options{ ...@@ -32,6 +36,24 @@ var defaultOptions = options{
// A KeyOption is used to generate keys of different types and sizes. // A KeyOption is used to generate keys of different types and sizes.
type KeyOption func(*options) 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. // KeyType sets the type of key to generate.
// Valid types are: "rsa", "ecdsa", "ed25519". // Valid types are: "rsa", "ecdsa", "ed25519".
// Default is "rsa" // Default is "rsa"
......
...@@ -23,7 +23,7 @@ var ( ...@@ -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)") 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") 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\")") 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)") useGRPC = pflag.Bool("use_grpc", false, "Use grpc (experimental)")
) )
...@@ -72,5 +72,8 @@ func main() { ...@@ -72,5 +72,8 @@ func main() {
if err := client.SavePublicFiles(c.PublicFilePrefix, cert, pub); err != nil { if err := client.SavePublicFiles(c.PublicFilePrefix, cert, pub); err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
if err := client.SavePrivateFiles(c.PublicFilePrefix, cert, priv); err != nil {
log.Fatalln(err)
}
fmt.Println("Credentials added.") fmt.Println("Credentials added.")
} }
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.
# 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)
}
```
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
}
...@@ -452,6 +452,12 @@ ...@@ -452,6 +452,12 @@
"revision": "c12348ce28de40eed0136aa2b644d0ee0650e56c", "revision": "c12348ce28de40eed0136aa2b644d0ee0650e56c",
"revisionTime": "2016-04-24T11:30:07Z" "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=", "checksumSHA1": "V/quM7+em2ByJbWBLOsEwnY3j/Q=",
"path": "github.com/mitchellh/go-homedir", "path": "github.com/mitchellh/go-homedir",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment