Browse Source

initial commit

Jonathan Turner 9 years ago
parent
commit
95ab435b4c

+ 4 - 0
.gitignore

@@ -1,3 +1,7 @@
+*.keytab
+*.cap
+*.raw
+
 # Compiled Object files, Static and Dynamic libs (Shared Objects)
 *.o
 *.a

+ 6 - 0
README.md

@@ -0,0 +1,6 @@
+# gokrb5
+
+This is work in progress and does not yet work...
+
+# References
+* https://www.ietf.org/rfc/rfc4120.txt

+ 218 - 0
keytab/keytab.go

@@ -0,0 +1,218 @@
+package keytab
+
+//Ref: https://web.mit.edu/kerberos/krb5-devel/doc/formats/keytab_file_format.html
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"io/ioutil"
+	"time"
+	"unsafe"
+)
+
+type Keytab struct {
+	Version uint16
+	Entries []KeytabEntry
+}
+
+type KeytabEntry struct {
+	Principal Principal
+	Timestamp time.Time
+	KVNO8     uint8
+	Key       KeyBlock
+	KVNO      uint32
+}
+
+type Principal struct {
+	NumComponents int16
+	Realm         string
+	Components    []string
+	NameType      int32
+}
+
+type KeyBlock struct {
+	EType       uint16
+	KeyMaterial []byte
+}
+
+func NewKeytab() Keytab {
+	var e []KeytabEntry
+	return Keytab{
+		Version: 0,
+		Entries: e,
+	}
+}
+
+func newKeytabEntry() KeytabEntry {
+	return KeytabEntry{
+		Principal: newPrincipal(),
+		Timestamp: time.Time{},
+		KVNO8:     0,
+		Key:       newKeyBlock(),
+		KVNO:      0,
+	}
+}
+
+func newPrincipal() Principal {
+	var c []string
+	return Principal{
+		NumComponents: 0,
+		Realm:         "",
+		Components:    c,
+		NameType:      0,
+	}
+}
+
+func newKeyBlock() KeyBlock {
+	var b []byte
+	return KeyBlock{
+		EType:       0,
+		KeyMaterial: b,
+	}
+}
+
+func Load(ktPath string) (kt Keytab, err error) {
+	k, err := ioutil.ReadFile(ktPath)
+	if err != nil {
+		return
+	}
+	return Parse(k)
+}
+
+func Parse(b []byte) (kt Keytab, err error) {
+	//The first byte of the file always has the value 5
+	if int8(b[0]) != 5 {
+		err = errors.New("Invalid keytab data. First byte does not equal 5")
+		return
+	}
+	//Get keytab version
+	//The second byte contains the version number (1 or 2)
+	kt.Version = uint16(b[1])
+	if kt.Version != 1 && kt.Version != 2 {
+		err = errors.New("Invalid keytab data. Keytab version is neither 1 nor 2")
+		return
+	}
+	//Version 1 of the file format uses native byte order for integer representations. Version 2 always uses big-endian byte order
+	var endian binary.ByteOrder
+	endian = binary.BigEndian
+	if kt.Version == 1 && IsNativeEndianLittle() {
+		endian = binary.LittleEndian
+	}
+	/*
+		After the two-byte version indicator, the file contains a sequence of signed 32-bit record lengths followed by key records or holes.
+		A positive record length indicates a valid key entry whose size is equal to or less than the record length.
+		A negative length indicates a zero-filled hole whose size is the inverse of the length.
+		A length of 0 indicates the end of the file.
+	*/
+	// n tracks position in the byte array
+	n := 2
+	//Version 2 so Big Endian
+	l := read_int32(b, &n, &endian)
+	for l != 0 {
+		if l < 0 {
+			//Zero padded so skip over
+			l = l * -1
+			n = n + int(l)
+		}
+		if l > 0 {
+			//fmt.Printf("Bytes for entry: %v\n", b[p:p+int(l)])
+			eb := b[n : n+int(l)]
+			n = n + int(l)
+			ke := newKeytabEntry()
+			// p keeps track as to where we are in the byte stream
+			var p int
+			parse_principal(eb, &p, &kt, &ke, &endian)
+			ke.Timestamp = read_timestamp(eb, &p, &endian)
+			ke.KVNO8 = uint8(read_int8(eb, &p, &endian))
+			ke.Key.EType = uint16(read_int16(eb, &p, &endian))
+			key_len := int(read_int16(eb, &p, &endian))
+			ke.Key.KeyMaterial = read_Bytes(eb, &p, key_len, &endian)
+			//The 32-bit key version overrides the 8-bit key version.
+			// 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,
+			// and that the value of the 32-bit integer contained in those bytes is non-zero.
+			if len(eb)-p >= 4 {
+				// The 32-bit key may be present
+				ke.KVNO = uint32(read_int32(eb, &p, &endian))
+			}
+			if ke.KVNO == 0 {
+				// 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
+				ke.KVNO = uint32(ke.KVNO8)
+			}
+			// Add the entry to the keytab
+			kt.Entries = append(kt.Entries, ke)
+		}
+		// Read the size of the next entry
+		l = read_int32(b, &n, &endian)
+	}
+	return
+}
+
+func parse_principal(b []byte, p *int, kt *Keytab, ke *KeytabEntry, e *binary.ByteOrder) (err error) {
+	ke.Principal.NumComponents = read_int16(b, p, e)
+	if kt.Version == 1 {
+		//In version 1 the number of components includes the realm. Minus 1 to make consistent with version 2
+		ke.Principal.NumComponents -= 1
+	}
+	len_realm := read_int16(b, p, e)
+	ke.Principal.Realm = string(read_Bytes(b, p, int(len_realm), e))
+	for i := 0; i < int(ke.Principal.NumComponents); i++ {
+		l := read_int16(b, p, e)
+		ke.Principal.Components = append(ke.Principal.Components, string(read_Bytes(b, p, int(l), e)))
+	}
+	if kt.Version != 1 {
+		//Name Type is omitted in version 1
+		ke.Principal.NameType = read_int32(b, p, e)
+	}
+	return
+}
+
+func read_timestamp(b []byte, p *int, e *binary.ByteOrder) time.Time {
+	return time.Unix(int64(read_int32(b, p, e)), 0)
+}
+
+func read_int8(b []byte, p *int, e *binary.ByteOrder) (i int8) {
+	buf := bytes.NewBuffer(b[*p : *p+1])
+	binary.Read(buf, *e, &i)
+	*p += 1
+	return
+}
+
+func read_int16(b []byte, p *int, e *binary.ByteOrder) (i int16) {
+	buf := bytes.NewBuffer(b[*p : *p+2])
+	binary.Read(buf, *e, &i)
+	*p += 2
+	return
+}
+
+func read_int32(b []byte, p *int, e *binary.ByteOrder) (i int32) {
+	buf := bytes.NewBuffer(b[*p : *p+4])
+	binary.Read(buf, *e, &i)
+	*p += 4
+	return
+}
+
+func read_Bytes(b []byte, p *int, s int, e *binary.ByteOrder) []byte {
+	buf := bytes.NewBuffer(b[*p : *p+s])
+	r := make([]byte, s)
+	binary.Read(buf, *e, &r)
+	*p += s
+	return r
+}
+
+func IsNativeEndianLittle() bool {
+	var x int = 0x012345678
+	var p unsafe.Pointer = unsafe.Pointer(&x)
+	var bp *[4]byte = (*[4]byte)(p)
+
+	var endian bool
+	if 0x01 == bp[0] {
+		endian = false
+	} else if (0x78 & 0xff) == (bp[0] & 0xff) {
+		endian = true
+	} else {
+		// Default to big endian
+		endian = false
+	}
+	return endian
+}

