Skip to content

Commit

Permalink
Add info endpoint to return public keys.
Browse files Browse the repository at this point in the history
  • Loading branch information
kisom committed Apr 7, 2015
1 parent bdeecca commit 1d24afd
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 4 deletions.
67 changes: 66 additions & 1 deletion api/info/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ func NewHandler(s signer.Signer) (http.Handler, error) {
// Handle listens for incoming requests for CA information, and returns
// a list containing information on each root certificate.
func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {

req := new(client.InfoReq)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
Expand All @@ -60,3 +59,69 @@ func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
enc := json.NewEncoder(w)
return enc.Encode(response)
}

// MultiHandler is a handler for providing the public certificates for
// a multi-root certificate authority. It takes a mapping of label to
// signer and a default label, and handles the standard information
// request as defined in the client package.
type MultiHandler struct {
signers map[string]signer.Signer
defaultLabel string
}

// NewMultiHandler constructs a MultiHandler from a mapping of labels
// to signers and the default label.
func NewMultiHandler(signers map[string]signer.Signer, defaultLabel string) (http.Handler, error) {
return &api.HTTPHandler{
Handler: &MultiHandler{
signers: signers,
defaultLabel: defaultLabel,
},
Method: "POST",
}, nil
}

// Handle accepts client information requests, and uses the label to
// look up the signer whose public certificate should be retrieved. If
// the label is empty, the default label is used.
func (h *MultiHandler) Handle(w http.ResponseWriter, r *http.Request) error {
log.Info("multi handler")
req := new(client.InfoReq)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Warningf("failed to read request body: %v", err)
return errors.NewBadRequest(err)
}
err = json.Unmarshal(body, req)
if err != nil {
log.Warningf("failed to unmarshal request: %v", err)
return errors.NewBadRequest(err)
}

log.Info("checking label")

if req.Label == "" {
req.Label = h.defaultLabel
}

if _, ok := h.signers[req.Label]; !ok {
log.Warningf("request for invalid endpoint")
return errors.NewBadRequestString("bad label")
}

log.Info("getting cert")
cert, err := h.signers[req.Label].Certificate("", req.Profile)
if err != nil {
log.Infof("error getting certificate: %v", err)
return err
}

resp := client.InfoResp{
Certificate: bundler.PemBlockToString(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}),
}

response := api.NewSuccessResponse(resp)
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
return enc.Encode(response)
}
129 changes: 129 additions & 0 deletions api/info/info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,22 @@ import (
"testing"

"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/signer"
"github.com/cloudflare/cfssl/signer/local"
)

const (
testCaFile = "../testdata/ca.pem"
testCaKeyFile = "../testdata/ca_key.pem"

// second test CA for multiroot
testCaFile2 = "../testdata/ca2.pem"
testCaKeyFile2 = "../testdata/ca2-key.pem"
)

// Generally, the single root function and its multiroot analogue will
// be presented together.

