blob: 90ced3b58c9c202825200fcc7cf8239b23695d43 [file] [log] [blame]
Scott Baker2c1c4822019-10-16 11:02:41 -07001package rfc8009
2
3import (
4 "crypto/hmac"
5 "encoding/binary"
6 "encoding/hex"
7 "errors"
8
9 "golang.org/x/crypto/pbkdf2"
10 "gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
11 "gopkg.in/jcmturner/gokrb5.v7/iana/etypeID"
12)
13
14const (
15 s2kParamsZero = 32768
16)
17
18// DeriveRandom for key derivation as defined in RFC 8009
19func DeriveRandom(protocolKey, usage []byte, e etype.EType) ([]byte, error) {
20 h := e.GetHashFunc()()
21 return KDF_HMAC_SHA2(protocolKey, []byte("prf"), usage, h.Size(), e), nil
22}
23
24// DeriveKey derives a key from the protocol key based on the usage and the etype's specific methods.
25//
26// https://tools.ietf.org/html/rfc8009#section-5
27//
28// If the enctype is aes128-cts-hmac-sha256-128:
29// Kc = KDF-HMAC-SHA2(base-key, usage | 0x99, 128)
30// Ke = KDF-HMAC-SHA2(base-key, usage | 0xAA, 128)
31// Ki = KDF-HMAC-SHA2(base-key, usage | 0x55, 128)
32//
33// If the enctype is aes256-cts-hmac-sha384-192:
34// Kc = KDF-HMAC-SHA2(base-key, usage | 0x99, 192)
35// Ke = KDF-HMAC-SHA2(base-key, usage | 0xAA, 256)
36// Ki = KDF-HMAC-SHA2(base-key, usage | 0x55, 192)
37func DeriveKey(protocolKey, label []byte, e etype.EType) []byte {
38 var context []byte
39 var kl int
40 // Key length is longer for aes256-cts-hmac-sha384-192 is it is a Ke or from StringToKey (where label is "kerberos")
41 if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 {
42 switch label[len(label)-1] {
43 case 0x73:
44 // 0x73 is "s" so label could be kerberos meaning StringToKey so now check if the label is "kerberos"
45 kerblabel := []byte("kerberos")
46 if len(label) != len(kerblabel) {
47 break
48 }
49 for i, b := range label {
50 if b != kerblabel[i] {
51 kl = e.GetKeySeedBitLength()
52 break
53 }
54 }
55 if kl == 0 {
56 // This is StringToKey
57 kl = 256
58 }
59 case 0xAA:
60 // This is a Ke
61 kl = 256
62 }
63 }
64 if kl == 0 {
65 kl = e.GetKeySeedBitLength()
66 }
67 return e.RandomToKey(KDF_HMAC_SHA2(protocolKey, label, context, kl, e))
68}
69
70// RandomToKey returns a key from the bytes provided according to the definition in RFC 8009.
71func RandomToKey(b []byte) []byte {
72 return b
73}
74
75// StringToKey returns a key derived from the string provided according to the definition in RFC 8009.
76func StringToKey(secret, salt, s2kparams string, e etype.EType) ([]byte, error) {
77 i, err := S2KparamsToItertions(s2kparams)
78 if err != nil {
79 return nil, err
80 }
81 return StringToKeyIter(secret, salt, i, e)
82}
83
84// StringToKeyIter returns a key derived from the string provided according to the definition in RFC 8009.
85func StringToKeyIter(secret, salt string, iterations int, e etype.EType) ([]byte, error) {
86 tkey := e.RandomToKey(StringToPBKDF2(secret, salt, iterations, e))
87 return e.DeriveKey(tkey, []byte("kerberos"))
88}
89
90// StringToPBKDF2 generates an encryption key from a pass phrase and salt string using the PBKDF2 function from PKCS #5 v2.0
91func StringToPBKDF2(secret, salt string, iterations int, e etype.EType) []byte {
92 kl := e.GetKeyByteSize()
93 if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 {
94 kl = 32
95 }
96 return pbkdf2.Key([]byte(secret), []byte(salt), iterations, kl, e.GetHashFunc())
97}
98
99// KDF_HMAC_SHA2 key derivation: https://tools.ietf.org/html/rfc8009#section-3
100func KDF_HMAC_SHA2(protocolKey, label, context []byte, kl int, e etype.EType) []byte {
101 //k: Length in bits of the key to be outputted, expressed in big-endian binary representation in 4 bytes.
102 k := make([]byte, 4, 4)
103 binary.BigEndian.PutUint32(k, uint32(kl))
104
105 c := make([]byte, 4, 4)
106 binary.BigEndian.PutUint32(c, uint32(1))
107 c = append(c, label...)
108 c = append(c, byte(0))
109 if len(context) > 0 {
110 c = append(c, context...)
111 }
112 c = append(c, k...)
113
114 mac := hmac.New(e.GetHashFunc(), protocolKey)
115 mac.Write(c)
116 return mac.Sum(nil)[:(kl / 8)]
117}
118
119// GetSaltP returns the salt value based on the etype name: https://tools.ietf.org/html/rfc8009#section-4
120func GetSaltP(salt, ename string) string {
121 b := []byte(ename)
122 b = append(b, byte(0))
123 b = append(b, []byte(salt)...)
124 return string(b)
125}
126
127// S2KparamsToItertions converts the string representation of iterations to an integer for RFC 8009.
128func S2KparamsToItertions(s2kparams string) (int, error) {
129 var i uint32
130 if len(s2kparams) != 8 {
131 return s2kParamsZero, errors.New("Invalid s2kparams length")
132 }
133 b, err := hex.DecodeString(s2kparams)
134 if err != nil {
135 return s2kParamsZero, errors.New("Invalid s2kparams, cannot decode string to bytes")
136 }
137 i = binary.BigEndian.Uint32(b)
138 //buf := bytes.NewBuffer(b)
139 //err = binary.Read(buf, binary.BigEndian, &i)
140 if err != nil {
141 return s2kParamsZero, errors.New("Invalid s2kparams, cannot convert to big endian int32")
142 }
143 return int(i), nil
144}