keytab.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. package keytab
  2. //Ref: https://web.mit.edu/kerberos/krb5-devel/doc/formats/keytab_file_format.html
  3. import (
  4. "bytes"
  5. "encoding/binary"
  6. "errors"
  7. "io/ioutil"
  8. "time"
  9. "unsafe"
  10. "github.com/jcmturner/gokrb5/types"
  11. )
  12. type Keytab struct {
  13. Version uint16
  14. Entries []KeytabEntry
  15. }
  16. type KeytabEntry struct {
  17. Principal Principal
  18. Timestamp time.Time
  19. KVNO8 uint8
  20. Key types.EncryptionKey
  21. KVNO uint32
  22. }
  23. type Principal struct {
  24. NumComponents int16
  25. Realm string
  26. Components []string
  27. NameType int32
  28. }
  29. //Create new, empty Keytab type
  30. func NewKeytab() Keytab {
  31. var e []KeytabEntry
  32. return Keytab{
  33. Version: 0,
  34. Entries: e,
  35. }
  36. }
  37. // Get the key material from the keytab for the newest entry with the required kvno, etype and matching principal
  38. /*func (kt *Keytab) GetKey(username, realm string, kvno, etype int) ([]byte, error) {
  39. var key []byte
  40. var t time.Time
  41. for _, k := range kt.Entries {
  42. if k.Principal.Realm == realm && int(k.Key.KeyType) == etype && (int(k.KVNO) == kvno || kvno == 0) && k.Timestamp.After(t) {
  43. for _, n := range k.Principal.Components {
  44. if n == username {
  45. key = k.Key.KeyValue
  46. }
  47. }
  48. }
  49. }
  50. if len(key) < 1 {
  51. return key, errors.New("Matching key not found in keytab")
  52. }
  53. return key, nil
  54. }*/
  55. // Get the EncryptionKey from the keytab for the newest entry with the required kvno, etype and matching principal
  56. func (kt *Keytab) GetEncryptionKey(username, realm string, kvno, etype int) (types.EncryptionKey, error) {
  57. var key types.EncryptionKey
  58. var t time.Time
  59. for _, k := range kt.Entries {
  60. if k.Principal.Realm == realm && int(k.Key.KeyType) == etype && (int(k.KVNO) == kvno || kvno == 0) && k.Timestamp.After(t) {
  61. for _, n := range k.Principal.Components {
  62. if n == username {
  63. key = k.Key
  64. }
  65. }
  66. }
  67. }
  68. if len(key.KeyValue) < 1 {
  69. return key, errors.New("Matching key not found in keytab")
  70. }
  71. return key, nil
  72. }
  73. func newKeytabEntry() KeytabEntry {
  74. var b []byte
  75. return KeytabEntry{
  76. Principal: newPrincipal(),
  77. Timestamp: time.Time{},
  78. KVNO8: 0,
  79. Key: types.EncryptionKey{
  80. KeyType: 0,
  81. KeyValue: b,
  82. },
  83. KVNO: 0,
  84. }
  85. }
  86. func newPrincipal() Principal {
  87. var c []string
  88. return Principal{
  89. NumComponents: 0,
  90. Realm: "",
  91. Components: c,
  92. NameType: 0,
  93. }
  94. }
  95. //Load a keytab file into Keytab type
  96. func Load(ktPath string) (kt Keytab, err error) {
  97. k, err := ioutil.ReadFile(ktPath)
  98. if err != nil {
  99. return
  100. }
  101. return Parse(k)
  102. }
  103. //Parse byte slice of keytab data into Keytab type
  104. func Parse(b []byte) (kt Keytab, err error) {
  105. //The first byte of the file always has the value 5
  106. if int8(b[0]) != 5 {
  107. err = errors.New("Invalid keytab data. First byte does not equal 5")
  108. return
  109. }
  110. //Get keytab version
  111. //The second byte contains the version number (1 or 2)
  112. kt.Version = uint16(b[1])
  113. if kt.Version != 1 && kt.Version != 2 {
  114. err = errors.New("Invalid keytab data. Keytab version is neither 1 nor 2")
  115. return
  116. }
  117. //Version 1 of the file format uses native byte order for integer representations. Version 2 always uses big-endian byte order
  118. var endian binary.ByteOrder
  119. endian = binary.BigEndian
  120. if kt.Version == 1 && isNativeEndianLittle() {
  121. endian = binary.LittleEndian
  122. }
  123. /*
  124. After the two-byte version indicator, the file contains a sequence of signed 32-bit record lengths followed by key records or holes.
  125. A positive record length indicates a valid key entry whose size is equal to or less than the record length.
  126. A negative length indicates a zero-filled hole whose size is the inverse of the length.
  127. A length of 0 indicates the end of the file.
  128. */
  129. // n tracks position in the byte array
  130. n := 2
  131. l := read_int32(b, &n, &endian)
  132. for l != 0 {
  133. if l < 0 {
  134. //Zero padded so skip over
  135. l = l * -1
  136. n = n + int(l)
  137. }
  138. if l > 0 {
  139. //fmt.Printf("Bytes for entry: %v\n", b[p:p+int(l)])
  140. eb := b[n : n+int(l)]
  141. n = n + int(l)
  142. ke := newKeytabEntry()
  143. // p keeps track as to where we are in the byte stream
  144. var p int
  145. parse_principal(eb, &p, &kt, &ke, &endian)
  146. ke.Timestamp = read_timestamp(eb, &p, &endian)
  147. ke.KVNO8 = uint8(read_int8(eb, &p, &endian))
  148. ke.Key.KeyType = int(read_int16(eb, &p, &endian))
  149. key_len := int(read_int16(eb, &p, &endian))
  150. ke.Key.KeyValue = read_Bytes(eb, &p, key_len, &endian)
  151. //The 32-bit key version overrides the 8-bit key version.
  152. // To determine if it is present, the implementation must check that at least 4 bytes remain in the record after the other fields are read,
  153. // and that the value of the 32-bit integer contained in those bytes is non-zero.
  154. if len(eb)-p >= 4 {
  155. // The 32-bit key may be present
  156. ke.KVNO = uint32(read_int32(eb, &p, &endian))
  157. }
  158. if ke.KVNO == 0 {
  159. // Handles if the value from the last 4 bytes was zero and also if there are not the 4 bytes present. Makes sense to put the same value here as KVNO8
  160. ke.KVNO = uint32(ke.KVNO8)
  161. }
  162. // Add the entry to the keytab
  163. kt.Entries = append(kt.Entries, ke)
  164. }
  165. // Check if there are still 4 bytes left to read
  166. if len(b[n:]) < 4 {
  167. break
  168. }
  169. // Read the size of the next entry
  170. l = read_int32(b, &n, &endian)
  171. }
  172. return
  173. }
  174. func parse_principal(b []byte, p *int, kt *Keytab, ke *KeytabEntry, e *binary.ByteOrder) (err error) {
  175. ke.Principal.NumComponents = read_int16(b, p, e)
  176. if kt.Version == 1 {
  177. //In version 1 the number of components includes the realm. Minus 1 to make consistent with version 2
  178. ke.Principal.NumComponents -= 1
  179. }
  180. len_realm := read_int16(b, p, e)
  181. ke.Principal.Realm = string(read_Bytes(b, p, int(len_realm), e))
  182. for i := 0; i < int(ke.Principal.NumComponents); i++ {
  183. l := read_int16(b, p, e)
  184. ke.Principal.Components = append(ke.Principal.Components, string(read_Bytes(b, p, int(l), e)))
  185. }
  186. if kt.Version != 1 {
  187. //Name Type is omitted in version 1
  188. ke.Principal.NameType = read_int32(b, p, e)
  189. }
  190. return
  191. }
  192. func read_timestamp(b []byte, p *int, e *binary.ByteOrder) time.Time {
  193. return time.Unix(int64(read_int32(b, p, e)), 0)
  194. }
  195. func read_int8(b []byte, p *int, e *binary.ByteOrder) (i int8) {
  196. buf := bytes.NewBuffer(b[*p : *p+1])
  197. binary.Read(buf, *e, &i)
  198. *p += 1
  199. return
  200. }
  201. func read_int16(b []byte, p *int, e *binary.ByteOrder) (i int16) {
  202. buf := bytes.NewBuffer(b[*p : *p+2])
  203. binary.Read(buf, *e, &i)
  204. *p += 2
  205. return
  206. }
  207. func read_int32(b []byte, p *int, e *binary.ByteOrder) (i int32) {
  208. buf := bytes.NewBuffer(b[*p : *p+4])
  209. binary.Read(buf, *e, &i)
  210. *p += 4
  211. return
  212. }
  213. func read_Bytes(b []byte, p *int, s int, e *binary.ByteOrder) []byte {
  214. buf := bytes.NewBuffer(b[*p : *p+s])
  215. r := make([]byte, s)
  216. binary.Read(buf, *e, &r)
  217. *p += s
  218. return r
  219. }
  220. func isNativeEndianLittle() bool {
  221. var x int = 0x012345678
  222. var p unsafe.Pointer = unsafe.Pointer(&x)
  223. var bp *[4]byte = (*[4]byte)(p)
  224. var endian bool
  225. if 0x01 == bp[0] {
  226. endian = false
  227. } else if (0x78 & 0xff) == (bp[0] & 0xff) {
  228. endian = true
  229. } else {
  230. // Default to big endian
  231. endian = false
  232. }
  233. return endian
  234. }