checking in some more research and primitive functions. i cannot, for the life of me, figure out why i can't (seemingly) properly decrypt private keys.

This commit is contained in:
brent s. 2020-09-17 08:37:05 -04:00
parent 5f5d77a2a6
commit 6b956a3a49
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
10 changed files with 190 additions and 65 deletions

3
TODO Normal file
View File

@ -0,0 +1,3 @@
-sshkeys (need to figure out generation process)
-moduli dhparams generation (dh.c? moduli.c?)
--ssh-keygen.c, ~L3565

View File

@ -1,14 +0,0 @@
package sshsecure

import (
"git.square-r00t.net/sshsecure/sshkeys"
)

const (
RoundsDefUser uint = 100
RoundsDefHost uint = 100
RSABitSize uint = 4096
DefKeyType string = sshkeys.KEY_ED25519
DefCipher string = sshkeys.CIPHER_AES256_CTR
DefKDF string = sshkeys.KDF_BCRYPT
)

5
go.mod
View File

@ -1,3 +1,8 @@
module git.square-r00t.net/sshsecure module git.square-r00t.net/sshsecure


go 1.15 go 1.15

require (
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
)

9
go.sum Normal file
View File

@ -0,0 +1,9 @@
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU=
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -5,26 +5,53 @@ const (
KeyV1Magic string = "openssh-key-v1" KeyV1Magic string = "openssh-key-v1"
) )


// Cipher names. I believe only AES256-CTR is supported upstream currently. // "Meta". Used for comment strings, etc.

const projUrl = "https://git.square-r00t.net/SSHSecure"

// Defaults.
const ( const (
CIPHER_NULL string = "none" defCipher string = CipherAes256Ctr
CIPHER_AES256_CTR string = "aes256-ctr" defKeyType string = KeyEd25519
defKDF string = KdfBcrypt
defRounds uint32 = 100
defRSABitSize uint32 = 4096
defSaltLen int = 16
) )


var allowed_ciphers = [...]string{CIPHER_NULL, CIPHER_AES256_CTR} // Cipher names. I believe only AES256-CTR is supported upstream currently.
const (
CipherNull string = "none"
CipherAes256Ctr string = "aes256-ctr"
)

var allowed_ciphers = [...]string{CipherNull, CipherAes256Ctr}


// Key types. // Key types.
const ( const (
KEY_ED25519 string = "ssh-ed25519" KeyEd25519 string = "ssh-ed25519"
KEY_RSA string = "ssh-rsa" KeyRsa string = "ssh-rsa"
) )


var allowed_keytypes = [...]string{KEY_ED25519, KEY_RSA} var allowed_keytypes = [...]string{KeyEd25519, KeyRsa}


// KDF names. I believe only bcrypt is supported upstream currently. // KDF names. I believe only bcrypt is supported upstream currently.
const ( const (
KDF_NULL string = "none" KdfNull string = "none"
KDF_BCRYPT string = "bcrypt" KdfBcrypt string = "bcrypt"
) )


var allowed_kdfnames = [...]string{KDF_NULL, KDF_BCRYPT} var allowed_kdfnames = [...]string{KdfNull, KdfBcrypt}

// Key lengths.
const (
// ED25519 in OpenSSH uses a static key size of 64 bytes.
ed25519Len uint32 = 64
)

// Key/Block sizes.
const (
keyEd25519 uint32 = 32
blockEd25519 uint32 = 16
blockNull uint32 = 8
)

View File

@ -1,51 +1,104 @@
package sshkeys package sshkeys


import ( import (
"crypto/rand"
"errors" "errors"
"fmt"


"r00t2.io/goutils/checks" // `golang.org/x/crypto/ssh/internal/bcrypt_pbkdf` Golang doesn't let you import "internal" libs. Fine. Lame language.
"github.com/dchest/bcrypt_pbkdf"
) )


func genPrivKey(cipherAlgo string, kdf string, salt []byte, rounds uint32, passphrase string) ([]byte, error) { func (k *EncryptedSSHKeyV1) validate() error {
return nil, nil if k.Passphrase == nil {
}

func genPubKey(privKey *[]byte) ([]byte, error) {
if *privKey == nil {
return nil, errors.New("must generate private key before public key")
}
return nil, nil
}

func (k *EncryptedSSHKeyV1) GeneratePrivate(force bool) error {
if k.Passphrase == "" {
return errors.New("cannot use encrypted key with empty passphrase") 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")
}
return nil
}

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
}
if k.PrivateKeys != nil && !force { if k.PrivateKeys != nil && !force {
return nil // Already generated. return nil // Already generated.
} }
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
}
if k.DefKeyType == KeyRsa && k.BitSize == 0 {
k.BitSize = defRSABitSize
} else if k.DefKeyType == KeyEd25519 {
k.BitSize = ed25519Len
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
}
// 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.
if k.KDFName == KdfBcrypt {
if pk.Key, err = bcrypt_pbkdf.Key(k.Passphrase, k.KDFOpts.Salt, int(k.KDFOpts.Rounds), int(k.KeySize)); err != nil {
return err
}
}


return nil return nil
} }


func (k *EncryptedSSHKeyV1) GeneratePublic(force bool) error { func (k *SSHKeyV1) validate() error {
if err := k.GeneratePrivate(force); err != nil {
return err
}

return nil return nil
} }


func (k *SSHKeyV1) GeneratePrivate(force bool) error { func (k *SSHKeyV1) GeneratePrivate(force bool) error {
k.validate()
if k.PrivateKeys != nil && !force { if k.PrivateKeys != nil && !force {
return nil // Already generated. return nil // Already generated.
} }
return nil return nil
} }