BIN
keytab/test.keytabtest


+ 24 - 0
krb5crypto/EncryptionEngine.go

@@ -0,0 +1,24 @@
+package krb5crypto
+
+type encryptFunc func([]byte, []byte) []byte
+
+func deriveRandom(key, usage []byte, n, k int, encrypt encryptFunc) {
+	nFoldUsage := Nfold(usage, n)
+	out := make([]byte, k / 8)
+
+	fillBytes := encrypt(nFoldUsage, key)
+	p := 0
+	for i := 0; i < len(out); i++ {
+		if p < len(fillBytes) {
+			out[i] = fillBytes[p]
+			p += 1
+		} else {
+			fillBytes = encrypt(nFoldUsage, key)
+			p = 0
+			out[i] = fillBytes[p]
+			p += 1
+		}
+	}
+	return out
+}
+

+ 33 - 0
krb5crypto/aes256-cts-hmac-sha1-96.go

@@ -0,0 +1,33 @@
+package krb5crypto
+
+import (
+	"errors"
+)
+
+const (
+
+)
+
+func StringToKey(secret string, salt string, s2kparams []byte) (protocolKey []byte) {
+	return
+}
+
+func RandomToKey(b []byte) (protocolKey []byte) {
+	return
+}
+
+func DeriveKey(protocolKey []byte, usage int) (specificKey []byte) {
+	return
+}
+
+func Encrypt(specificKey []byte, ivec []byte, plaintext []byte) (new_ivec []byte, cyphertext []byte, err error) {
+	if len(plaintext) < 1 {
+		err = errors.New("Plain text is empty")
+		return
+	}
+	return
+}
+
+func Decrypt(specificKey []byte, ivec []byte, cyphertext []byte) (new_ivec []byte, plaintext []byte, err error) {
+ return
+}

+ 125 - 0
krb5crypto/nfold.go

