bcrypt and null kdf done, work on ciphers next (then keytypes)

This commit is contained in:
2022-04-25 04:27:24 -04:00
parent 91d5e99404
commit ff3f8243d1
43 changed files with 755 additions and 423 deletions

11
kdf/bcrypt/consts.go Normal file
View File

@@ -0,0 +1,11 @@
package bcrypt
const (
Name string = "bcrypt"
// DefaultRounds is the default per OpenSSH, not per the bcrypt_pbkdf spec itself. It is recommended to use e.g. 100 rounds.
DefaultRounds uint32 = 16
// DefaultSaltLen is the default per OpenSSH, not per the bcrypt_pbkdf spec itself.
DefaultSaltLen int = 16
// DefaultKeyLen is suitable for AES256-CTR but may not be for others. TODO: revisit this and find something more flexible?
DefaultKeyLen uint32 = 48
)

246
kdf/bcrypt/funcs.go Normal file
View File

@@ -0,0 +1,246 @@
package bcrypt
import (
"bytes"
"crypto/rand"
"encoding/binary"
"github.com/dchest/bcrypt_pbkdf"
"r00t2.io/sshkeys/internal"
`r00t2.io/sshkeys/kdf/errs`
)
/*
Setup must be called before DeriveKey. It configures a .
If secret is nil or an empty byte slice, ErrNoSecret will be returned.
If salt is nil or an empty byte slice, one will be randomly generated of DefaultSaltLen length.
If rounds is 0, DefaultRounds will be used.
If keyLen is 0, DefaultKeyLen will be used.
*/
func (k *KDF) Setup(secret, salt []byte, rounds, keyLen uint32) (err error) {
if secret == nil || len(secret) == 0 {
err = errs.ErrNoSecret
return
}
if salt == nil || len(salt) == 0 {
salt = make([]byte, DefaultSaltLen)
if _, err = rand.Read(salt); err != nil {
return
}
}
if rounds == 0 {
rounds = DefaultRounds
}
if keyLen == 0 {
keyLen = DefaultKeyLen
}
k.secret = secret
k.salt = salt
k.rounds = rounds
k.keyLen = keyLen
k.hasSalt = true
k.hasRounds = true
return
}
/*
SetupAuto is used to provide out-of-band configuration if the KDF options were found via kdf.UnpackKDF.
You can test this by running KDF.AutoOK.
*/
func (k *KDF) SetupAuto(secret []byte, keyLen uint32) (err error) {
if !k.AutoOK() {
err = errs.ErrUnknownKdf
return
}
if keyLen == 0 {
keyLen = DefaultKeyLen
}
k.secret = secret
k.keyLen = keyLen
return
}
/*
DeriveKey returns the derived key from a configured bcrypt.KDF.
It must be called *after* Setup.
*/
func (k *KDF) DeriveKey() (key []byte, err error) {
if k.secret == nil {
err = errs.ErrNoSecret
return
}
if k.salt == nil {
err = errs.ErrNoSalt
return
}
if k.rounds == 0 {
err = errs.ErrNoRounds
return
}
if k.keyLen == 0 {
err = errs.ErrNoKeyLen
}
if k.key, err = bcrypt_pbkdf.Key(k.secret, k.salt, int(k.rounds), int(k.keyLen)); err != nil {
return
}
key = k.key
return
}
// Name returns bcrypt.Name.
func (k *KDF) Name() (name string) {
name = Name
return
}
// NameBytes returns the byte form of bcrypt.Name with leading bytecount allocator.
func (k *KDF) NameBytes() (name []byte) {
var nb []byte
var s = k.Name()
nb = []byte(s)
name = make([]byte, 4)
binary.BigEndian.PutUint32(name, uint32(len(nb)))
name = append(name, nb...)
return
}
// PackedBytes returns block 3.0 and recursed.
func (k *KDF) PackedBytes() (buf *bytes.Reader, err error) {
var rounds = make([]byte, 4)
var packer *bytes.Reader
var w = new(bytes.Buffer)
// block 3.0.0.0 and block 3.0.0.0.0
if packer, err = internal.ReadSizeBytes(k.salt, true); err != nil {
return
}
if _, err = packer.WriteTo(w); err != nil {
return
}
// block 3.0.0.1
binary.BigEndian.PutUint32(rounds, k.rounds)
if _, err = w.Write(rounds); err != nil {
return
}
// block 3.0
if buf, err = internal.ReadSizeBytes(w, true); err != nil {
return
}
return
}
// Rounds returns the number of rounds used in derivation.
func (k *KDF) Rounds() (rounds uint32) {
rounds = k.rounds
return
}
// Salt returns the salt bytes.
func (k *KDF) Salt() (salt []byte) {
salt = k.salt
return
}
/*
AutoOK returns true if a kdf.UnpackKDF call was able to fetch the bcrypt.KDF options successfully, in which case the caller may use bcrypt.KDF.SetupAuto.
If false, it will need to be manually configured via bcrypt.KDF.Setup.
*/
func (k *KDF) AutoOK() (ok bool) {
ok = true
if k.salt == nil || len(k.salt) == 0 {
ok = false
}
if k.rounds == 0 {
ok = false
}
return
}
// IsPlain indicates if this kdf.KDF actually does derivation (false) or not (true).
func (k *KDF) IsPlain() (plain bool) {
plain = false
return
}
/*
AddSalt adds a salt as parsed from kdf.UnpackKDF.
If a salt has already been set, a no-op will be performed.
If you want to use a different salt, you will need to create a new bcrypt.KDF.
*/
func (k *KDF) AddSalt(salt []byte) (err error) {
if salt == nil || len(salt) == 0 {
err = errs.ErrNoSalt
return
}
if k.hasSalt {
return
}
k.salt = salt
k.hasSalt = true
return
}
/*
AddRounds adds the rounds as parsed from kdf.UnpackKDF.
If the number of rounds has already been set, a no-op will be performed.
If you want to use a different number of rounds, you will need to create a new bcrypt.KDF.
*/
func (k *KDF) AddRounds(rounds uint32) (err error) {
if rounds == 0 {
err = errs.ErrNoRounds
return
}
if k.hasRounds {
return
}
k.rounds = rounds
k.hasRounds = true
return
}

