123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533 |
- package sign
- import (
- "bytes"
- "encoding/json"
- "io"
- "net/http"
- "net/http/httptest"
- "os"
- "strings"
- "testing"
- "github.com/cloudflare/cfssl/api"
- "github.com/cloudflare/cfssl/auth"
- "github.com/cloudflare/cfssl/config"
- "github.com/cloudflare/cfssl/signer"
- )
- const (
- testCaFile = "../testdata/ca.pem"
- testCaKeyFile = "../testdata/ca_key.pem"
- testCSRFile = "../testdata/csr.pem"
- testBrokenCertFile = "../testdata/broken.pem"
- testBrokenCSRFile = "../testdata/broken_csr.pem"
- )
- var validLocalConfig = `
- {
- "signing": {
- "default": {
- "usages": ["digital signature", "email protection"],
- "expiry": "1m"
- }
- }
- }`
- var validAuthLocalConfig = `
- {
- "signing": {
- "default": {
- "usages": ["digital signature", "email protection"],
- "expiry": "1m",
- "auth_key": "sample"
- }
- },
- "auth_keys": {
- "sample": {
- "type":"standard",
- "key":"0123456789ABCDEF0123456789ABCDEF"
- }
- }
- }`
- var validMixedLocalConfig = `
- {
- "signing": {
- "default": {
- "usages": ["digital signature", "email protection"],
- "expiry": "1m"
- },
- "profiles": {
- "auth": {
- "usages": ["digital signature", "email protection"],
- "expiry": "1m",
- "auth_key": "sample"
- }
- }
- },
- "auth_keys": {
- "sample": {
- "type":"standard",
- "key":"0123456789ABCDEF0123456789ABCDEF"
- }
- }
- }`
- var alsoValidMixedLocalConfig = `
- {
- "signing": {
- "default": {
- "usages": ["digital signature", "email protection"],
- "expiry": "1m",
- "auth_key": "sample"
- },
- "profiles": {
- "no-auth": {
- "usages": ["digital signature", "email protection"],
- "expiry": "1m"
- }
- }
- },
- "auth_keys": {
- "sample": {
- "type":"standard",
- "key":"0123456789ABCDEF0123456789ABCDEF"
- }
- }
- }`
- func newTestHandler(t *testing.T) (h http.Handler) {
- h, err := NewHandler(testCaFile, testCaKeyFile, nil)
- if err != nil {
- t.Fatal(err)
- }
- return
- }
- func TestNewHandler(t *testing.T) {
- newTestHandler(t)
- }
- func TestNewHandlerWithProfile(t *testing.T) {
- conf, err := config.LoadConfig([]byte(validLocalConfig))
- if err != nil {
- t.Fatal(err)
- }
- _, err = NewHandler(testCaFile, testCaKeyFile, conf.Signing)
- if err != nil {
- t.Fatal(err)
- }
- }
- func TestNewHandlerWithAuthProfile(t *testing.T) {
- conf, err := config.LoadConfig([]byte(validAuthLocalConfig))
- if err != nil {
- t.Fatal(err)
- }
- _, err = NewHandler(testCaFile, testCaKeyFile, conf.Signing)
- if err == nil {
- t.Fatal("All profiles have auth keys. Should have failed to create non-auth sign handler.")
- }
- }
- func TestNewHandlerError(t *testing.T) {
- // using testBrokenCSRFile as badly formed key
- _, err := NewHandler(testCaFile, testBrokenCSRFile, nil)
- if err == nil {
- t.Fatal("Expect error when create a signer with broken file.")
- }
- }
- func TestNewAuthHandlerWithNonAuthProfile(t *testing.T) {
- conf, err := config.LoadConfig([]byte(validLocalConfig))
- if err != nil {
- t.Fatal(err)
- }
- _, err = NewAuthHandler(testCaFile, testCaKeyFile, conf.Signing)
- if err == nil {
- t.Fatal("No profile have auth keys. Should have failed to create auth sign handler.")
- }
- }
- func TestNewHandlersWithMixedProfile(t *testing.T) {
- conf, err := config.LoadConfig([]byte(validMixedLocalConfig))
- if err != nil {
- t.Fatal(err)
- }
- _, err = NewHandler(testCaFile, testCaKeyFile, conf.Signing)
- if err != nil {
- t.Fatal("Should be able to create non-auth sign handler.")
- }
- _, err = NewAuthHandler(testCaFile, testCaKeyFile, conf.Signing)
- if err != nil {
- t.Fatal("Should be able to create auth sign handler.")
- }
- }
- func TestNewHandlersWithAnotherMixedProfile(t *testing.T) {
- conf, err := config.LoadConfig([]byte(alsoValidMixedLocalConfig))
- if err != nil {
- t.Fatal(err)
- }
- _, err = NewHandler(testCaFile, testCaKeyFile, conf.Signing)
- if err != nil {
- t.Fatal("Should be able to create non-auth sign handler.")
- }
- _, err = NewAuthHandler(testCaFile, testCaKeyFile, conf.Signing)
- if err != nil {
- t.Fatal("Should be able to create auth sign handler.")
- }
- }
- func newSignServer(t *testing.T) *httptest.Server {
- ts := httptest.NewServer(newTestHandler(t))
- return ts
- }
- func testSignFileOldInterface(t *testing.T, hostname, csrFile string) (resp *http.Response, body []byte) {
- ts := newSignServer(t)
- defer ts.Close()
- var csrPEM []byte
- if csrFile != "" {
- var err error
- csrPEM, err = os.ReadFile(csrFile)
- if err != nil {
- t.Fatal(err)
- }
- }
- obj := map[string]string{}
- if len(hostname) > 0 {
- obj["hostname"] = hostname
- }
- if len(csrPEM) > 0 {
- obj["certificate_request"] = string(csrPEM)
- }
- blob, err := json.Marshal(obj)
- 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 = io.ReadAll(resp.Body)
- if err != nil {
- t.Fatal(err)
- }
- return
- }
- func testSignFile(t *testing.T, hosts []string, subject *signer.Subject, csrFile string) (resp *http.Response, body []byte) {
- ts := newSignServer(t)
- defer ts.Close()
- var csrPEM []byte
- if csrFile != "" {
- var err error
- csrPEM, err = os.ReadFile(csrFile)
- if err != nil {
- t.Fatal(err)
- }
- }
- obj := map[string]interface{}{}
- if hosts != nil {
- obj["hosts"] = hosts
- }
- if len(csrPEM) > 0 {
- obj["certificate_request"] = string(csrPEM)
- }
- if subject != nil {
- obj["subject"] = subject
- }
- blob, err := json.Marshal(obj)
- 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 = io.ReadAll(resp.Body)
- if err != nil {
- t.Fatal(err)
- }
- return
- }
- const (
- testHostName = "localhost"
- testDomainName = "cloudflare.com"
- )
- type signTest struct {
- Hosts []string
- Subject *signer.Subject
- CSRFile string
- ExpectedHTTPStatus int
- ExpectedSuccess bool
- ExpectedErrorCode int
- }
- var signTests = []signTest{
- {
- Hosts: []string{testHostName},
- CSRFile: testCSRFile,
- ExpectedHTTPStatus: http.StatusOK,
- ExpectedSuccess: true,
- ExpectedErrorCode: 0,
- },
- {
- Hosts: []string{testDomainName},
- CSRFile: testCSRFile,
- ExpectedHTTPStatus: http.StatusOK,
- ExpectedSuccess: true,
- ExpectedErrorCode: 0,
- },
- {
- Hosts: []string{testDomainName, testHostName},
- CSRFile: testCSRFile,
- ExpectedHTTPStatus: http.StatusOK,
- ExpectedSuccess: true,
- ExpectedErrorCode: 0,
- },
- {
- Hosts: []string{testDomainName},
- Subject: &signer.Subject{CN: "example.com"},
- CSRFile: testCSRFile,
- ExpectedHTTPStatus: http.StatusOK,
- ExpectedSuccess: true,
- ExpectedErrorCode: 0,
- },
- {
- Hosts: []string{},
- Subject: &signer.Subject{CN: "example.com"},
- CSRFile: testCSRFile,
- ExpectedHTTPStatus: http.StatusOK,
- ExpectedSuccess: true,
- ExpectedErrorCode: 0,
- },
- {
- Hosts: nil,
- CSRFile: testCSRFile,
- ExpectedHTTPStatus: http.StatusOK,
- ExpectedSuccess: true,
- ExpectedErrorCode: 0,
- },
- {
- Hosts: []string{testDomainName},
- CSRFile: "",
- ExpectedHTTPStatus: http.StatusBadRequest,
- ExpectedSuccess: false,
- ExpectedErrorCode: http.StatusBadRequest,
- },
- {
- Hosts: []string{testDomainName},
- CSRFile: testBrokenCSRFile,
- ExpectedHTTPStatus: http.StatusBadRequest,
- ExpectedSuccess: false,
- ExpectedErrorCode: 9002,
- },
- }
- func TestSign(t *testing.T) {
- for i, test := range signTests {
- resp, body := testSignFile(t, test.Hosts, test.Subject, test.CSRFile)
- if resp.StatusCode != test.ExpectedHTTPStatus {
- t.Logf("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.Logf("failed to read response body: %v", err)
- t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
- }
- if test.ExpectedSuccess != message.Success {
- t.Logf("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)
- }
- }
- // Test for backward compatibility
- // TODO remove after API transition is complete.
- for i, test := range signTests {
- // an empty hostname is not accepted by the old interface but an empty hosts array should be accepted
- // so skip the case of empty hosts array for the old interface.
- if test.Hosts != nil && len(test.Hosts) == 0 {
- continue
- }
- hostname := strings.Join(test.Hosts, ",")
- resp, body := testSignFileOldInterface(t, hostname, test.CSRFile)
- if resp.StatusCode != test.ExpectedHTTPStatus {
- t.Logf("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.Logf("failed to read response body: %v", err)
- t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
- }
- if test.ExpectedSuccess != message.Success {
- t.Logf("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)
- }
- }
- }
- func newTestAuthHandler(t *testing.T) http.Handler {
- conf, err := config.LoadConfig([]byte(validAuthLocalConfig))
- if err != nil {
- t.Fatal(err)
- }
- h, err := NewAuthHandler(testCaFile, testCaKeyFile, conf.Signing)
- if err != nil {
- t.Fatal(err)
- }
- return h
- }
- func TestNewAuthHandler(t *testing.T) {
- newTestAuthHandler(t)
- }
- func TestNewAuthHandlerWithNoAuthConfig(t *testing.T) {
- conf, err := config.LoadConfig([]byte(validLocalConfig))
- if err != nil {
- t.Fatal(err)
- }
- _, err = NewAuthHandler(testCaFile, testCaKeyFile, conf.Signing)
- if err == nil {
- t.Fatal("Config doesn't have auth keys. Should have failed.")
- }
- return
- }
- func testAuthSignFile(t *testing.T, hosts []string, subject *signer.Subject, csrFile string, profile *config.SigningProfile) (resp *http.Response, body []byte) {
- ts := newAuthSignServer(t)
- defer ts.Close()
- var csrPEM []byte
- if csrFile != "" {
- var err error
- csrPEM, err = os.ReadFile(csrFile)
- if err != nil {
- t.Fatal(err)
- }
- }
- obj := map[string]interface{}{}
- if hosts != nil {
- obj["hosts"] = hosts
- }
- if subject != nil {
- obj["subject"] = subject
- }
- if len(csrPEM) > 0 {
- obj["certificate_request"] = string(csrPEM)
- }
- reqBlob, err := json.Marshal(obj)
- if err != nil {
- t.Fatal(err)
- }
- var aReq auth.AuthenticatedRequest
- aReq.Request = reqBlob
- aReq.Token, err = profile.Provider.Token(aReq.Request)
- if err != nil {
- t.Fatal(err)
- }
- blob, err := json.Marshal(aReq)
- 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 = io.ReadAll(resp.Body)
- if err != nil {
- t.Fatal(err)
- }
- return
- }
- func newAuthSignServer(t *testing.T) *httptest.Server {
- ts := httptest.NewServer(newTestAuthHandler(t))
- return ts
- }
- func TestAuthSign(t *testing.T) {
- conf, err := config.LoadConfig([]byte(validAuthLocalConfig))
- if err != nil {
- t.Fatal(err)
- }
- for i, test := range signTests {
- resp, body := testAuthSignFile(t, test.Hosts, test.Subject, test.CSRFile, conf.Signing.Default)
- if resp.StatusCode != test.ExpectedHTTPStatus {
- t.Logf("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.Logf("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)
- }
- }
- }
|