// 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 khttp is a transport that authenticates all outgoing requests using
// SPNEGO (negotiate authentication) http://tools.ietf.org/html/rfc4559.
package khttp
import (
"errors"
"fmt"
"net"
"net/http"
"os"
"strings"
"github.com/ubccr/kerby"
)
var (
negotiateHeader = "Negotiate"
wwwAuthenticateHeader = "WWW-Authenticate"
authorizationHeader = "Authorization"
)
// HTTP client transport that authenticates all outgoing
// requests using SPNEGO. Implements the http.RoundTripper interface
type Transport struct {
// keytab file to use
KeyTab string
// principal
Principal string
// Next specifies the next transport to be used or http.DefaultTransport if nil.
Next http.RoundTripper
}
// RoundTrip executes a single HTTP transaction performing SPNEGO negotiate
// authentication.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
if len(t.KeyTab) > 0 {
os.Setenv("KRB5_CLIENT_KTNAME", t.KeyTab)
}
host, _, err := net.SplitHostPort(req.URL.Host)
if err != nil {
host = req.URL.Host
}
service := fmt.Sprintf("HTTP@%s", host)
kc := new(kerby.KerbClient)
err = kc.Init(service, t.Principal)
if err != nil {
return nil, err
}
defer kc.Clean()
err = kc.Step("")
if err != nil {
return nil, err
}
req.Header.Set(authorizationHeader, negotiateHeader+" "+kc.Response())
tr := t.Next
if tr == nil {
tr = http.DefaultTransport
if tr == nil {
return nil, errors.New("khttp: no Next transport or DefaultTransport")
}
}
resp, err := tr.RoundTrip(req)
if err != nil {
return nil, err
}
authReply := strings.Split(resp.Header.Get(wwwAuthenticateHeader), " ")
if len(authReply) != 2 || strings.ToLower(authReply[0]) != strings.ToLower(negotiateHeader) {
return nil, errors.New("khttp: server replied with invalid www-authenticate header")
}
// Authenticate the reply from the server
err = kc.Step(authReply[1])
if err != nil {
return nil, err
}
return resp, nil
}