2020-09-18 18:01:16 -04:00
|
|
|
/*
|
|
|
|
SSHSecure - a program to harden OpenSSH from defaults
|
|
|
|
Copyright (C) 2020 Brent Saner
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2020-09-11 23:06:51 -04:00
|
|
|
package sshkeys
|
|
|
|
|
2020-09-12 00:58:58 -04:00
|
|
|
import (
|
2020-09-18 04:04:39 -04:00
|
|
|
"crypto/aes"
|
|
|
|
"crypto/cipher"
|
2020-09-17 08:37:05 -04:00
|
|
|
"crypto/rand"
|
2020-09-12 00:58:58 -04:00
|
|
|
"errors"
|
2020-09-17 08:37:05 -04:00
|
|
|
"fmt"
|
2020-09-12 00:58:58 -04:00
|
|
|
|
2020-09-17 08:37:05 -04:00
|
|
|
// `golang.org/x/crypto/ssh/internal/bcrypt_pbkdf` Golang doesn't let you import "internal" libs. Fine. Lame language.
|
|
|
|
"github.com/dchest/bcrypt_pbkdf"
|
2020-09-12 01:25:44 -04:00
|
|
|
)
|
2020-09-12 00:58:58 -04:00
|
|
|
|
2020-09-17 08:37:05 -04:00
|
|
|
func (k *EncryptedSSHKeyV1) validate() error {
|
|
|
|
if k.Passphrase == nil {
|
|
|
|
return errors.New("cannot use encrypted key with empty passphrase")
|
|
|
|
}
|
|
|
|
var validCipher bool
|
|
|
|
var validKDF bool
|
|
|
|
var validKT bool
|
|
|
|
for _, v := range allowed_ciphers {
|
|
|
|
if v == k.CipherName {
|
|
|
|
validCipher = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, v := range allowed_kdfnames {
|
|
|
|
if v == k.KDFName {
|
|
|
|
validKDF = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, v := range allowed_keytypes {
|
|
|
|
if v == k.DefKeyType {
|
|
|
|
validKT = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !validCipher || !validKDF || !validKT {
|
|
|
|
return errors.New("invalid CipherName, KDFName, or DefKeyType specified")
|
2020-09-12 01:25:44 -04:00
|
|
|
}
|
2020-09-17 08:37:05 -04:00
|
|
|
return nil
|
2020-09-12 00:58:58 -04:00
|
|
|
}
|
|
|
|
|
2020-09-17 08:37:05 -04:00
|
|
|
func (k *EncryptedSSHKeyV1) Generate(force bool) error {
|
|
|
|
if k.DefKeyType == "" {
|
|
|
|
k.DefKeyType = defKeyType
|
|
|
|
}
|
|
|
|
if k.KDFName == "" {
|
|
|
|
k.KDFName = defKDF
|
|
|
|
}
|
|
|
|
if k.CipherName == "" {
|
|
|
|
k.CipherName = defCipher
|
|
|
|
}
|
|
|
|
if err := k.validate(); err != nil {
|
|
|
|
return err
|
2020-09-12 00:58:58 -04:00
|
|
|
}
|
2020-09-18 04:04:39 -04:00
|
|
|
if len(k.Keys) > 0 && !force {
|
2020-09-12 00:58:58 -04:00
|
|
|
return nil // Already generated.
|
|
|
|
}
|
2020-09-17 08:37:05 -04:00
|
|
|
if k.KDFOpts.Salt == nil {
|
|
|
|
k.KDFOpts.Salt = make([]byte, defSaltLen)
|
|
|
|
if _, err := rand.Read(k.KDFOpts.Salt); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if k.KDFOpts.Rounds == 0 {
|
|
|
|
k.KDFOpts.Rounds = defRounds
|
|
|
|
}
|
2020-09-18 04:04:39 -04:00
|
|
|
if k.DefKeyType == KeyEd25519 {
|
2020-09-17 08:37:05 -04:00
|
|
|
k.KeySize = keyEd25519
|
|
|
|
k.BlockSize = blockEd25519
|
|
|
|
}
|
|
|
|
// Currently, OpenSSH has an option for multiple private keys. However, it is hardcoded to 1.
|
|
|
|
// If multiple key support is added in the future, will need to re-tool how I do this, perhaps, in the future. TODO.
|
|
|
|
pk := SSHPrivKey{
|
|
|
|
Comment: fmt.Sprintf("Autogenerated via SSHSecure (%v)", projUrl),
|
|
|
|
}
|
|
|
|
pk.Checksum = make([]byte, 4)
|
|
|
|
if _, err := rand.Read(pk.Checksum); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-09-18 04:04:39 -04:00
|
|
|
switch k.DefKeyType {
|
|
|
|
case KeyRsa:
|
|
|
|
if err := pk.generateRsa(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
case KeyEd25519:
|
|
|
|
if err := pk.generateEd25519(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return errors.New("unknown key type; could not generate private/public keypair")
|
|
|
|
}
|
|
|
|
k.Keys = append(k.Keys, pk)
|
|
|
|
// We also need an encrypter/decrypter since this is an encrypted key.
|
2020-09-17 08:37:05 -04:00
|
|
|
// Upstream only currently supports bcrypt_pbkdf ("bcrypt").
|
|
|
|
// This should always eval to true, but is here for future planning in case other KDF are implemented.
|
2020-09-18 04:04:39 -04:00
|
|
|
switch k.KDFName {
|
|
|
|
case KdfBcrypt:
|
|
|
|
if k.Crypt.CryptKey, err = bcrypt_pbkdf.Key(k.Passphrase, k.KDFOpts.Salt, int(k.KDFOpts.Rounds), kdfKeyLen+len(k.KDFOpts.Salt)); err != nil {
|
2020-09-17 08:37:05 -04:00
|
|
|
return err
|
2020-09-18 04:04:39 -04:00
|
|
|
} else {
|
|
|
|
k.Crypt.PrivateKey = k.Crypt.CryptKey[0:kdfSplit]
|
|
|
|
k.Crypt.CryptSalt = k.Crypt.CryptKey[kdfSplit:]
|
2020-09-17 08:37:05 -04:00
|
|
|
}
|
2020-09-18 04:04:39 -04:00
|
|
|
default:
|
|
|
|
return errors.New("could not find KDF")
|
2020-09-17 08:37:05 -04:00
|
|
|
}
|
2020-09-18 04:04:39 -04:00
|
|
|
switch k.CipherName {
|
|
|
|
case CipherAes256Ctr:
|
|
|
|
if k.Crypt.Cipher, err = aes.NewCipher(k.Crypt.PrivateKey); err != nil {
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
k.Crypt.Stream = cipher.NewCTR(k.Crypt.Cipher, k.Crypt.CryptSalt)
|
|
|
|
// Can then be used as k.Crypt.Stream.XORKeyStream(dst []byte, src []byte)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return errors.New("could not find Cipher")
|
|
|
|
}
|
|
|
|
k.build()
|
2020-09-11 23:06:51 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-09-17 08:37:05 -04:00
|
|
|
func (k *SSHKeyV1) validate() error {
|
2020-09-18 04:04:39 -04:00
|
|
|
var validKT bool
|
|
|
|
for _, v := range allowed_keytypes {
|
|
|
|
if v == k.DefKeyType {
|
|
|
|
validKT = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !validKT {
|
|
|
|
return errors.New("invalid DefKeyType specified")
|
|
|
|
}
|
2020-09-11 23:06:51 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-09-18 04:04:39 -04:00
|
|
|
func (k *SSHKeyV1) Generate(force bool) error {
|
|
|
|
if len(k.Keys) > 0 && !force {
|
2020-09-12 00:58:58 -04:00
|
|
|
return nil // Already generated.
|
|
|
|
}
|
2020-09-18 04:04:39 -04:00
|
|
|
if k.DefKeyType == KeyEd25519 {
|
|
|
|
k.KeySize = keyEd25519
|
|
|
|
k.BlockSize = blockEd25519
|
|
|
|
}
|
2020-09-18 18:01:16 -04:00
|
|
|
k.CipherName = CipherNull
|
|
|
|
k.KDFName = KdfNull
|
2020-09-18 04:04:39 -04:00
|
|
|
// Currently, OpenSSH has an option for multiple private keys. However, it is hardcoded to 1.
|
|
|
|
// If multiple key support is added in the future, will need to re-tool how I do this, perhaps, in the future. TODO.
|
|
|
|
pk := SSHPrivKey{
|
|
|
|
Comment: fmt.Sprintf("Autogenerated via SSHSecure (%v)", projUrl),
|
|
|
|
}
|
|
|
|
pk.Checksum = make([]byte, 4)
|
|
|
|
if _, err := rand.Read(pk.Checksum); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
switch k.DefKeyType {
|
|
|
|
case KeyRsa:
|
|
|
|
if err := pk.generateRsa(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
case KeyEd25519:
|
|
|
|
if err := pk.generateEd25519(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return errors.New("unknown key type; could not generate private/public keypair")
|
|
|
|
}
|
|
|
|
k.Keys = append(k.Keys, pk)
|
|
|
|
k.build()
|
2020-09-11 23:06:51 -04:00
|
|
|
return nil
|
|
|
|
}
|
2020-09-18 04:04:39 -04:00
|
|
|
|
2020-09-18 18:01:16 -04:00
|
|
|
func (k *SSHKeyV1) build() error {
|
2020-09-18 04:04:39 -04:00
|
|
|
// We actually assemble the key buffer here. Translation to bytes where needed, case switches (ED25519 vs. RSA), etc.
|
2020-09-18 18:01:16 -04:00
|
|
|
k.Buffer.Truncate(0)
|
|
|
|
// First we need to do some prep for the common header.
|
|
|
|
kdfOptsBytes := []byte{}
|
|
|
|
switch k.(type) {
|
|
|
|
case EncryptedSSHKeyV1:
|
|
|
|
kdfOptsBytes = append(kdfOptsBytes, byte(len(k.KDFOpts.Salt)))
|
|
|
|
}
|
|
|
|
// For this, we can use a generic list and sequentially write.
|
|
|
|
cipher := []byte(k.CipherName)
|
|
|
|
kdf := []byte(k.KDFName)
|
|
|
|
commonHeader := [][]byte{
|
|
|
|
[]byte(KeyV1Magic + "\x00"),
|
|
|
|
{byte(len(cipher))},
|
|
|
|
cipher,
|
|
|
|
{byte(len(kdf))},
|
|
|
|
kdf,
|
|
|
|
}
|
|
|
|
for _, v := range commonHeader {
|
|
|
|
if _, err := k.Buffer.Write(v); err != nil {
|
|
|
|
k.Buffer.Truncate(0)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-18 04:04:39 -04:00
|
|
|
}
|