@@ -0,0 +1,125 @@
+package krb5crypto
+
+/*
+Implementation of the n-fold algorithm as defined in RFC 3961.
+
+n-fold is an algorithm that takes m input bits and "stretches" them
+to form n output bits with equal contribution from each input bit to
+the output, as described in [Blumenthal96]:
+
+We first define a primitive called n-folding, which takes a
+variable-length input block and produces a fixed-length output
+sequence.  The intent is to give each input bit approximately
+equal weight in determining the value of each output bit.  Note
+that whenever we need to treat a string of octets as a number, the
+assumed representation is Big-Endian -- Most Significant Byte
+first.
+
+To n-fold a number X, replicate the input value to a length that
+is the least common multiple of n and the length of X.  Before
+each repetition, the input is rotated to the right by 13 bit
+positions.  The successive n-bit chunks are added together using
+1's-complement addition (that is, with end-around carry) to yield
+a n-bit result....
+*/
+
+/* Credits
+This golang implementation of nfold used the following project for help with implementation detail.
+Although their source is in java it was helpful as a reference implementation of the RFC.
+You can find the source code of their open source project along with license information below.
+We acknowledge and are grateful to these developers for their contributions to open source
+
+Project: Apache Directory (http://http://directory.apache.org/)
+https://svn.apache.org/repos/asf/directory/apacheds/tags/1.5.1/kerberos-shared/src/main/java/org/apache/directory/server/kerberos/shared/crypto/encryption/NFold.java
+License: http://www.apache.org/licenses/LICENSE-2.0
+*/
+
+// n is in bits not bytes
+func Nfold(in []byte, n int) []byte {
+	k := len(in) * 8
+
+	//Get the lowest common multiple of the two bit sizes
+	lcm := lcm(n, k)
+	relicate := lcm / k
+	var sumBytes []byte
+
+	for i := 0; i < relicate; i++ {
+		rotation := 13 * i
+		sumBytes = append(sumBytes, rotateRight(in, rotation)...)
+	}
+
+	nfold := make([]byte, n/8)
+	sum := make([]byte, n/8)
+	for i := 0; i < lcm/n; i++ {
+		for j := 0; j < n/8; j++ {
+			sum[j] = sumBytes[j+(i*len(sum))]
+		}
+		nfold = onesComplementAddition(nfold, sum)
+	}
+	return nfold
+}
+
+func onesComplementAddition(n1, n2 []byte) []byte {
+	numBits := len(n1) * 8
+	out := make([]byte, numBits / 8)
+	carry := 0
+	for i := numBits - 1; i > -1; i-- {
+		n1b := getBit(&n1, i)
+		n2b := getBit(&n2, i)
+		s := n1b + n2b + carry
+
+		if s == 0 || s == 1 {
+			setBit(&out, i, s)
+			carry = 0
+		} else if s == 2 {
+			carry = 1
+		} else if s == 3 {
+			setBit(&out, i, 1)
+			carry = 1
+		}
+	}
+	if carry == 1 {
+		carryArray := make([]byte, len(n1))
+		carryArray[len(carryArray)-1] = 1
+		out = onesComplementAddition(out, carryArray)
+	}
+	return out
+}
+
+func rotateRight(b []byte, step int) []byte {
+	out := make([]byte, len(b))
+	bitLen := len(b) * 8
+	for i := 0; i < bitLen; i++ {
+		v := getBit(&b, i)
+		setBit(&out, (i + step) % bitLen, v)
+	}
+	return out
+}
+
+func lcm(x, y int) int {
+	return (x * y) / gcd(x, y)
+}
+
+func gcd(x, y int) int {
+	for y != 0 {
+		x, y = y, x%y
+	}
+	return x
+}
+
+func getBit(b *[]byte, p int) int {
+	pByte := p / 8
+	pBit := uint(p % 8)
+	vByte := (*b)[pByte]
+	vInt := int(vByte >> (8 - (pBit + 1)) & 0x0001)
+	return vInt
+}
+
+func setBit(b *[]byte, p, v int) {
+	pByte := p / 8
+	pBit := uint(p % 8)
+	oldByte := (*b)[pByte]
+	var newByte byte
+	newByte = (byte((v << (8 - (pBit + 1)))) | oldByte)
+	(*b)[pByte] = newByte
+}

+ 26 - 0
krb5crypto/nfold_test.go

@@ -0,0 +1,26 @@
+package krb5crypto
+
+import (
+	"testing"
+	"github.com/stretchr/testify/assert"
+	"encoding/hex"
+)
+
+func Test_nfold(t *testing.T) {
+	var tests = []struct {
+		n int
+		b []byte
+		folded string
+	}{
+		{64, []byte("012345"), "be072631276b1955"},
+		{56, []byte("password"), "78a07b6caf85fa"},
+		{64, []byte("Rough Consensus, and Running Code"), "bb6ed30870b7f0e0"},
+		{168, []byte("password"), "59e4a8ca7c0385c3c37b3f6d2000247cb6e6bd5b3e"},
+		{192, []byte("MASSACHVSETTS INSTITVTE OF TECHNOLOGY"), "db3b0d8f0b061e603282b308a50841229ad798fab9540c1b"},
+		{168, []byte("Q"), "518a54a215a8452a518a54a215a8452a518a54a215"},
+		{168, []byte("ba"), "fb25d531ae8974499f52fd92ea9857c4ba24cf297e"},
+	}
+	for _, test := range tests {
+		assert.Equal(t, test.folded, hex.EncodeToString(Nfold(test.b,test.n)), "Folded not as expected")
+	}
+}

+ 108 - 0
krb5types/AuthorizationData.go

