2016-12-26 14:03:27 +00:00
package ssh
import (
2018-01-08 18:31:38 +00:00
"context"
2022-02-15 19:14:05 +00:00
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
2017-03-02 21:22:06 +00:00
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
2022-02-17 20:17:59 +00:00
"errors"
2016-12-26 14:03:27 +00:00
"fmt"
2021-09-15 16:59:28 +00:00
"io"
2017-03-02 21:22:06 +00:00
2017-03-08 22:36:21 +00:00
multierror "github.com/hashicorp/go-multierror"
2019-04-12 21:54:35 +00:00
"github.com/hashicorp/vault/sdk/framework"
2019-04-13 07:44:06 +00:00
"github.com/hashicorp/vault/sdk/logical"
2016-12-26 14:03:27 +00:00
"golang.org/x/crypto/ssh"
2022-02-17 20:17:59 +00:00
"github.com/mikesmitty/edkey"
2016-12-26 14:03:27 +00:00
)
2017-03-08 22:36:21 +00:00
const (
caPublicKey = "ca_public_key"
caPrivateKey = "ca_private_key"
caPublicKeyStoragePath = "config/ca_public_key"
caPublicKeyStoragePathDeprecated = "public_key"
caPrivateKeyStoragePath = "config/ca_private_key"
caPrivateKeyStoragePathDeprecated = "config/ca_bundle"
)
type keyStorageEntry struct {
Key string ` json:"key" structs:"key" mapstructure:"key" `
}
2016-12-26 14:03:27 +00:00
func pathConfigCA ( b * backend ) * framework . Path {
return & framework . Path {
Pattern : "config/ca" ,
Fields : map [ string ] * framework . FieldSchema {
2021-04-08 16:43:39 +00:00
"private_key" : {
2016-12-26 14:03:27 +00:00
Type : framework . TypeString ,
Description : ` Private half of the SSH key that will be used to sign certificates. ` ,
} ,
2021-04-08 16:43:39 +00:00
"public_key" : {
2016-12-26 14:03:27 +00:00
Type : framework . TypeString ,
Description : ` Public half of the SSH key that will be used to sign certificates. ` ,
} ,
2021-04-08 16:43:39 +00:00
"generate_signing_key" : {
2017-03-02 09:32:50 +00:00
Type : framework . TypeBool ,
Description : ` Generate SSH key pair internally rather than use the private_key and public_key fields. ` ,
2017-03-02 21:22:06 +00:00
Default : true ,
2017-03-02 09:32:50 +00:00
} ,
2022-02-15 19:14:05 +00:00
"key_type" : {
Type : framework . TypeString ,
Description : ` Specifies the desired key type when generating; could be a OpenSSH key type identifier (ssh-rsa, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, or ssh-ed25519) or an algorithm (rsa, ec, ed25519). ` ,
Default : "ssh-rsa" ,
} ,
"key_bits" : {
Type : framework . TypeInt ,
Description : ` Specifies the desired key bits when generating variable-length keys (such as when key_type="ssh-rsa") or which NIST P-curve to use when key_type="ec" (256, 384, or 521). ` ,
Default : 0 ,
} ,
2016-12-26 14:03:27 +00:00
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
2017-03-03 15:19:45 +00:00
logical . UpdateOperation : b . pathConfigCAUpdate ,
logical . DeleteOperation : b . pathConfigCADelete ,
2017-03-14 13:31:15 +00:00
logical . ReadOperation : b . pathConfigCARead ,
2016-12-26 14:03:27 +00:00
} ,
HelpSynopsis : ` Set the SSH private key used for signing certificates. ` ,
HelpDescription : ` This sets the CA information used for certificates generated by this
by this mount . The fields must be in the standard private and public SSH format .
2017-03-14 13:31:15 +00:00
For security reasons , the private key cannot be retrieved later .
Read operations will return the public key , if already stored / generated . ` ,
}
}
2018-01-08 18:31:38 +00:00
func ( b * backend ) pathConfigCARead ( ctx context . Context , req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
2018-01-19 06:44:44 +00:00
publicKeyEntry , err := caKey ( ctx , req . Storage , caPublicKey )
2017-03-14 13:31:15 +00:00
if err != nil {
2021-04-22 15:20:59 +00:00
return nil , fmt . Errorf ( "failed to read CA public key: %w" , err )
2016-12-26 14:03:27 +00:00
}
2017-03-14 13:31:15 +00:00
if publicKeyEntry == nil {
2017-03-14 13:58:28 +00:00
return logical . ErrorResponse ( "keys haven't been configured yet" ) , nil
2017-03-14 13:31:15 +00:00
}
response := & logical . Response {
Data : map [ string ] interface { } {
"public_key" : publicKeyEntry . Key ,
} ,
}
return response , nil
2016-12-26 14:03:27 +00:00
}
2018-01-08 18:31:38 +00:00
func ( b * backend ) pathConfigCADelete ( ctx context . Context , req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
2018-01-19 06:44:44 +00:00
if err := req . Storage . Delete ( ctx , caPrivateKeyStoragePath ) ; err != nil {
2017-03-03 15:19:45 +00:00
return nil , err
}
2018-01-19 06:44:44 +00:00
if err := req . Storage . Delete ( ctx , caPublicKeyStoragePath ) ; err != nil {
2017-03-03 15:19:45 +00:00
return nil , err
}
return nil , nil
}
2018-01-19 06:44:44 +00:00
func caKey ( ctx context . Context , storage logical . Storage , keyType string ) ( * keyStorageEntry , error ) {
2017-03-08 22:36:21 +00:00
var path , deprecatedPath string
switch keyType {
case caPrivateKey :
path = caPrivateKeyStoragePath
deprecatedPath = caPrivateKeyStoragePathDeprecated
case caPublicKey :
path = caPublicKeyStoragePath
deprecatedPath = caPublicKeyStoragePathDeprecated
default :
return nil , fmt . Errorf ( "unrecognized key type %q" , keyType )
}
2018-01-19 06:44:44 +00:00
entry , err := storage . Get ( ctx , path )
2017-03-08 22:36:21 +00:00
if err != nil {
2021-04-22 15:20:59 +00:00
return nil , fmt . Errorf ( "failed to read CA key of type %q: %w" , keyType , err )
2017-03-08 22:36:21 +00:00
}
if entry == nil {
// If the entry is not found, look at an older path. If found, upgrade
// it.
2018-01-19 06:44:44 +00:00
entry , err = storage . Get ( ctx , deprecatedPath )
2017-03-08 22:36:21 +00:00
if err != nil {
return nil , err
}
if entry != nil {
entry , err = logical . StorageEntryJSON ( path , keyStorageEntry {
Key : string ( entry . Value ) ,
} )
if err != nil {
return nil , err
}
2018-01-19 06:44:44 +00:00
if err := storage . Put ( ctx , entry ) ; err != nil {
2017-03-08 22:36:21 +00:00
return nil , err
}
2018-01-19 06:44:44 +00:00
if err = storage . Delete ( ctx , deprecatedPath ) ; err != nil {
2017-03-08 22:36:21 +00:00
return nil , err
}
}
}
if entry == nil {
return nil , nil
}
var keyEntry keyStorageEntry
if err := entry . DecodeJSON ( & keyEntry ) ; err != nil {
return nil , err
}
return & keyEntry , nil
}
2018-01-08 18:31:38 +00:00
func ( b * backend ) pathConfigCAUpdate ( ctx context . Context , req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
2017-03-02 09:32:50 +00:00
var err error
2017-03-02 21:22:06 +00:00
publicKey := data . Get ( "public_key" ) . ( string )
privateKey := data . Get ( "private_key" ) . ( string )
2017-03-02 21:37:03 +00:00
var generateSigningKey bool
2017-03-02 21:22:06 +00:00
generateSigningKeyRaw , ok := data . GetOk ( "generate_signing_key" )
2017-03-02 21:37:03 +00:00
switch {
// explicitly set true
case ok && generateSigningKeyRaw . ( bool ) :
if publicKey != "" || privateKey != "" {
return logical . ErrorResponse ( "public_key and private_key must not be set when generate_signing_key is set to true" ) , nil
2017-03-02 21:22:06 +00:00
}
2017-03-02 21:37:03 +00:00
generateSigningKey = true
2017-03-03 15:19:45 +00:00
// explicitly set to false, or not set and we have both a public and private key
2017-03-02 21:37:03 +00:00
case ok , publicKey != "" && privateKey != "" :
2017-03-02 21:22:06 +00:00
if publicKey == "" {
return logical . ErrorResponse ( "missing public_key" ) , nil
}
if privateKey == "" {
return logical . ErrorResponse ( "missing private_key" ) , nil
}
_ , err := ssh . ParsePrivateKey ( [ ] byte ( privateKey ) )
2017-03-02 09:32:50 +00:00
if err != nil {
2017-03-02 21:22:06 +00:00
return logical . ErrorResponse ( fmt . Sprintf ( "Unable to parse private_key as an SSH private key: %v" , err ) ) , nil
2017-03-02 09:32:50 +00:00
}
2017-02-28 22:08:10 +00:00
2017-03-02 21:22:06 +00:00
_ , err = parsePublicSSHKey ( publicKey )
2017-03-02 09:32:50 +00:00
if err != nil {
2017-03-02 21:22:06 +00:00
return logical . ErrorResponse ( fmt . Sprintf ( "Unable to parse public_key as an SSH public key: %v" , err ) ) , nil
2017-03-02 09:32:50 +00:00
}
2017-03-02 21:37:03 +00:00
2017-03-03 15:19:45 +00:00
// not set and no public/private key provided so generate
2017-03-02 21:37:03 +00:00
case publicKey == "" && privateKey == "" :
generateSigningKey = true
2017-03-03 15:19:45 +00:00
// not set, but one or the other supplied
default :
2017-03-02 21:37:03 +00:00
return logical . ErrorResponse ( "only one of public_key and private_key set; both must be set to use, or both must be blank to auto-generate" ) , nil
}
if generateSigningKey {
2022-02-15 19:14:05 +00:00
keyType := data . Get ( "key_type" ) . ( string )
keyBits := data . Get ( "key_bits" ) . ( int )
publicKey , privateKey , err = generateSSHKeyPair ( b . Backend . GetRandomReader ( ) , keyType , keyBits )
2017-03-02 21:37:03 +00:00
if err != nil {
return nil , err
}
2017-02-28 22:08:10 +00:00
}
2017-03-02 21:22:06 +00:00
if publicKey == "" || privateKey == "" {
return nil , fmt . Errorf ( "failed to generate or parse the keys" )
}
2018-01-19 06:44:44 +00:00
publicKeyEntry , err := caKey ( ctx , req . Storage , caPublicKey )
2017-03-03 15:19:45 +00:00
if err != nil {
2021-04-22 15:20:59 +00:00
return nil , fmt . Errorf ( "failed to read CA public key: %w" , err )
2017-03-03 15:19:45 +00:00
}
2018-01-19 06:44:44 +00:00
privateKeyEntry , err := caKey ( ctx , req . Storage , caPrivateKey )
2017-03-03 15:19:45 +00:00
if err != nil {
2021-04-22 15:20:59 +00:00
return nil , fmt . Errorf ( "failed to read CA private key: %w" , err )
2017-03-03 15:19:45 +00:00
}
2017-03-08 22:36:21 +00:00
if ( publicKeyEntry != nil && publicKeyEntry . Key != "" ) || ( privateKeyEntry != nil && privateKeyEntry . Key != "" ) {
2018-12-04 18:29:11 +00:00
return logical . ErrorResponse ( "keys are already configured; delete them before reconfiguring" ) , nil
2017-03-03 15:19:45 +00:00
}
2017-03-08 22:36:21 +00:00
entry , err := logical . StorageEntryJSON ( caPublicKeyStoragePath , & keyStorageEntry {
Key : publicKey ,
2016-12-26 14:03:27 +00:00
} )
if err != nil {
return nil , err
}
2017-03-08 22:36:21 +00:00
// Save the public key
2018-01-19 06:44:44 +00:00
err = req . Storage . Put ( ctx , entry )
2017-03-08 22:36:21 +00:00
if err != nil {
return nil , err
2016-12-26 14:03:27 +00:00
}
2017-03-08 22:36:21 +00:00
entry , err = logical . StorageEntryJSON ( caPrivateKeyStoragePath , & keyStorageEntry {
Key : privateKey ,
} )
2016-12-26 14:03:27 +00:00
if err != nil {
return nil , err
}
2017-03-08 22:36:21 +00:00
// Save the private key
2018-01-19 06:44:44 +00:00
err = req . Storage . Put ( ctx , entry )
2017-03-08 22:36:21 +00:00
if err != nil {
var mErr * multierror . Error
2021-04-22 15:20:59 +00:00
mErr = multierror . Append ( mErr , fmt . Errorf ( "failed to store CA private key: %w" , err ) )
2017-03-08 22:36:21 +00:00
// If storing private key fails, the corresponding public key should be
// removed
2018-01-19 06:44:44 +00:00
if delErr := req . Storage . Delete ( ctx , caPublicKeyStoragePath ) ; delErr != nil {
2021-04-22 15:20:59 +00:00
mErr = multierror . Append ( mErr , fmt . Errorf ( "failed to cleanup CA public key: %w" , delErr ) )
2017-03-08 22:36:21 +00:00
return nil , mErr
}
return nil , err
}
2017-03-14 11:06:01 +00:00
if generateSigningKey {
response := & logical . Response {
Data : map [ string ] interface { } {
"public_key" : publicKey ,
} ,
}
return response , nil
}
2017-03-08 22:36:21 +00:00
return nil , nil
2016-12-26 14:03:27 +00:00
}
2017-03-02 09:32:50 +00:00
2022-02-15 19:14:05 +00:00
func generateSSHKeyPair ( randomSource io . Reader , keyType string , keyBits int ) ( string , string , error ) {
2021-09-15 16:59:28 +00:00
if randomSource == nil {
randomSource = rand . Reader
}
2017-03-02 09:32:50 +00:00
2022-02-15 19:14:05 +00:00
var publicKey crypto . PublicKey
var privateBlock * pem . Block
switch keyType {
case ssh . KeyAlgoRSA , "rsa" :
if keyBits == 0 {
keyBits = 4096
}
if keyBits < 2048 {
return "" , "" , fmt . Errorf ( "refusing to generate weak %v key: %v bits < 2048 bits" , keyType , keyBits )
}
privateSeed , err := rsa . GenerateKey ( randomSource , keyBits )
if err != nil {
return "" , "" , err
}
privateBlock = & pem . Block {
Type : "RSA PRIVATE KEY" ,
Headers : nil ,
Bytes : x509 . MarshalPKCS1PrivateKey ( privateSeed ) ,
}
publicKey = privateSeed . Public ( )
case ssh . KeyAlgoECDSA256 , ssh . KeyAlgoECDSA384 , ssh . KeyAlgoECDSA521 , "ec" :
var curve elliptic . Curve
switch keyType {
case ssh . KeyAlgoECDSA256 :
curve = elliptic . P256 ( )
case ssh . KeyAlgoECDSA384 :
curve = elliptic . P384 ( )
case ssh . KeyAlgoECDSA521 :
curve = elliptic . P521 ( )
default :
switch keyBits {
case 0 , 256 :
curve = elliptic . P256 ( )
case 384 :
curve = elliptic . P384 ( )
case 521 :
curve = elliptic . P521 ( )
default :
return "" , "" , fmt . Errorf ( "unknown ECDSA key pair algorithm: %v" , keyType )
}
}
privateSeed , err := ecdsa . GenerateKey ( curve , randomSource )
if err != nil {
return "" , "" , err
}
marshalled , err := x509 . MarshalECPrivateKey ( privateSeed )
if err != nil {
return "" , "" , err
}
privateBlock = & pem . Block {
Type : "EC PRIVATE KEY" ,
Headers : nil ,
Bytes : marshalled ,
}
publicKey = privateSeed . Public ( )
case ssh . KeyAlgoED25519 , "ed25519" :
_ , privateSeed , err := ed25519 . GenerateKey ( randomSource )
if err != nil {
return "" , "" , err
}
2022-02-17 20:17:59 +00:00
marshalled := edkey . MarshalED25519PrivateKey ( privateSeed )
if marshalled == nil {
return "" , "" , errors . New ( "unable to marshal ed25519 private key" )
2022-02-15 19:14:05 +00:00
}
privateBlock = & pem . Block {
Type : "OPENSSH PRIVATE KEY" ,
Headers : nil ,
Bytes : marshalled ,
}
publicKey = privateSeed . Public ( )
default :
return "" , "" , fmt . Errorf ( "unknown ssh key pair algorithm: %v" , keyType )
2017-03-02 09:32:50 +00:00
}
2022-02-15 19:14:05 +00:00
public , err := ssh . NewPublicKey ( publicKey )
2017-03-02 09:32:50 +00:00
if err != nil {
return "" , "" , err
}
return string ( ssh . MarshalAuthorizedKey ( public ) ) , string ( pem . EncodeToMemory ( privateBlock ) ) , nil
}