func (k *SSHKeyV1) GeneratePublic(force bool) error {
if err := k.GeneratePrivate(force); err != nil {
return err
}
return nil
}

View File

@ -1,6 +1,6 @@
package sshkeys package sshkeys


type OpenSSHKeypair interface { type OpenSSHKeypair interface {
GeneratePrivate(keyType uint8) error GeneratePrivate(force bool) error
GeneratePublic(keyType uint8) error GeneratePublic(force bool) error
} }

View File

@ -54,12 +54,24 @@ ANNOTATED HEX:
4.0.0.1 00000020 (32) 4.0.0.1 00000020 (32)
4.0.0.1.0 bfa2031aa5463113e40e16896af503c5299ead76b09cb63846f41cc4de1740f6 (bytes) 4.0.0.1.0 bfa2031aa5463113e40e16896af503c5299ead76b09cb63846f41cc4de1740f6 (bytes)
4.0.1 000000a0 (160) 4.0.1 000000a0 (160)
4.0.1 (AES256-CTR encrypted block) (bytes) 4.0.1.0 - 4.0.1.5 (AES256-CBC encrypted block) (bytes)
c49777cd0d1a7d37db77a1814991278f8ce99d57 c49777cd0d1a7d37db77a1814991278f
2e2c666b93b99867425c60da4652fddb85550985 8ce99d572e2c666b93b99867425c60da
32b51beeee2959f9db5cf5a0905052720c5de25f 4652fddb8555098532b51beeee2959f9
2c4dd87ebcc7bb5ea3d7bcbeacc6b732e4c39295 db5cf5a0905052720c5de25f2c4dd87e
d9991a97ef3f0838f8a9bfd43edb340318964908 bcc7bb5ea3d7bcbeacc6b732e4c39295
8f6cfb78946fb914e358ac6abc64691072f5f278 d9991a97ef3f0838f8a9bfd43edb3403
8534d9d42d7f406bc5090b30df23cb7dd8c5cb93 189649088f6cfb78946fb914e358ac6a
8e41facd6e38e8845b8160bff840598118d447c2 bc64691072f5f2788534d9d42d7f406b
c5090b30df23cb7dd8c5cb938e41facd
6e38e8845b8160bff840598118d447c2

DECRYPTED 4.0.1:
(...)
4.0.1 000000a0 (160)
4.0.1.0
4.0.1.1
4.0.1.2
4.0.2.3
4.0.2.4
4.0.2.5

View File

@ -5,3 +5,27 @@ https://stackoverflow.com/a/56300901/733214
https://stackoverflow.com/a/59283692/733214 https://stackoverflow.com/a/59283692/733214
https://coolaj86.com/articles/the-openssh-private-key-format/ https://coolaj86.com/articles/the-openssh-private-key-format/
https://coolaj86.com/articles/the-ssh-public-key-format/ https://coolaj86.com/articles/the-ssh-public-key-format/
https://crypto.stackexchange.com/a/40910
https://flak.tedunangst.com/post/new-openssh-key-format-and-bcrypt-pbkdf
("(Technical note: PBKDF2, aka PKCS #5, supports pluggable hash functions, though in practice everybody uses HMAC-SHA1. The bcrypt pbkdf essentially is PBKDF2, but with bcrypt plugged into it instead.)"
http://www.tedunangst.com/flak/post/bcrypt-pbkdf

## UPSTREAM
https://github.com/openssh/openssh-portable/blob/master/sshkey.c
funcs:
sshkey_generate (~L1714)
sshkey_private_to_blob2 (~L3833)
sshkey_private_to_fileblob (~L4413)
https://github.com/openssh/openssh-portable/blob/master/cipher.c
funcs:
cipher_ivlen
https://github.com/openssh/openssh-portable/blob/master/ed25519.c
funcs:
crypto_sign_ed25519_keypair (~L26)
https://github.com/openssh/openssh-portable/blob/master/authfile.c
funcs:
sshkey_save_private (~L68)
sshkey_save_private_blob (~L56)
https://github.com/openssh/openssh-portable/blob/master/ssh-keygen.c
funcs:
main (~L3145; ~L3673 onwards for key generation)

View File

@ -4,9 +4,8 @@ package sshkeys
type EncryptedSSHKeyV1 struct { type EncryptedSSHKeyV1 struct {
SSHKeyV1 SSHKeyV1
CipherName string CipherName string
KDFName string
KDFOpts SSHKDFOpts KDFOpts SSHKDFOpts
Passphrase string Passphrase []byte
} }


// SSHKDFOpts contains a set of KDF options. // SSHKDFOpts contains a set of KDF options.
@ -20,19 +19,26 @@ type SSHKDFOpts struct {
// Patch your shit. // Patch your shit.
type SSHKeyV1 struct { type SSHKeyV1 struct {
Magic string Magic string
BitSize uint32
DefKeyType string
KDFName string
KeySize uint32
BlockSize uint32
PublicKeys []SSHPubKey PublicKeys []SSHPubKey
PrivateKeys []SSHPrivKey PrivateKeys []SSHPrivKey
} }


// SSHPubKey contains the Public key of an SSH Keypair. // SSHPubKey contains the Public key of an SSH Keypair.
type SSHPubKey struct { type SSHPubKey struct {
KeyType string
PrivateKey *SSHPrivKey PrivateKey *SSHPrivKey
KeyType string
Key []byte
} }


// SSHPrivKey contains the Private key of an SSH Keypair. // SSHPrivKey contains the Private key of an SSH Keypair.
type SSHPrivKey struct { type SSHPrivKey struct {
PublicKey *SSHPubKey PublicKey *SSHPubKey
Checksum uint32 Key []byte
Checksum []byte
Comment string Comment string
} }