Scott Baker | 8487c5d | 2019-10-18 12:49:46 -0700 | [diff] [blame] | 1 | package rfc8009 |
| 2 | |
| 3 | import ( |
| 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 | |
| 14 | const ( |
| 15 | s2kParamsZero = 32768 |
| 16 | ) |
| 17 | |
| 18 | // DeriveRandom for key derivation as defined in RFC 8009 |
| 19 | func 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) |
| 37 | func 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. |
| 71 | func 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. |
| 76 | func 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. |
| 85 | func 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 |
| 91 | func 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 |
| 100 | func 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 |
| 120 | func 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. |
| 128 | func 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 | } |