25
kdf/bcrypt/types.go Normal file
View File

@@ -0,0 +1,25 @@
package bcrypt
/*
BcryptPbkdf combines bcrypt hashing algorithm with PBKDF2 key derivation.
(bcrypt) https://www.usenix.org/legacy/events/usenix99/provos/provos_html/node1.html
(PBKDF2) https://datatracker.ietf.org/doc/html/rfc2898
http://www.tedunangst.com/flak/post/bcrypt-pbkdf
*/
type KDF struct {
// salt is used to salt the hash for each round in rounds.
salt []byte
// rounds controls how many iterations of salting/hashing is done.
rounds uint32
// keyLen is how long the derived key should be in bytes.
keyLen uint32
// secret is the "passphrase" used to seed the key creation.
secret []byte
// key is used to store the derived key.
key []byte
// hasSalt is true if a salt has been set.
hasSalt bool
// hasRounds is true if a number of rounds have been set.
hasRounds bool
}

View File

@@ -1,11 +0,0 @@
package kdf
const (
BcryptPbkdfName string = "bcrypt"
// BcryptPbkdfDefaultRounds is the default per OpenSSH, not per the bcrypt_pbkdf spec itself. It is recommended to use e.g. 100 rounds.
BcryptPbkdfDefaultRounds uint32 = 16
// BcryptPbkdfDefaultSaltLen is the default per OpenSSH, not per the bcrypt_pbkdf spec itself.
BcryptPbkdfDefaultSaltLen int = 16
// BcryptPbkdfDefaultKeyLen is suitable for AES256-CTR but may not be for others. TODO: revisit this and find something more flexible.
BcryptPbkdfDefaultKeyLen uint32 = 48
)

View File

@@ -1,5 +0,0 @@
package kdf
const (
NullName string = "none"
)

View File

