checking in prelim stuff

This commit is contained in:
2022-03-05 19:22:40 -05:00
parent 5a9e16f5eb
commit c4783ed1e9
27 changed files with 2082 additions and 4 deletions

9
kdf/consts.go Normal file
View File

@@ -0,0 +1,9 @@
package kdf
var (
// kdfNames is a collection of valid KDF name strings.
kdfNames []string = []string{
"none",
"bcrypt",
}
)

View File

@@ -0,0 +1,11 @@
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
)

5
kdf/consts_null.go Normal file
View File

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

14
kdf/errs.go Normal file
View File

@@ -0,0 +1,14 @@
package kdf
import (
"errors"
)
var (
ErrBadData error = errors.New("unable to cast data into buffer for KDF")
ErrNoKeyLen error = errors.New("no key length configured or bad key length for KDF")
ErrNoRounds error = errors.New("no rounds number configured or bad number of rounds for KDF")
ErrNoSalt error = errors.New("no salt configured or bad salt value for KDF")
ErrNoSecret error = errors.New("no secret configured or bad secret value for KDF")
ErrUnknownKdf error = errors.New("unable to determine matching KDF")
)

42
kdf/funcs.go Normal file
View File

@@ -0,0 +1,42 @@
package kdf
import (
"strings"
)
/*
GetKDF returns a KDF from a name of the function.
KDF.Setup must be called on the resulting KDF.
*/
func GetKDF(name string) (k KDF, err error) {
switch strings.ToLower(name) {
case BcryptPbkdfName:
k = &BcryptPbkdf{}
case NullName, "":
k = &Null{}
default:
err = ErrUnknownKdf
return
}
return
}
/*
UnpackKDF returns a KDF from raw data as packed in a private key's bytes.
KDF.Setup must be called on the resulting 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))
- a *bytes.Buffer, which supports the same as above
*/
func UnpackKDF(data interface{}) (k KDF, err error) {
return
}

226
kdf/funcs_bcrypt_pbkdf.go Normal file
View File

@@ -0,0 +1,226 @@
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 string = 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 []byte = make([]byte, 4)
var packer *bytes.Reader
var w *bytes.Buffer = 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
}

137
kdf/funcs_null.go Normal file
View File

@@ -0,0 +1,137 @@
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 string = 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
}

56
kdf/types.go Normal file
View File

@@ -0,0 +1,56 @@
package kdf
import (
"bytes"
)
// KDF is a type of KDF (Key Derivation Function).
type KDF interface {
// Name returns the string form of the KDF name.
Name() (name string)
// NameBytes returns the Name result but in bytes with a leading uint32 bytecount packed in.
NameBytes() (name []byte)
// Rounds returns the number of rounds used in derivation.
Rounds() (rounds uint32)
// Salt returns the salt bytes.
Salt() (salt []byte)
// Setup initializes the KDF with the given derivation secret (password) and KDF options.
Setup(secret, salt []byte, rounds, keyLen uint32) (err error)
// DeriveKey derives the key. Setup (or SetupAuto) must have been run first.
DeriveKey() (key []byte, err error)
// SetupAuto configures a partially reconstructed KDF options that were parsed from GetKdfFromBytes (if KDF.AutoOK returns true).
SetupAuto(secret []byte, keyLen uint32) (err error)
// AutoOK returns true if all components were able to be parsed from GetKdfFromBytes.
AutoOK() (ok bool)
// IsPlain returns true if this is a "null" kdf; i.e. no derivation is actually performed.
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)
}
/*
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 that 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{}