145 lines
4.5 KiB
Go
145 lines
4.5 KiB
Go
|
package rfc8009
|
||
|
|
||
|
import (
|
||
|
"crypto/hmac"
|
||
|
"encoding/binary"
|
||
|
"encoding/hex"
|
||
|
"errors"
|
||
|
|
||
|
"golang.org/x/crypto/pbkdf2"
|
||
|
"gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
|
||
|
"gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
s2kParamsZero = 32768
|
||
|
)
|
||
|
|
||
|
// DeriveRandom for key derivation as defined in RFC 8009
|
||
|
func DeriveRandom(protocolKey, usage []byte, e etype.EType) ([]byte, error) {
|
||
|
h := e.GetHashFunc()()
|
||
|
return KDF_HMAC_SHA2(protocolKey, []byte("prf"), usage, h.Size(), e), nil
|
||
|
}
|
||
|
|
||
|
// DeriveKey derives a key from the protocol key based on the usage and the etype's specific methods.
|
||
|
//
|
||
|
// https://tools.ietf.org/html/rfc8009#section-5
|
||
|
//
|
||
|
// If the enctype is aes128-cts-hmac-sha256-128:
|
||
|
// Kc = KDF-HMAC-SHA2(base-key, usage | 0x99, 128)
|
||
|
// Ke = KDF-HMAC-SHA2(base-key, usage | 0xAA, 128)
|
||
|
// Ki = KDF-HMAC-SHA2(base-key, usage | 0x55, 128)
|
||
|
//
|
||
|
// If the enctype is aes256-cts-hmac-sha384-192:
|
||
|
// Kc = KDF-HMAC-SHA2(base-key, usage | 0x99, 192)
|
||
|
// Ke = KDF-HMAC-SHA2(base-key, usage | 0xAA, 256)
|
||
|
// Ki = KDF-HMAC-SHA2(base-key, usage | 0x55, 192)
|
||
|
func DeriveKey(protocolKey, label []byte, e etype.EType) []byte {
|
||
|
var context []byte
|
||
|
var kl int
|
||
|
// Key length is longer for aes256-cts-hmac-sha384-192 is it is a Ke or from StringToKey (where label is "kerberos")
|
||
|
if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 {
|
||
|
switch label[len(label)-1] {
|
||
|
case 0x73:
|
||
|
// 0x73 is "s" so label could be kerberos meaning StringToKey so now check if the label is "kerberos"
|
||
|
kerblabel := []byte("kerberos")
|
||
|
if len(label) != len(kerblabel) {
|
||
|
break
|
||
|
}
|
||
|
for i, b := range label {
|
||
|
if b != kerblabel[i] {
|
||
|
kl = e.GetKeySeedBitLength()
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
if kl == 0 {
|
||
|
// This is StringToKey
|
||
|
kl = 256
|
||
|
}
|
||
|
case 0xAA:
|
||
|
// This is a Ke
|
||
|
kl = 256
|
||
|
}
|
||
|
}
|
||
|
if kl == 0 {
|
||
|
kl = e.GetKeySeedBitLength()
|
||
|
}
|
||
|
return e.RandomToKey(KDF_HMAC_SHA2(protocolKey, label, context, kl, e))
|
||
|
}
|
||
|
|
||
|
// RandomToKey returns a key from the bytes provided according to the definition in RFC 8009.
|
||
|
func RandomToKey(b []byte) []byte {
|
||
|
return b
|
||
|
}
|
||
|
|
||
|
// StringToKey returns a key derived from the string provided according to the definition in RFC 8009.
|
||
|
func StringToKey(secret, salt, s2kparams string, e etype.EType) ([]byte, error) {
|
||
|
i, err := S2KparamsToItertions(s2kparams)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return StringToKeyIter(secret, salt, i, e)
|
||
|
}
|
||
|
|
||
|
// StringToKeyIter returns a key derived from the string provided according to the definition in RFC 8009.
|
||
|
func StringToKeyIter(secret, salt string, iterations int, e etype.EType) ([]byte, error) {
|
||
|
tkey := e.RandomToKey(StringToPBKDF2(secret, salt, iterations, e))
|
||
|
return e.DeriveKey(tkey, []byte("kerberos"))
|
||
|
}
|
||
|
|
||
|
// StringToPBKDF2 generates an encryption key from a pass phrase and salt string using the PBKDF2 function from PKCS #5 v2.0
|
||
|
func StringToPBKDF2(secret, salt string, iterations int, e etype.EType) []byte {
|
||
|
kl := e.GetKeyByteSize()
|
||
|
if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 {
|
||
|
kl = 32
|
||
|
}
|
||
|
return pbkdf2.Key([]byte(secret), []byte(salt), iterations, kl, e.GetHashFunc())
|
||
|
}
|
||
|
|
||
|
// KDF_HMAC_SHA2 key derivation: https://tools.ietf.org/html/rfc8009#section-3
|
||
|
func KDF_HMAC_SHA2(protocolKey, label, context []byte, kl int, e etype.EType) []byte {
|
||
|
//k: Length in bits of the key to be outputted, expressed in big-endian binary representation in 4 bytes.
|
||
|
k := make([]byte, 4, 4)
|
||
|
binary.BigEndian.PutUint32(k, uint32(kl))
|
||
|
|
||
|
c := make([]byte, 4, 4)
|
||
|
binary.BigEndian.PutUint32(c, uint32(1))
|
||
|
c = append(c, label...)
|
||
|
c = append(c, byte(0))
|
||
|
if len(context) > 0 {
|
||
|
c = append(c, context...)
|
||
|
}
|
||
|
c = append(c, k...)
|
||
|
|
||
|
mac := hmac.New(e.GetHashFunc(), protocolKey)
|
||
|
mac.Write(c)
|
||
|
return mac.Sum(nil)[:(kl / 8)]
|
||
|
}
|
||
|
|
||
|
// GetSaltP returns the salt value based on the etype name: https://tools.ietf.org/html/rfc8009#section-4
|
||
|
func GetSaltP(salt, ename string) string {
|
||
|
b := []byte(ename)
|
||
|
b = append(b, byte(0))
|
||
|
b = append(b, []byte(salt)...)
|
||
|
return string(b)
|
||
|
}
|
||
|
|
||
|
// S2KparamsToItertions converts the string representation of iterations to an integer for RFC 8009.
|
||
|
func S2KparamsToItertions(s2kparams string) (int, error) {
|
||
|
var i uint32
|
||
|
if len(s2kparams) != 8 {
|
||
|
return s2kParamsZero, errors.New("Invalid s2kparams length")
|
||
|
}
|
||
|
b, err := hex.DecodeString(s2kparams)
|
||
|
if err != nil {
|
||
|
return s2kParamsZero, errors.New("Invalid s2kparams, cannot decode string to bytes")
|
||
|
}
|
||
|
i = binary.BigEndian.Uint32(b)
|
||
|
//buf := bytes.NewBuffer(b)
|
||
|
//err = binary.Read(buf, binary.BigEndian, &i)
|
||
|
if err != nil {
|
||
|
return s2kParamsZero, errors.New("Invalid s2kparams, cannot convert to big endian int32")
|
||
|
}
|
||
|
return int(i), nil
|
||
|
}
|