@@ -1,4 +1,4 @@
package kdf
package errs
import (
"errors"

View File

@@ -2,6 +2,10 @@ package kdf
import (
"strings"
`r00t2.io/sshkeys/kdf/bcrypt`
`r00t2.io/sshkeys/kdf/errs`
`r00t2.io/sshkeys/kdf/null`
)
/*
@@ -12,12 +16,12 @@ import (
func GetKDF(name string) (k KDF, err error) {
switch strings.ToLower(name) {
case BcryptPbkdfName:
k = &BcryptPbkdf{}
case NullName, "":
k = &Null{}
case bcrypt.Name:
k = &bcrypt.KDF{}
case null.Name, "":
k = &null.KDF{}
default:
err = ErrUnknownKdf
err = errs.ErrUnknownKdf
return
}
@@ -25,18 +29,21 @@ func GetKDF(name string) (k KDF, err error) {
}
/*
UnpackKDF returns a KDF from raw data as packed in a private key's bytes.
UnpackKDF returns a kdf.KDF from raw data as packed in a private key's bytes.
KDF.Setup must be called on the resulting KDF.
kdf.KDF.Setup must be called on the resulting kdf.KDF.
data can be:
- a []byte, which can be:
- the full private key bytes
- KDF section (e.g. _ref/format.ed25519 2.0 + (3.0 to 3.0.0.1) or 2.0.0 + (3.0 to 3.0.0.1))
- KDF section (e.g. ED25519 private key block 2.0 + (block 3.0 to block 3.0.0.1) OR
block 2.0.0 + (block 3.0 to block 3.0.0.1))
- a *bytes.Buffer, which supports the same as above
*/
func UnpackKDF(data interface{}) (k KDF, err error) {
// TODO
return
}

View File

@@ -1,226 +0,0 @@
package kdf
import (
"bytes"
"crypto/rand"
"encoding/binary"
bcryptPbkdf "github.com/dchest/bcrypt_pbkdf"
"r00t2.io/sshkeys/internal"
)
/*
Setup must be called before DeriveKey. It configures a BcryptPbkdf.
If secret is nil or an empty byte slice, ErrNoSecret will be returned.
If salt is nil or an empty byte slice, one will be randomly generated of BcryptPbkdfDefaultSaltLen length.
If rounds is 0, BcryptPbkdfDefaultRounds will be used.
If keyLen is 0, BcryptPbkdfDefaultKeyLen will be used.
*/
func (b *BcryptPbkdf) Setup(secret, salt []byte, rounds, keyLen uint32) (err error) {
if secret == nil || len(secret) == 0 {
err = ErrNoSecret
return
}
if salt == nil || len(salt) == 0 {
salt = make([]byte, BcryptPbkdfDefaultSaltLen)
if _, err = rand.Read(salt); err != nil {
return
}
}
if rounds == 0 {
rounds = BcryptPbkdfDefaultRounds
}
if keyLen == 0 {
keyLen = BcryptPbkdfDefaultKeyLen
}
b.secret = secret
b.salt = salt
b.rounds = rounds
b.keyLen = keyLen
return
}
/*
SetupAuto is used to provide out-of-band configuration if the KDF options were found via GetKdfFromBytes.
You can test this by running KDF.AutoOK.
*/
func (b *BcryptPbkdf) SetupAuto(secret []byte, keyLen uint32) (err error) {
if !b.AutoOK() {
err = ErrUnknownKdf
return
}
if keyLen == 0 {
keyLen = BcryptPbkdfDefaultKeyLen
}
b.secret = secret
b.keyLen = keyLen
return
}
/*
DeriveKey returns the derived key from a BcryptPbkdf.
It must be called *after* Setup.
*/
func (b *BcryptPbkdf) DeriveKey() (key []byte, err error) {
if b.secret == nil {
err = ErrNoSecret
return
}
if b.salt == nil {
err = ErrNoSalt
return
}
if b.rounds == 0 {
err = ErrNoRounds
return
}
if b.keyLen == 0 {
err = ErrNoKeyLen
}
if b.key, err = bcryptPbkdf.Key(b.secret, b.salt, int(b.rounds), int(b.keyLen)); err != nil {
return
}
key = b.key
return
}
// Name returns BcryptPbkdfName.
func (b *BcryptPbkdf) Name() (name string) {
name = BcryptPbkdfName
return
}
// NameBytes returns the byte form of BcryptPbkdf.Name with leading bytecount allocator.
func (b *BcryptPbkdf) NameBytes() (name []byte) {
var nb []byte
var s = b.Name()
nb = []byte(s)
name = make([]byte, 4)
binary.BigEndian.PutUint32(name, uint32(len(nb)))
name = append(name, nb...)
return
}
// PackedBytes returns 3.0 and recursed.
func (b *BcryptPbkdf) PackedBytes() (buf *bytes.Reader, err error) {
var rounds = make([]byte, 4)
var packer *bytes.Reader
var w = new(bytes.Buffer)
// 3.0.0.0 and 3.0.0.0.0
if packer, err = internal.ReadSizeBytes(b.salt, true); err != nil {
return
}
if _, err = packer.WriteTo(w); err != nil {
return
}
// 3.0.0.1
binary.BigEndian.PutUint32(rounds, b.rounds)
if _, err = w.Write(rounds); err != nil {
return
}
// 3.0
if buf, err = internal.ReadSizeBytes(w, true); err != nil {
return
}
return
}
// Rounds returns the number of rounds used in derivation.
func (b *BcryptPbkdf) Rounds() (rounds uint32) {
rounds = b.rounds
return
}
// Salt returns the salt bytes.
func (b *BcryptPbkdf) Salt() (salt []byte) {
salt = b.salt
return
}
/*
AutoOK returns true if a GetKdfFromBytes call was able to fetch the KDF options successfully, in which case the caller may use KDF.SetupAuto.
If false, it will need to be manually configured via KDF.Setup.
*/
func (b *BcryptPbkdf) AutoOK() (ok bool) {
ok = true
if b.salt == nil || len(b.salt) == 0 {
ok = false
}
if b.rounds == 0 {
ok = false
}
return
}
// IsPlain indicates if this KDF actually does derivation (false) or not (true).
func (b *BcryptPbkdf) IsPlain() (plain bool) {
plain = false
return
}
// addSalt adds a salt as parsed from GetKdfFromBytes.
func (b *BcryptPbkdf) addSalt(salt []byte) (err error) {
if salt == nil || len(salt) == 0 {
err = ErrNoSalt
return
}
b.salt = salt
return
}
// addRounds adds the rounds as parsed from GetKdfFromBytes
func (b *BcryptPbkdf) addRounds(rounds uint32) (err error) {
if rounds == 0 {
err = ErrNoRounds
return
}
b.rounds = rounds
return
}

View File

@@ -1,137 +0,0 @@
package kdf
import (
"bytes"
"encoding/binary"
)
/*
Setup must be called before DeriveKey. It configures a Null.
Note that this doesn't actually do anything, it's here for interface compat.
It is recommended to use nil/zero values.
*/
func (n *Null) Setup(secret, salt []byte, rounds, keyLen uint32) (err error) {
_, _, _, _ = secret, salt, rounds, keyLen
return
}
/*
SetupAuto is used to provide out-of-band configuration if the KDF options were found via GetKdfFromBytes.
Note that this doesn't actually do anything, it's here for interface compat.
It is recommended to use nil/zero values.
*/
func (n *Null) SetupAuto(secret []byte, keyLen uint32) (err error) {
_, _ = secret, keyLen
return
}
/*
DeriveKey returns the derived key from a Null.
Note that this doesn't actually do anything, it's here for interface compat.
key will ALWAYS be a nil byte slice.
*/
func (n *Null) DeriveKey() (key []byte, err error) {
return
}
// Name returns NullName.
func (n *Null) Name() (name string) {
name = NullName
return
}
// NameBytes returns the byte form of Null.Name with leading bytecount allocator.
func (n *Null) NameBytes() (name []byte) {
var b []byte
var s = n.Name()
b = []byte(s)
name = make([]byte, 4)
binary.BigEndian.PutUint32(name, uint32(len(b)))
name = append(name, b...)
return
}
// PackedBytes returns 3.0 and recursed.
func (n *Null) PackedBytes() (buf *bytes.Reader, err error) {
// This is static.
buf = bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0})
return
}
/*
Rounds returns the number of rounds used in derivation.
Note that this will always return 0; it's here for interface compat.
*/
func (n *Null) Rounds() (rounds uint32) {
rounds = 0
return
}
/*
Salt returns the salt bytes.
Note that this will always return nil; it's here for interface compat.
*/
func (n *Null) Salt() (salt []byte) {
salt = nil
return
}
/*
AutoOK returns true if a GetKdfFromBytes call was able to fetch the KDF options successfully, in which case the caller may use KDF.SetupAuto.
If false, it will need to be manually configured via KDF.Setup.
Note that this won't actually do anything and ok will always return as true.
*/
func (n *Null) AutoOK() (ok bool) {
ok = true
return
}
// IsPlain indicates if this KDF actually does derivation (false) or not (true).
func (n *Null) IsPlain() (plain bool) {
plain = true
return
}
// addSalt is a no-op, just here for interface compat.
func (n *Null) addSalt(salt []byte) (err error) {
_ = salt
return
}
// addRounds is a no-op; just here for interface compat.
func (n *Null) addRounds(rounds uint32) (err error) {
_ = rounds
return
}