@@ -0,0 +1,108 @@
+package krb5types
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.2.6
+
+import "encoding/asn1"
+
+/*
+AuthorizationData
+
+-- NOTE: AuthorizationData is always used as an OPTIONAL field and
+-- should not be empty.
+AuthorizationData       ::= SEQUENCE OF SEQUENCE {
+ad-type         [0] Int32,
+ad-data         [1] OCTET STRING
+}
+
+ad-data
+This field contains authorization data to be interpreted according
+to the value of the corresponding ad-type field.
+
+ad-type
+	This field specifies the format for the ad-data subfield.  All
+negative values are reserved for local use.  Non-negative values
+are reserved for registered use.
+
+Each sequence of type and data is referred to as an authorization
+element.  Elements MAY be application specific; however, there is a
+common set of recursive elements that should be understood by all
+implementations.  These elements contain other elements embedded
+within them, and the interpretation of the encapsulating element
+determines which of the embedded elements must be interpreted, and
+which may be ignored.
+
+These common authorization data elements are recursively defined,
+meaning that the ad-data for these types will itself contain a
+sequence of authorization data whose interpretation is affected by
+the encapsulating element.  Depending on the meaning of the
+encapsulating element, the encapsulated elements may be ignored,
+might be interpreted as issued directly by the KDC, or might be
+stored in a separate plaintext part of the ticket.  The types of the
+encapsulating elements are specified as part of the Kerberos
+specification because the behavior based on these values should be
+understood across implementations, whereas other elements need only
+be understood by the applications that they affect.
+
+Authorization data elements are considered critical if present in a
+ticket or authenticator.  If an unknown authorization data element
+type is received by a server either in an AP-REQ or in a ticket
+contained in an AP-REQ, then, unless it is encapsulated in a known
+authorization data element amending the criticality of the elements
+it contains, authentication MUST fail.  Authorization data is
+intended to restrict the use of a ticket.  If the service cannot
+determine whether the restriction applies to that service, then a
+security weakness may result if the ticket can be used for that
+service.  Authorization elements that are optional can be enclosed in
+an AD-IF-RELEVANT element.
+
+In the definitions that follow, the value of the ad-type for the
+element will be specified as the least significant part of the
+subsection number, and the value of the ad-data will be as shown in
+the ASN.1 structure that follows the subsection heading.
+
+   Contents of ad-data                ad-type
+
+   DER encoding of AD-IF-RELEVANT        1
+
+   DER encoding of AD-KDCIssued          4
+
+   DER encoding of AD-AND-OR             5
+
+   DER encoding of AD-MANDATORY-FOR-KDC  8
+
+*/
+
+type AuthorizationData struct {
+	ADType int
+	// TODO may make the following a slice of AuthorizationDataEntry
+	ADData []byte
+}
+
+type ADIfRelevant struct {
+	AuthorizationData
+}
+
+type ADAndOr struct {
+	ConditionCount int
+	Elements AuthorizationData
+}
+
+type ADKDCIssued struct {
+	ADChecksum string
+	IRealm string
+	Isname PrincipalName
+	Elements AuthorizationData
+}
+
+type ADMandatoryForKDC struct {
+	AuthorizationData
+}
+
+
+func (a *AuthorizationData) GetData() (string, error) {
+	var b []byte
+	_, err := asn1.Unmarshal(a.ADData, &b)
+	return string(b), err
+}
+

+ 22 - 0
krb5types/Cryptosystem.go

@@ -0,0 +1,22 @@
+package krb5types
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.2.9
+
+// Reference: https://www.ietf.org/rfc/rfc3961.txt
+
+type EncryptedData struct {
+	EType  int    `asn1:"explicit,tag:0"`
+	KVNO   int `asn1:"explicit,optional,tag:1"`
+	Cipher []byte `asn1:"explicit,tag:2"`
+}
+
+type EncryptionKey struct {
+	KeyType  int    `asn1:"explicit,tag:0"`
+	KeyValue []byte `asn1:"explicit,tag:1"`
+}
+
+type Checksum struct {
+	CksumType int    `asn1:"explicit,tag:0"`
+	Checksum  []byte `asn1:"explicit,tag:1"`
+}

+ 43 - 0
krb5types/HostAddress.go

@@ -0,0 +1,43 @@
+package krb5types
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.2.5
+
+import (
+	"encoding/asn1"
+)
+
+/*
+HostAddress and HostAddresses
+
+HostAddress     ::= SEQUENCE  {
+	addr-type       [0] Int32,
+	address         [1] OCTET STRING
+}
+
+-- NOTE: HostAddresses is always used as an OPTIONAL field and
+-- should not be empty.
+HostAddresses   -- NOTE: subtly different from rfc1510,
+		-- but has a value mapping and encodes the same
+	::= SEQUENCE OF HostAddress
+
+The host address encodings consist of two fields:
+
+addr-type
+	This field specifies the type of address that follows.  Pre-
+	defined values for this field are specified in Section 7.5.3.
+
+address
+	This field encodes a single address of type addr-type.
+*/
+
+type HostAddress struct {
+	AddrType int    `asn1:"explicit,tag:0"`
+	Address  []byte `asn1:"explicit,tag:1"`
+}
+
+func (h *HostAddress) GetAddress() (string, error) {
+	var b []byte
+	_, err := asn1.Unmarshal(h.Address, &b)
+	return string(b), err
+}

+ 99 - 0
krb5types/KerberosFlags.go