func newTestHandler(t *testing.T) (h http.Handler) {
signer, err := local.NewSignerFromFile(testCaFile, testCaKeyFile, nil)
if err != nil {
Expand All @@ -30,15 +38,47 @@ func newTestHandler(t *testing.T) (h http.Handler) {
return
}

func newTestMultiHandler(t *testing.T) (h http.Handler) {
signer1, err := local.NewSignerFromFile(testCaFile, testCaKeyFile, nil)
if err != nil {
t.Fatal(err)
}

signer2, err := local.NewSignerFromFile(testCaFile2, testCaKeyFile2, nil)
if err != nil {
t.Fatal(err)
}

signers := map[string]signer.Signer{
"test1": signer1,
"test2": signer2,
}

h, err = NewMultiHandler(signers, "test1")
if err != nil {
t.Fatalf("%v", err)
}

return
}

func TestNewHandler(t *testing.T) {
newTestHandler(t)
}

func TestNewMultiHandler(t *testing.T) {
newTestMultiHandler(t)
}

func newInfoServer(t *testing.T) *httptest.Server {
ts := httptest.NewServer(newTestHandler(t))
return ts
}

func newMultiInfoServer(t *testing.T) *httptest.Server {
return httptest.NewServer(newTestMultiHandler(t))
}

func testInfoFile(t *testing.T, req map[string]interface{}) (resp *http.Response, body []byte) {
ts := newInfoServer(t)
defer ts.Close()
Expand All @@ -59,6 +99,26 @@ func testInfoFile(t *testing.T, req map[string]interface{}) (resp *http.Response
return
}

func testMultiInfoFile(t *testing.T, req map[string]interface{}) (resp *http.Response, body []byte) {
ts := newMultiInfoServer(t)
defer ts.Close()

blob, err := json.Marshal(req)
if err != nil {
t.Fatal(err)
}

resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}

type infoTest struct {
RequestObject map[string]interface{}
ExpectedHTTPStatus int
Expand All @@ -76,6 +136,44 @@ var infoTests = []infoTest{
true,
0,
},
{
map[string]interface{}{
"label": 123,
},
http.StatusBadRequest,
false,
http.StatusBadRequest,
},
}

var multiInfoTests = []infoTest{
{
map[string]interface{}{
"label": "",
"profile": "",
},
http.StatusOK,
true,
0,
},
{
map[string]interface{}{
"label": "test1",
"profile": "",
},
http.StatusOK,
true,
0,
},
{
map[string]interface{}{
"label": "test2",
"profile": "",
},
http.StatusOK,
true,
0,
},
{
map[string]interface{}{
"label": "badlabel",
Expand Down Expand Up @@ -125,3 +223,34 @@ func TestInfo(t *testing.T) {

}
}

func TestMultiInfo(t *testing.T) {
for i, test := range multiInfoTests {
resp, body := testMultiInfoFile(t, test.RequestObject)
if resp.StatusCode != test.ExpectedHTTPStatus {
t.Fatalf("Test %d: expected: %d, have %d", i, test.ExpectedHTTPStatus, resp.StatusCode)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body))
}

message := new(api.Response)
err := json.Unmarshal(body, message)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}

if test.ExpectedSuccess != message.Success {
t.Fatalf("Test %d: expected: %v, have %v", i, test.ExpectedSuccess, message.Success)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess == true {
continue
}

if test.ExpectedErrorCode != message.Errors[0].Code {
t.Fatalf("Test %d: expected: %v, have %v", i, test.ExpectedErrorCode, message.Errors[0].Code)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}

}
}
5 changes: 5 additions & 0 deletions api/testdata/ca2-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEILOI+Ox7VUA+HaiOuAbBtf1IOXffEsOoI/443rTOPzD5oAoGCCqGSM49
AwEHoUQDQgAEoY1dLpXLl1bN5p8GFqOKrYu8C7QF0OLCMlfoiJInE6XI+PKlxXx+
KlwasHd9zxV1HA4YtHifkrAL9u0CvrbdOg==
-----END EC PRIVATE KEY-----
15 changes: 15 additions & 0 deletions api/testdata/ca2.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICYjCCAgigAwIBAgIIB/ijVOdMMDMwCgYIKoZIzj0EAwIwgYIxCzAJBgNVBAYT
AlVTMRUwEwYDVQQKEwxDRlNTTCBURVNUIDIxGzAZBgNVBAsTEkNGU1NMIFRlc3Qg
Um9vdCBDQTETMBEGA1UEBxMKQ2FsaWZvcm5pYTETMBEGA1UECBMKQ2FsaWZvcm5p
YTEVMBMGA1UEAxMMQ0ZTU0wgVEVTVCAyMB4XDTE1MDQwNjIzNTkwMFoXDTIwMDQw
NDIzNTkwMFowgYIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxDRlNTTCBURVNUIDIx
GzAZBgNVBAsTEkNGU1NMIFRlc3QgUm9vdCBDQTETMBEGA1UEBxMKQ2FsaWZvcm5p
YTETMBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEAxMMQ0ZTU0wgVEVTVCAyMFkw
EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoY1dLpXLl1bN5p8GFqOKrYu8C7QF0OLC
MlfoiJInE6XI+PKlxXx+KlwasHd9zxV1HA4YtHifkrAL9u0CvrbdOqNmMGQwDgYD
VR0PAQH/BAQDAgAGMBIGA1UdEwEB/wQIMAYBAf8CAQIwHQYDVR0OBBYEFGNz0lWe
3YnOP5PykkQ+ZVcHCZp2MB8GA1UdIwQYMBaAFGNz0lWe3YnOP5PykkQ+ZVcHCZp2
MAoGCCqGSM49BAMCA0gAMEUCIQCuxcZqp9vyJ8mH9eFS9cvMAbTildshZJYn7QB6
8WDscAIga1np4tMDrsIynHrmYI1GnD/TgmUi4ElBNoyUnob+B+U=
-----END CERTIFICATE-----
8 changes: 8 additions & 0 deletions cmd/multirootca/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"os"

"github.com/cloudflare/cfssl/api/info"
"github.com/cloudflare/cfssl/cmd/multirootca/config"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/signer"
Expand Down Expand Up @@ -64,8 +65,15 @@ func main() {
defaultLabel = *flagDefaultLabel
initStats()

infoHandler, err := info.NewMultiHandler(signers, defaultLabel)
if err != nil {
log.Critical("%v", err)
}

http.HandleFunc("/api/v1/cfssl/authsign", dispatchRequest)
http.HandleFunc("/api/v1/cfssl/metrics", dumpMetrics)
http.Handle("/api/v1/cfssl/info", infoHandler)

log.Info("listening on ", *flagAddr)
log.Error(http.ListenAndServe(*flagAddr, nil))
}
3 changes: 0 additions & 3 deletions signer/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,6 @@ func (s *Signer) SigAlgo() x509.SignatureAlgorithm {

// Certificate returns the signer's certificate.
func (s *Signer) Certificate(label, profile string) (*x509.Certificate, error) {
if label != "" {
return nil, cferr.NewBadRequestString("label specified for local operation")
}
cert := *s.ca
return &cert, nil
}
Expand Down

0 comments on commit 1d24afd

Please sign in to comment.