5
kdf/null/consts.go Normal file
View File

@@ -0,0 +1,5 @@
package null
const (
Name string = "none"
)

137
kdf/null/funcs.go Normal file
View File

@@ -0,0 +1,137 @@
package null
import (
"bytes"
"encoding/binary"
)
/*
Setup must be called before DeriveKey. It configures a null.KDF.
Note that this doesn't actually do anything, it's here for interface compat.
It is recommended to use nil/zero values.
*/
func (k *KDF) Setup(secret, salt []byte, rounds, keyLen uint32) (err error) {
_, _, _, _ = secret, salt, rounds, keyLen
return
}
/*
SetupAuto is used to provide out-of-band configuration if the null.KDF options were found via kdf.UnpackKDF.
Note that this doesn't actually do anything, it's here for interface compat.
It is recommended to use nil/zero values.
*/
func (k *KDF) SetupAuto(secret []byte, keyLen uint32) (err error) {
_, _ = secret, keyLen
return
}
/*
DeriveKey returns the derived key from a null.KDF.
Note that this doesn't actually do anything, it's here for interface compat.
key will ALWAYS be a nil byte slice.
*/
func (k *KDF) DeriveKey() (key []byte, err error) {
return
}
// Name returns null.Name.
func (k *KDF) Name() (name string) {
name = Name
return
}
// NameBytes returns the byte form of null.Name with leading bytecount allocator.
func (k *KDF) NameBytes() (name []byte) {
var b []byte
var s = k.Name()
b = []byte(s)
name = make([]byte, 4)
binary.BigEndian.PutUint32(name, uint32(len(b)))
name = append(name, b...)
return
}
// PackedBytes returns block 3.0 and recursed.
func (k *KDF) PackedBytes() (buf *bytes.Reader, err error) {
// This is static.
buf = bytes.NewReader([]byte{0x0, 0x0, 0x0, 0x0})
return
}
/*
Rounds returns the number of rounds used in derivation.
Note that this will always return 0; it's here for interface compat.
*/
func (k *KDF) Rounds() (rounds uint32) {
rounds = 0
return
}
/*
Salt returns the salt bytes.
Note that this will always return nil; it's here for interface compat.
*/
func (k *KDF) Salt() (salt []byte) {
salt = nil
return
}
/*
AutoOK returns true if a kdf.UnpackKDF call was able to fetch the null.KDF options successfully, in which case the caller may use null.KDF.SetupAuto.
If false, it will need to be manually configured via null.KDF.Setup.
Note that this won't actually do anything and ok will always return as true.
*/
func (k *KDF) AutoOK() (ok bool) {
ok = true
return
}
// IsPlain indicates if this kdf.KDF actually does derivation (false) or not (true).
func (k *KDF) IsPlain() (plain bool) {
plain = true
return
}
// AddSalt is a no-op, just here for interface compat.
func (k *KDF) AddSalt(salt []byte) (err error) {
_ = salt
return
}
// AddRounds is a no-op; just here for interface compat.
func (k *KDF) AddRounds(rounds uint32) (err error) {
_ = rounds
return
}