@@ -0,0 +1,99 @@
+package krb5types
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.2.8
+
+import "encoding/asn1"
+
+/*
+KerberosFlags
+
+For several message types, a specific constrained bit string type,
+KerberosFlags, is used.
+
+KerberosFlags   ::= BIT STRING (SIZE (32..MAX))
+-- minimum number of bits shall be sent,
+-- but no fewer than 32
+
+Compatibility note: The following paragraphs describe a change from
+the RFC 1510 description of bit strings that would result in
+incompatility in the case of an implementation that strictly
+conformed to ASN.1 DER and RFC 1510.
+
+ASN.1 bit strings have multiple uses.  The simplest use of a bit
+string is to contain a vector of bits, with no particular meaning
+attached to individual bits.  This vector of bits is not necessarily
+a multiple of eight bits long.  The use in Kerberos of a bit string
+as a compact boolean vector wherein each element has a distinct
+meaning poses some problems.  The natural notation for a compact
+boolean vector is the ASN.1 "NamedBit" notation, and the DER require
+that encodings of a bit string using "NamedBit" notation exclude any
+trailing zero bits.  This truncation is easy to neglect, especially
+given C language implementations that naturally choose to store
+boolean vectors as 32-bit integers.
+
+For example, if the notation for KDCOptions were to include the
+"NamedBit" notation, as in RFC 1510, and a KDCOptions value to be
+encoded had only the "forwardable" (bit number one) bit set, the DER
+encoding MUST include only two bits: the first reserved bit
+("reserved", bit number zero, value zero) and the one-valued bit (bit
+number one) for "forwardable".
+
+Most existing implementations of Kerberos unconditionally send 32
+bits on the wire when encoding bit strings used as boolean vectors.
+This behavior violates the ASN.1 syntax used for flag values in RFC
+1510, but it occurs on such a widely installed base that the protocol
+description is being modified to accommodate it.
+
+Consequently, this document removes the "NamedBit" notations for
+individual bits, relegating them to comments.  The size constraint on
+the KerberosFlags type requires that at least 32 bits be encoded at
+all times, though a lenient implementation MAY choose to accept fewer
+than 32 bits and to treat the missing bits as set to zero.
+
+Currently, no uses of KerberosFlags specify more than 32 bits' worth
+of flags, although future revisions of this document may do so.  When
+more than 32 bits are to be transmitted in a KerberosFlags value,
+future revisions to this document will likely specify that the
+smallest number of bits needed to encode the highest-numbered one-
+valued bit should be sent.  This is somewhat similar to the DER
+encoding of a bit string that is declared with the "NamedBit"
+notation.
+*/
+
+type KerberosFlag asn1.BitString
+
+/*// TODO do I want to make this into a map or should each be a type?
+const (
+	Reserved              KerberosFlag = 0
+	Forwardable           KerberosFlag = 1
+	Forwarded             KerberosFlag = 2
+	Proxiable             KerberosFlag = 3
+	Proxy                 KerberosFlag = 4
+	AllowPostDate         KerberosFlag = 5
+	MayPostDate         KerberosFlag = 5
+	PostDated             KerberosFlag = 6
+	Invalid               KerberosFlag = 7
+	Unused7               KerberosFlag = 7
+	Renewable             KerberosFlag = 8
+	Initial               KerberosFlag = 9
+	Unused9               KerberosFlag = 9
+	PreAuthent            KerberosFlag = 10
+	Unused10              KerberosFlag = 10
+	HWAuthent       KerberosFlag = 11
+	OptHardwareAuth       KerberosFlag = 11
+	TransitedPolicyChecked KerberosFlag = 12
+	unused12              KerberosFlag = 12
+	OKAsDelegate          KerberosFlag = 13
+	Unused13              KerberosFlag = 13
+	Unused15              KerberosFlag = 15
+	DisableTransitedCheck KerberosFlag = 26
+	RenewableOK           KerberosFlag = 27
+	EncTktInSkey          KerberosFlag = 28
+	Renew                 KerberosFlag = 30
+	Validate              KerberosFlag = 31
+)*/
+
+type KDCOptions asn1.BitString
+
+type TicketFlags asn1.BitString

+ 17 - 0
krb5types/KerberosString.go

@@ -0,0 +1,17 @@
+package krb5types
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.2.1
+
+import "encoding/asn1"
+
+type KerberosString []byte
+
+type krbStr struct {
+	Str string `asn1:"ia5"`
+}
+
+func ConvertToKerberosString(s string) (KerberosString, error) {
+	val := krbStr{Str: s}
+	return asn1.Marshal(val)
+}

+ 59 - 0
krb5types/PAData.go

@@ -0,0 +1,59 @@
+package krb5types
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.2.7
+import (
+	"time"
+	"fmt"
+	"encoding/asn1"
+)
+
+type PAData struct {
+	PADataType  int    `asn1:"explicit,tag:1"`
+	PADataValue []byte `asn1:"explicit,tag:2"`
+}
+
+// Do I need to define this one?
+type PAEncTimestamp struct {
+	PAEncTSEnc
+}
+
+type PAEncTSEnc struct {
+	PATimestamp time.Time `asn1:"explicit,tag:0"`
+	PAUSec      int `asn1:"explicit,optional,tag:1"`
+}
+
+type ETypeInfoEntry struct {
+	EType int `asn1:"explicit,tag:0"`
+	Salt  []byte `asn1:"explicit,optional,tag:1"`
+}
+
+type ETypeInfo []ETypeInfoEntry
+
+type ETypeInfo2Entry struct {
+	EType     int `asn1:"explicit,tag:0"`
+	Salt      string `asn1:"explicit,optional,tag:1,ia5"`
+	S2KParams []byte `asn1:"explicit,optional,tag:2"`
+}
+
+type ETypeInfo2 []ETypeInfo2Entry
+
+func (pa *PAData) GetETypeInfo() (d ETypeInfo, err error) {
+	dt := krbDictionary.PADataTypesByName["pa-etype-info"]
+	if pa.PADataType != dt {
+		err = fmt.Errorf("PAData does not contain PA EType Info data. TypeID Expected: %v; Actual: %v", dt, pa.PADataType)
+		return
+	}
+	_, err = asn1.Unmarshal(pa.PADataValue, &d)
+	return
+}
+
+func (pa *PAData) GetETypeInfo2() (d ETypeInfo2, err error) {
+	dt := krbDictionary.PADataTypesByName["pa-etype-info2"]
+	if pa.PADataType != dt {
+		err = fmt.Errorf("PAData does not contain PA EType Info 2 data. TypeID Expected: %v; Actual: %v", dt, pa.PADataType)
+		return
+	}
+	_, err = asn1.Unmarshal(pa.PADataValue, &d)
+	return
+}

+ 9 - 0
krb5types/PrincipalName.go

@@ -0,0 +1,9 @@
+package krb5types
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.2.2
+
+type PrincipalName struct {
+	NameType int `asn1:"explicit,tag:0"`
+	NameString []string `asn1:"explicit,tag:1,ia5"`
+}

+ 42 - 0
krb5types/Ticket.go

