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-21 01:43:22 -04:00
|
|
|
`bytes`
|
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 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
|
|
|
|
}
|
|
|
|
// 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-21 01:43:22 -04:00
|
|
|
if err := k.setCrypt(); err != nil {
|
|
|
|
return err
|
2020-09-17 08:37:05 -04:00
|
|
|
}
|
2020-09-21 01:43:22 -04:00
|
|
|
// And then we need to build the key buffer.
|
|
|
|
if err := k.buildKeybuf(); err != nil {
|
|
|
|
return err
|
2020-09-18 04:04:39 -04:00
|
|
|
}
|
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
|
|
|
|
}
|
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)
|
2020-09-21 01:43:22 -04:00
|
|
|
if err := k.buildKeybuf(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// buildKeybuf is where an EncryptedSSHKeyV1 key buffer is actually assembled.
|
|
|
|
// Entries are commented with their annotated reference.
|
|
|
|
// (see ref/(encrypted|plain)/(public|private).(rsa|ed25519) and ref/format.(ed25519|rsa) for details)
|
|
|
|
func (k *EncryptedSSHKeyV1) buildKeybuf() error {
|
|
|
|
// TODO: error handling for each <buf>.Write()?
|
|
|
|
// Before anything, we want a clean buffer. Just in case.
|
|
|
|
k.Buffer.Reset()
|
|
|
|
// Add the common header.
|
|
|
|
if err := k.addHeader(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Add the keypairs.
|
|
|
|
// Since this is encrypted, the private key blobs are encrypted.
|
|
|
|
// OpenSSH keys currently only support 1 keypair but support more *in theory*. This *seems* to be how they plan on doing it.
|
|
|
|
// But boy, is it a pain. So much wasted RAM and CPU cycles. They should use terminating byte sequences IMHO but whatever.
|
|
|
|
for _, i := range k.Keys { // 4.0.0 and 4.0.1
|
|
|
|
switch k.CipherName {
|
|
|
|
case CipherAes256Ctr:
|
|
|
|
i.BlockSize = k.Crypt.Cipher.BlockSize()
|
|
|
|
}
|
|
|
|
kbPtr, err := i.keyBlob(&k.Crypt, true)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
k.Buffer.Write(*kbPtr)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// buildKeybuf is where an SSHKeyV1 key buffer is actually assembled.
|
|
|
|
// Entries are commented with their annotated reference.
|
|
|
|
// (see ref/(encrypted|plain)/(public|private).(rsa|ed25519) and ref/format.(ed25519|rsa) for details)
|
|
|
|
func (k *SSHKeyV1) buildKeybuf() error {
|
|
|
|
// TODO: error handling for each <buf>.Write()?
|
|
|
|
// Before anything, we want a clean buffer. Just in case.
|
|
|
|
k.Buffer.Reset()
|
|
|
|
// Add the common header.
|
|
|
|
if err := k.addHeader(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Add the keypairs.
|
|
|
|
// OpenSSH keys currently only support 1 keypair but support more *in theory*. This *seems* to be how they plan on doing it.
|
|
|
|
// But boy, is it a pain. So much wasted RAM and CPU cycles. They should use terminating byte sequences IMHO but whatever.
|
|
|
|
for _, i := range k.Keys { // 4.0.0 and 4.0.1
|
|
|
|
i.BlockSize = 8
|
|
|
|
kbPtr, err := i.keyBlob(nil, false)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
k.Buffer.Write(*kbPtr)
|
|
|
|
}
|
2020-09-11 23:06:51 -04:00
|
|
|
return nil
|
|
|
|
}
|
2020-09-18 04:04:39 -04:00
|
|
|
|
2020-09-21 01:43:22 -04:00
|
|
|
func (k *SSHKeyV1) addHeader() error {
|
|
|
|
// TODO: error handling for each <buf>.Write()?
|
|
|
|
// First we need to do some prep for the plaintext header.
|
|
|
|
var kdfOptsBytes []byte
|
|
|
|
kdfOptsBytes = k.getKdfOptBytes() // 3.0.0
|
|
|
|
cipherBytes := []byte(k.CipherName) // 1.0
|
|
|
|
kdf := []byte(k.KDFName) // 2.0.0
|
|
|
|
// This is just cast to an array for visual readability.
|
2020-09-18 18:01:16 -04:00
|
|
|
commonHeader := [][]byte{
|
2020-09-21 01:43:22 -04:00
|
|
|
[]byte(KeyV1Magic + "\x00"), // 0
|
|
|
|
getBytelenByteArr(cipherBytes), // 1.0
|
|
|
|
cipherBytes, // 1.0.0
|
|
|
|
getBytelenByteArr(kdf), // 2.0
|
|
|
|
kdf, // 2.0.0
|
|
|
|
getBytelenByteArr(kdfOptsBytes), // 3.0
|
|
|
|
kdfOptsBytes, // 3.0.0
|
|
|
|
getByteInt(len(k.Keys)), // 4.0
|
2020-09-18 18:01:16 -04:00
|
|
|
}
|
|
|
|
for _, v := range commonHeader {
|
|
|
|
if _, err := k.Buffer.Write(v); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2020-09-21 01:43:22 -04:00
|
|
|
return nil
|
|
|
|
}
|
2020-09-18 18:01:16 -04:00
|
|
|
|
2020-09-21 01:43:22 -04:00
|
|
|
func (k *EncryptedSSHKeyV1) getKdfOptBytes() []byte {
|
|
|
|
var kdfOptsBytes []byte // 3.0.0
|
|
|
|
// This is *probably* more efficient than using a buffer just for these bytes.
|
|
|
|
kdfOptsBytes = append(kdfOptsBytes, byte(len(k.KDFOpts.Salt))) // 3.0.0.0
|
|
|
|
kdfOptsBytes = append(kdfOptsBytes, k.KDFOpts.Salt...) // 3.0.0.0.0
|
|
|
|
kdfOptsBytes = append(kdfOptsBytes, byte(k.KDFOpts.Rounds)) // 3.0.0.1
|
|
|
|
return kdfOptsBytes
|
2020-09-18 04:04:39 -04:00
|
|
|
}
|
2020-09-21 01:43:22 -04:00
|
|
|
|
|
|
|
func (k *SSHKeyV1) getKdfOptBytes() []byte {
|
|
|
|
var kdfOptsBytes []byte // 3.0.0
|
|
|
|
// No-op; unencrypted keys' KDFOpts are encapsulated by a single null byte (which the caller implements).
|
|
|
|
return kdfOptsBytes
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pk *SSHPrivKey) keyBlob(c *SSHCrypt, encrypt bool) (*[]byte, error) {
|
|
|
|
// TODO: error handling for each <buf>.Write()?
|
|
|
|
var keypairBytes bytes.Buffer // (4.0's children)
|
|
|
|
var pubkeyBytes bytes.Buffer // 4.0.0 children (4.0.0 itself is handled before writing to keypairBytes)
|
|
|
|
var privkeyBytes bytes.Buffer // 4.0.1 (and children)
|
|
|
|
pubkeyName := []byte(pk.PublicKey.KeyType) // (4.0.0.0.0, cast to var because I'm lazy)
|
|
|
|
pubkeyBytes.Write(getBytelenByteArr(pubkeyName)) // 4.0.0.0
|
|
|
|
pubkeyBytes.Write(pubkeyName) // 4.0.0.0.0
|
|
|
|
// TODO: Optimize?
|
|
|
|
/*
|
|
|
|
THE PUBLIC KEY
|
|
|
|
This is unencrypted, even if it's an encrypted key.
|
|
|
|
*/
|
|
|
|
switch pk.PublicKey.KeyType {
|
|
|
|
case KeyEd25519:
|
|
|
|
pubkeyBytes.Write(getBytelenByteArr(pk.PublicKey.Key.([]byte))) // 4.0.0.1
|
|
|
|
pubkeyBytes.Write(pk.PublicKey.Key.([]byte)) // 4.0.0.1.0
|
|
|
|
case KeyRsa:
|
|
|
|
// How messy.
|
|
|
|
var en bytes.Buffer // 4.0.0.1 and 4.0.0.2
|
|
|
|
// TODO: does e need getByteInt()?
|
|
|
|
e := pk.PublicKey.Key.E.Bytes() // 4.0.0.1.0
|
|
|
|
// TODO: does n need nullbyte prefix?
|
|
|
|
n := pk.PublicKey.Key.N.Bytes() // 4.0.0.2.0
|
|
|
|
en.Write(getBytelenByteArr(e)) // 4.0.0.1
|
|
|
|
en.Write(e) // 4.0.0.1.0
|
|
|
|
en.Write(getBytelenByteArr(n)) // 4.0.0.2
|
|
|
|
en.Write(n) // 4.0.0.2.0
|
|
|
|
pubkeyBytes.Write(getBytelenByteArr(en.Bytes())) // 4.0.0
|
|
|
|
if _, err := en.WriteTo(&pubkeyBytes); err != nil { // (4.0.0 children)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
THE PRIVATE KEY
|
|
|
|
This is encrypted if it's an encrypted key, otherwise it's just plain readable bytes.
|
|
|
|
*/
|
|
|
|
// First we need two checksums.
|
|
|
|
for i := 1; i <= 2; i++ {
|
|
|
|
privkeyBytes.Write(pk.Checksum) // 4.0.1.0 and 4.0.1.1
|
|
|
|
}
|
|
|
|
// And then add the public keys. Yes, the public keys are included in the private keys.
|
|
|
|
privkeyBytes.Write(getBytelenByteArr(pubkeyName)) // 4.0.1.2.0
|
|
|
|
privkeyBytes.Write(pubkeyName) // 4.0.1.2.0.0
|
|
|
|
switch pk.PublicKey.KeyType {
|
|
|
|
case KeyEd25519:
|
|
|
|
// This is easy.
|
|
|
|
privkeyBytes.Write(pubkeyBytes.Bytes()) // 4.0.1.2.1
|
|
|
|
if _, err := pubkeyBytes.WriteTo(&privkeyBytes); err != nil { // 4.0.1.2.1.0
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
case KeyRsa:
|
|
|
|
// This is not. We more or less have to do the same thing as the public key, BUT with e and n flipped. Gorram it.
|
|
|
|
var ne bytes.Buffer // (4.0.1.2 children)
|
|
|
|
// TODO: does n need nullbyte prefix?
|
|
|
|
n := pk.PublicKey.Key.N.Bytes() // 4.0.1.2.1.0
|
|
|
|
// TODO: does e need getByteInt()?
|
|
|
|
e := pk.PublicKey.Key.E.Bytes() // 4.0.1.2.2.0
|
|
|
|
ne.Write(getBytelenByteArr(n)) // 4.0.1.2.1
|
|
|
|
ne.Write(n) // 4.0.1.2.1.0
|
|
|
|
ne.Write(getBytelenByteArr(e)) // 4.0.1.2.2
|
|
|
|
ne.Write(e) // 4.0.1.2.2.0
|
|
|
|
if _, err := ne.WriteTo(&privkeyBytes); err != nil { // (4.0.1.2 children)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// And then we add the *actual* private keys.
|
|
|
|
switch pk.PublicKey.KeyType {
|
|
|
|
case KeyEd25519:
|
|
|
|
privkeyBytes.Write(getBytelenByteArr(pk.KeyAlt)) // 4.0.1.3
|
|
|
|
privkeyBytes.Write(pk.KeyAlt) // 4.0.1.3.0
|
|
|
|
case KeyRsa:
|
|
|
|
var dcpq bytes.Buffer // 4.0.1.3 to 4.0.1.6
|
|
|
|
d := pk.Key.D.Bytes() // 4.0.1.3.0
|
|
|
|
crt := pk.Key.Precomputed.Qinv.Bytes() // 4.0.1.4.0
|
|
|
|
// TODO: does p need nullbyte prefix?
|
|
|
|
p := pk.Key.Primes[0].Bytes() // 4.0.1.5.0
|
|
|
|
// TODO: does q need nullbyte prefix?
|
|
|
|
q := pk.Key.Primes[1].Bytes() // 4.0.1.6.0
|
|
|
|
dcpq.Write(getBytelenByteArr(d)) // 4.0.1.3
|
|
|
|
dcpq.Write(d) // 4.0.1.3.0
|
|
|
|
dcpq.Write(getBytelenByteArr(crt)) // 4.0.1.4
|
|
|
|
dcpq.Write(crt) // 4.0.1.4.0
|
|
|
|
dcpq.Write(getBytelenByteArr(p)) // 4.0.1.5
|
|
|
|
dcpq.Write(p) // 4.0.1.5.0
|
|
|
|
dcpq.Write(getBytelenByteArr(q)) // 4.0.1.6
|
|
|
|
dcpq.Write(q) // 4.0.1.6.0
|
|
|
|
if _, err := dcpq.WriteTo(&privkeyBytes); err != nil { // 4.0.1.3 to 4.0.1.6
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Add the comment.
|
|
|
|
privkeyBytes.Write(getBytelenByteArr([]byte(pk.Comment))) // 4.0.1.4 (ED25519), 4.0.1.7 (RSA)
|
|
|
|
privkeyBytes.Write([]byte(pk.Comment)) // 4.0.1.4.0 (ED25519), 4.0.1.7.0 (RSA)
|
|
|
|
// Add padding
|
|
|
|
pad := 0
|
|
|
|
n := 0
|
|
|
|
for len(privkeyBytes.Bytes()) % pk.BlockSize != 0 { // 4.0.1.5 (ED25519), 4.0.1.8 (RSA)
|
|
|
|
n++
|
|
|
|
pad = n & pk.BlockSize
|
|
|
|
privkeyBytes.Write(getSingleByteInt(pad))
|
|
|
|
}
|
|
|
|
// Check if the private key should be encrypted or not.
|
|
|
|
if encrypt {
|
|
|
|
// We encrypt here to a "new" buffer.
|
|
|
|
// We actually just clear the buffer and then write the new encrypted data to it, and then clear the intermediate byte slice.
|
|
|
|
if c == nil || c.Stream == nil {
|
|
|
|
return nil, errors.New("if encrypting, you must specify a populated SSHCrypt in c")
|
|
|
|
}
|
|
|
|
var encBytes []byte
|
|
|
|
c.Stream.XORKeyStream(encBytes, privkeyBytes.Bytes())
|
|
|
|
privkeyBytes.Reset()
|
|
|
|
privkeyBytes.Write(encBytes)
|
|
|
|
encBytes = []byte{}
|
|
|
|
}
|
|
|
|
// Get the respective lengths and add child buffers to buffer.
|
|
|
|
keypairBytes.Write(getByteInt(len(pubkeyBytes.Bytes()))) // 4.0.0
|
|
|
|
if _, err := pubkeyBytes.WriteTo(&keypairBytes); err != nil { // (4.0.0 children)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
keypairBytes.Write(getByteInt(len(privkeyBytes.Bytes()))) // 4.0.1
|
|
|
|
if _, err := privkeyBytes.WriteTo(&keypairBytes); err != nil { // (4.0.1 children)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Done!
|
|
|
|
kpSlice := keypairBytes.Bytes()
|
|
|
|
return &kpSlice, nil
|
|
|
|
}
|