4
kdf/null/types.go Normal file
View File

@@ -0,0 +1,4 @@
package null
// KDF is a dummy kdf.KDF that is used for unencrypted/plain SSH private keys. It literally does nothing.
type KDF struct{}

View File

@@ -26,31 +26,8 @@ type KDF interface {
IsPlain() (plain bool)
// PackedBytes returns the bytes suitable for serializing into a key file.
PackedBytes() (buf *bytes.Reader, err error)
// addSalt adds the salt as parsed from the private key.
addSalt(salt []byte) (err error)
// addRounds adds the rounds as parsed from the private key.
addRounds(rounds uint32) (err error)
// AddSalt adds the salt as parsed from the private key.
AddSalt(salt []byte) (err error)
// AddRounds adds the rounds as parsed from the private key.
AddRounds(rounds uint32) (err error)
}
/*
BcryptPbkdf combines bcrypt hashing algorithm with PBKDF2 key derivation.
(bcrypt) https://www.usenix.org/legacy/events/usenix99/provos/provos_html/node1.html
(PBKDF2) https://datatracker.ietf.org/doc/html/rfc2898
http://www.tedunangst.com/flak/post/bcrypt-pbkdf
*/
type BcryptPbkdf struct {
// salt is used to salt the hash for each round in rounds.
salt []byte
// rounds controls how many iterations of salting/hashing is done.
rounds uint32
// keyLen is how long the derived key should be in bytes.
keyLen uint32
// secret is the "passphrase" used to seed the key creation.
secret []byte
// key is used to store the derived key.
key []byte
}
// Null is a dummy KDF that is used for unencrypted/plain SSH private keys. It literally does nothing.
type Null struct{}