@@ -0,0 +1,42 @@
+package krb5types
+
+import (
+	"time"
+	"encoding/asn1"
+	"fmt"
+	"github.com/jcmturner/gokrb5/krb5types/asnAppTag"
+)
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.3
+
+type Ticket struct {
+	TktVNO  int           `asn1:"explicit,tag:0"`
+	Realm   string         `asn1:"explicit,tag:1"`
+	SName   PrincipalName `asn1:"explicit,tag:2"`
+	EncPart EncryptedData `asn1:"explicit,tag:3"`
+}
+
+type EncTicketPart struct {
+	Flags             TicketFlags       `asn1:"explicit,tag:0"`
+	Key               EncryptionKey     `asn1:"explicit,tag:1"`
+	CRealm            string             `asn1:"explicit,tag:2"`
+	CName             PrincipalName     `asn1:"explicit,tag:3"`
+	Transited         TransitedEncoding `asn1:"explicit,tag:4"`
+	AuthTime          time.Time         `asn1:"explicit,tag:5"`
+	StartTime         time.Time         `asn1:"explicit,optional,tag:6"`
+	EndTime           time.Time         `asn1:"explicit,tag:7"`
+	RenewTill         time.Time         `asn1:"explicit,optional,tag:8"`
+	CAddr             HostAddress       `asn1:"explicit,optional,tag:9"`
+	AuthorizationData AuthorizationData `asn1:"explicit,optional,tag:10"`
+}
+
+type TransitedEncoding struct {
+	TRType   int    `asn1:"explicit,tag:0"`
+	Contents []byte `asn1:"explicit,tag:1"`
+}
+
+func UnmarshalTicket(b []byte) (t Ticket, err error) {
+	_, err = asn1.UnmarshalWithParams(b, &t, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.Ticket))
+	return
+}

+ 22 - 0
krb5types/asnAppTag/constants.go

@@ -0,0 +1,22 @@
+package asnAppTag
+
+const (
+	Ticket = 1
+	Authenticator = 2
+	EncTicketPart = 3
+	ASREQ = 10
+	TGSREQ = 12
+	ASREP = 11
+	TGSREP = 13
+	APREQ = 14
+	APREP = 15
+	KRBSafe = 20
+	KRBPriv = 21
+	KRBCred = 22
+	EncASRepPart = 25
+	EncTGSRepPart = 26
+	EncAPRepPart = 27
+	EncKrbPrivPart  = 28
+	EncKrbCredPart = 29
+	KRBError = 30
+)

+ 174 - 0
krb5types/dictionary.go

