// Copyright 2015 Andrew E. Bruno // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package kerby is a cgo wrapper for Kerberos GSSAPI package kerby /* #cgo CFLAGS: -std=gnu99 #cgo LDFLAGS: -lgssapi_krb5 -lkrb5 -lk5crypto -lcom_err #include "kerberosgss.h" #include #include #include */ import "C" import ( "errors" "fmt" "strings" "unsafe" ) // Kerberos GSSAPI Client type KerbClient struct { state *C.gss_client_state } // Kerberos GSSAPI Server type KerbServer struct { state *C.gss_server_state } // Returns the last major/minor GSSAPI error messages func (kc KerbClient) GssError() error { bufMaj := (*C.char)(C.calloc(C.GSS_ERRBUF_SIZE, 1)) bufMin := (*C.char)(C.calloc(C.GSS_ERRBUF_SIZE, 1)) defer C.free(unsafe.Pointer(bufMaj)) defer C.free(unsafe.Pointer(bufMin)) C.get_gss_error(kc.state.maj_stat, bufMaj, kc.state.min_stat, bufMin) return errors.New(C.GoString(bufMaj) + " - " + C.GoString(bufMin)) } // Initializes a context for Kerberos GSSAPI client-side authentication. // KerbClient.Clean must be called after this function returns succesfully to // dispose of the context once all GSSAPI operations are complete. srv is the // service principal in the form "type@fqdn". princ is the client principal in the // form "user@realm". func (kc *KerbClient) Init(srv, princ string) error { service := C.CString(srv) defer C.free(unsafe.Pointer(service)) principal := C.CString(princ) defer C.free(unsafe.Pointer(principal)) var delegatestate *C.gss_server_state gss_flags := C.long(C.GSS_C_MUTUAL_FLAG | C.GSS_C_SEQUENCE_FLAG) result := 0 kc.state = C.new_gss_client_state() if kc.state == nil { return errors.New("Failed to allocate memory for gss_client_state") } result = int(C.authenticate_gss_client_init(service, principal, gss_flags, delegatestate, kc.state)) if result == C.AUTH_GSS_ERROR { return kc.GssError() } return nil } // Get the client response from the last successful GSSAPI client-side step. func (kc *KerbClient) Response() string { return C.GoString(kc.state.response) } // Processes a single GSSAPI client-side step using the supplied server data. func (kc *KerbClient) Step(chlg string) error { challenge := C.CString(chlg) defer C.free(unsafe.Pointer(challenge)) result := 0 if kc.state == nil { return errors.New("Invalid client state") } result = int(C.authenticate_gss_client_step(kc.state, challenge)) if result == C.AUTH_GSS_ERROR { return kc.GssError() } return nil } // Destroys the context for GSSAPI client-side authentication. After this call // the KerbClient.state object is invalid and should not be used again. func (kc *KerbClient) Clean() { if kc.state != nil { C.authenticate_gss_client_clean(kc.state) C.free_gss_client_state(kc.state) kc.state = nil } } // Returns the service principal for the server given a service type and // hostname. Adopted from PyKerberos. func ServerPrincipalDetails(service, hostname string) (string, error) { var code C.krb5_error_code var kcontext C.krb5_context var kt C.krb5_keytab var cursor C.krb5_kt_cursor var entry C.krb5_keytab_entry var pname *C.char match := fmt.Sprintf("%s/%s@", service, hostname) code = C.krb5_init_context(&kcontext) if code != 0 { return "", fmt.Errorf("Cannot initialize Kerberos5 context: %d", code) } code = C.krb5_kt_default(kcontext, &kt) if code != 0 { return "", fmt.Errorf("Cannot get default keytab: %d", int(code)) } code = C.krb5_kt_start_seq_get(kcontext, kt, &cursor) if code != 0 { return "", fmt.Errorf("Cannot get sequence cursor from keytab: %d", int(code)) } result := "" for { code = C.krb5_kt_next_entry(kcontext, kt, &entry, &cursor) if code != 0 { break } code = C.krb5_unparse_name(kcontext, entry.principal, &pname) if code != 0 { return "", fmt.Errorf("Cannot parse principal name from keytab: %d", int(code)) } result = C.GoString(pname) if strings.HasPrefix(result, match) { C.krb5_free_unparsed_name(kcontext, pname) C.krb5_free_keytab_entry_contents(kcontext, &entry) break } result = "" C.krb5_free_unparsed_name(kcontext, pname) C.krb5_free_keytab_entry_contents(kcontext, &entry) } if len(result) == 0 { return "", errors.New("Principal not found in keytab") } if cursor != nil { C.krb5_kt_end_seq_get(kcontext, kt, &cursor) } if kt != nil { C.krb5_kt_close(kcontext, kt) } C.krb5_free_context(kcontext) return result, nil } // Returns the last major/minor GSSAPI error messages func (ks KerbServer) GssError() error { bufMaj := (*C.char)(C.calloc(C.GSS_ERRBUF_SIZE, 1)) bufMin := (*C.char)(C.calloc(C.GSS_ERRBUF_SIZE, 1)) defer C.free(unsafe.Pointer(bufMaj)) defer C.free(unsafe.Pointer(bufMin)) C.get_gss_error(ks.state.maj_stat, bufMaj, ks.state.min_stat, bufMin) return errors.New(C.GoString(bufMaj) + " - " + C.GoString(bufMin)) } // Initializes a context for GSSAPI server-side authentication with the given // service principal. KerbServer.Clean must be called after this function // returns succesfully to dispose of the context once all GSSAPI operations are // complete. srv is the service principal in the form "type@fqdn". func (ks *KerbServer) Init(srv string) error { service := C.CString(srv) defer C.free(unsafe.Pointer(service)) result := 0 ks.state = C.new_gss_server_state() if ks.state == nil { return errors.New("Failed to allocate memory for gss_server_state") } result = int(C.authenticate_gss_server_init(service, ks.state)) if result == C.AUTH_GSS_ERROR { return ks.GssError() } return nil } // Get the user name of the principal trying to authenticate to the server. // This method must only be called after KerbServer.Step returns a complete or // continue response code. func (ks *KerbServer) UserName() string { return C.GoString(ks.state.username) } // Get the target name if the server did not supply its own credentials. This // method must only be called after KerbServer.Step returns a complete or // continue response code. func (ks *KerbServer) TargetName() string { return C.GoString(ks.state.targetname) } // Get the server response from the last successful GSSAPI server-side step. func (ks *KerbServer) Response() string { return C.GoString(ks.state.response) } // Processes a single GSSAPI server-side step using the supplied client data. func (ks *KerbServer) Step(chlg string) error { challenge := C.CString(chlg) defer C.free(unsafe.Pointer(challenge)) result := 0 if ks.state == nil { return errors.New("Invalid client state") } result = int(C.authenticate_gss_server_step(ks.state, challenge)) if result == C.AUTH_GSS_ERROR { return ks.GssError() } return nil } // Destroys the context for GSSAPI server-side authentication. After this call // the KerbServer.state object is invalid and should not be used again. func (ks *KerbServer) Clean() { if ks.state != nil { C.authenticate_gss_server_clean(ks.state) C.free_gss_server_state(ks.state) ks.state = nil } }