| package pac |
| |
| import ( |
| "bytes" |
| "errors" |
| "fmt" |
| "log" |
| |
| "gopkg.in/jcmturner/gokrb5.v7/crypto" |
| "gopkg.in/jcmturner/gokrb5.v7/iana/keyusage" |
| "gopkg.in/jcmturner/gokrb5.v7/types" |
| "gopkg.in/jcmturner/rpc.v1/mstypes" |
| ) |
| |
| const ( |
| infoTypeKerbValidationInfo uint32 = 1 |
| infoTypeCredentials uint32 = 2 |
| infoTypePACServerSignatureData uint32 = 6 |
| infoTypePACKDCSignatureData uint32 = 7 |
| infoTypePACClientInfo uint32 = 10 |
| infoTypeS4UDelegationInfo uint32 = 11 |
| infoTypeUPNDNSInfo uint32 = 12 |
| infoTypePACClientClaimsInfo uint32 = 13 |
| infoTypePACDeviceInfo uint32 = 14 |
| infoTypePACDeviceClaimsInfo uint32 = 15 |
| ) |
| |
| // PACType implements: https://msdn.microsoft.com/en-us/library/cc237950.aspx |
| type PACType struct { |
| CBuffers uint32 |
| Version uint32 |
| Buffers []InfoBuffer |
| Data []byte |
| KerbValidationInfo *KerbValidationInfo |
| CredentialsInfo *CredentialsInfo |
| ServerChecksum *SignatureData |
| KDCChecksum *SignatureData |
| ClientInfo *ClientInfo |
| S4UDelegationInfo *S4UDelegationInfo |
| UPNDNSInfo *UPNDNSInfo |
| ClientClaimsInfo *ClientClaimsInfo |
| DeviceInfo *DeviceInfo |
| DeviceClaimsInfo *DeviceClaimsInfo |
| ZeroSigData []byte |
| } |
| |
| // InfoBuffer implements the PAC Info Buffer: https://msdn.microsoft.com/en-us/library/cc237954.aspx |
| type InfoBuffer struct { |
| ULType uint32 // A 32-bit unsigned integer in little-endian format that describes the type of data present in the buffer contained at Offset. |
| CBBufferSize uint32 // A 32-bit unsigned integer in little-endian format that contains the size, in bytes, of the buffer in the PAC located at Offset. |
| Offset uint64 // A 64-bit unsigned integer in little-endian format that contains the offset to the beginning of the buffer, in bytes, from the beginning of the PACTYPE structure. The data offset MUST be a multiple of eight. The following sections specify the format of each type of element. |
| } |
| |
| // Unmarshal bytes into the PACType struct |
| func (pac *PACType) Unmarshal(b []byte) (err error) { |
| pac.Data = b |
| zb := make([]byte, len(b), len(b)) |
| copy(zb, b) |
| pac.ZeroSigData = zb |
| r := mstypes.NewReader(bytes.NewReader(b)) |
| pac.CBuffers, err = r.Uint32() |
| if err != nil { |
| return |
| } |
| pac.Version, err = r.Uint32() |
| if err != nil { |
| return |
| } |
| buf := make([]InfoBuffer, pac.CBuffers, pac.CBuffers) |
| for i := range buf { |
| buf[i].ULType, err = r.Uint32() |
| if err != nil { |
| return |
| } |
| buf[i].CBBufferSize, err = r.Uint32() |
| if err != nil { |
| return |
| } |
| buf[i].Offset, err = r.Uint64() |
| if err != nil { |
| return |
| } |
| } |
| pac.Buffers = buf |
| return nil |
| } |
| |
| // ProcessPACInfoBuffers processes the PAC Info Buffers. |
| // https://msdn.microsoft.com/en-us/library/cc237954.aspx |
| func (pac *PACType) ProcessPACInfoBuffers(key types.EncryptionKey, l *log.Logger) error { |
| for _, buf := range pac.Buffers { |
| p := make([]byte, buf.CBBufferSize, buf.CBBufferSize) |
| copy(p, pac.Data[int(buf.Offset):int(buf.Offset)+int(buf.CBBufferSize)]) |
| switch buf.ULType { |
| case infoTypeKerbValidationInfo: |
| if pac.KerbValidationInfo != nil { |
| //Must ignore subsequent buffers of this type |
| continue |
| } |
| var k KerbValidationInfo |
| err := k.Unmarshal(p) |
| if err != nil { |
| return fmt.Errorf("error processing KerbValidationInfo: %v", err) |
| } |
| pac.KerbValidationInfo = &k |
| case infoTypeCredentials: |
| // Currently PAC parsing is only useful on the service side in gokrb5 |
| // The CredentialsInfo are only useful when gokrb5 has implemented RFC4556 and only applied on the client side. |
| // Skipping CredentialsInfo - will be revisited under RFC4556 implementation. |
| continue |
| //if pac.CredentialsInfo != nil { |
| // //Must ignore subsequent buffers of this type |
| // continue |
| //} |
| //var k CredentialsInfo |
| //err := k.Unmarshal(p, key) // The encryption key used is the AS reply key only available to the client. |
| //if err != nil { |
| // return fmt.Errorf("error processing CredentialsInfo: %v", err) |
| //} |
| //pac.CredentialsInfo = &k |
| case infoTypePACServerSignatureData: |
| if pac.ServerChecksum != nil { |
| //Must ignore subsequent buffers of this type |
| continue |
| } |
| var k SignatureData |
| zb, err := k.Unmarshal(p) |
| copy(pac.ZeroSigData[int(buf.Offset):int(buf.Offset)+int(buf.CBBufferSize)], zb) |
| if err != nil { |
| return fmt.Errorf("error processing ServerChecksum: %v", err) |
| } |
| pac.ServerChecksum = &k |
| case infoTypePACKDCSignatureData: |
| if pac.KDCChecksum != nil { |
| //Must ignore subsequent buffers of this type |
| continue |
| } |
| var k SignatureData |
| zb, err := k.Unmarshal(p) |
| copy(pac.ZeroSigData[int(buf.Offset):int(buf.Offset)+int(buf.CBBufferSize)], zb) |
| if err != nil { |
| return fmt.Errorf("error processing KDCChecksum: %v", err) |
| } |
| pac.KDCChecksum = &k |
| case infoTypePACClientInfo: |
| if pac.ClientInfo != nil { |
| //Must ignore subsequent buffers of this type |
| continue |
| } |
| var k ClientInfo |
| err := k.Unmarshal(p) |
| if err != nil { |
| return fmt.Errorf("error processing ClientInfo: %v", err) |
| } |
| pac.ClientInfo = &k |
| case infoTypeS4UDelegationInfo: |
| if pac.S4UDelegationInfo != nil { |
| //Must ignore subsequent buffers of this type |
| continue |
| } |
| var k S4UDelegationInfo |
| err := k.Unmarshal(p) |
| if err != nil { |
| l.Printf("could not process S4U_DelegationInfo: %v", err) |
| continue |
| } |
| pac.S4UDelegationInfo = &k |
| case infoTypeUPNDNSInfo: |
| if pac.UPNDNSInfo != nil { |
| //Must ignore subsequent buffers of this type |
| continue |
| } |
| var k UPNDNSInfo |
| err := k.Unmarshal(p) |
| if err != nil { |
| l.Printf("could not process UPN_DNSInfo: %v", err) |
| continue |
| } |
| pac.UPNDNSInfo = &k |
| case infoTypePACClientClaimsInfo: |
| if pac.ClientClaimsInfo != nil || len(p) < 1 { |
| //Must ignore subsequent buffers of this type |
| continue |
| } |
| var k ClientClaimsInfo |
| err := k.Unmarshal(p) |
| if err != nil { |
| l.Printf("could not process ClientClaimsInfo: %v", err) |
| continue |
| } |
| pac.ClientClaimsInfo = &k |
| case infoTypePACDeviceInfo: |
| if pac.DeviceInfo != nil { |
| //Must ignore subsequent buffers of this type |
| continue |
| } |
| var k DeviceInfo |
| err := k.Unmarshal(p) |
| if err != nil { |
| l.Printf("could not process DeviceInfo: %v", err) |
| continue |
| } |
| pac.DeviceInfo = &k |
| case infoTypePACDeviceClaimsInfo: |
| if pac.DeviceClaimsInfo != nil { |
| //Must ignore subsequent buffers of this type |
| continue |
| } |
| var k DeviceClaimsInfo |
| err := k.Unmarshal(p) |
| if err != nil { |
| l.Printf("could not process DeviceClaimsInfo: %v", err) |
| continue |
| } |
| pac.DeviceClaimsInfo = &k |
| } |
| } |
| |
| if ok, err := pac.verify(key); !ok { |
| return err |
| } |
| |
| return nil |
| } |
| |
| func (pac *PACType) verify(key types.EncryptionKey) (bool, error) { |
| if pac.KerbValidationInfo == nil { |
| return false, errors.New("PAC Info Buffers does not contain a KerbValidationInfo") |
| } |
| if pac.ServerChecksum == nil { |
| return false, errors.New("PAC Info Buffers does not contain a ServerChecksum") |
| } |
| if pac.KDCChecksum == nil { |
| return false, errors.New("PAC Info Buffers does not contain a KDCChecksum") |
| } |
| if pac.ClientInfo == nil { |
| return false, errors.New("PAC Info Buffers does not contain a ClientInfo") |
| } |
| etype, err := crypto.GetChksumEtype(int32(pac.ServerChecksum.SignatureType)) |
| if err != nil { |
| return false, err |
| } |
| if ok := etype.VerifyChecksum(key.KeyValue, |
| pac.ZeroSigData, |
| pac.ServerChecksum.Signature, |
| keyusage.KERB_NON_KERB_CKSUM_SALT); !ok { |
| return false, errors.New("PAC service checksum verification failed") |
| } |
| |
| return true, nil |
| } |