@@ -0,0 +1,174 @@
+package krb5types
+
+var krbDictionary = struct {
+	MsgTypesByID      map[int]string
+	NameTypesByID     map[int]string
+	ErrorCodesByID    map[int]string
+	ADTypesByID       map[int]string
+	PADataTypesByID   map[int]string
+	PADataTypesByName map[string]int
+	ETypesByID        map[int]string
+	ETypesByName      map[string]int
+}{
+	MsgTypesByID: map[int]string{
+		10: "KRB_AS_REQ",
+		11: "KRB_AS_REP",
+		12: "KRB_TGS_REQ",
+		13: "KRB_TGS_REP",
+		14: "KRB_AP_REQ",
+		15: "KRB_AP_REP",
+		16: "KRB_RESERVED16",
+		17: "KRB_RESERVED17",
+		20: "KRB_SAFE",
+		21: "KRB_PRIV",
+		22: "KRB_CRED",
+		30: "KRB_ERROR",
+	},
+	NameTypesByID: map[int]string{
+		0:  "KRB_NT_UNKNOWN",
+		1:  "KRB_NT_PRINCIPAL",
+		2:  "KRB_NT_SRV_INST",
+		3:  "KRB_NT_SRV_HST",
+		4:  "KRB_NT_SRV_XHST",
+		5:  "KRB_NT_UID",
+		6:  "KRB_NT_X500_PRINCIPAL",
+		7:  "KRB_NT_SMTP_NAME",
+		10: "KRB_NT_ENTERPRISE",
+	},
+	ErrorCodesByID: map[int]string{
+		0:  "KDC_ERR_NONE",
+		1:  "KDC_ERR_NAME_EXP",
+		2:  "KDC_ERR_SERVICE_EXP",
+		3:  "KDC_ERR_BAD_PVNO",
+		4:  "KDC_ERR_C_OLD_MAST_KVNO",
+		5:  "KDC_ERR_S_OLD_MAST_KVNO",
+		6:  "KDC_ERR_C_PRINCIPAL_UNKNOWN",
+		7:  "KDC_ERR_S_PRINCIPAL_UNKNOWN",
+		8:  "KDC_ERR_PRINCIPAL_NOT_UNIQUE",
+		9:  "KDC_ERR_NULL_KEY",
+		10: "KDC_ERR_CANNOT_POSTDATE",
+		11: "KDC_ERR_NEVER_VALID",
+		12: "KDC_ERR_POLICY",
+		13: "KDC_ERR_BADOPTION",
+		14: "KDC_ERR_ETYPE_NOSUPP",
+		15: "KDC_ERR_SUMTYPE_NOSUPP",
+		16: "KDC_ERR_PADATA_TYPE_NOSUPP",
+		17: "KDC_ERR_TRTYPE_NOSUPP",
+		18: "KDC_ERR_CLIENT_REVOKED",
+		19: "KDC_ERR_SERVICE_REVOKED",
+		20: "KDC_ERR_TGT_REVOKED",
+		21: "KDC_ERR_CLIENT_NOTYET",
+		22: "KDC_ERR_SERVICE_NOTYET",
+		23: "KDC_ERR_KEY_EXPIRED",
+		24: "KDC_ERR_PREAUTH_FAILED",
+		25: "KDC_ERR_PREAUTH_REQUIRED",
+		26: "KDC_ERR_SERVER_NOMATCH",
+		27: "KDC_ERR_MUST_USE_USER2USER",
+		28: "KDC_ERR_PATH_NOT_ACCEPTED",
+		29: "KDC_ERR_SVC_UNAVAILABLE",
+		31: "KRB_AP_ERR_BAD_INTEGRITY",
+		32: "KRB_AP_ERR_TKT_EXPIRED",
+		33: "KRB_AP_ERR_TKT_NYV",
+		34: "KRB_AP_ERR_REPEAT",
+		35: "KRB_AP_ERR_NOT_US",
+		36: "KRB_AP_ERR_BADMATCH",
+		37: "KRB_AP_ERR_SKEW",
+		38: "KRB_AP_ERR_BADADDR",
+		39: "KRB_AP_ERR_BADVERSION",
+		40: "KRB_AP_ERR_MSG_TYPE",
+		41: "KRB_AP_ERR_MODIFIED",
+		42: "KRB_AP_ERR_BADORDER",
+		44: "KRB_AP_ERR_BADKEYVER",
+		45: "KRB_AP_ERR_NOKEY",
+		46: "KRB_AP_ERR_MUT_FAIL",
+		47: "KRB_AP_ERR_BADDIRECTION",
+		48: "KRB_AP_ERR_METHOD",
+		49: "KRB_AP_ERR_BADSEQ",
+		50: "KRB_AP_ERR_INAPP_CKSUM",
+		51: "KRB_AP_PATH_NOT_ACCEPTED",
+		52: "KRB_ERR_RESPONSE_TOO_BIG",
+		60: "KRB_ERR_GENERIC",
+		61: "KRB_ERR_FIELD_TOOLONG",
+		62: "KDC_ERROR_CLIENT_NOT_TRUSTED",
+		63: "KDC_ERROR_KDC_NOT_TRUSTED",
+		64: "KDC_ERROR_INVALID_SIG",
+		65: "KDC_ERR_KEY_TOO_WEAK",
+		66: "KDC_ERR_CERTIFICATE_MISMATCH",
+		67: "KRB_AP_ERR_NO_TGT",
+		68: "KDC_ERR_WRONG_REALM",
+		69: "KRB_AP_ERR_USER_TO_USER_REQUIRED",
+		70: "KDC_ERR_CANT_VERIFY_CERTIFICATE",
+		71: "KDC_ERR_INVALID_CERTIFICATE",
+		72: "KDC_ERR_REVOKED_CERTIFICATE",
+		73: "KDC_ERR_REVOCATION_STATUS_UNKNOWN",
+		74: "KDC_ERR_REVOCATION_STATUS_UNAVAILABLE",
+		75: "KDC_ERR_CLIENT_NAME_MISMATCH",
+		76: "KDC_ERR_KDC_NAME_MISMATCH",
+	},
+	ADTypesByID: map[int]string{
+		1: "AD-IF-RELEVANT",
+		4: "AD-KDCIssued",
+		5: "AD-AND-OR",
+		8: "AD-MANDATORY-FOR-KDC",
+	},
+	PADataTypesByID: map[int]string{
+		1:  "pa-tgs-req",
+		2:  "pa-enc-timestamp",
+		3:  "pa-pw-salt",
+		11: "pa-etype-info",
+		19: "pa-etype-info2",
+	},
+	PADataTypesByName: map[string]int{
+		"pa-tgs-req":       1,
+		"pa-enc-timestamp": 2,
+		"pa-pw-salt":       3,
+		"pa-etype-info":    11,
+		"pa-etype-info2":   19,
+	},
+	ETypesByID: map[int]string{
+		1:  "des-cbc-crc",
+		2:  "des-cbc-md4",
+		3:  "des-cbc-md5",
+		4:  "reserved4",
+		5:  "des3-cbc-md5",
+		6:  "reserved6",
+		7:  "des3-cbc-sha1",
+		9:  "dsaWithSHA1-CmsOID",
+		10: "md5WithRSAEncryption-CmsOID",
+		11: "sha1WithRSAEncryption-CmsOID",
+		12: "rc2CBC-EnvOID",
+		13: "rsaEncryption-EnvOID",
+		14: "rsaES-OAEP-ENV-OID",
+		15: "des-ede3-cbc-Env-OID",
+		16: "des3-cbc-sha1-kd",
+		17: "aes128-cts-hmac-sha1-96",
+		18: "aes256-cts-hmac-sha1-96",
+		23: "rc4-hmac",
+		24: "rc4-hmac-exp",
+		65: "subkey-keymaterial",
+	},
+	ETypesByName: map[string]int{
+		"des-cbc-crc":                  1,
+		"des-cbc-md4":                  2,
+		"des-cbc-md5":                  3,
+		"reserved4":                    4,
+		"des3-cbc-md5":                 5,
+		"reserved6":                    6,
+		"des3-cbc-sha1":                7,
+		"dsaWithSHA1-CmsOID":           9,
+		"md5WithRSAEncryption-CmsOID":  10,
+		"sha1WithRSAEncryption-CmsOID": 11,
+		"rc2CBC-EnvOID":                12,
+		"rsaEncryption-EnvOID":         13,
+		"rsaES-OAEP-ENV-OID":           14,
+		"des-ede3-cbc-Env-OID":         15,
+		"des3-cbc-sha1-kd":             16,
+		"aes128-cts-hmac-sha1-96":      17,
+		"aes256-cts-hmac-sha1-96":      18,
+		"rc4-hmac":                     23,
+		"rc4-hmac-exp":                 23,
+		"subkey-keymaterial":           65,
+	},
+}
+
+// TODO I think we should have a map of message type map[int]string or message interface???

+ 75 - 0
messages/KDCRep.go

@@ -0,0 +1,75 @@
+package messages
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.4.2
+
+import (
+	"github.com/jcmturner/gokrb5/krb5types"
+	"time"
+	"encoding/asn1"
+	"fmt"
+	"github.com/jcmturner/gokrb5/krb5types/asnAppTag"
+)
+
+type marshalKDCRep struct {
+	PVNO    int                     `asn1:"explicit,tag:0"`
+	MsgType int                     `asn1:"explicit,tag:1"`
+	PAData  []krb5types.PAData      `asn1:"explicit,optional,tag:2"`
+	CRealm  string        `asn1:"explicit,tag:3"`
+	CName   krb5types.PrincipalName `asn1:"explicit,tag:4"`
+	// Ticket needs to be a raw value as it is wrapped in an APPLICATION tag
+	Ticket  asn1.RawValue	        `asn1:"explicit,tag:5"`
+	EncPart krb5types.EncryptedData `asn1:"explicit,tag:6"`
+}
+
+type marshalEncKDCRepPart struct {
+	Key           krb5types.EncryptionKey `asn1:"explicit,tag:0"`
+	LastReq       marshalLastReq                 `asn1:"explicit,tag:1"`
+	Nonce         int                     `asn1:"explicit,tag:2"`
+	KeyExpiration time.Time               `asn1:"explicit,optional,tag:3"`
+	Flags         krb5types.TicketFlags   `asn1:"explicit,tag:4"`
+	AuthTime      time.Time               `asn1:"explicit,tag:5"`
+	StartTime     time.Time               `asn1:"explicit,optional,tag:6"`
+	EndTime       time.Time               `asn1:"explicit,tag:7"`
+	RenewTill     time.Time               `asn1:"explicit,optional,tag:8"`
+	SRealm        string         `asn1:"explicit,tag:9"`
+	SName         krb5types.PrincipalName `asn1:"explicit,tag:10"`
+	CAddr         []krb5types.HostAddress `asn1:"explicit,optional,tag:11"`
+}
+
+type marshalLastReq struct {
+	LRType  int       `asn1:"explicit,tag:0"`
+	LRValue time.Time `asn1:"explicit,tag:1"`
+}
+
+type KDCRep struct {
+	PVNO    int
+	MsgType int
+	PAData  []krb5types.PAData
+	CRealm  string
+	CName   krb5types.PrincipalName
+	// Ticket needs to be a raw value as it is wrapped in an APPLICATION tag
+	Ticket  krb5types.Ticket
+	EncPart krb5types.EncryptedData
+}
+
+func UnmarshalASRep(b []byte) (k KDCRep, err error) {
+	var asRep marshalKDCRep
+	_, err = asn1.UnmarshalWithParams(b, &asRep, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.ASREP))
+	if err != nil {
+		return
+	}
+	//Process the raw ticket within
+	k.Ticket, err = krb5types.UnmarshalTicket(asRep.Ticket.Bytes)
+	if err != nil {
+		return
+	}
+	k.PVNO = asRep.PVNO
+	k.MsgType = asRep.MsgType
+	k.PAData = asRep.PAData
+	k.CRealm = asRep.CRealm
+	k.CName = asRep.CName
+	k.EncPart = asRep.EncPart
+	return
+}
+

+ 39 - 0
messages/KDCReq.go

@@ -0,0 +1,39 @@
+package messages
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.4.1
+
+import (
+	"encoding/asn1"
+	"github.com/jcmturner/gokrb5/krb5types"
+	"time"
+	"fmt"
+	"github.com/jcmturner/gokrb5/krb5types/asnAppTag"
+)
+
+type KDCReq struct {
+	PVNO    int                `asn1:"explicit,tag:1"`
+	MsgType int                `asn1:"explicit,tag:2"`
+	PAData  []krb5types.PAData `asn1:"explicit,general,tag:3"`
+	ReqBody KDCReqBody         `asn1:"explicit,tag:4"`
+}
+
+type KDCReqBody struct {
+	KDCOptions        asn1.BitString          `asn1:"explicit,tag:0"`
+	CName             krb5types.PrincipalName `asn1:"explicit,optional,tag:1"`
+	Realm             string         `asn1:"explicit,tag:2"`
+	SName             krb5types.PrincipalName `asn1:"explicit,optional,tag:3"`
+	From              time.Time               `asn1:"explicit,optional,tag:4"`
+	Till              time.Time               `asn1:"explicit,tag:5"`
+	RTime             time.Time               `asn1:"explicit,optional,tag:6"`
+	Nonce             int                     `asn1:"explicit,tag:7"`
+	EType             []int                   `asn1:"explicit,tag:8"`
+	Address           []krb5types.HostAddress `asn1:"explicit,optional,tag:9"`
+	EncAuthData       krb5types.EncryptedData `asn1:"explicit,optional,tag:10"`
+	AdditionalTickets []krb5types.Ticket      `asn1:"explicit,optional,tag:11"`
+}
+
+func UnmarshalASReq(b []byte) (k KDCReq, err error) {
+	_, err = asn1.UnmarshalWithParams(b, &k, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.ASREQ))
+	return
+}