Merge branch 'master' into issued-names-limit-2
Conflicts: mocks/mocks.go rpc/rpc-wrappers.go sa/storage-authority.go
This commit is contained in:
		
						commit
						acdb1fa91b
					
				|  | @ -203,7 +203,7 @@ func TestRevoke(t *testing.T) { | ||||||
| 	test.AssertNotError(t, err, "Failed to create CA") | 	test.AssertNotError(t, err, "Failed to create CA") | ||||||
| 	ca.PA = ctx.pa | 	ca.PA = ctx.pa | ||||||
| 	ca.SA = ctx.sa | 	ca.SA = ctx.sa | ||||||
| 	ca.Publisher = &mocks.MockPublisher{} | 	ca.Publisher = &mocks.Publisher{} | ||||||
| 
 | 
 | ||||||
| 	csr, _ := x509.ParseCertificateRequest(CNandSANCSR) | 	csr, _ := x509.ParseCertificateRequest(CNandSANCSR) | ||||||
| 	certObj, err := ca.IssueCertificate(*csr, ctx.reg.ID) | 	certObj, err := ca.IssueCertificate(*csr, ctx.reg.ID) | ||||||
|  | @ -242,7 +242,7 @@ func TestIssueCertificate(t *testing.T) { | ||||||
| 	defer ctx.cleanUp() | 	defer ctx.cleanUp() | ||||||
| 	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile) | 	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile) | ||||||
| 	test.AssertNotError(t, err, "Failed to create CA") | 	test.AssertNotError(t, err, "Failed to create CA") | ||||||
| 	ca.Publisher = &mocks.MockPublisher{} | 	ca.Publisher = &mocks.Publisher{} | ||||||
| 	ca.PA = ctx.pa | 	ca.PA = ctx.pa | ||||||
| 	ca.SA = ctx.sa | 	ca.SA = ctx.sa | ||||||
| 
 | 
 | ||||||
|  | @ -319,7 +319,7 @@ func TestRejectNoName(t *testing.T) { | ||||||
| 	defer ctx.cleanUp() | 	defer ctx.cleanUp() | ||||||
| 	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile) | 	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile) | ||||||
| 	test.AssertNotError(t, err, "Failed to create CA") | 	test.AssertNotError(t, err, "Failed to create CA") | ||||||
| 	ca.Publisher = &mocks.MockPublisher{} | 	ca.Publisher = &mocks.Publisher{} | ||||||
| 	ca.PA = ctx.pa | 	ca.PA = ctx.pa | ||||||
| 	ca.SA = ctx.sa | 	ca.SA = ctx.sa | ||||||
| 
 | 
 | ||||||
|  | @ -336,7 +336,7 @@ func TestRejectTooManyNames(t *testing.T) { | ||||||
| 	defer ctx.cleanUp() | 	defer ctx.cleanUp() | ||||||
| 	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile) | 	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile) | ||||||
| 	test.AssertNotError(t, err, "Failed to create CA") | 	test.AssertNotError(t, err, "Failed to create CA") | ||||||
| 	ca.Publisher = &mocks.MockPublisher{} | 	ca.Publisher = &mocks.Publisher{} | ||||||
| 	ca.PA = ctx.pa | 	ca.PA = ctx.pa | ||||||
| 	ca.SA = ctx.sa | 	ca.SA = ctx.sa | ||||||
| 
 | 
 | ||||||
|  | @ -353,7 +353,7 @@ func TestDeduplication(t *testing.T) { | ||||||
| 	defer ctx.cleanUp() | 	defer ctx.cleanUp() | ||||||
| 	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile) | 	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile) | ||||||
| 	test.AssertNotError(t, err, "Failed to create CA") | 	test.AssertNotError(t, err, "Failed to create CA") | ||||||
| 	ca.Publisher = &mocks.MockPublisher{} | 	ca.Publisher = &mocks.Publisher{} | ||||||
| 	ca.PA = ctx.pa | 	ca.PA = ctx.pa | ||||||
| 	ca.SA = ctx.sa | 	ca.SA = ctx.sa | ||||||
| 
 | 
 | ||||||
|  | @ -377,7 +377,7 @@ func TestRejectValidityTooLong(t *testing.T) { | ||||||
| 	defer ctx.cleanUp() | 	defer ctx.cleanUp() | ||||||
| 	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile) | 	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile) | ||||||
| 	test.AssertNotError(t, err, "Failed to create CA") | 	test.AssertNotError(t, err, "Failed to create CA") | ||||||
| 	ca.Publisher = &mocks.MockPublisher{} | 	ca.Publisher = &mocks.Publisher{} | ||||||
| 	ca.PA = ctx.pa | 	ca.PA = ctx.pa | ||||||
| 	ca.SA = ctx.sa | 	ca.SA = ctx.sa | ||||||
| 
 | 
 | ||||||
|  | @ -394,7 +394,7 @@ func TestShortKey(t *testing.T) { | ||||||
| 	ctx := setup(t) | 	ctx := setup(t) | ||||||
| 	defer ctx.cleanUp() | 	defer ctx.cleanUp() | ||||||
| 	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile) | 	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile) | ||||||
| 	ca.Publisher = &mocks.MockPublisher{} | 	ca.Publisher = &mocks.Publisher{} | ||||||
| 	ca.PA = ctx.pa | 	ca.PA = ctx.pa | ||||||
| 	ca.SA = ctx.sa | 	ca.SA = ctx.sa | ||||||
| 
 | 
 | ||||||
|  | @ -410,7 +410,7 @@ func TestRejectBadAlgorithm(t *testing.T) { | ||||||
| 	ctx := setup(t) | 	ctx := setup(t) | ||||||
| 	defer ctx.cleanUp() | 	defer ctx.cleanUp() | ||||||
| 	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile) | 	ca, err := NewCertificateAuthorityImpl(ctx.caConfig, ctx.fc, caCertFile) | ||||||
| 	ca.Publisher = &mocks.MockPublisher{} | 	ca.Publisher = &mocks.Publisher{} | ||||||
| 	ca.PA = ctx.pa | 	ca.PA = ctx.pa | ||||||
| 	ca.SA = ctx.sa | 	ca.SA = ctx.sa | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -130,6 +130,21 @@ func revokeByReg(regID int64, reasonCode core.RevocationCode, deny bool, rac rpc | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // This abstraction is needed so that we can use sort.Sort below
 | ||||||
|  | type revocationCodes []core.RevocationCode | ||||||
|  | 
 | ||||||
|  | func (rc revocationCodes) Len() int { | ||||||
|  | 	return len(rc) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (rc revocationCodes) Less(i, j int) bool { | ||||||
|  | 	return rc[i] < rc[j] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (rc revocationCodes) Swap(i, j int) { | ||||||
|  | 	rc[i], rc[j] = rc[j], rc[i] | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func main() { | func main() { | ||||||
| 	app := cli.NewApp() | 	app := cli.NewApp() | ||||||
| 	app.Name = "admin-revoker" | 	app.Name = "admin-revoker" | ||||||
|  | @ -219,7 +234,7 @@ func main() { | ||||||
| 			Name:  "list-reasons", | 			Name:  "list-reasons", | ||||||
| 			Usage: "List all revocation reason codes", | 			Usage: "List all revocation reason codes", | ||||||
| 			Action: func(c *cli.Context) { | 			Action: func(c *cli.Context) { | ||||||
| 				var codes core.RevocationCodes | 				var codes revocationCodes | ||||||
| 				for k := range core.RevocationReasons { | 				for k := range core.RevocationReasons { | ||||||
| 					codes = append(codes, k) | 					codes = append(codes, k) | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | @ -39,18 +39,18 @@ func main() { | ||||||
| 		go cmd.ProfileCmd("VA", stats) | 		go cmd.ProfileCmd("VA", stats) | ||||||
| 
 | 
 | ||||||
| 		pc := &va.PortConfig{ | 		pc := &va.PortConfig{ | ||||||
| 			SimpleHTTPPort:  80, | 			HTTPPort:  80, | ||||||
| 			SimpleHTTPSPort: 443, | 			HTTPSPort: 443, | ||||||
| 			DVSNIPort:       443, | 			TLSPort:   443, | ||||||
| 		} | 		} | ||||||
| 		if c.VA.PortConfig.SimpleHTTPPort != 0 { | 		if c.VA.PortConfig.HTTPPort != 0 { | ||||||
| 			pc.SimpleHTTPPort = c.VA.PortConfig.SimpleHTTPPort | 			pc.HTTPPort = c.VA.PortConfig.HTTPPort | ||||||
| 		} | 		} | ||||||
| 		if c.VA.PortConfig.SimpleHTTPSPort != 0 { | 		if c.VA.PortConfig.HTTPSPort != 0 { | ||||||
| 			pc.SimpleHTTPSPort = c.VA.PortConfig.SimpleHTTPSPort | 			pc.HTTPSPort = c.VA.PortConfig.HTTPSPort | ||||||
| 		} | 		} | ||||||
| 		if c.VA.PortConfig.DVSNIPort != 0 { | 		if c.VA.PortConfig.TLSPort != 0 { | ||||||
| 			pc.DVSNIPort = c.VA.PortConfig.DVSNIPort | 			pc.TLSPort = c.VA.PortConfig.TLSPort | ||||||
| 		} | 		} | ||||||
| 		vai := va.NewValidationAuthorityImpl(pc, stats, clock.Default()) | 		vai := va.NewValidationAuthorityImpl(pc, stats, clock.Default()) | ||||||
| 		dnsTimeout, err := time.ParseDuration(c.Common.DNSTimeout) | 		dnsTimeout, err := time.ParseDuration(c.Common.DNSTimeout) | ||||||
|  |  | ||||||
|  | @ -145,7 +145,7 @@ func TestCheckCert(t *testing.T) { | ||||||
| 		} | 		} | ||||||
| 		delete(problemsMap, p) | 		delete(problemsMap, p) | ||||||
| 	} | 	} | ||||||
| 	for k, _ := range problemsMap { | 	for k := range problemsMap { | ||||||
| 		t.Errorf("Found unexpected problem '%s'.", k) | 		t.Errorf("Found unexpected problem '%s'.", k) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -56,11 +56,11 @@ func (m *mockMail) SendMail(to []string, msg string) (err error) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type fakeRegStore struct { | type fakeRegStore struct { | ||||||
| 	RegById map[int64]core.Registration | 	RegByID map[int64]core.Registration | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (f fakeRegStore) GetRegistration(id int64) (core.Registration, error) { | func (f fakeRegStore) GetRegistration(id int64) (core.Registration, error) { | ||||||
| 	r, ok := f.RegById[id] | 	r, ok := f.RegByID[id] | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		msg := fmt.Sprintf("no such registration %d", id) | 		msg := fmt.Sprintf("no such registration %d", id) | ||||||
| 		return r, core.NoSuchRegistrationError(msg) | 		return r, core.NoSuchRegistrationError(msg) | ||||||
|  | @ -69,7 +69,7 @@ func (f fakeRegStore) GetRegistration(id int64) (core.Registration, error) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func newFakeRegStore() fakeRegStore { | func newFakeRegStore() fakeRegStore { | ||||||
| 	return fakeRegStore{RegById: make(map[int64]core.Registration)} | 	return fakeRegStore{RegByID: make(map[int64]core.Registration)} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const testTmpl = `hi, cert for DNS names {{.DNSNames}} is going to expire in {{.DaysToExpiration}} days ({{.ExpirationDate}})` | const testTmpl = `hi, cert for DNS names {{.DNSNames}} is going to expire in {{.DaysToExpiration}} days ({{.ExpirationDate}})` | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"crypto/x509" | 	"crypto/x509" | ||||||
| 	"encoding/hex" | 	"encoding/hex" | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
|  | @ -66,14 +67,13 @@ func NewSourceFromDatabase(dbMap *gorp.DbMap, caKeyHash []byte) (src *DBSource, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Response is called by the HTTP server to handle a new OCSP request.
 | // Response is called by the HTTP server to handle a new OCSP request.
 | ||||||
| func (src *DBSource) Response(req *ocsp.Request) (response []byte, present bool) { | func (src *DBSource) Response(req *ocsp.Request) ([]byte, bool) { | ||||||
| 	log := blog.GetAuditLogger() | 	log := blog.GetAuditLogger() | ||||||
| 
 | 
 | ||||||
| 	// Check that this request is for the proper CA
 | 	// Check that this request is for the proper CA
 | ||||||
| 	if bytes.Compare(req.IssuerKeyHash, src.caKeyHash) != 0 { | 	if bytes.Compare(req.IssuerKeyHash, src.caKeyHash) != 0 { | ||||||
| 		log.Debug(fmt.Sprintf("Request intended for CA Cert ID: %s", hex.EncodeToString(req.IssuerKeyHash))) | 		log.Debug(fmt.Sprintf("Request intended for CA Cert ID: %s", hex.EncodeToString(req.IssuerKeyHash))) | ||||||
| 		present = false | 		return nil, false | ||||||
| 		return |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	serialString := core.SerialToString(req.SerialNumber) | 	serialString := core.SerialToString(req.SerialNumber) | ||||||
|  | @ -86,37 +86,33 @@ func (src *DBSource) Response(req *ocsp.Request) (response []byte, present bool) | ||||||
| 	err := src.dbMap.SelectOne(&ocspResponse, "SELECT * from ocspResponses WHERE serial = :serial ORDER BY id DESC LIMIT 1;", | 	err := src.dbMap.SelectOne(&ocspResponse, "SELECT * from ocspResponses WHERE serial = :serial ORDER BY id DESC LIMIT 1;", | ||||||
| 		map[string]interface{}{"serial": serialString}) | 		map[string]interface{}{"serial": serialString}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		present = false | 		return nil, false | ||||||
| 		return |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	log.Info(fmt.Sprintf("OCSP Response sent for CA=%s, Serial=%s", hex.EncodeToString(src.caKeyHash), serialString)) | 	log.Info(fmt.Sprintf("OCSP Response sent for CA=%s, Serial=%s", hex.EncodeToString(src.caKeyHash), serialString)) | ||||||
| 
 | 
 | ||||||
| 	response = ocspResponse.Response | 	return ocspResponse.Response, true | ||||||
| 	present = true |  | ||||||
| 	return |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func makeDBSource(dbConnect, issuerCert string, sqlDebug bool) (cfocsp.Source, error) { | func makeDBSource(dbConnect, issuerCert string, sqlDebug bool) (*DBSource, error) { | ||||||
| 	var noSource cfocsp.Source |  | ||||||
| 	// Configure DB
 | 	// Configure DB
 | ||||||
| 	dbMap, err := sa.NewDbMap(dbConnect) | 	dbMap, err := sa.NewDbMap(dbConnect) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return noSource, fmt.Errorf("Could not connect to database: %s", err) | 		return nil, fmt.Errorf("Could not connect to database: %s", err) | ||||||
| 	} | 	} | ||||||
| 	sa.SetSQLDebug(dbMap, sqlDebug) | 	sa.SetSQLDebug(dbMap, sqlDebug) | ||||||
| 
 | 
 | ||||||
| 	// Load the CA's key so we can store its SubjectKey in the DB
 | 	// Load the CA's key so we can store its SubjectKey in the DB
 | ||||||
| 	caCertDER, err := cmd.LoadCert(issuerCert) | 	caCertDER, err := cmd.LoadCert(issuerCert) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return noSource, fmt.Errorf("Could not read issuer cert %s: %s", issuerCert, err) | 		return nil, fmt.Errorf("Could not read issuer cert %s: %s", issuerCert, err) | ||||||
| 	} | 	} | ||||||
| 	caCert, err := x509.ParseCertificate(caCertDER) | 	caCert, err := x509.ParseCertificate(caCertDER) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return noSource, fmt.Errorf("Could not parse issuer cert %s: %s", issuerCert, err) | 		return nil, fmt.Errorf("Could not parse issuer cert %s: %s", issuerCert, err) | ||||||
| 	} | 	} | ||||||
| 	if len(caCert.SubjectKeyId) == 0 { | 	if len(caCert.SubjectKeyId) == 0 { | ||||||
| 		return noSource, fmt.Errorf("Empty subjectKeyID") | 		return nil, fmt.Errorf("Empty subjectKeyID") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Construct source from DB
 | 	// Construct source from DB
 | ||||||
|  | @ -161,6 +157,8 @@ func main() { | ||||||
| 			} | 			} | ||||||
| 			source, err = cfocsp.NewSourceFromFile(filename) | 			source, err = cfocsp.NewSourceFromFile(filename) | ||||||
| 			cmd.FailOnError(err, fmt.Sprintf("Couldn't read file: %s", url.Path)) | 			cmd.FailOnError(err, fmt.Sprintf("Couldn't read file: %s", url.Path)) | ||||||
|  | 		} else { | ||||||
|  | 			cmd.FailOnError(errors.New(`"source" parameter not found in JSON config`), "unable to start ocsp-responder") | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		stopTimeout, err := time.ParseDuration(c.OCSPResponder.ShutdownStopTimeout) | 		stopTimeout, err := time.ParseDuration(c.OCSPResponder.ShutdownStopTimeout) | ||||||
|  |  | ||||||
|  | @ -1,16 +1,23 @@ | ||||||
| package main | package main | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/http/httptest" | 	"net/http/httptest" | ||||||
|  | 	"os" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp" | 	cfocsp "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/ocsp" | ||||||
|  | 	"github.com/letsencrypt/boulder/Godeps/_workspace/src/golang.org/x/crypto/ocsp" | ||||||
|  | 	"github.com/letsencrypt/boulder/core" | ||||||
|  | 	"github.com/letsencrypt/boulder/test" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestCacheControl(t *testing.T) { | func TestCacheControl(t *testing.T) { | ||||||
| 	src := make(ocsp.InMemorySource) | 	src := make(cfocsp.InMemorySource) | ||||||
| 	h := handler(src, 10*time.Second) | 	h := handler(src, 10*time.Second) | ||||||
| 	w := httptest.NewRecorder() | 	w := httptest.NewRecorder() | ||||||
| 	r, err := http.NewRequest("GET", "/", nil) | 	r, err := http.NewRequest("GET", "/", nil) | ||||||
|  | @ -24,3 +31,80 @@ func TestCacheControl(t *testing.T) { | ||||||
| 		t.Errorf("Cache-Control value: want %#v, got %#v", expected, actual) | 		t.Errorf("Cache-Control value: want %#v, got %#v", expected, actual) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	req  = mustRead("./testdata/ocsp.req") | ||||||
|  | 	resp = mustRead("./testdata/ocsp.resp") | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestHandler(t *testing.T) { | ||||||
|  | 	ocspReq, err := ocsp.ParseRequest(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("ocsp.ParseRequest: %s", err) | ||||||
|  | 	} | ||||||
|  | 	src := make(cfocsp.InMemorySource) | ||||||
|  | 	src[ocspReq.SerialNumber.String()] = resp | ||||||
|  | 
 | ||||||
|  | 	h := handler(src, 10*time.Second) | ||||||
|  | 	w := httptest.NewRecorder() | ||||||
|  | 	r, err := http.NewRequest("POST", "/", bytes.NewReader(req)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	h.ServeHTTP(w, r) | ||||||
|  | 	if w.Code != http.StatusOK { | ||||||
|  | 		t.Errorf("Code: want %d, got %d", http.StatusOK, w.Code) | ||||||
|  | 	} | ||||||
|  | 	if !bytes.Equal(w.Body.Bytes(), resp) { | ||||||
|  | 		t.Errorf("Mismatched body: want %#v, got %#v", resp, w.Body.Bytes()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestDBHandler(t *testing.T) { | ||||||
|  | 	src, err := makeDBSource("mysql+tcp://boulder@localhost:3306/boulder_sa_test", "./testdata/test-ca.der.pem", false) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("makeDBSource: %s", err) | ||||||
|  | 	} | ||||||
|  | 	defer test.ResetTestDatabase(t, src.dbMap.Db) | ||||||
|  | 	ocspResp, err := ocsp.ParseResponse(resp, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("ocsp.ParseResponse: %s", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	dbOCSP := &core.OCSPResponse{ | ||||||
|  | 		Serial:    core.SerialToString(ocspResp.SerialNumber), | ||||||
|  | 		CreatedAt: time.Now(), | ||||||
|  | 		Response:  resp, | ||||||
|  | 	} | ||||||
|  | 	err = src.dbMap.Insert(dbOCSP) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unable to insert response: %s", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	h := handler(src, 10*time.Second) | ||||||
|  | 	w := httptest.NewRecorder() | ||||||
|  | 	r, err := http.NewRequest("POST", "/", bytes.NewReader(req)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	h.ServeHTTP(w, r) | ||||||
|  | 	if w.Code != http.StatusOK { | ||||||
|  | 		t.Errorf("Code: want %d, got %d", http.StatusOK, w.Code) | ||||||
|  | 	} | ||||||
|  | 	if !bytes.Equal(w.Body.Bytes(), resp) { | ||||||
|  | 		t.Errorf("Mismatched body: want %#v, got %#v", resp, w.Body.Bytes()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func mustRead(path string) []byte { | ||||||
|  | 	f, err := os.Open(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(fmt.Sprintf("open %#v: %s", path, err)) | ||||||
|  | 	} | ||||||
|  | 	b, err := ioutil.ReadAll(f) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(fmt.Sprintf("read all %#v: %s", path, err)) | ||||||
|  | 	} | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | -----BEGIN CERTIFICATE----- | ||||||
|  | MIIDETCCAfmgAwIBAgIJAJzxkS6o1QkIMA0GCSqGSIb3DQEBCwUAMB8xHTAbBgNV | ||||||
|  | BAMMFGhhcHB5IGhhY2tlciBmYWtlIENBMB4XDTE1MDQwNzIzNTAzOFoXDTI1MDQw | ||||||
|  | NDIzNTAzOFowHzEdMBsGA1UEAwwUaGFwcHkgaGFja2VyIGZha2UgQ0EwggEiMA0G | ||||||
|  | CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCCkd5mgXFErJ3F2M0E9dw+Ta/md5i | ||||||
|  | 8TDId01HberAApqmydG7UZYF3zLTSzNjlNSOmtybvrSGUnZ9r9tSQcL8VM6WUOM8 | ||||||
|  | tnIpiIjEA2QkBycMwvRmZ/B2ltPdYs/R9BqNwO1g18GDZrHSzUYtNKNeFI6Glamj | ||||||
|  | 7GK2Vr0SmiEamlNIR5ktAFsEErzf/d4jCF7sosMsJpMCm1p58QkP4LHLShVLXDa8 | ||||||
|  | BMfVoI+ipYcA08iNUFkgW8VWDclIDxcysa0psDDtMjX3+4aPkE/cefmP+1xOfUuD | ||||||
|  | HOGV8XFynsP4EpTfVOZr0/g9gYQ7ZArqXX7GTQkFqduwPm/w5qxSPTarAgMBAAGj | ||||||
|  | UDBOMB0GA1UdDgQWBBT7eE8S+WAVgyyfF380GbMuNupBiTAfBgNVHSMEGDAWgBT7 | ||||||
|  | eE8S+WAVgyyfF380GbMuNupBiTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA | ||||||
|  | A4IBAQAd9Da+Zv+TjMv7NTAmliqnWHY6d3UxEZN3hFEJ58IQVHbBZVZdW7zhRktB | ||||||
|  | vR05Kweac0HJeK91TKmzvXl21IXLvh0gcNLU/uweD3no/snfdB4OoFompljThmgl | ||||||
|  | zBqiqWoKBJQrLCA8w5UB+ReomRYd/EYXF/6TAfzm6hr//Xt5mPiUHPdvYt75lMAo | ||||||
|  | vRxLSbF8TSQ6b7BYxISWjPgFASNNqJNHEItWsmQMtAjjwzb9cs01XH9pChVAWn9L | ||||||
|  | oeMKa+SlHSYrWG93+EcrIH/dGU76uNOiaDzBSKvaehG53h25MHuO1anNICJvZovW | ||||||
|  | rFo4Uv1EnkKJm3vJFe50eJGhEKlx | ||||||
|  | -----END CERTIFICATE----- | ||||||
|  | @ -0,0 +1,28 @@ | ||||||
|  | -----BEGIN PRIVATE KEY----- | ||||||
|  | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDCCkd5mgXFErJ3 | ||||||
|  | F2M0E9dw+Ta/md5i8TDId01HberAApqmydG7UZYF3zLTSzNjlNSOmtybvrSGUnZ9 | ||||||
|  | r9tSQcL8VM6WUOM8tnIpiIjEA2QkBycMwvRmZ/B2ltPdYs/R9BqNwO1g18GDZrHS | ||||||
|  | zUYtNKNeFI6Glamj7GK2Vr0SmiEamlNIR5ktAFsEErzf/d4jCF7sosMsJpMCm1p5 | ||||||
|  | 8QkP4LHLShVLXDa8BMfVoI+ipYcA08iNUFkgW8VWDclIDxcysa0psDDtMjX3+4aP | ||||||
|  | kE/cefmP+1xOfUuDHOGV8XFynsP4EpTfVOZr0/g9gYQ7ZArqXX7GTQkFqduwPm/w | ||||||
|  | 5qxSPTarAgMBAAECggEAZh00uhjFOo35X1TufwSGF0z/c9uMvfMB4i1ufM2qgXud | ||||||
|  | WXLSLcrksZhhTfLAS4KSTa3PtSKqLBoPg1tdhy9WZqZWxaIxw8ybzaGtn8HNHGyr | ||||||
|  | LzsVlSLT2ATN4C7VAT9+DeVext0kWHtdz3r5mGagJq2Yx9jRGpQW6rBA9h4ol699 | ||||||
|  | BM09UPCcdlGmpdrb0jDjyfohG139EBSmEeB+Jim+oLO1sXe/LvWllU0UL527CExp | ||||||
|  | ykiIjASd4s7tFErV9sVJ+bDI97GOyBUGcVMiQ+TRPKFr0kfLgbJz24l8ycPI4odp | ||||||
|  | IGY+6igicg67n5BktAH+UfCQlUIpWbF2SwRAMht0AQKBgQD8gocy2VuCPj285hBY | ||||||
|  | 8g/1GFd58HkCh54bOhAOb2PK+NE4mRuHCBlBj/tQOmgYz2Pna2k5ldJSUwXsUKkx | ||||||
|  | 9R7hutnwXbcQTSQIRcjhYDLeGetJYXR96ylDig+6XjdW3A5SIc2JzlbVThP39TTm | ||||||
|  | gRqE/rj9G4ARMfHxffp7YT5AqwKBgQDEuN0pYMKjaW0xvc7WYUOqGHqt2di/BwMr | ||||||
|  | Ur438MtePArELY35P6kDcrfnlacDToA3Tebk9Rw18y1kl3BFO7VdJbQJSa6RWbp5 | ||||||
|  | aK7E5lq1pCrdyhGwiaI1f5VgzeY8ywS3TqGqU9GOqpENiZqgs1ly9l8gZSaw8/yF | ||||||
|  | uDWGg7jiAQKBgQCyLtGEmkiuoYkjUR1cBoQoKeMgkwZxOI3jHJfT99ptkiLhU3lP | ||||||
|  | UfGwiA+JT43BZCdVWEBKeGSP3zIgzdJ3BEekdhvwN9FEWYsBo2zbTOzYOWYExBZV | ||||||
|  | /KmDlVr/4hge3O3mGyBVDBvOLWh94rRPq+6wxqZ3RP6cI6hdBs7IXZh2PQKBgQDB | ||||||
|  | rav4kA4xKpvaDCC2yj3/Gmi1/zO5J2NEZQtoMgdXeM+0w5Dy4204Otq7A4jR5Ziw | ||||||
|  | Wl9H7dZfe1Kmpb5gO1/dHEC7oDJhYjEIVTs0GgMWsFGP2OE/qNHtz/W2wCC8m7jB | ||||||
|  | 7IWYFzvLNTzoUiDNtKYNXGjdkRjdwOlOkcUI8Wi2AQKBgQC9EJsMz/ySt58IvwWy | ||||||
|  | fQJyg742j21pXHqlMnmHygnSgNa7f3yPQK3FxjvhIPmgu7x8+sSUtXHOjKhZML3p | ||||||
|  | SdTm/yN487hOYp03jy/wVXLcCDp9XhBeIt/z/TZMPMjAHOLG9xG6cF8AOVq7mLBc | ||||||
|  | tsDWUHoXPZj/YciXZLq3fPuXyw== | ||||||
|  | -----END PRIVATE KEY----- | ||||||
							
								
								
									
										30
									
								
								cmd/shell.go
								
								
								
								
							
							
						
						
									
										30
									
								
								cmd/shell.go
								
								
								
								
							|  | @ -30,7 +30,7 @@ import ( | ||||||
| 	"log" | 	"log" | ||||||
| 	"net" | 	"net" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	_ "net/http/pprof" | 	_ "net/http/pprof" // HTTP performance profiling, added transparently to HTTP APIs
 | ||||||
| 	"os" | 	"os" | ||||||
| 	"runtime" | 	"runtime" | ||||||
| 	"time" | 	"time" | ||||||
|  | @ -42,6 +42,7 @@ import ( | ||||||
| 	"github.com/letsencrypt/boulder/core" | 	"github.com/letsencrypt/boulder/core" | ||||||
| 	blog "github.com/letsencrypt/boulder/log" | 	blog "github.com/letsencrypt/boulder/log" | ||||||
| 	"github.com/letsencrypt/boulder/publisher" | 	"github.com/letsencrypt/boulder/publisher" | ||||||
|  | 	"github.com/letsencrypt/boulder/va" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Config stores configuration parameters that applications
 | // Config stores configuration parameters that applications
 | ||||||
|  | @ -114,11 +115,7 @@ type Config struct { | ||||||
| 	VA struct { | 	VA struct { | ||||||
| 		UserAgent string | 		UserAgent string | ||||||
| 
 | 
 | ||||||
| 		PortConfig struct { | 		PortConfig va.PortConfig | ||||||
| 			SimpleHTTPPort  int |  | ||||||
| 			SimpleHTTPSPort int |  | ||||||
| 			DVSNIPort       int |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		MaxConcurrentRPCServerRequests int64 | 		MaxConcurrentRPCServerRequests int64 | ||||||
| 
 | 
 | ||||||
|  | @ -220,6 +217,9 @@ type Config struct { | ||||||
| 	SubscriberAgreementURL string | 	SubscriberAgreementURL string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // CAConfig structs have configuration information for the certificate
 | ||||||
|  | // authority, including database parameters as well as controls for
 | ||||||
|  | // issued certificates.
 | ||||||
| type CAConfig struct { | type CAConfig struct { | ||||||
| 	Profile      string | 	Profile      string | ||||||
| 	TestMode     bool | 	TestMode     bool | ||||||
|  | @ -242,6 +242,8 @@ type CAConfig struct { | ||||||
| 	DebugAddr string | 	DebugAddr string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // PAConfig specifies how a policy authority should connect to its
 | ||||||
|  | // database, and what policies it should enforce.
 | ||||||
| type PAConfig struct { | type PAConfig struct { | ||||||
| 	DBConnect              string | 	DBConnect              string | ||||||
| 	EnforcePolicyWhitelist bool | 	EnforcePolicyWhitelist bool | ||||||
|  | @ -301,6 +303,7 @@ type AppShell struct { | ||||||
| 	App    *cli.App | 	App    *cli.App | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Version returns a string representing the version of boulder running.
 | ||||||
| func Version() string { | func Version() string { | ||||||
| 	return fmt.Sprintf("0.1.0 [%s]", core.GetBuildID()) | 	return fmt.Sprintf("0.1.0 [%s]", core.GetBuildID()) | ||||||
| } | } | ||||||
|  | @ -412,6 +415,11 @@ func LoadCert(path string) (cert []byte, err error) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // DebugServer starts a server to receive debug information.  Typical
 | ||||||
|  | // usage is to start it in a goroutine, configured with an address
 | ||||||
|  | // from the appropriate configuration object:
 | ||||||
|  | //
 | ||||||
|  | //   go cmd.DebugServer(c.XA.DebugAddr)
 | ||||||
| func DebugServer(addr string) { | func DebugServer(addr string) { | ||||||
| 	if addr == "" { | 	if addr == "" { | ||||||
| 		log.Fatalf("unable to boot debug server because no address was given for it. Set debugAddr.") | 		log.Fatalf("unable to boot debug server because no address was given for it. Set debugAddr.") | ||||||
|  | @ -424,12 +432,19 @@ func DebugServer(addr string) { | ||||||
| 	log.Println(http.Serve(ln, nil)) | 	log.Println(http.Serve(ln, nil)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // ConfigDuration is just an alias for time.Duration that allows
 | ||||||
|  | // serialization to YAML as well as JSON.
 | ||||||
| type ConfigDuration struct { | type ConfigDuration struct { | ||||||
| 	time.Duration | 	time.Duration | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // ErrDurationMustBeString is returned when a non-string value is
 | ||||||
|  | // presented to be deserialized as a ConfigDuration
 | ||||||
| var ErrDurationMustBeString = errors.New("cannot JSON unmarshal something other than a string into a ConfigDuration") | var ErrDurationMustBeString = errors.New("cannot JSON unmarshal something other than a string into a ConfigDuration") | ||||||
| 
 | 
 | ||||||
|  | // UnmarshalJSON parses a string into a ConfigDuration using
 | ||||||
|  | // time.ParseDuration.  If the input does not unmarshal as a
 | ||||||
|  | // string, then UnmarshalJSON returns ErrDurationMustBeString.
 | ||||||
| func (d *ConfigDuration) UnmarshalJSON(b []byte) error { | func (d *ConfigDuration) UnmarshalJSON(b []byte) error { | ||||||
| 	s := "" | 	s := "" | ||||||
| 	err := json.Unmarshal(b, &s) | 	err := json.Unmarshal(b, &s) | ||||||
|  | @ -444,10 +459,13 @@ func (d *ConfigDuration) UnmarshalJSON(b []byte) error { | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // MarshalJSON returns the string form of the duration, as a byte array.
 | ||||||
| func (d ConfigDuration) MarshalJSON() ([]byte, error) { | func (d ConfigDuration) MarshalJSON() ([]byte, error) { | ||||||
| 	return []byte(d.Duration.String()), nil | 	return []byte(d.Duration.String()), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // UnmarshalYAML uses the same frmat as JSON, but is called by the YAML
 | ||||||
|  | // parser (vs. the JSON parser).
 | ||||||
| func (d *ConfigDuration) UnmarshalYAML(unmarshal func(interface{}) error) error { | func (d *ConfigDuration) UnmarshalYAML(unmarshal func(interface{}) error) error { | ||||||
| 	var s string | 	var s string | ||||||
| 	if err := unmarshal(&s); err != nil { | 	if err := unmarshal(&s); err != nil { | ||||||
|  |  | ||||||
|  | @ -5,31 +5,45 @@ | ||||||
| 
 | 
 | ||||||
| package core | package core | ||||||
| 
 | 
 | ||||||
| // SimpleHTTPChallenge constructs a random HTTP challenge
 | import ( | ||||||
| func SimpleHTTPChallenge() Challenge { | 	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose" | ||||||
| 	tls := true | ) | ||||||
|  | 
 | ||||||
|  | func newChallenge(challengeType string, accountKey *jose.JsonWebKey) Challenge { | ||||||
| 	return Challenge{ | 	return Challenge{ | ||||||
| 		Type:   ChallengeTypeSimpleHTTP, | 		Type:       challengeType, | ||||||
| 		Status:     StatusPending, | 		Status:     StatusPending, | ||||||
|  | 		AccountKey: accountKey, | ||||||
| 		Token:      NewToken(), | 		Token:      NewToken(), | ||||||
| 		TLS:    &tls, |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // SimpleHTTPChallenge constructs a random HTTP challenge
 | ||||||
|  | // TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
 | ||||||
|  | func SimpleHTTPChallenge(accountKey *jose.JsonWebKey) Challenge { | ||||||
|  | 	challenge := newChallenge(ChallengeTypeSimpleHTTP, accountKey) | ||||||
|  | 	tls := true | ||||||
|  | 	challenge.TLS = &tls | ||||||
|  | 	return challenge | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // DvsniChallenge constructs a random DVSNI challenge
 | // DvsniChallenge constructs a random DVSNI challenge
 | ||||||
| func DvsniChallenge() Challenge { | // TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
 | ||||||
| 	return Challenge{ | func DvsniChallenge(accountKey *jose.JsonWebKey) Challenge { | ||||||
| 		Type:   ChallengeTypeDVSNI, | 	return newChallenge(ChallengeTypeDVSNI, accountKey) | ||||||
| 		Status: StatusPending, |  | ||||||
| 		Token:  NewToken(), |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // DNSChallenge constructs a random DNS challenge
 | // HTTPChallenge01 constructs a random http-01 challenge
 | ||||||
| func DNSChallenge() Challenge { | func HTTPChallenge01(accountKey *jose.JsonWebKey) Challenge { | ||||||
| 	return Challenge{ | 	return newChallenge(ChallengeTypeHTTP01, accountKey) | ||||||
| 		Type:   ChallengeTypeDNS, | } | ||||||
| 		Status: StatusPending, | 
 | ||||||
| 		Token:  NewToken(), | // TLSSNIChallenge01 constructs a random tls-sni-00 challenge
 | ||||||
| 	} | func TLSSNIChallenge01(accountKey *jose.JsonWebKey) Challenge { | ||||||
|  | 	return newChallenge(ChallengeTypeTLSSNI01, accountKey) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DNSChallenge01 constructs a random DNS challenge
 | ||||||
|  | func DNSChallenge01(accountKey *jose.JsonWebKey) Challenge { | ||||||
|  | 	return newChallenge(ChallengeTypeDNS01, accountKey) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -13,22 +13,48 @@ import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // challenges.go
 | // challenges.go
 | ||||||
| 
 | 
 | ||||||
|  | var accountKeyJSON = `{ | ||||||
|  |   "kty":"RSA", | ||||||
|  |   "n":"yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ", | ||||||
|  |   "e":"AQAB" | ||||||
|  | }` | ||||||
|  | 
 | ||||||
| func TestChallenges(t *testing.T) { | func TestChallenges(t *testing.T) { | ||||||
| 	simpleHTTP := SimpleHTTPChallenge() | 	var accountKey *jose.JsonWebKey | ||||||
| 	if simpleHTTP.Status != StatusPending { | 	err := json.Unmarshal([]byte(accountKeyJSON), &accountKey) | ||||||
| 		t.Errorf("Incorrect status for challenge: %v", simpleHTTP.Status) | 	if err != nil { | ||||||
| 	} | 		t.Errorf("Error unmarshaling JWK: %v", err) | ||||||
| 	if len(simpleHTTP.Token) != 43 { |  | ||||||
| 		t.Errorf("Incorrect length for simpleHTTP token: %v", simpleHTTP.Token) |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	dvsni := DvsniChallenge() | 	simpleHTTP := SimpleHTTPChallenge(accountKey) | ||||||
| 	if dvsni.Status != StatusPending { | 	if !simpleHTTP.IsSane(false) { | ||||||
| 		t.Errorf("Incorrect status for challenge: %v", dvsni.Status) | 		t.Errorf("New HTTP challenge is not sane: %v", simpleHTTP) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	dvsni := DvsniChallenge(accountKey) | ||||||
|  | 	if !dvsni.IsSane(false) { | ||||||
|  | 		t.Errorf("New DVSNI challenge is not sane: %v", dvsni) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	http01 := HTTPChallenge01(accountKey) | ||||||
|  | 	if !http01.IsSane(false) { | ||||||
|  | 		t.Errorf("New http-01 challenge is not sane: %v", http01) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	tlssni01 := TLSSNIChallenge01(accountKey) | ||||||
|  | 	if !tlssni01.IsSane(false) { | ||||||
|  | 		t.Errorf("New tls-sni-01 challenge is not sane: %v", tlssni01) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	dns01 := DNSChallenge01(accountKey) | ||||||
|  | 	if !dns01.IsSane(false) { | ||||||
|  | 		t.Errorf("New dns-01 challenge is not sane: %v", dns01) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -101,8 +127,8 @@ func TestMergeChallenge(t *testing.T) { | ||||||
| 	if probe.Validated != merged.Validated { | 	if probe.Validated != merged.Validated { | ||||||
| 		t.Errorf("MergeChallenge allowed response to overwrite completed time") | 		t.Errorf("MergeChallenge allowed response to overwrite completed time") | ||||||
| 	} | 	} | ||||||
| 	if probe.Token != merged.Token { | 	if probe.KeyAuthorization != merged.KeyAuthorization { | ||||||
| 		t.Errorf("MergeChallenge allowed response to overwrite status") | 		t.Errorf("MergeChallenge allowed response to overwrite authorized key") | ||||||
| 	} | 	} | ||||||
| 	if probe.TLS != merged.TLS { | 	if probe.TLS != merged.TLS { | ||||||
| 		t.Errorf("MergeChallenge failed to overwrite TLS") | 		t.Errorf("MergeChallenge failed to overwrite TLS") | ||||||
|  |  | ||||||
|  | @ -98,7 +98,7 @@ type CertificateAuthority interface { | ||||||
| // PolicyAuthority defines the public interface for the Boulder PA
 | // PolicyAuthority defines the public interface for the Boulder PA
 | ||||||
| type PolicyAuthority interface { | type PolicyAuthority interface { | ||||||
| 	WillingToIssue(id AcmeIdentifier, regID int64) error | 	WillingToIssue(id AcmeIdentifier, regID int64) error | ||||||
| 	ChallengesFor(AcmeIdentifier) ([]Challenge, [][]int) | 	ChallengesFor(AcmeIdentifier, *jose.JsonWebKey) ([]Challenge, [][]int, error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // StorageGetter are the Boulder SA's read-only methods
 | // StorageGetter are the Boulder SA's read-only methods
 | ||||||
|  |  | ||||||
							
								
								
									
										225
									
								
								core/objects.go
								
								
								
								
							
							
						
						
									
										225
									
								
								core/objects.go
								
								
								
								
							|  | @ -6,6 +6,7 @@ | ||||||
| package core | package core | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/subtle" | ||||||
| 	"crypto/x509" | 	"crypto/x509" | ||||||
| 	"encoding/asn1" | 	"encoding/asn1" | ||||||
| 	"encoding/base64" | 	"encoding/base64" | ||||||
|  | @ -91,11 +92,13 @@ const ( | ||||||
| const ( | const ( | ||||||
| 	ChallengeTypeSimpleHTTP = "simpleHttp" | 	ChallengeTypeSimpleHTTP = "simpleHttp" | ||||||
| 	ChallengeTypeDVSNI      = "dvsni" | 	ChallengeTypeDVSNI      = "dvsni" | ||||||
| 	ChallengeTypeDNS        = "dns" | 	ChallengeTypeHTTP01     = "http-01" | ||||||
|  | 	ChallengeTypeTLSSNI01   = "tls-sni-01" | ||||||
|  | 	ChallengeTypeDNS01      = "dns-01" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // The suffix appended to pseudo-domain names in DVSNI challenges
 | // The suffix appended to pseudo-domain names in DVSNI challenges
 | ||||||
| const DVSNISuffix = "acme.invalid" | const TLSSNISuffix = "acme.invalid" | ||||||
| 
 | 
 | ||||||
| // The label attached to DNS names in DNS challenges
 | // The label attached to DNS names in DNS challenges
 | ||||||
| const DNSPrefix = "_acme-challenge" | const DNSPrefix = "_acme-challenge" | ||||||
|  | @ -192,6 +195,97 @@ type ValidationRecord struct { | ||||||
| 	AddressUsed       net.IP   `json:"addressUsed"` | 	AddressUsed       net.IP   `json:"addressUsed"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // KeyAuthorization represents a domain holder's authorization for a
 | ||||||
|  | // specific account key to satisfy a specific challenge.
 | ||||||
|  | type KeyAuthorization struct { | ||||||
|  | 	Token      string | ||||||
|  | 	Thumbprint string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewKeyAuthorization computes the thumbprint and assembles the object
 | ||||||
|  | func NewKeyAuthorization(token string, key *jose.JsonWebKey) (KeyAuthorization, error) { | ||||||
|  | 	if key == nil { | ||||||
|  | 		return KeyAuthorization{}, fmt.Errorf("Cannot authorize a nil key") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	thumbprint, err := Thumbprint(key) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return KeyAuthorization{}, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return KeyAuthorization{ | ||||||
|  | 		Token:      token, | ||||||
|  | 		Thumbprint: thumbprint, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewKeyAuthorizationFromString parses the string and composes a key authorization struct
 | ||||||
|  | func NewKeyAuthorizationFromString(input string) (ka KeyAuthorization, err error) { | ||||||
|  | 	parts := strings.Split(input, ".") | ||||||
|  | 	if len(parts) != 2 { | ||||||
|  | 		err = fmt.Errorf("Invalid key authorization: %d parts", len(parts)) | ||||||
|  | 		return | ||||||
|  | 	} else if !LooksLikeAToken(parts[0]) { | ||||||
|  | 		err = fmt.Errorf("Invalid key authorization: malformed token") | ||||||
|  | 		return | ||||||
|  | 	} else if !LooksLikeAToken(parts[1]) { | ||||||
|  | 		// Thumbprints have the same syntax as tokens in boulder
 | ||||||
|  | 		// Both are base64-encoded and 32 octets
 | ||||||
|  | 		err = fmt.Errorf("Invalid key authorization: malformed key thumbprint") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ka = KeyAuthorization{ | ||||||
|  | 		Token:      parts[0], | ||||||
|  | 		Thumbprint: parts[1], | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // String produces the string representation of a key authorization
 | ||||||
|  | func (ka KeyAuthorization) String() string { | ||||||
|  | 	return ka.Token + "." + ka.Thumbprint | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Match determines whether this KeyAuthorization matches the given token and key
 | ||||||
|  | func (ka KeyAuthorization) Match(token string, key *jose.JsonWebKey) bool { | ||||||
|  | 	if key == nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	thumbprint, err := Thumbprint(key) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	tokensEqual := subtle.ConstantTimeCompare([]byte(token), []byte(ka.Token)) | ||||||
|  | 	thumbprintsEqual := subtle.ConstantTimeCompare([]byte(thumbprint), []byte(ka.Thumbprint)) | ||||||
|  | 
 | ||||||
|  | 	return tokensEqual == 1 && thumbprintsEqual == 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MarshalJSON packs a key authorization into its string representation
 | ||||||
|  | func (ka KeyAuthorization) MarshalJSON() (result []byte, err error) { | ||||||
|  | 	return json.Marshal(ka.String()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UnmarshalJSON unpacks a key authorization from a string
 | ||||||
|  | func (ka *KeyAuthorization) UnmarshalJSON(data []byte) (err error) { | ||||||
|  | 	var str string | ||||||
|  | 	err = json.Unmarshal(data, &str) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	parsed, err := NewKeyAuthorizationFromString(str) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	*ka = parsed | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Challenge is an aggregate of all data needed for any challenges.
 | // Challenge is an aggregate of all data needed for any challenges.
 | ||||||
| //
 | //
 | ||||||
| // Rather than define individual types for different types of
 | // Rather than define individual types for different types of
 | ||||||
|  | @ -216,15 +310,18 @@ type Challenge struct { | ||||||
| 	// A URI to which a response can be POSTed
 | 	// A URI to which a response can be POSTed
 | ||||||
| 	URI string `json:"uri"` | 	URI string `json:"uri"` | ||||||
| 
 | 
 | ||||||
| 	// Used by simpleHttp, dvsni, and dns challenges
 | 	// Used by simpleHttp, http-00, tls-sni-00, and dns-00 challenges
 | ||||||
| 	Token string `json:"token,omitempty"` | 	Token string `json:"token,omitempty"` | ||||||
| 
 | 
 | ||||||
| 	// Used by simpleHTTP challenges
 | 	// Used by simpleHttp challenges
 | ||||||
| 	TLS *bool `json:"tls,omitempty"` | 	TLS *bool `json:"tls,omitempty"` | ||||||
| 
 | 
 | ||||||
| 	// Used by dns and dvsni challenges
 | 	// Used by dvsni challenges
 | ||||||
| 	Validation *jose.JsonWebSignature `json:"validation,omitempty"` | 	Validation *jose.JsonWebSignature `json:"validation,omitempty"` | ||||||
| 
 | 
 | ||||||
|  | 	// Used by http-00, tls-sni-00, and dns-00 challenges
 | ||||||
|  | 	KeyAuthorization *KeyAuthorization `json:"keyAuthorization,omitempty"` | ||||||
|  | 
 | ||||||
| 	// Contains information about URLs used or redirected to and IPs resolved and
 | 	// Contains information about URLs used or redirected to and IPs resolved and
 | ||||||
| 	// used
 | 	// used
 | ||||||
| 	ValidationRecord []ValidationRecord `json:"validationRecord,omitempty"` | 	ValidationRecord []ValidationRecord `json:"validationRecord,omitempty"` | ||||||
|  | @ -249,6 +346,9 @@ func (ch Challenge) RecordsSane() bool { | ||||||
| 
 | 
 | ||||||
| 	switch ch.Type { | 	switch ch.Type { | ||||||
| 	case ChallengeTypeSimpleHTTP: | 	case ChallengeTypeSimpleHTTP: | ||||||
|  | 		// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this case
 | ||||||
|  | 		fallthrough | ||||||
|  | 	case ChallengeTypeHTTP01: | ||||||
| 		for _, rec := range ch.ValidationRecord { | 		for _, rec := range ch.ValidationRecord { | ||||||
| 			if rec.URL == "" || rec.Hostname == "" || rec.Port == "" || rec.AddressUsed == nil || | 			if rec.URL == "" || rec.Hostname == "" || rec.Port == "" || rec.AddressUsed == nil || | ||||||
| 				len(rec.AddressesResolved) == 0 { | 				len(rec.AddressesResolved) == 0 { | ||||||
|  | @ -256,6 +356,9 @@ func (ch Challenge) RecordsSane() bool { | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	case ChallengeTypeDVSNI: | 	case ChallengeTypeDVSNI: | ||||||
|  | 		// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this case
 | ||||||
|  | 		fallthrough | ||||||
|  | 	case ChallengeTypeTLSSNI01: | ||||||
| 		if len(ch.ValidationRecord) > 1 { | 		if len(ch.ValidationRecord) > 1 { | ||||||
| 			return false | 			return false | ||||||
| 		} | 		} | ||||||
|  | @ -266,16 +369,25 @@ func (ch Challenge) RecordsSane() bool { | ||||||
| 			ch.ValidationRecord[0].AddressUsed == nil || len(ch.ValidationRecord[0].AddressesResolved) == 0 { | 			ch.ValidationRecord[0].AddressUsed == nil || len(ch.ValidationRecord[0].AddressesResolved) == 0 { | ||||||
| 			return false | 			return false | ||||||
| 		} | 		} | ||||||
| 	case ChallengeTypeDNS: | 	case ChallengeTypeDNS01: | ||||||
| 		// Nothing for now
 | 		// Nothing for now
 | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return true | 	return true | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // IsSane checks the sanity of a challenge object before issued to the client
 | // isLegacy returns true if the challenge is of a legacy type (i.e., one defined
 | ||||||
| // (completed = false) and before validation (completed = true).
 | // before draft-ietf-acme-acme-00)
 | ||||||
| func (ch Challenge) IsSane(completed bool) bool { | // TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
 | ||||||
|  | func (ch Challenge) isLegacy() bool { | ||||||
|  | 	return (ch.Type == ChallengeTypeSimpleHTTP) || | ||||||
|  | 		(ch.Type == ChallengeTypeDVSNI) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // legacyIsSane performs sanity checks for legacy challenge types, which have
 | ||||||
|  | // a different structure / logic than current challenges.
 | ||||||
|  | // TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
 | ||||||
|  | func (ch Challenge) legacyIsSane(completed bool) bool { | ||||||
| 	if ch.Status != StatusPending { | 	if ch.Status != StatusPending { | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
|  | @ -303,9 +415,6 @@ func (ch Challenge) IsSane(completed bool) bool { | ||||||
| 			return false | 			return false | ||||||
| 		} | 		} | ||||||
| 	case ChallengeTypeDVSNI: | 	case ChallengeTypeDVSNI: | ||||||
| 		// Same as DNS
 |  | ||||||
| 		fallthrough |  | ||||||
| 	case ChallengeTypeDNS: |  | ||||||
| 		// check extra fields aren't used
 | 		// check extra fields aren't used
 | ||||||
| 		if ch.TLS != nil { | 		if ch.TLS != nil { | ||||||
| 			return false | 			return false | ||||||
|  | @ -330,9 +439,10 @@ func (ch Challenge) IsSane(completed bool) bool { | ||||||
| 	return true | 	return true | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // MergeResponse copies a subset of client-provided data to the current Challenge.
 | // legacyMergeResponse copies a subset of client-provided data to the current Challenge.
 | ||||||
| // Note: This method does not update the challenge on the left side of the '.'
 | // Note: This method does not update the challenge on the left side of the '.'
 | ||||||
| func (ch Challenge) MergeResponse(resp Challenge) Challenge { | // TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
 | ||||||
|  | func (ch Challenge) legacyMergeResponse(resp Challenge) Challenge { | ||||||
| 	switch ch.Type { | 	switch ch.Type { | ||||||
| 	case ChallengeTypeSimpleHTTP: | 	case ChallengeTypeSimpleHTTP: | ||||||
| 		// For simpleHttp, only "tls" is client-provided
 | 		// For simpleHttp, only "tls" is client-provided
 | ||||||
|  | @ -345,8 +455,6 @@ func (ch Challenge) MergeResponse(resp Challenge) Challenge { | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	case ChallengeTypeDVSNI: | 	case ChallengeTypeDVSNI: | ||||||
| 		fallthrough |  | ||||||
| 	case ChallengeTypeDNS: |  | ||||||
| 		// For dvsni and dns, only "validation" is client-provided
 | 		// For dvsni and dns, only "validation" is client-provided
 | ||||||
| 		if resp.Validation != nil { | 		if resp.Validation != nil { | ||||||
| 			ch.Validation = resp.Validation | 			ch.Validation = resp.Validation | ||||||
|  | @ -356,6 +464,65 @@ func (ch Challenge) MergeResponse(resp Challenge) Challenge { | ||||||
| 	return ch | 	return ch | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // IsSane checks the sanity of a challenge object before issued to the client
 | ||||||
|  | // (completed = false) and before validation (completed = true).
 | ||||||
|  | func (ch Challenge) IsSane(completed bool) bool { | ||||||
|  | 	// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this branch
 | ||||||
|  | 	if ch.isLegacy() { | ||||||
|  | 		return ch.legacyIsSane(completed) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if ch.Status != StatusPending { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// There always needs to be an account key and token
 | ||||||
|  | 	if ch.AccountKey == nil || !LooksLikeAToken(ch.Token) { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Before completion, the key authorization field should be empty
 | ||||||
|  | 	if !completed && ch.KeyAuthorization != nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// If the challenge is completed, then there should be a key authorization,
 | ||||||
|  | 	// and it should match the challenge.
 | ||||||
|  | 	if completed { | ||||||
|  | 		if ch.KeyAuthorization == nil { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if !ch.KeyAuthorization.Match(ch.Token, ch.AccountKey) { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // MergeResponse copies a subset of client-provided data to the current Challenge.
 | ||||||
|  | // Note: This method does not update the challenge on the left side of the '.'
 | ||||||
|  | func (ch Challenge) MergeResponse(resp Challenge) Challenge { | ||||||
|  | 	// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this branch
 | ||||||
|  | 	if ch.isLegacy() { | ||||||
|  | 		return ch.legacyMergeResponse(resp) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// The only client-provided field is the key authorization, and all current
 | ||||||
|  | 	// challenge types use it.
 | ||||||
|  | 	switch ch.Type { | ||||||
|  | 	case ChallengeTypeHTTP01: | ||||||
|  | 		fallthrough | ||||||
|  | 	case ChallengeTypeTLSSNI01: | ||||||
|  | 		fallthrough | ||||||
|  | 	case ChallengeTypeDNS01: | ||||||
|  | 		ch.KeyAuthorization = resp.KeyAuthorization | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return ch | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Authorization represents the authorization of an account key holder
 | // Authorization represents the authorization of an account key holder
 | ||||||
| // to act on behalf of a domain.  This struct is intended to be used both
 | // to act on behalf of a domain.  This struct is intended to be used both
 | ||||||
| // internally and for JSON marshaling on the wire.  Any fields that should be
 | // internally and for JSON marshaling on the wire.  Any fields that should be
 | ||||||
|  | @ -450,11 +617,6 @@ type Certificate struct { | ||||||
| 	Expires time.Time `db:"expires"` | 	Expires time.Time `db:"expires"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type IssuedCertIdentifierData struct { |  | ||||||
| 	ReversedName string |  | ||||||
| 	Serial       string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // IdentifierData holds information about what certificates are known for a
 | // IdentifierData holds information about what certificates are known for a
 | ||||||
| // given identifier. This is used to present Proof of Posession challenges in
 | // given identifier. This is used to present Proof of Posession challenges in
 | ||||||
| // the case where a certificate already exists. The DB table holding
 | // the case where a certificate already exists. The DB table holding
 | ||||||
|  | @ -557,6 +719,8 @@ type OCSPSigningRequest struct { | ||||||
| 	RevokedAt time.Time | 	RevokedAt time.Time | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // SignedCertificateTimestamp represents objects used by Certificate Transparency
 | ||||||
|  | // to demonstrate that a certificate was submitted to a CT log. See RFC 6962.
 | ||||||
| type SignedCertificateTimestamp struct { | type SignedCertificateTimestamp struct { | ||||||
| 	ID int `db:"id"` | 	ID int `db:"id"` | ||||||
| 	// The version of the protocol to which the SCT conforms
 | 	// The version of the protocol to which the SCT conforms
 | ||||||
|  | @ -577,8 +741,6 @@ type SignedCertificateTimestamp struct { | ||||||
| 	LockCol int64 | 	LockCol int64 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type RPCSignedCertificateTimestamp SignedCertificateTimestamp |  | ||||||
| 
 |  | ||||||
| type rawSignedCertificateTimestamp struct { | type rawSignedCertificateTimestamp struct { | ||||||
| 	Version    uint8  `json:"sct_version"` | 	Version    uint8  `json:"sct_version"` | ||||||
| 	LogID      string `json:"id"` | 	LogID      string `json:"id"` | ||||||
|  | @ -587,6 +749,9 @@ type rawSignedCertificateTimestamp struct { | ||||||
| 	Extensions string `json:"extensions"` | 	Extensions string `json:"extensions"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // UnmarshalJSON parses the add-chain response from a CT log. It fills all of
 | ||||||
|  | // the fields in the SignedCertificateTimestamp struct except for ID and
 | ||||||
|  | // CertificateSerial, which are used for local recordkeeping in the Boulder DB.
 | ||||||
| func (sct *SignedCertificateTimestamp) UnmarshalJSON(data []byte) error { | func (sct *SignedCertificateTimestamp) UnmarshalJSON(data []byte) error { | ||||||
| 	var err error | 	var err error | ||||||
| 	var rawSCT rawSignedCertificateTimestamp | 	var rawSCT rawSignedCertificateTimestamp | ||||||
|  | @ -649,20 +814,6 @@ func (sct *SignedCertificateTimestamp) CheckSignature() error { | ||||||
| // RevocationCode is used to specify a certificate revocation reason
 | // RevocationCode is used to specify a certificate revocation reason
 | ||||||
| type RevocationCode int | type RevocationCode int | ||||||
| 
 | 
 | ||||||
| type RevocationCodes []RevocationCode |  | ||||||
| 
 |  | ||||||
| func (rc RevocationCodes) Len() int { |  | ||||||
| 	return len(rc) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (rc RevocationCodes) Less(i, j int) bool { |  | ||||||
| 	return rc[i] < rc[j] |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (rc RevocationCodes) Swap(i, j int) { |  | ||||||
| 	rc[i], rc[j] = rc[j], rc[i] |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // RevocationReasons provides a map from reason code to string explaining the
 | // RevocationReasons provides a map from reason code to string explaining the
 | ||||||
| // code
 | // code
 | ||||||
| var RevocationReasons = map[RevocationCode]string{ | var RevocationReasons = map[RevocationCode]string{ | ||||||
|  |  | ||||||
|  | @ -6,6 +6,8 @@ | ||||||
| package core | package core | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"crypto/rsa" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"net" | 	"net" | ||||||
| 	"testing" | 	"testing" | ||||||
|  | @ -40,6 +42,24 @@ func TestRegistrationUpdate(t *testing.T) { | ||||||
| 	test.Assert(t, reg.Agreement == update.Agreement, "Agreement was not updated") | 	test.Assert(t, reg.Agreement == update.Agreement, "Agreement was not updated") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | var testKey1, _ = rsa.GenerateKey(rand.Reader, 2048) | ||||||
|  | var testKey2, _ = rsa.GenerateKey(rand.Reader, 2048) | ||||||
|  | 
 | ||||||
|  | func TestKeyAuthorization(t *testing.T) { | ||||||
|  | 	jwk1 := &jose.JsonWebKey{Key: testKey1.Public()} | ||||||
|  | 	jwk2 := &jose.JsonWebKey{Key: testKey2.Public()} | ||||||
|  | 
 | ||||||
|  | 	ka1, err := NewKeyAuthorization("99DrlWuy-4Nc82olAy0cK7Shnm4uV32pJovyucGEWME", jwk1) | ||||||
|  | 	test.AssertNotError(t, err, "Failed to create a new key authorization") | ||||||
|  | 	ka2, err := NewKeyAuthorization("Iy2_-2OA8lyD0lwhmD8dD3TIL3wlNpiUhLTXPJG5qOM", jwk2) | ||||||
|  | 	test.AssertNotError(t, err, "Failed to create a new key authorization") | ||||||
|  | 
 | ||||||
|  | 	test.Assert(t, ka1.Match(ka1.Token, jwk1), "Authorized key should match itself") | ||||||
|  | 	test.Assert(t, !ka1.Match(ka1.Token, jwk2), "Authorized key should not match a different key") | ||||||
|  | 	test.Assert(t, !ka1.Match(ka2.Token, jwk1), "Authorized key should not match a different token") | ||||||
|  | 	test.Assert(t, !ka1.Match(ka2.Token, jwk2), "Authorized key should not match a completely different key") | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func TestRecordSanityCheck(t *testing.T) { | func TestRecordSanityCheck(t *testing.T) { | ||||||
| 	rec := []ValidationRecord{ | 	rec := []ValidationRecord{ | ||||||
| 		ValidationRecord{ | 		ValidationRecord{ | ||||||
|  | @ -66,7 +86,8 @@ func TestRecordSanityCheck(t *testing.T) { | ||||||
| 	test.Assert(t, !chall.RecordsSane(), "Record should not be sane") | 	test.Assert(t, !chall.RecordsSane(), "Record should not be sane") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestChallengeSanityCheck(t *testing.T) { | // TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this test
 | ||||||
|  | func TestChallengeSanityCheck_Legacy(t *testing.T) { | ||||||
| 	// Make a temporary account key
 | 	// Make a temporary account key
 | ||||||
| 	var accountKey *jose.JsonWebKey | 	var accountKey *jose.JsonWebKey | ||||||
| 	err := json.Unmarshal([]byte(`{ | 	err := json.Unmarshal([]byte(`{ | ||||||
|  | @ -76,7 +97,7 @@ func TestChallengeSanityCheck(t *testing.T) { | ||||||
|   }`), &accountKey) |   }`), &accountKey) | ||||||
| 	test.AssertNotError(t, err, "Error unmarshaling JWK") | 	test.AssertNotError(t, err, "Error unmarshaling JWK") | ||||||
| 
 | 
 | ||||||
| 	types := []string{ChallengeTypeSimpleHTTP, ChallengeTypeDVSNI, ChallengeTypeDNS} | 	types := []string{ChallengeTypeSimpleHTTP, ChallengeTypeDVSNI} | ||||||
| 	for _, challengeType := range types { | 	for _, challengeType := range types { | ||||||
| 		chall := Challenge{ | 		chall := Challenge{ | ||||||
| 			Type:       challengeType, | 			Type:       challengeType, | ||||||
|  | @ -107,7 +128,7 @@ func TestChallengeSanityCheck(t *testing.T) { | ||||||
| 				AddressUsed:       net.IP{127, 0, 0, 1}, | 				AddressUsed:       net.IP{127, 0, 0, 1}, | ||||||
| 			}} | 			}} | ||||||
| 			test.Assert(t, chall.IsSane(true), "IsSane should be true") | 			test.Assert(t, chall.IsSane(true), "IsSane should be true") | ||||||
| 		} else if challengeType == ChallengeTypeDVSNI || challengeType == ChallengeTypeDNS { | 		} else if challengeType == ChallengeTypeDVSNI { | ||||||
| 			chall.Validation = new(jose.JsonWebSignature) | 			chall.Validation = new(jose.JsonWebSignature) | ||||||
| 			if challengeType == ChallengeTypeDVSNI { | 			if challengeType == ChallengeTypeDVSNI { | ||||||
| 				chall.ValidationRecord = []ValidationRecord{ValidationRecord{ | 				chall.ValidationRecord = []ValidationRecord{ValidationRecord{ | ||||||
|  | @ -128,6 +149,43 @@ func TestChallengeSanityCheck(t *testing.T) { | ||||||
| 	test.Assert(t, !chall.IsSane(true), "IsSane should be false") | 	test.Assert(t, !chall.IsSane(true), "IsSane should be false") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func TestChallengeSanityCheck(t *testing.T) { | ||||||
|  | 	// Make a temporary account key
 | ||||||
|  | 	var accountKey *jose.JsonWebKey | ||||||
|  | 	err := json.Unmarshal([]byte(`{ | ||||||
|  |     "kty":"RSA", | ||||||
|  |     "n":"yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ", | ||||||
|  |     "e":"AQAB" | ||||||
|  |   }`), &accountKey) | ||||||
|  | 	test.AssertNotError(t, err, "Error unmarshaling JWK") | ||||||
|  | 
 | ||||||
|  | 	ka, err := NewKeyAuthorization("KQqLsiS5j0CONR_eUXTUSUDNVaHODtc-0pD6ACif7U4", accountKey) | ||||||
|  | 	test.AssertNotError(t, err, "Error creating key authorization") | ||||||
|  | 
 | ||||||
|  | 	types := []string{ChallengeTypeHTTP01, ChallengeTypeTLSSNI01, ChallengeTypeDNS01} | ||||||
|  | 	for _, challengeType := range types { | ||||||
|  | 		chall := Challenge{ | ||||||
|  | 			Type:       challengeType, | ||||||
|  | 			Status:     StatusInvalid, | ||||||
|  | 			AccountKey: accountKey, | ||||||
|  | 		} | ||||||
|  | 		test.Assert(t, !chall.IsSane(false), "IsSane should be false") | ||||||
|  | 
 | ||||||
|  | 		chall.Status = StatusPending | ||||||
|  | 		test.Assert(t, !chall.IsSane(false), "IsSane should be false") | ||||||
|  | 
 | ||||||
|  | 		chall.Token = ka.Token | ||||||
|  | 		test.Assert(t, chall.IsSane(false), "IsSane should be true") | ||||||
|  | 
 | ||||||
|  | 		chall.KeyAuthorization = &ka | ||||||
|  | 		test.Assert(t, chall.IsSane(true), "IsSane should be true") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	chall := Challenge{Type: "bogus", Status: StatusPending} | ||||||
|  | 	test.Assert(t, !chall.IsSane(false), "IsSane should be false") | ||||||
|  | 	test.Assert(t, !chall.IsSane(true), "IsSane should be false") | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func TestJSONBufferUnmarshal(t *testing.T) { | func TestJSONBufferUnmarshal(t *testing.T) { | ||||||
| 	testStruct := struct { | 	testStruct := struct { | ||||||
| 		Buffer JSONBuffer | 		Buffer JSONBuffer | ||||||
|  |  | ||||||
							
								
								
									
										104
									
								
								core/util.go
								
								
								
								
							
							
						
						
									
										104
									
								
								core/util.go
								
								
								
								
							|  | @ -8,6 +8,7 @@ package core | ||||||
| import ( | import ( | ||||||
| 	"crypto" | 	"crypto" | ||||||
| 	"crypto/ecdsa" | 	"crypto/ecdsa" | ||||||
|  | 	"crypto/elliptic" | ||||||
| 	"crypto/rand" | 	"crypto/rand" | ||||||
| 	"crypto/rsa" | 	"crypto/rsa" | ||||||
| 	"crypto/sha1" | 	"crypto/sha1" | ||||||
|  | @ -26,6 +27,7 @@ import ( | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"math/big" | 	"math/big" | ||||||
| 	"net/url" | 	"net/url" | ||||||
|  | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose" | 	jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose" | ||||||
|  | @ -153,6 +155,14 @@ func NewToken() string { | ||||||
| 	return RandomString(32) | 	return RandomString(32) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | var tokenFormat = regexp.MustCompile("^[\\w-]{43}$") | ||||||
|  | 
 | ||||||
|  | // LooksLikeAToken checks whether a string represents a 32-octet value in
 | ||||||
|  | // the URL-safe base64 alphabet.
 | ||||||
|  | func LooksLikeAToken(token string) bool { | ||||||
|  | 	return tokenFormat.MatchString(token) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Fingerprints
 | // Fingerprints
 | ||||||
| 
 | 
 | ||||||
| // Fingerprint256 produces an unpadded, URL-safe Base64-encoded SHA256 digest
 | // Fingerprint256 produces an unpadded, URL-safe Base64-encoded SHA256 digest
 | ||||||
|  | @ -163,6 +173,96 @@ func Fingerprint256(data []byte) string { | ||||||
| 	return B64enc(d.Sum(nil)) | 	return B64enc(d.Sum(nil)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Thumbprint produces a JWK thumbprint [RFC7638] of a JWK.
 | ||||||
|  | // XXX(rlb): This is adapted from a PR to go-jose, but we need it here until
 | ||||||
|  | //           that PR is merged and we update to that version.
 | ||||||
|  | //           https://github.com/square/go-jose/pull/37
 | ||||||
|  | // XXX(rlb): Once that lands, we should replace the digest methods below
 | ||||||
|  | //           with this standard thumbprint.
 | ||||||
|  | const rsaThumbprintTemplate = `{"e":"%s","kty":"RSA","n":"%s"}` | ||||||
|  | const ecThumbprintTemplate = `{"crv":"%s","kty":"EC","x":"%s","y":"%s"}` | ||||||
|  | 
 | ||||||
|  | // Get JOSE name of curve
 | ||||||
|  | func curveName(crv elliptic.Curve) (string, error) { | ||||||
|  | 	switch crv { | ||||||
|  | 	case elliptic.P256(): | ||||||
|  | 		return "P-256", nil | ||||||
|  | 	case elliptic.P384(): | ||||||
|  | 		return "P-384", nil | ||||||
|  | 	case elliptic.P521(): | ||||||
|  | 		return "P-521", nil | ||||||
|  | 	default: | ||||||
|  | 		return "", fmt.Errorf("square/go-jose: unsupported/unknown elliptic curve") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Get size of curve in bytes
 | ||||||
|  | func curveSize(crv elliptic.Curve) int { | ||||||
|  | 	bits := crv.Params().BitSize | ||||||
|  | 
 | ||||||
|  | 	div := bits / 8 | ||||||
|  | 	mod := bits % 8 | ||||||
|  | 
 | ||||||
|  | 	if mod == 0 { | ||||||
|  | 		return div | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return div + 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newFixedSizeBuffer(data []byte, length int) []byte { | ||||||
|  | 	if len(data) > length { | ||||||
|  | 		panic("square/go-jose: invalid call to newFixedSizeBuffer (len(data) > length)") | ||||||
|  | 	} | ||||||
|  | 	pad := make([]byte, length-len(data)) | ||||||
|  | 	return append(pad, data...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ecThumbprintInput(curve elliptic.Curve, x, y *big.Int) (string, error) { | ||||||
|  | 	coordLength := curveSize(curve) | ||||||
|  | 	crv, err := curveName(curve) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return fmt.Sprintf(ecThumbprintTemplate, crv, | ||||||
|  | 		B64enc(newFixedSizeBuffer(x.Bytes(), coordLength)), | ||||||
|  | 		B64enc(newFixedSizeBuffer(y.Bytes(), coordLength))), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func rsaThumbprintInput(n *big.Int, e int) (string, error) { | ||||||
|  | 	return fmt.Sprintf(rsaThumbprintTemplate, | ||||||
|  | 		B64enc(big.NewInt(int64(e)).Bytes()), | ||||||
|  | 		B64enc(n.Bytes())), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Thumbprint computes the JWK Thumbprint of a key using the
 | ||||||
|  | // indicated hash algorithm.
 | ||||||
|  | func Thumbprint(k *jose.JsonWebKey) (string, error) { | ||||||
|  | 	var input string | ||||||
|  | 	var err error | ||||||
|  | 	switch key := k.Key.(type) { | ||||||
|  | 	case *ecdsa.PublicKey: | ||||||
|  | 		input, err = ecThumbprintInput(key.Curve, key.X, key.Y) | ||||||
|  | 	case *ecdsa.PrivateKey: | ||||||
|  | 		input, err = ecThumbprintInput(key.Curve, key.X, key.Y) | ||||||
|  | 	case *rsa.PublicKey: | ||||||
|  | 		input, err = rsaThumbprintInput(key.N, key.E) | ||||||
|  | 	case *rsa.PrivateKey: | ||||||
|  | 		input, err = rsaThumbprintInput(key.N, key.E) | ||||||
|  | 	default: | ||||||
|  | 		return "", fmt.Errorf("square/go-jose: unkown key type") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	h := sha256.New() | ||||||
|  | 	h.Write([]byte(input)) | ||||||
|  | 	return B64enc(h.Sum(nil)), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // KeyDigest produces a padded, standard Base64-encoded SHA256 digest of a
 | // KeyDigest produces a padded, standard Base64-encoded SHA256 digest of a
 | ||||||
| // provided public key.
 | // provided public key.
 | ||||||
| func KeyDigest(key crypto.PublicKey) (string, error) { | func KeyDigest(key crypto.PublicKey) (string, error) { | ||||||
|  | @ -201,6 +301,7 @@ func KeyDigestEquals(j, k crypto.PublicKey) bool { | ||||||
| // AcmeURL is a URL that automatically marshal/unmarshal to JSON strings
 | // AcmeURL is a URL that automatically marshal/unmarshal to JSON strings
 | ||||||
| type AcmeURL url.URL | type AcmeURL url.URL | ||||||
| 
 | 
 | ||||||
|  | // ParseAcmeURL is just a wrapper around url.Parse that returns an *AcmeURL
 | ||||||
| func ParseAcmeURL(s string) (*AcmeURL, error) { | func ParseAcmeURL(s string) (*AcmeURL, error) { | ||||||
| 	u, err := url.Parse(s) | 	u, err := url.Parse(s) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -327,6 +428,9 @@ func StringToSerial(serial string) (*big.Int, error) { | ||||||
| 	return &serialNum, err | 	return &serialNum, err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // ValidSerial tests whether the input string represents a syntactically
 | ||||||
|  | // valid serial number, i.e., that it is a valid hex string between 32
 | ||||||
|  | // and 36 characters long.
 | ||||||
| func ValidSerial(serial string) bool { | func ValidSerial(serial string) bool { | ||||||
| 	// Originally, serial numbers were 32 hex characters long. We later increased
 | 	// Originally, serial numbers were 32 hex characters long. We later increased
 | ||||||
| 	// them to 36, but we allow the shorter ones because they exist in some
 | 	// them to 36, but we allow the shorter ones because they exist in some
 | ||||||
|  |  | ||||||
|  | @ -35,6 +35,12 @@ func TestNewToken(t *testing.T) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func TestLooksLikeAToken(t *testing.T) { | ||||||
|  | 	test.Assert(t, !LooksLikeAToken("R-UL_7MrV3tUUjO9v5ym2srK3dGGCwlxbVyKBdwLOS"), "Accepted short token") | ||||||
|  | 	test.Assert(t, !LooksLikeAToken("R-UL_7MrV3tUUjO9v5ym2srK3dGGCwlxbVyKBdwLOS%"), "Accepted invalid token") | ||||||
|  | 	test.Assert(t, LooksLikeAToken("R-UL_7MrV3tUUjO9v5ym2srK3dGGCwlxbVyKBdwLOSU"), "Rejected valid token") | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func TestSerialUtils(t *testing.T) { | func TestSerialUtils(t *testing.T) { | ||||||
| 	serial := SerialToString(big.NewInt(100000000000000000)) | 	serial := SerialToString(big.NewInt(100000000000000000)) | ||||||
| 	test.AssertEquals(t, serial, "00000000000000000000016345785d8a0000") | 	test.AssertEquals(t, serial, "00000000000000000000016345785d8a0000") | ||||||
|  | @ -58,12 +64,22 @@ const JWK1JSON = `{ | ||||||
|   "e": "AQAB" |   "e": "AQAB" | ||||||
| }` | }` | ||||||
| const JWK1Digest = `ul04Iq07ulKnnrebv2hv3yxCGgVvoHs8hjq2tVKx3mc=` | const JWK1Digest = `ul04Iq07ulKnnrebv2hv3yxCGgVvoHs8hjq2tVKx3mc=` | ||||||
|  | const JWK1Thumbprint = `-kVpHjJCDNQQk-j9BGMpzHAVCiOqvoTRZB-Ov4CAiM4` | ||||||
| const JWK2JSON = `{ | const JWK2JSON = `{ | ||||||
|   "kty":"RSA", |   "kty":"RSA", | ||||||
|   "n":"yTsLkI8n4lg9UuSKNRC0UPHsVjNdCYk8rGXIqeb_rRYaEev3D9-kxXY8HrYfGkVt5CiIVJ-n2t50BKT8oBEMuilmypSQqJw0pCgtUm-e6Z0Eg3Ly6DMXFlycyikegiZ0b-rVX7i5OCEZRDkENAYwFNX4G7NNCwEZcH7HUMUmty9dchAqDS9YWzPh_dde1A9oy9JMH07nRGDcOzIh1rCPwc71nwfPPYeeS4tTvkjanjeigOYBFkBLQuv7iBB4LPozsGF1XdoKiIIi-8ye44McdhOTPDcQp3xKxj89aO02pQhBECv61rmbPinvjMG9DYxJmZvjsKF4bN2oy0DxdC1jDw", |   "n":"yTsLkI8n4lg9UuSKNRC0UPHsVjNdCYk8rGXIqeb_rRYaEev3D9-kxXY8HrYfGkVt5CiIVJ-n2t50BKT8oBEMuilmypSQqJw0pCgtUm-e6Z0Eg3Ly6DMXFlycyikegiZ0b-rVX7i5OCEZRDkENAYwFNX4G7NNCwEZcH7HUMUmty9dchAqDS9YWzPh_dde1A9oy9JMH07nRGDcOzIh1rCPwc71nwfPPYeeS4tTvkjanjeigOYBFkBLQuv7iBB4LPozsGF1XdoKiIIi-8ye44McdhOTPDcQp3xKxj89aO02pQhBECv61rmbPinvjMG9DYxJmZvjsKF4bN2oy0DxdC1jDw", | ||||||
|   "e":"AQAB" |   "e":"AQAB" | ||||||
| }` | }` | ||||||
| 
 | 
 | ||||||
|  | func TestKeyThumbprint(t *testing.T) { | ||||||
|  | 	var jwk jose.JsonWebKey | ||||||
|  | 	json.Unmarshal([]byte(JWK1JSON), &jwk) | ||||||
|  | 	thumbprint, err := Thumbprint(&jwk) | ||||||
|  | 
 | ||||||
|  | 	test.AssertNotError(t, err, "Failed to compute JWK digest") | ||||||
|  | 	test.AssertEquals(t, thumbprint, JWK1Thumbprint) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func TestKeyDigest(t *testing.T) { | func TestKeyDigest(t *testing.T) { | ||||||
| 	// Test with JWK (value, reference, and direct)
 | 	// Test with JWK (value, reference, and direct)
 | ||||||
| 	var jwk jose.JsonWebKey | 	var jwk jose.JsonWebKey | ||||||
|  |  | ||||||
							
								
								
									
										40
									
								
								mocks/log.go
								
								
								
								
							
							
						
						
									
										40
									
								
								mocks/log.go
								
								
								
								
							|  | @ -13,10 +13,10 @@ import ( | ||||||
| 	blog "github.com/letsencrypt/boulder/log" | 	blog "github.com/letsencrypt/boulder/log" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // MockSyslogWriter implements the blog.SyslogWriter interface. It
 | // SyslogWriter implements the blog.SyslogWriter interface. It
 | ||||||
| // stores all logged messages in a buffer for inspection by test
 | // stores all logged messages in a buffer for inspection by test
 | ||||||
| // functions (via GetAll()) instead of sending them to syslog.
 | // functions (via GetAll()) instead of sending them to syslog.
 | ||||||
| type MockSyslogWriter struct { | type SyslogWriter struct { | ||||||
| 	logged    []*LogMessage | 	logged    []*LogMessage | ||||||
| 	msgChan   chan<- *LogMessage | 	msgChan   chan<- *LogMessage | ||||||
| 	getChan   <-chan []*LogMessage | 	getChan   <-chan []*LogMessage | ||||||
|  | @ -24,7 +24,7 @@ type MockSyslogWriter struct { | ||||||
| 	closeChan chan<- struct{} | 	closeChan chan<- struct{} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // LogMessage is a log entry that has been sent to a MockSyslogWriter.
 | // LogMessage is a log entry that has been sent to a SyslogWriter.
 | ||||||
| type LogMessage struct { | type LogMessage struct { | ||||||
| 	Priority syslog.Priority // aka Log level
 | 	Priority syslog.Priority // aka Log level
 | ||||||
| 	Message  string          // content of log message
 | 	Message  string          // content of log message
 | ||||||
|  | @ -54,19 +54,19 @@ func (lm *LogMessage) String() string { | ||||||
| //		// ...
 | //		// ...
 | ||||||
| //		Assert(t, len(log.GetAll()) > 0, "Should have logged something")
 | //		Assert(t, len(log.GetAll()) > 0, "Should have logged something")
 | ||||||
| //	}
 | //	}
 | ||||||
| func UseMockLog() *MockSyslogWriter { | func UseMockLog() *SyslogWriter { | ||||||
| 	sw := NewSyslogWriter() | 	sw := NewSyslogWriter() | ||||||
| 	blog.GetAuditLogger().SyslogWriter = sw | 	blog.GetAuditLogger().SyslogWriter = sw | ||||||
| 	return sw | 	return sw | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewSyslogWriter returns a new MockSyslogWriter.
 | // NewSyslogWriter returns a new SyslogWriter.
 | ||||||
| func NewSyslogWriter() *MockSyslogWriter { | func NewSyslogWriter() *SyslogWriter { | ||||||
| 	msgChan := make(chan *LogMessage) | 	msgChan := make(chan *LogMessage) | ||||||
| 	getChan := make(chan []*LogMessage) | 	getChan := make(chan []*LogMessage) | ||||||
| 	clearChan := make(chan struct{}) | 	clearChan := make(chan struct{}) | ||||||
| 	closeChan := make(chan struct{}) | 	closeChan := make(chan struct{}) | ||||||
| 	msw := &MockSyslogWriter{ | 	msw := &SyslogWriter{ | ||||||
| 		logged:    []*LogMessage{}, | 		logged:    []*LogMessage{}, | ||||||
| 		msgChan:   msgChan, | 		msgChan:   msgChan, | ||||||
| 		getChan:   getChan, | 		getChan:   getChan, | ||||||
|  | @ -91,7 +91,7 @@ func NewSyslogWriter() *MockSyslogWriter { | ||||||
| 	return msw | 	return msw | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (msw *MockSyslogWriter) write(m string, priority syslog.Priority) error { | func (msw *SyslogWriter) write(m string, priority syslog.Priority) error { | ||||||
| 	msw.msgChan <- &LogMessage{Message: m, Priority: priority} | 	msw.msgChan <- &LogMessage{Message: m, Priority: priority} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  | @ -100,7 +100,7 @@ func (msw *MockSyslogWriter) write(m string, priority syslog.Priority) error { | ||||||
| // Clear(), if applicable).
 | // Clear(), if applicable).
 | ||||||
| //
 | //
 | ||||||
| // The caller must not modify the returned slice or its elements.
 | // The caller must not modify the returned slice or its elements.
 | ||||||
| func (msw *MockSyslogWriter) GetAll() []*LogMessage { | func (msw *SyslogWriter) GetAll() []*LogMessage { | ||||||
| 	return <-msw.getChan | 	return <-msw.getChan | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -110,7 +110,7 @@ func (msw *MockSyslogWriter) GetAll() []*LogMessage { | ||||||
| // is more important than performance.
 | // is more important than performance.
 | ||||||
| //
 | //
 | ||||||
| // The caller must not modify the elements of the returned slice.
 | // The caller must not modify the elements of the returned slice.
 | ||||||
| func (msw *MockSyslogWriter) GetAllMatching(reString string) (matches []*LogMessage) { | func (msw *SyslogWriter) GetAllMatching(reString string) (matches []*LogMessage) { | ||||||
| 	re := regexp.MustCompile(reString) | 	re := regexp.MustCompile(reString) | ||||||
| 	for _, logMsg := range <-msw.getChan { | 	for _, logMsg := range <-msw.getChan { | ||||||
| 		if re.MatchString(logMsg.Message) { | 		if re.MatchString(logMsg.Message) { | ||||||
|  | @ -121,52 +121,52 @@ func (msw *MockSyslogWriter) GetAllMatching(reString string) (matches []*LogMess | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Clear resets the log buffer.
 | // Clear resets the log buffer.
 | ||||||
| func (msw *MockSyslogWriter) Clear() { | func (msw *SyslogWriter) Clear() { | ||||||
| 	msw.clearChan <- struct{}{} | 	msw.clearChan <- struct{}{} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Close releases resources. No other methods may be called after this.
 | // Close releases resources. No other methods may be called after this.
 | ||||||
| func (msw *MockSyslogWriter) Close() error { | func (msw *SyslogWriter) Close() error { | ||||||
| 	msw.closeChan <- struct{}{} | 	msw.closeChan <- struct{}{} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Alert logs at LOG_ALERT
 | // Alert logs at LOG_ALERT
 | ||||||
| func (msw *MockSyslogWriter) Alert(m string) error { | func (msw *SyslogWriter) Alert(m string) error { | ||||||
| 	return msw.write(m, syslog.LOG_ALERT) | 	return msw.write(m, syslog.LOG_ALERT) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Crit logs at LOG_CRIT
 | // Crit logs at LOG_CRIT
 | ||||||
| func (msw *MockSyslogWriter) Crit(m string) error { | func (msw *SyslogWriter) Crit(m string) error { | ||||||
| 	return msw.write(m, syslog.LOG_CRIT) | 	return msw.write(m, syslog.LOG_CRIT) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Debug logs at LOG_DEBUG
 | // Debug logs at LOG_DEBUG
 | ||||||
| func (msw *MockSyslogWriter) Debug(m string) error { | func (msw *SyslogWriter) Debug(m string) error { | ||||||
| 	return msw.write(m, syslog.LOG_DEBUG) | 	return msw.write(m, syslog.LOG_DEBUG) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Emerg logs at LOG_EMERG
 | // Emerg logs at LOG_EMERG
 | ||||||
| func (msw *MockSyslogWriter) Emerg(m string) error { | func (msw *SyslogWriter) Emerg(m string) error { | ||||||
| 	return msw.write(m, syslog.LOG_EMERG) | 	return msw.write(m, syslog.LOG_EMERG) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Err logs at LOG_ERR
 | // Err logs at LOG_ERR
 | ||||||
| func (msw *MockSyslogWriter) Err(m string) error { | func (msw *SyslogWriter) Err(m string) error { | ||||||
| 	return msw.write(m, syslog.LOG_ERR) | 	return msw.write(m, syslog.LOG_ERR) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Info logs at LOG_INFO
 | // Info logs at LOG_INFO
 | ||||||
| func (msw *MockSyslogWriter) Info(m string) error { | func (msw *SyslogWriter) Info(m string) error { | ||||||
| 	return msw.write(m, syslog.LOG_INFO) | 	return msw.write(m, syslog.LOG_INFO) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Notice logs at LOG_NOTICE
 | // Notice logs at LOG_NOTICE
 | ||||||
| func (msw *MockSyslogWriter) Notice(m string) error { | func (msw *SyslogWriter) Notice(m string) error { | ||||||
| 	return msw.write(m, syslog.LOG_NOTICE) | 	return msw.write(m, syslog.LOG_NOTICE) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Warning logs at LOG_WARNING
 | // Warning logs at LOG_WARNING
 | ||||||
| func (msw *MockSyslogWriter) Warning(m string) error { | func (msw *SyslogWriter) Warning(m string) error { | ||||||
| 	return msw.write(m, syslog.LOG_WARNING) | 	return msw.write(m, syslog.LOG_WARNING) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -20,17 +20,17 @@ import ( | ||||||
| 	"github.com/letsencrypt/boulder/core" | 	"github.com/letsencrypt/boulder/core" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // MockDNS is a mock
 | // DNSResolver is a mock
 | ||||||
| type MockDNS struct { | type DNSResolver struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ExchangeOne is a mock
 | // ExchangeOne is a mock
 | ||||||
| func (mock *MockDNS) ExchangeOne(hostname string, qt uint16) (rsp *dns.Msg, rtt time.Duration, err error) { | func (mock *DNSResolver) ExchangeOne(hostname string, qt uint16) (rsp *dns.Msg, rtt time.Duration, err error) { | ||||||
| 	return nil, 0, nil | 	return nil, 0, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // LookupTXT is a mock
 | // LookupTXT is a mock
 | ||||||
| func (mock *MockDNS) LookupTXT(hostname string) ([]string, time.Duration, error) { | func (mock *DNSResolver) LookupTXT(hostname string) ([]string, time.Duration, error) { | ||||||
| 	if hostname == "_acme-challenge.servfail.com" { | 	if hostname == "_acme-challenge.servfail.com" { | ||||||
| 		return nil, 0, fmt.Errorf("SERVFAIL") | 		return nil, 0, fmt.Errorf("SERVFAIL") | ||||||
| 	} | 	} | ||||||
|  | @ -38,7 +38,7 @@ func (mock *MockDNS) LookupTXT(hostname string) ([]string, time.Duration, error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // LookupHost is a mock
 | // LookupHost is a mock
 | ||||||
| func (mock *MockDNS) LookupHost(hostname string) ([]net.IP, time.Duration, error) { | func (mock *DNSResolver) LookupHost(hostname string) ([]net.IP, time.Duration, error) { | ||||||
| 	if hostname == "always.invalid" || hostname == "invalid.invalid" { | 	if hostname == "always.invalid" || hostname == "invalid.invalid" { | ||||||
| 		return []net.IP{}, 0, nil | 		return []net.IP{}, 0, nil | ||||||
| 	} | 	} | ||||||
|  | @ -47,7 +47,7 @@ func (mock *MockDNS) LookupHost(hostname string) ([]net.IP, time.Duration, error | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // LookupCNAME is a mock
 | // LookupCNAME is a mock
 | ||||||
| func (mock *MockDNS) LookupCNAME(domain string) (string, time.Duration, error) { | func (mock *DNSResolver) LookupCNAME(domain string) (string, time.Duration, error) { | ||||||
| 	switch strings.TrimRight(domain, ".") { | 	switch strings.TrimRight(domain, ".") { | ||||||
| 	case "cname-absent.com": | 	case "cname-absent.com": | ||||||
| 		return "absent.com.", 30, nil | 		return "absent.com.", 30, nil | ||||||
|  | @ -76,7 +76,7 @@ func (mock *MockDNS) LookupCNAME(domain string) (string, time.Duration, error) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // LookupDNAME is a mock
 | // LookupDNAME is a mock
 | ||||||
| func (mock *MockDNS) LookupDNAME(domain string) (string, time.Duration, error) { | func (mock *DNSResolver) LookupDNAME(domain string) (string, time.Duration, error) { | ||||||
| 	switch strings.TrimRight(domain, ".") { | 	switch strings.TrimRight(domain, ".") { | ||||||
| 	case "cname-and-dname.com", "dname-present.com": | 	case "cname-and-dname.com", "dname-present.com": | ||||||
| 		return "dname-target.present.com.", time.Minute, nil | 		return "dname-target.present.com.", time.Minute, nil | ||||||
|  | @ -94,7 +94,7 @@ func (mock *MockDNS) LookupDNAME(domain string) (string, time.Duration, error) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // LookupCAA is a mock
 | // LookupCAA is a mock
 | ||||||
| func (mock *MockDNS) LookupCAA(domain string) ([]*dns.CAA, time.Duration, error) { | func (mock *DNSResolver) LookupCAA(domain string) ([]*dns.CAA, time.Duration, error) { | ||||||
| 	var results []*dns.CAA | 	var results []*dns.CAA | ||||||
| 	var record dns.CAA | 	var record dns.CAA | ||||||
| 	switch strings.TrimRight(domain, ".") { | 	switch strings.TrimRight(domain, ".") { | ||||||
|  | @ -118,7 +118,7 @@ func (mock *MockDNS) LookupCAA(domain string) ([]*dns.CAA, time.Duration, error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // LookupMX is a mock
 | // LookupMX is a mock
 | ||||||
| func (mock *MockDNS) LookupMX(domain string) ([]string, time.Duration, error) { | func (mock *DNSResolver) LookupMX(domain string) ([]string, time.Duration, error) { | ||||||
| 	switch strings.TrimRight(domain, ".") { | 	switch strings.TrimRight(domain, ".") { | ||||||
| 	case "letsencrypt.org": | 	case "letsencrypt.org": | ||||||
| 		fallthrough | 		fallthrough | ||||||
|  | @ -128,8 +128,8 @@ func (mock *MockDNS) LookupMX(domain string) ([]string, time.Duration, error) { | ||||||
| 	return nil, 0, nil | 	return nil, 0, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // MockSA is a mock
 | // StorageAuthority is a mock
 | ||||||
| type MockSA struct { | type StorageAuthority struct { | ||||||
| 	authorizedDomains map[string]bool | 	authorizedDomains map[string]bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -149,7 +149,7 @@ const ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // GetRegistration is a mock
 | // GetRegistration is a mock
 | ||||||
| func (sa *MockSA) GetRegistration(id int64) (core.Registration, error) { | func (sa *StorageAuthority) GetRegistration(id int64) (core.Registration, error) { | ||||||
| 	if id == 100 { | 	if id == 100 { | ||||||
| 		// Tag meaning "Missing"
 | 		// Tag meaning "Missing"
 | ||||||
| 		return core.Registration{}, errors.New("missing") | 		return core.Registration{}, errors.New("missing") | ||||||
|  | @ -167,7 +167,7 @@ func (sa *MockSA) GetRegistration(id int64) (core.Registration, error) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetRegistrationByKey is a mock
 | // GetRegistrationByKey is a mock
 | ||||||
| func (sa *MockSA) GetRegistrationByKey(jwk jose.JsonWebKey) (core.Registration, error) { | func (sa *StorageAuthority) GetRegistrationByKey(jwk jose.JsonWebKey) (core.Registration, error) { | ||||||
| 	var test1KeyPublic jose.JsonWebKey | 	var test1KeyPublic jose.JsonWebKey | ||||||
| 	var test2KeyPublic jose.JsonWebKey | 	var test2KeyPublic jose.JsonWebKey | ||||||
| 	test1KeyPublic.UnmarshalJSON([]byte(test1KeyPublicJSON)) | 	test1KeyPublic.UnmarshalJSON([]byte(test1KeyPublicJSON)) | ||||||
|  | @ -187,7 +187,7 @@ func (sa *MockSA) GetRegistrationByKey(jwk jose.JsonWebKey) (core.Registration, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetAuthorization is a mock
 | // GetAuthorization is a mock
 | ||||||
| func (sa *MockSA) GetAuthorization(id string) (core.Authorization, error) { | func (sa *StorageAuthority) GetAuthorization(id string) (core.Authorization, error) { | ||||||
| 	if id == "valid" { | 	if id == "valid" { | ||||||
| 		exp := time.Now().AddDate(100, 0, 0) | 		exp := time.Now().AddDate(100, 0, 0) | ||||||
| 		return core.Authorization{ | 		return core.Authorization{ | ||||||
|  | @ -209,7 +209,7 @@ func (sa *MockSA) GetAuthorization(id string) (core.Authorization, error) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetCertificate is a mock
 | // GetCertificate is a mock
 | ||||||
| func (sa *MockSA) GetCertificate(serial string) (core.Certificate, error) { | func (sa *StorageAuthority) GetCertificate(serial string) (core.Certificate, error) { | ||||||
| 	// Serial ee == 238.crt
 | 	// Serial ee == 238.crt
 | ||||||
| 	if serial == "0000000000000000000000000000000000ee" { | 	if serial == "0000000000000000000000000000000000ee" { | ||||||
| 		certPemBytes, _ := ioutil.ReadFile("test/238.crt") | 		certPemBytes, _ := ioutil.ReadFile("test/238.crt") | ||||||
|  | @ -231,7 +231,7 @@ func (sa *MockSA) GetCertificate(serial string) (core.Certificate, error) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetCertificateStatus is a mock
 | // GetCertificateStatus is a mock
 | ||||||
| func (sa *MockSA) GetCertificateStatus(serial string) (core.CertificateStatus, error) { | func (sa *StorageAuthority) GetCertificateStatus(serial string) (core.CertificateStatus, error) { | ||||||
| 	// Serial ee == 238.crt
 | 	// Serial ee == 238.crt
 | ||||||
| 	if serial == "0000000000000000000000000000000000ee" { | 	if serial == "0000000000000000000000000000000000ee" { | ||||||
| 		return core.CertificateStatus{ | 		return core.CertificateStatus{ | ||||||
|  | @ -247,57 +247,57 @@ func (sa *MockSA) GetCertificateStatus(serial string) (core.CertificateStatus, e | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // AlreadyDeniedCSR is a mock
 | // AlreadyDeniedCSR is a mock
 | ||||||
| func (sa *MockSA) AlreadyDeniedCSR([]string) (bool, error) { | func (sa *StorageAuthority) AlreadyDeniedCSR([]string) (bool, error) { | ||||||
| 	return false, nil | 	return false, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // AddCertificate is a mock
 | // AddCertificate is a mock
 | ||||||
| func (sa *MockSA) AddCertificate(certDER []byte, regID int64) (digest string, err error) { | func (sa *StorageAuthority) AddCertificate(certDER []byte, regID int64) (digest string, err error) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // FinalizeAuthorization is a mock
 | // FinalizeAuthorization is a mock
 | ||||||
| func (sa *MockSA) FinalizeAuthorization(authz core.Authorization) (err error) { | func (sa *StorageAuthority) FinalizeAuthorization(authz core.Authorization) (err error) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // MarkCertificateRevoked is a mock
 | // MarkCertificateRevoked is a mock
 | ||||||
| func (sa *MockSA) MarkCertificateRevoked(serial string, ocspResponse []byte, reasonCode core.RevocationCode) (err error) { | func (sa *StorageAuthority) MarkCertificateRevoked(serial string, ocspResponse []byte, reasonCode core.RevocationCode) (err error) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // UpdateOCSP is a mock
 | // UpdateOCSP is a mock
 | ||||||
| func (sa *MockSA) UpdateOCSP(serial string, ocspResponse []byte) (err error) { | func (sa *StorageAuthority) UpdateOCSP(serial string, ocspResponse []byte) (err error) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewPendingAuthorization is a mock
 | // NewPendingAuthorization is a mock
 | ||||||
| func (sa *MockSA) NewPendingAuthorization(authz core.Authorization) (output core.Authorization, err error) { | func (sa *StorageAuthority) NewPendingAuthorization(authz core.Authorization) (output core.Authorization, err error) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewRegistration is a mock
 | // NewRegistration is a mock
 | ||||||
| func (sa *MockSA) NewRegistration(reg core.Registration) (regR core.Registration, err error) { | func (sa *StorageAuthority) NewRegistration(reg core.Registration) (regR core.Registration, err error) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // UpdatePendingAuthorization is a mock
 | // UpdatePendingAuthorization is a mock
 | ||||||
| func (sa *MockSA) UpdatePendingAuthorization(authz core.Authorization) (err error) { | func (sa *StorageAuthority) UpdatePendingAuthorization(authz core.Authorization) (err error) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // UpdateRegistration is a mock
 | // UpdateRegistration is a mock
 | ||||||
| func (sa *MockSA) UpdateRegistration(reg core.Registration) (err error) { | func (sa *StorageAuthority) UpdateRegistration(reg core.Registration) (err error) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetSCTReceipt  is a mock
 | // GetSCTReceipt  is a mock
 | ||||||
| func (sa *MockSA) GetSCTReceipt(serial string, logID string) (sct core.SignedCertificateTimestamp, err error) { | func (sa *StorageAuthority) GetSCTReceipt(serial string, logID string) (sct core.SignedCertificateTimestamp, err error) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // AddSCTReceipt is a mock
 | // AddSCTReceipt is a mock
 | ||||||
| func (sa *MockSA) AddSCTReceipt(sct core.SignedCertificateTimestamp) (err error) { | func (sa *StorageAuthority) AddSCTReceipt(sct core.SignedCertificateTimestamp) (err error) { | ||||||
| 	if sct.Signature == nil { | 	if sct.Signature == nil { | ||||||
| 		err = fmt.Errorf("Bad times") | 		err = fmt.Errorf("Bad times") | ||||||
| 	} | 	} | ||||||
|  | @ -305,8 +305,8 @@ func (sa *MockSA) AddSCTReceipt(sct core.SignedCertificateTimestamp) (err error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetLatestValidAuthorization is a mock
 | // GetLatestValidAuthorization is a mock
 | ||||||
| func (sa *MockSA) GetLatestValidAuthorization(registrationId int64, identifier core.AcmeIdentifier) (authz core.Authorization, err error) { | func (sa *StorageAuthority) GetLatestValidAuthorization(registrationID int64, identifier core.AcmeIdentifier) (authz core.Authorization, err error) { | ||||||
| 	if registrationId == 1 && identifier.Type == "dns" { | 	if registrationID == 1 && identifier.Type == "dns" { | ||||||
| 		if sa.authorizedDomains[identifier.Value] || identifier.Value == "not-an-example.com" { | 		if sa.authorizedDomains[identifier.Value] || identifier.Value == "not-an-example.com" { | ||||||
| 			exp := time.Now().AddDate(100, 0, 0) | 			exp := time.Now().AddDate(100, 0, 0) | ||||||
| 			return core.Authorization{Status: core.StatusValid, RegistrationID: 1, Expires: &exp, Identifier: identifier}, nil | 			return core.Authorization{Status: core.StatusValid, RegistrationID: 1, Expires: &exp, Identifier: identifier}, nil | ||||||
|  | @ -316,21 +316,21 @@ func (sa *MockSA) GetLatestValidAuthorization(registrationId int64, identifier c | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // CountCertificatesRange is a mock
 | // CountCertificatesRange is a mock
 | ||||||
| func (sa *MockSA) CountCertificatesRange(_, _ time.Time) (int64, error) { | func (sa *StorageAuthority) CountCertificatesRange(_, _ time.Time) (int64, error) { | ||||||
| 	return 0, nil | 	return 0, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // CountCertificatesByNames is a mock
 | // CountCertificatesByNames is a mock
 | ||||||
| func (sa *MockSA) CountCertificatesByNames(_ []string, _, _ time.Time) (ret map[string]int, err error) { | func (sa *StorageAuthority) CountCertificatesByNames(_ []string, _, _ time.Time) (ret map[string]int, err error) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // MockPublisher is a mock
 | // Publisher is a mock
 | ||||||
| type MockPublisher struct { | type Publisher struct { | ||||||
| 	// empty
 | 	// empty
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SubmitToCT is a mock
 | // SubmitToCT is a mock
 | ||||||
| func (*MockPublisher) SubmitToCT([]byte) error { | func (*Publisher) SubmitToCT([]byte) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ import ( | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose" | ||||||
| 	"github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1" | 	"github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1" | ||||||
| 	"github.com/letsencrypt/boulder/core" | 	"github.com/letsencrypt/boulder/core" | ||||||
| 	blog "github.com/letsencrypt/boulder/log" | 	blog "github.com/letsencrypt/boulder/log" | ||||||
|  | @ -198,14 +199,14 @@ func (pa PolicyAuthorityImpl) WillingToIssue(id core.AcmeIdentifier, regID int64 | ||||||
| // acceptable for the given identifier.
 | // acceptable for the given identifier.
 | ||||||
| //
 | //
 | ||||||
| // Note: Current implementation is static, but future versions may not be.
 | // Note: Current implementation is static, but future versions may not be.
 | ||||||
| func (pa PolicyAuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier) (challenges []core.Challenge, combinations [][]int) { | func (pa PolicyAuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier, accountKey *jose.JsonWebKey) (challenges []core.Challenge, combinations [][]int, err error) { | ||||||
|  | 	// TODO(https://github.com/letsencrypt/boulder/issues/894): Update these lines
 | ||||||
| 	challenges = []core.Challenge{ | 	challenges = []core.Challenge{ | ||||||
| 		core.SimpleHTTPChallenge(), | 		core.SimpleHTTPChallenge(accountKey), | ||||||
| 		core.DvsniChallenge(), | 		core.DvsniChallenge(accountKey), | ||||||
| 	} | 		core.HTTPChallenge01(accountKey), | ||||||
| 	combinations = [][]int{ | 		core.TLSSNIChallenge01(accountKey), | ||||||
| 		[]int{0}, |  | ||||||
| 		[]int{1}, |  | ||||||
| 	} | 	} | ||||||
|  | 	combinations = [][]int{[]int{0}, []int{1}, []int{2}, []int{3}} | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -6,8 +6,11 @@ | ||||||
| package policy | package policy | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"encoding/json" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose" | ||||||
|  | 
 | ||||||
| 	"github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1" | 	"github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1" | ||||||
| 	"github.com/letsencrypt/boulder/core" | 	"github.com/letsencrypt/boulder/core" | ||||||
| 	"github.com/letsencrypt/boulder/mocks" | 	"github.com/letsencrypt/boulder/mocks" | ||||||
|  | @ -167,17 +170,38 @@ func TestWillingToIssue(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | var accountKeyJSON = `{ | ||||||
|  |   "kty":"RSA", | ||||||
|  |   "n":"yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ", | ||||||
|  |   "e":"AQAB" | ||||||
|  | }` | ||||||
|  | 
 | ||||||
| func TestChallengesFor(t *testing.T) { | func TestChallengesFor(t *testing.T) { | ||||||
| 	pa, cleanup := paImpl(t) | 	pa, cleanup := paImpl(t) | ||||||
| 	defer cleanup() | 	defer cleanup() | ||||||
| 
 | 
 | ||||||
| 	challenges, combinations := pa.ChallengesFor(core.AcmeIdentifier{}) | 	var accountKey *jose.JsonWebKey | ||||||
|  | 	err := json.Unmarshal([]byte(accountKeyJSON), &accountKey) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("Error unmarshaling JWK: %v", err) | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	if len(challenges) != 2 || challenges[0].Type != core.ChallengeTypeSimpleHTTP || | 	challenges, combinations, err := pa.ChallengesFor(core.AcmeIdentifier{}, accountKey) | ||||||
| 		challenges[1].Type != core.ChallengeTypeDVSNI { | 	if err != nil { | ||||||
|  | 		t.Errorf("Error generating challenges: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// TODO(https://github.com/letsencrypt/boulder/issues/894): Update these tests
 | ||||||
|  | 	if len(challenges) != 4 || | ||||||
|  | 		challenges[0].Type != core.ChallengeTypeSimpleHTTP || | ||||||
|  | 		challenges[1].Type != core.ChallengeTypeDVSNI || | ||||||
|  | 		challenges[2].Type != core.ChallengeTypeHTTP01 || | ||||||
|  | 		challenges[3].Type != core.ChallengeTypeTLSSNI01 { | ||||||
| 		t.Error("Incorrect challenges returned") | 		t.Error("Incorrect challenges returned") | ||||||
| 	} | 	} | ||||||
| 	if len(combinations) != 2 || combinations[0][0] != 0 || combinations[1][0] != 1 { | 	if len(combinations) != 4 || | ||||||
|  | 		combinations[0][0] != 0 || combinations[1][0] != 1 || | ||||||
|  | 		combinations[2][0] != 2 || combinations[3][0] != 3 { | ||||||
| 		t.Error("Incorrect combinations returned") | 		t.Error("Incorrect combinations returned") | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -24,6 +24,7 @@ import ( | ||||||
| 	"github.com/letsencrypt/boulder/sa" | 	"github.com/letsencrypt/boulder/sa" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // LogDescription tells you how to connect to a log and verify its statements.
 | ||||||
| type LogDescription struct { | type LogDescription struct { | ||||||
| 	ID        string | 	ID        string | ||||||
| 	URI       string | 	URI       string | ||||||
|  | @ -35,6 +36,9 @@ type rawLogDescription struct { | ||||||
| 	PublicKey string `json:"key"` | 	PublicKey string `json:"key"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // UnmarshalJSON parses a simple JSON format for log descriptions.  Both the
 | ||||||
|  | // URI and the public key are expected to be strings.  The public key is a
 | ||||||
|  | // base64-encoded PKIX public key structure.
 | ||||||
| func (logDesc *LogDescription) UnmarshalJSON(data []byte) error { | func (logDesc *LogDescription) UnmarshalJSON(data []byte) error { | ||||||
| 	var rawLogDesc rawLogDescription | 	var rawLogDesc rawLogDescription | ||||||
| 	if err := json.Unmarshal(data, &rawLogDesc); err != nil { | 	if err := json.Unmarshal(data, &rawLogDesc); err != nil { | ||||||
|  |  | ||||||
|  | @ -196,7 +196,7 @@ func setup(t *testing.T, port, retries int) (PublisherImpl, *x509.Certificate) { | ||||||
| 	}) | 	}) | ||||||
| 	test.AssertNotError(t, err, "Couldn't create new Publisher") | 	test.AssertNotError(t, err, "Couldn't create new Publisher") | ||||||
| 	pub.issuerBundle = append(pub.issuerBundle, base64.StdEncoding.EncodeToString(intermediatePEM.Bytes)) | 	pub.issuerBundle = append(pub.issuerBundle, base64.StdEncoding.EncodeToString(intermediatePEM.Bytes)) | ||||||
| 	pub.SA = &mocks.MockSA{} | 	pub.SA = &mocks.StorageAuthority{} | ||||||
| 
 | 
 | ||||||
| 	leafPEM, _ := pem.Decode([]byte(testLeaf)) | 	leafPEM, _ := pem.Decode([]byte(testLeaf)) | ||||||
| 	leaf, err := x509.ParseCertificate(leafPEM.Bytes) | 	leaf, err := x509.ParseCertificate(leafPEM.Bytes) | ||||||
|  |  | ||||||
|  | @ -214,13 +214,8 @@ func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization | ||||||
| 		return authz, err | 		return authz, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Create validations, but we have to update them with URIs later
 | 	// Create validations. The WFE will  update them with URIs before sending them out.
 | ||||||
| 	challenges, combinations := ra.PA.ChallengesFor(identifier) | 	challenges, combinations, err := ra.PA.ChallengesFor(identifier, ®.Key) | ||||||
| 
 |  | ||||||
| 	for i := range challenges { |  | ||||||
| 		// Add the account key used to generate the challenge
 |  | ||||||
| 		challenges[i].AccountKey = ®.Key |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	expires := ra.clk.Now().Add(ra.authorizationLifetime) | 	expires := ra.clk.Now().Add(ra.authorizationLifetime) | ||||||
| 
 | 
 | ||||||
|  | @ -578,6 +573,12 @@ func (ra *RegistrationAuthorityImpl) UpdateAuthorization(base core.Authorization | ||||||
| 	} | 	} | ||||||
| 	authz.Challenges[challengeIndex] = authz.Challenges[challengeIndex].MergeResponse(response) | 	authz.Challenges[challengeIndex] = authz.Challenges[challengeIndex].MergeResponse(response) | ||||||
| 
 | 
 | ||||||
|  | 	// At this point, the challenge should be sane as a complete challenge
 | ||||||
|  | 	if !authz.Challenges[challengeIndex].IsSane(true) { | ||||||
|  | 		err = core.MalformedRequestError("Response does not complete challenge") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// Store the updated version
 | 	// Store the updated version
 | ||||||
| 	if err = ra.SA.UpdatePendingAuthorization(authz); err != nil { | 	if err = ra.SA.UpdatePendingAuthorization(authz); err != nil { | ||||||
| 		// This can pretty much only happen when the client corrupts the Challenge
 | 		// This can pretty much only happen when the client corrupts the Challenge
 | ||||||
|  |  | ||||||
|  | @ -101,9 +101,6 @@ var ( | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	ResponseIndex = 0 | 	ResponseIndex = 0 | ||||||
| 	Response      = core.Challenge{ |  | ||||||
| 		Type: "simpleHttp", |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	ExampleCSR = &x509.CertificateRequest{} | 	ExampleCSR = &x509.CertificateRequest{} | ||||||
| 
 | 
 | ||||||
|  | @ -116,10 +113,6 @@ var ( | ||||||
| 		Identifier:     core.AcmeIdentifier{Type: "dns", Value: "not-example.com"}, | 		Identifier:     core.AcmeIdentifier{Type: "dns", Value: "not-example.com"}, | ||||||
| 		RegistrationID: 1, | 		RegistrationID: 1, | ||||||
| 		Status:         "pending", | 		Status:         "pending", | ||||||
| 		Challenges: []core.Challenge{ |  | ||||||
| 			core.SimpleHTTPChallenge(), |  | ||||||
| 			core.DvsniChallenge(), |  | ||||||
| 		}, |  | ||||||
| 		Combinations:   [][]int{[]int{0}, []int{1}}, | 		Combinations:   [][]int{[]int{0}, []int{1}}, | ||||||
| 	} | 	} | ||||||
| 	AuthzFinal = core.Authorization{} | 	AuthzFinal = core.Authorization{} | ||||||
|  | @ -132,6 +125,16 @@ const ( | ||||||
| 	saDBConnStr = "mysql+tcp://boulder@localhost:3306/boulder_sa_test" | 	saDBConnStr = "mysql+tcp://boulder@localhost:3306/boulder_sa_test" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | func makeResponse(ch core.Challenge) (out core.Challenge, err error) { | ||||||
|  | 	keyAuthorization, err := core.NewKeyAuthorization(ch.Token, ch.AccountKey) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	out = core.Challenge{KeyAuthorization: &keyAuthorization} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAuthority, *RegistrationAuthorityImpl, clock.FakeClock, func()) { | func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAuthority, *RegistrationAuthorityImpl, clock.FakeClock, func()) { | ||||||
| 	err := json.Unmarshal(AccountKeyJSONA, &AccountKeyA) | 	err := json.Unmarshal(AccountKeyJSONA, &AccountKeyA) | ||||||
| 	test.AssertNotError(t, err, "Failed to unmarshal public JWK") | 	test.AssertNotError(t, err, "Failed to unmarshal public JWK") | ||||||
|  | @ -144,7 +147,11 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut | ||||||
| 	test.AssertNotError(t, err, "Failed to unmarshal private JWK") | 	test.AssertNotError(t, err, "Failed to unmarshal private JWK") | ||||||
| 
 | 
 | ||||||
| 	err = json.Unmarshal(ShortKeyJSON, &ShortKey) | 	err = json.Unmarshal(ShortKeyJSON, &ShortKey) | ||||||
| 	test.AssertNotError(t, err, "Failed to unmarshall JWK") | 	test.AssertNotError(t, err, "Failed to unmarshal JWK") | ||||||
|  | 
 | ||||||
|  | 	simpleHTTP := core.SimpleHTTPChallenge(&AccountKeyA) | ||||||
|  | 	dvsni := core.DvsniChallenge(&AccountKeyA) | ||||||
|  | 	AuthzInitial.Challenges = []core.Challenge{simpleHTTP, dvsni} | ||||||
| 
 | 
 | ||||||
| 	fc := clock.NewFake() | 	fc := clock.NewFake() | ||||||
| 
 | 
 | ||||||
|  | @ -195,7 +202,7 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut | ||||||
| 		ValidityPeriod: time.Hour * 2190, | 		ValidityPeriod: time.Hour * 2190, | ||||||
| 		NotAfter:       time.Now().Add(time.Hour * 8761), | 		NotAfter:       time.Now().Add(time.Hour * 8761), | ||||||
| 		Clk:            fc, | 		Clk:            fc, | ||||||
| 		Publisher:      &mocks.MockPublisher{}, | 		Publisher:      &mocks.Publisher{}, | ||||||
| 	} | 	} | ||||||
| 	cleanUp := func() { | 	cleanUp := func() { | ||||||
| 		saDBCleanUp() | 		saDBCleanUp() | ||||||
|  | @ -218,7 +225,7 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut | ||||||
| 	ra.VA = va | 	ra.VA = va | ||||||
| 	ra.CA = &ca | 	ra.CA = &ca | ||||||
| 	ra.PA = pa | 	ra.PA = pa | ||||||
| 	ra.DNSResolver = &mocks.MockDNS{} | 	ra.DNSResolver = &mocks.DNSResolver{} | ||||||
| 
 | 
 | ||||||
| 	AuthzInitial.RegistrationID = Registration.ID | 	AuthzInitial.RegistrationID = Registration.ID | ||||||
| 
 | 
 | ||||||
|  | @ -256,38 +263,38 @@ func TestValidateContacts(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	nStats, _ := statsd.NewNoopClient() | 	nStats, _ := statsd.NewNoopClient() | ||||||
| 
 | 
 | ||||||
| 	err := validateContacts([]*core.AcmeURL{}, &mocks.MockDNS{}, nStats) | 	err := validateContacts([]*core.AcmeURL{}, &mocks.DNSResolver{}, nStats) | ||||||
| 	test.AssertNotError(t, err, "No Contacts") | 	test.AssertNotError(t, err, "No Contacts") | ||||||
| 
 | 
 | ||||||
| 	err = validateContacts([]*core.AcmeURL{tel}, &mocks.MockDNS{}, nStats) | 	err = validateContacts([]*core.AcmeURL{tel}, &mocks.DNSResolver{}, nStats) | ||||||
| 	test.AssertNotError(t, err, "Simple Telephone") | 	test.AssertNotError(t, err, "Simple Telephone") | ||||||
| 
 | 
 | ||||||
| 	err = validateContacts([]*core.AcmeURL{validEmail}, &mocks.MockDNS{}, nStats) | 	err = validateContacts([]*core.AcmeURL{validEmail}, &mocks.DNSResolver{}, nStats) | ||||||
| 	test.AssertNotError(t, err, "Valid Email") | 	test.AssertNotError(t, err, "Valid Email") | ||||||
| 
 | 
 | ||||||
| 	err = validateContacts([]*core.AcmeURL{invalidEmail}, &mocks.MockDNS{}, nStats) | 	err = validateContacts([]*core.AcmeURL{invalidEmail}, &mocks.DNSResolver{}, nStats) | ||||||
| 	test.AssertError(t, err, "Invalid Email") | 	test.AssertError(t, err, "Invalid Email") | ||||||
| 
 | 
 | ||||||
| 	err = validateContacts([]*core.AcmeURL{malformedEmail}, &mocks.MockDNS{}, nStats) | 	err = validateContacts([]*core.AcmeURL{malformedEmail}, &mocks.DNSResolver{}, nStats) | ||||||
| 	test.AssertError(t, err, "Malformed Email") | 	test.AssertError(t, err, "Malformed Email") | ||||||
| 
 | 
 | ||||||
| 	err = validateContacts([]*core.AcmeURL{ansible}, &mocks.MockDNS{}, nStats) | 	err = validateContacts([]*core.AcmeURL{ansible}, &mocks.DNSResolver{}, nStats) | ||||||
| 	test.AssertError(t, err, "Unknown scehme") | 	test.AssertError(t, err, "Unknown scehme") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestValidateEmail(t *testing.T) { | func TestValidateEmail(t *testing.T) { | ||||||
| 	_, err := validateEmail("an email`", &mocks.MockDNS{}) | 	_, err := validateEmail("an email`", &mocks.DNSResolver{}) | ||||||
| 	test.AssertError(t, err, "Malformed") | 	test.AssertError(t, err, "Malformed") | ||||||
| 
 | 
 | ||||||
| 	_, err = validateEmail("a@not.a.domain", &mocks.MockDNS{}) | 	_, err = validateEmail("a@not.a.domain", &mocks.DNSResolver{}) | ||||||
| 	test.AssertError(t, err, "Cannot resolve") | 	test.AssertError(t, err, "Cannot resolve") | ||||||
| 	t.Logf("No Resolve: %s", err) | 	t.Logf("No Resolve: %s", err) | ||||||
| 
 | 
 | ||||||
| 	_, err = validateEmail("a@example.com", &mocks.MockDNS{}) | 	_, err = validateEmail("a@example.com", &mocks.DNSResolver{}) | ||||||
| 	test.AssertError(t, err, "No MX Record") | 	test.AssertError(t, err, "No MX Record") | ||||||
| 	t.Logf("No MX: %s", err) | 	t.Logf("No MX: %s", err) | ||||||
| 
 | 
 | ||||||
| 	_, err = validateEmail("a@email.com", &mocks.MockDNS{}) | 	_, err = validateEmail("a@email.com", &mocks.DNSResolver{}) | ||||||
| 	test.AssertNotError(t, err, "Valid") | 	test.AssertNotError(t, err, "Valid") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -377,12 +384,22 @@ func TestNewAuthorization(t *testing.T) { | ||||||
| 	test.Assert(t, authz.Status == core.StatusPending, "Initial authz not pending") | 	test.Assert(t, authz.Status == core.StatusPending, "Initial authz not pending") | ||||||
| 
 | 
 | ||||||
| 	// TODO Verify that challenges are correct
 | 	// TODO Verify that challenges are correct
 | ||||||
| 	test.Assert(t, len(authz.Challenges) == 2, "Incorrect number of challenges returned") | 	// TODO(https://github.com/letsencrypt/boulder/issues/894): Update these lines
 | ||||||
|  | 	test.Assert(t, len(authz.Challenges) == 4, "Incorrect number of challenges returned") | ||||||
| 	test.Assert(t, authz.Challenges[0].Type == core.ChallengeTypeSimpleHTTP, "Challenge 0 not SimpleHTTP") | 	test.Assert(t, authz.Challenges[0].Type == core.ChallengeTypeSimpleHTTP, "Challenge 0 not SimpleHTTP") | ||||||
| 	test.Assert(t, authz.Challenges[1].Type == core.ChallengeTypeDVSNI, "Challenge 1 not DVSNI") | 	test.Assert(t, authz.Challenges[1].Type == core.ChallengeTypeDVSNI, "Challenge 1 not DVSNI") | ||||||
|  | 
 | ||||||
|  | 	// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete these lines
 | ||||||
|  | 	test.Assert(t, authz.Challenges[2].Type == core.ChallengeTypeHTTP01, "Challenge 2 not http-00") | ||||||
|  | 	test.Assert(t, authz.Challenges[3].Type == core.ChallengeTypeTLSSNI01, "Challenge 3 not tlssni-00") | ||||||
|  | 
 | ||||||
| 	test.Assert(t, authz.Challenges[0].IsSane(false), "Challenge 0 is not sane") | 	test.Assert(t, authz.Challenges[0].IsSane(false), "Challenge 0 is not sane") | ||||||
| 	test.Assert(t, authz.Challenges[1].IsSane(false), "Challenge 1 is not sane") | 	test.Assert(t, authz.Challenges[1].IsSane(false), "Challenge 1 is not sane") | ||||||
| 
 | 
 | ||||||
|  | 	// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete these lines
 | ||||||
|  | 	test.Assert(t, authz.Challenges[2].IsSane(false), "Challenge 2 is not sane") | ||||||
|  | 	test.Assert(t, authz.Challenges[3].IsSane(false), "Challenge 3 is not sane") | ||||||
|  | 
 | ||||||
| 	t.Log("DONE TestNewAuthorization") | 	t.Log("DONE TestNewAuthorization") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -394,7 +411,9 @@ func TestUpdateAuthorization(t *testing.T) { | ||||||
| 	authz, err := ra.NewAuthorization(AuthzRequest, Registration.ID) | 	authz, err := ra.NewAuthorization(AuthzRequest, Registration.ID) | ||||||
| 	test.AssertNotError(t, err, "NewAuthorization failed") | 	test.AssertNotError(t, err, "NewAuthorization failed") | ||||||
| 
 | 
 | ||||||
| 	authz, err = ra.UpdateAuthorization(authz, ResponseIndex, Response) | 	response, err := makeResponse(authz.Challenges[ResponseIndex]) | ||||||
|  | 	test.AssertNotError(t, err, "Unable to construct response to challenge") | ||||||
|  | 	authz, err = ra.UpdateAuthorization(authz, ResponseIndex, response) | ||||||
| 	test.AssertNotError(t, err, "UpdateAuthorization failed") | 	test.AssertNotError(t, err, "UpdateAuthorization failed") | ||||||
| 
 | 
 | ||||||
| 	// Verify that returned authz same as DB
 | 	// Verify that returned authz same as DB
 | ||||||
|  | @ -428,7 +447,9 @@ func TestUpdateAuthorizationReject(t *testing.T) { | ||||||
| 	test.AssertNotError(t, err, "UpdateRegistration failed") | 	test.AssertNotError(t, err, "UpdateRegistration failed") | ||||||
| 
 | 
 | ||||||
| 	// Verify that the RA rejected the authorization request
 | 	// Verify that the RA rejected the authorization request
 | ||||||
| 	_, err = ra.UpdateAuthorization(authz, ResponseIndex, Response) | 	response, err := makeResponse(authz.Challenges[ResponseIndex]) | ||||||
|  | 	test.AssertNotError(t, err, "Unable to construct response to challenge") | ||||||
|  | 	_, err = ra.UpdateAuthorization(authz, ResponseIndex, response) | ||||||
| 	test.AssertEquals(t, err, core.UnauthorizedError("Challenge cannot be updated with a different key")) | 	test.AssertEquals(t, err, core.UnauthorizedError("Challenge cannot be updated with a different key")) | ||||||
| 
 | 
 | ||||||
| 	t.Log("DONE TestUpdateAuthorizationReject") | 	t.Log("DONE TestUpdateAuthorizationReject") | ||||||
|  | @ -437,7 +458,9 @@ func TestUpdateAuthorizationReject(t *testing.T) { | ||||||
| func TestOnValidationUpdateSuccess(t *testing.T) { | func TestOnValidationUpdateSuccess(t *testing.T) { | ||||||
| 	_, sa, ra, fclk, cleanUp := initAuthorities(t) | 	_, sa, ra, fclk, cleanUp := initAuthorities(t) | ||||||
| 	defer cleanUp() | 	defer cleanUp() | ||||||
| 	authzUpdated, _ := sa.NewPendingAuthorization(AuthzInitial) | 	authzUpdated, err := sa.NewPendingAuthorization(AuthzInitial) | ||||||
|  | 	test.AssertNotError(t, err, "Failed to create new pending authz") | ||||||
|  | 
 | ||||||
| 	expires := fclk.Now().Add(300 * 24 * time.Hour) | 	expires := fclk.Now().Add(300 * 24 * time.Hour) | ||||||
| 	authzUpdated.Expires = &expires | 	authzUpdated.Expires = &expires | ||||||
| 	sa.UpdatePendingAuthorization(authzUpdated) | 	sa.UpdatePendingAuthorization(authzUpdated) | ||||||
|  | @ -632,7 +655,7 @@ func TestDomainsForRateLimiting(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type mockSAWithNameCounts struct { | type mockSAWithNameCounts struct { | ||||||
| 	mocks.MockSA | 	mocks.StorageAuthority | ||||||
| 	nameCounts map[string]int | 	nameCounts map[string]int | ||||||
| 	t          *testing.T | 	t          *testing.T | ||||||
| 	clk        clock.FakeClock | 	clk        clock.FakeClock | ||||||
|  |  | ||||||
|  | @ -200,16 +200,16 @@ func (rpc *AmqpRPCServer) Handle(method string, handler func([]byte) ([]byte, er | ||||||
| 	rpc.dispatchTable[method] = handler | 	rpc.dispatchTable[method] = handler | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // RPCError is a JSON wrapper for error as it cannot be un/marshalled
 | // rpcError is a JSON wrapper for error as it cannot be un/marshalled
 | ||||||
| // due to type interface{}.
 | // due to type interface{}.
 | ||||||
| type RPCError struct { | type rpcError struct { | ||||||
| 	Value string `json:"value"` | 	Value string `json:"value"` | ||||||
| 	Type  string `json:"type,omitempty"` | 	Type  string `json:"type,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Wraps a error in a RPCError so it can be marshalled to
 | // Wraps a error in a rpcError so it can be marshalled to
 | ||||||
| // JSON.
 | // JSON.
 | ||||||
| func wrapError(err error) (rpcError RPCError) { | func wrapError(err error) (rpcError rpcError) { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		rpcError.Value = err.Error() | 		rpcError.Value = err.Error() | ||||||
| 		switch err.(type) { | 		switch err.(type) { | ||||||
|  | @ -240,8 +240,8 @@ func wrapError(err error) (rpcError RPCError) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Unwraps a RPCError and returns the correct error type.
 | // Unwraps a rpcError and returns the correct error type.
 | ||||||
| func unwrapError(rpcError RPCError) (err error) { | func unwrapError(rpcError rpcError) (err error) { | ||||||
| 	if rpcError.Value != "" { | 	if rpcError.Value != "" { | ||||||
| 		switch rpcError.Type { | 		switch rpcError.Type { | ||||||
| 		case "InternalServerError": | 		case "InternalServerError": | ||||||
|  | @ -273,11 +273,11 @@ func unwrapError(rpcError RPCError) (err error) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // RPCResponse is a stuct for wire-representation of response messages
 | // rpcResponse is a stuct for wire-representation of response messages
 | ||||||
| // used by DispatchSync
 | // used by DispatchSync
 | ||||||
| type RPCResponse struct { | type rpcResponse struct { | ||||||
| 	ReturnVal []byte   `json:"returnVal,omitempty"` | 	ReturnVal []byte   `json:"returnVal,omitempty"` | ||||||
| 	Error     RPCError `json:"error,omitempty"` | 	Error     rpcError `json:"error,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // AmqpChannel sets a AMQP connection up using SSL if configuration is provided
 | // AmqpChannel sets a AMQP connection up using SSL if configuration is provided
 | ||||||
|  | @ -362,7 +362,7 @@ func (rpc *AmqpRPCServer) processMessage(msg amqp.Delivery) { | ||||||
| 		rpc.log.Audit(fmt.Sprintf(" [s<][%s][%s] Misrouted message: %s - %s - %s", rpc.serverQueue, msg.ReplyTo, msg.Type, core.B64enc(msg.Body), msg.CorrelationId)) | 		rpc.log.Audit(fmt.Sprintf(" [s<][%s][%s] Misrouted message: %s - %s - %s", rpc.serverQueue, msg.ReplyTo, msg.Type, core.B64enc(msg.Body), msg.CorrelationId)) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	var response RPCResponse | 	var response rpcResponse | ||||||
| 	var err error | 	var err error | ||||||
| 	response.ReturnVal, err = cb(msg.Body) | 	response.ReturnVal, err = cb(msg.Body) | ||||||
| 	response.Error = wrapError(err) | 	response.Error = wrapError(err) | ||||||
|  | @ -407,7 +407,7 @@ func (rpc *AmqpRPCServer) replyTooManyRequests(msg amqp.Delivery) { | ||||||
| // until a fatal error is returned or AmqpRPCServer.Stop() is called and all
 | // until a fatal error is returned or AmqpRPCServer.Stop() is called and all
 | ||||||
| // remaining messages are processed.
 | // remaining messages are processed.
 | ||||||
| func (rpc *AmqpRPCServer) Start(c cmd.Config) error { | func (rpc *AmqpRPCServer) Start(c cmd.Config) error { | ||||||
| 	tooManyGoroutines := RPCResponse{ | 	tooManyGoroutines := rpcResponse{ | ||||||
| 		Error: wrapError(core.TooManyRPCRequestsError("RPC server has spawned too many Goroutines")), | 		Error: wrapError(core.TooManyRPCRequestsError("RPC server has spawned too many Goroutines")), | ||||||
| 	} | 	} | ||||||
| 	tooManyRequestsResponse, err := json.Marshal(tooManyGoroutines) | 	tooManyRequestsResponse, err := json.Marshal(tooManyGoroutines) | ||||||
|  | @ -632,7 +632,7 @@ func (rpc *AmqpRPCCLient) DispatchSync(method string, body []byte) (response []b | ||||||
| 	callStarted := time.Now() | 	callStarted := time.Now() | ||||||
| 	select { | 	select { | ||||||
| 	case jsonResponse := <-rpc.Dispatch(method, body): | 	case jsonResponse := <-rpc.Dispatch(method, body): | ||||||
| 		var rpcResponse RPCResponse | 		var rpcResponse rpcResponse | ||||||
| 		err = json.Unmarshal(jsonResponse, &rpcResponse) | 		err = json.Unmarshal(jsonResponse, &rpcResponse) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return | 			return | ||||||
|  |  | ||||||
|  | @ -9,14 +9,14 @@ import ( | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // RPCClient describes the functions an RPC Client performs
 | // Client describes the functions an RPC Client performs
 | ||||||
| type RPCClient interface { | type Client interface { | ||||||
| 	SetTimeout(time.Duration) | 	SetTimeout(time.Duration) | ||||||
| 	Dispatch(string, []byte) chan []byte | 	Dispatch(string, []byte) chan []byte | ||||||
| 	DispatchSync(string, []byte) ([]byte, error) | 	DispatchSync(string, []byte) ([]byte, error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // RPCServer describes the functions an RPC Server performs
 | // Server describes the functions an RPC Server performs
 | ||||||
| type RPCServer interface { | type Server interface { | ||||||
| 	Handle(string, func([]byte) ([]byte, error)) | 	Handle(string, func([]byte) ([]byte, error)) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -168,7 +168,7 @@ func errorCondition(method string, err error, obj interface{}) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewRegistrationAuthorityServer constructs an RPC server
 | // NewRegistrationAuthorityServer constructs an RPC server
 | ||||||
| func NewRegistrationAuthorityServer(rpc RPCServer, impl core.RegistrationAuthority) error { | func NewRegistrationAuthorityServer(rpc Server, impl core.RegistrationAuthority) error { | ||||||
| 	log := blog.GetAuditLogger() | 	log := blog.GetAuditLogger() | ||||||
| 
 | 
 | ||||||
| 	rpc.Handle(MethodNewRegistration, func(req []byte) (response []byte, err error) { | 	rpc.Handle(MethodNewRegistration, func(req []byte) (response []byte, err error) { | ||||||
|  | @ -345,11 +345,11 @@ func NewRegistrationAuthorityServer(rpc RPCServer, impl core.RegistrationAuthori | ||||||
| 
 | 
 | ||||||
| // RegistrationAuthorityClient represents an RA RPC client
 | // RegistrationAuthorityClient represents an RA RPC client
 | ||||||
| type RegistrationAuthorityClient struct { | type RegistrationAuthorityClient struct { | ||||||
| 	rpc RPCClient | 	rpc Client | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewRegistrationAuthorityClient constructs an RPC client
 | // NewRegistrationAuthorityClient constructs an RPC client
 | ||||||
| func NewRegistrationAuthorityClient(client RPCClient) (rac RegistrationAuthorityClient, err error) { | func NewRegistrationAuthorityClient(client Client) (rac RegistrationAuthorityClient, err error) { | ||||||
| 	rac = RegistrationAuthorityClient{rpc: client} | 	rac = RegistrationAuthorityClient{rpc: client} | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  | @ -496,7 +496,7 @@ func (rac RegistrationAuthorityClient) OnValidationUpdate(authz core.Authorizati | ||||||
| //
 | //
 | ||||||
| // ValidationAuthorityClient / Server
 | // ValidationAuthorityClient / Server
 | ||||||
| //  -> UpdateValidations
 | //  -> UpdateValidations
 | ||||||
| func NewValidationAuthorityServer(rpc RPCServer, impl core.ValidationAuthority) (err error) { | func NewValidationAuthorityServer(rpc Server, impl core.ValidationAuthority) (err error) { | ||||||
| 	rpc.Handle(MethodUpdateValidations, func(req []byte) (response []byte, err error) { | 	rpc.Handle(MethodUpdateValidations, func(req []byte) (response []byte, err error) { | ||||||
| 		var vaReq validationRequest | 		var vaReq validationRequest | ||||||
| 		if err = json.Unmarshal(req, &vaReq); err != nil { | 		if err = json.Unmarshal(req, &vaReq); err != nil { | ||||||
|  | @ -540,11 +540,11 @@ func NewValidationAuthorityServer(rpc RPCServer, impl core.ValidationAuthority) | ||||||
| 
 | 
 | ||||||
| // ValidationAuthorityClient represents an RPC client for the VA
 | // ValidationAuthorityClient represents an RPC client for the VA
 | ||||||
| type ValidationAuthorityClient struct { | type ValidationAuthorityClient struct { | ||||||
| 	rpc RPCClient | 	rpc Client | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewValidationAuthorityClient constructs an RPC client
 | // NewValidationAuthorityClient constructs an RPC client
 | ||||||
| func NewValidationAuthorityClient(client RPCClient) (vac ValidationAuthorityClient, err error) { | func NewValidationAuthorityClient(client Client) (vac ValidationAuthorityClient, err error) { | ||||||
| 	vac = ValidationAuthorityClient{rpc: client} | 	vac = ValidationAuthorityClient{rpc: client} | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  | @ -589,7 +589,8 @@ func (vac ValidationAuthorityClient) CheckCAARecords(ident core.AcmeIdentifier) | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewPublisherServer(rpc RPCServer, impl core.Publisher) (err error) { | // NewPublisherServer creates a new server that wraps a CT publisher
 | ||||||
|  | func NewPublisherServer(rpc Server, impl core.Publisher) (err error) { | ||||||
| 	rpc.Handle(MethodSubmitToCT, func(req []byte) (response []byte, err error) { | 	rpc.Handle(MethodSubmitToCT, func(req []byte) (response []byte, err error) { | ||||||
| 		err = impl.SubmitToCT(req) | 		err = impl.SubmitToCT(req) | ||||||
| 		return | 		return | ||||||
|  | @ -600,11 +601,11 @@ func NewPublisherServer(rpc RPCServer, impl core.Publisher) (err error) { | ||||||
| 
 | 
 | ||||||
| // PublisherClient is a client to communicate with the Publisher Authority
 | // PublisherClient is a client to communicate with the Publisher Authority
 | ||||||
| type PublisherClient struct { | type PublisherClient struct { | ||||||
| 	rpc RPCClient | 	rpc Client | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewPublisherClient constructs an RPC client
 | // NewPublisherClient constructs an RPC client
 | ||||||
| func NewPublisherClient(client RPCClient) (pub PublisherClient, err error) { | func NewPublisherClient(client Client) (pub PublisherClient, err error) { | ||||||
| 	pub = PublisherClient{rpc: client} | 	pub = PublisherClient{rpc: client} | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  | @ -619,7 +620,7 @@ func (pub PublisherClient) SubmitToCT(der []byte) (err error) { | ||||||
| //
 | //
 | ||||||
| // CertificateAuthorityClient / Server
 | // CertificateAuthorityClient / Server
 | ||||||
| //  -> IssueCertificate
 | //  -> IssueCertificate
 | ||||||
| func NewCertificateAuthorityServer(rpc RPCServer, impl core.CertificateAuthority) (err error) { | func NewCertificateAuthorityServer(rpc Server, impl core.CertificateAuthority) (err error) { | ||||||
| 	rpc.Handle(MethodIssueCertificate, func(req []byte) (response []byte, err error) { | 	rpc.Handle(MethodIssueCertificate, func(req []byte) (response []byte, err error) { | ||||||
| 		var icReq issueCertificateRequest | 		var icReq issueCertificateRequest | ||||||
| 		err = json.Unmarshal(req, &icReq) | 		err = json.Unmarshal(req, &icReq) | ||||||
|  | @ -686,11 +687,11 @@ func NewCertificateAuthorityServer(rpc RPCServer, impl core.CertificateAuthority | ||||||
| 
 | 
 | ||||||
| // CertificateAuthorityClient is a client to communicate with the CA.
 | // CertificateAuthorityClient is a client to communicate with the CA.
 | ||||||
| type CertificateAuthorityClient struct { | type CertificateAuthorityClient struct { | ||||||
| 	rpc RPCClient | 	rpc Client | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewCertificateAuthorityClient constructs an RPC client
 | // NewCertificateAuthorityClient constructs an RPC client
 | ||||||
| func NewCertificateAuthorityClient(client RPCClient) (cac CertificateAuthorityClient, err error) { | func NewCertificateAuthorityClient(client Client) (cac CertificateAuthorityClient, err error) { | ||||||
| 	cac = CertificateAuthorityClient{rpc: client} | 	cac = CertificateAuthorityClient{rpc: client} | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  | @ -752,7 +753,7 @@ func (cac CertificateAuthorityClient) GenerateOCSP(signRequest core.OCSPSigningR | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewStorageAuthorityServer constructs an RPC server
 | // NewStorageAuthorityServer constructs an RPC server
 | ||||||
| func NewStorageAuthorityServer(rpc RPCServer, impl core.StorageAuthority) error { | func NewStorageAuthorityServer(rpc Server, impl core.StorageAuthority) error { | ||||||
| 	rpc.Handle(MethodUpdateRegistration, func(req []byte) (response []byte, err error) { | 	rpc.Handle(MethodUpdateRegistration, func(req []byte) (response []byte, err error) { | ||||||
| 		var reg core.Registration | 		var reg core.Registration | ||||||
| 		if err = json.Unmarshal(req, ®); err != nil { | 		if err = json.Unmarshal(req, ®); err != nil { | ||||||
|  | @ -1055,7 +1056,7 @@ func NewStorageAuthorityServer(rpc RPCServer, impl core.StorageAuthority) error | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		sct, err := impl.GetSCTReceipt(gsctReq.Serial, gsctReq.LogID) | 		sct, err := impl.GetSCTReceipt(gsctReq.Serial, gsctReq.LogID) | ||||||
| 		jsonResponse, err := json.Marshal(core.RPCSignedCertificateTimestamp(sct)) | 		jsonResponse, err := json.Marshal(core.SignedCertificateTimestamp(sct)) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
 | 			// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
 | ||||||
| 			errorCondition(MethodGetSCTReceipt, err, req) | 			errorCondition(MethodGetSCTReceipt, err, req) | ||||||
|  | @ -1066,7 +1067,7 @@ func NewStorageAuthorityServer(rpc RPCServer, impl core.StorageAuthority) error | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	rpc.Handle(MethodAddSCTReceipt, func(req []byte) (response []byte, err error) { | 	rpc.Handle(MethodAddSCTReceipt, func(req []byte) (response []byte, err error) { | ||||||
| 		var sct core.RPCSignedCertificateTimestamp | 		var sct core.SignedCertificateTimestamp | ||||||
| 		err = json.Unmarshal(req, &sct) | 		err = json.Unmarshal(req, &sct) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
 | 			// AUDIT[ Improper Messages ] 0786b6f2-91ca-4f48-9883-842a19084c64
 | ||||||
|  | @ -1089,11 +1090,11 @@ func NewStorageAuthorityServer(rpc RPCServer, impl core.StorageAuthority) error | ||||||
| 
 | 
 | ||||||
| // StorageAuthorityClient is a client to communicate with the Storage Authority
 | // StorageAuthorityClient is a client to communicate with the Storage Authority
 | ||||||
| type StorageAuthorityClient struct { | type StorageAuthorityClient struct { | ||||||
| 	rpc RPCClient | 	rpc Client | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewStorageAuthorityClient constructs an RPC client
 | // NewStorageAuthorityClient constructs an RPC client
 | ||||||
| func NewStorageAuthorityClient(client RPCClient) (sac StorageAuthorityClient, err error) { | func NewStorageAuthorityClient(client Client) (sac StorageAuthorityClient, err error) { | ||||||
| 	sac = StorageAuthorityClient{rpc: client} | 	sac = StorageAuthorityClient{rpc: client} | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  | @ -1145,10 +1146,10 @@ func (cac StorageAuthorityClient) GetAuthorization(id string) (authz core.Author | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetLatestValidAuthorization sends a request to get an Authorization by RegID, Identifier
 | // GetLatestValidAuthorization sends a request to get an Authorization by RegID, Identifier
 | ||||||
| func (cac StorageAuthorityClient) GetLatestValidAuthorization(registrationId int64, identifier core.AcmeIdentifier) (authz core.Authorization, err error) { | func (cac StorageAuthorityClient) GetLatestValidAuthorization(registrationID int64, identifier core.AcmeIdentifier) (authz core.Authorization, err error) { | ||||||
| 
 | 
 | ||||||
| 	var lvar latestValidAuthorizationRequest | 	var lvar latestValidAuthorizationRequest | ||||||
| 	lvar.RegID = registrationId | 	lvar.RegID = registrationID | ||||||
| 	lvar.Identifier = identifier | 	lvar.Identifier = identifier | ||||||
| 
 | 
 | ||||||
| 	data, err := json.Marshal(lvar) | 	data, err := json.Marshal(lvar) | ||||||
|  | @ -1369,6 +1370,8 @@ func (cac StorageAuthorityClient) CountCertificatesByNames(names []string, earli | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // GetSCTReceipt retrieves an SCT according to the serial number of a certificate
 | ||||||
|  | // and the logID of the log to which it was submitted.
 | ||||||
| func (cac StorageAuthorityClient) GetSCTReceipt(serial string, logID string) (receipt core.SignedCertificateTimestamp, err error) { | func (cac StorageAuthorityClient) GetSCTReceipt(serial string, logID string) (receipt core.SignedCertificateTimestamp, err error) { | ||||||
| 	var gsctReq struct { | 	var gsctReq struct { | ||||||
| 		Serial string | 		Serial string | ||||||
|  | @ -1391,6 +1394,7 @@ func (cac StorageAuthorityClient) GetSCTReceipt(serial string, logID string) (re | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // AddSCTReceipt adds a new SCT to the database.
 | ||||||
| func (cac StorageAuthorityClient) AddSCTReceipt(sct core.SignedCertificateTimestamp) (err error) { | func (cac StorageAuthorityClient) AddSCTReceipt(sct core.SignedCertificateTimestamp) (err error) { | ||||||
| 	data, err := json.Marshal(sct) | 	data, err := json.Marshal(sct) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,12 @@ | ||||||
|  | 
 | ||||||
|  | -- +goose Up | ||||||
|  | -- SQL in section 'Up' is executed when this migration is applied | ||||||
|  | 
 | ||||||
|  | ALTER TABLE `challenges` ADD COLUMN (`keyAuthorization` varchar(255)); | ||||||
|  | ALTER TABLE `challenges` DROP COLUMN `validation`; | ||||||
|  | 
 | ||||||
|  | -- +goose Down | ||||||
|  | -- SQL section 'Down' is executed when this migration is rolled back | ||||||
|  | 
 | ||||||
|  | ALTER TABLE `challenges` DROP COLUMN `keyAuthorization`; | ||||||
|  | ALTER TABLE `challenges` ADD COLUMN (`validation` mediumblob); | ||||||
|  | @ -11,6 +11,7 @@ import ( | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
|  | 	// Provide access to the MySQL driver
 | ||||||
| 	_ "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/go-sql-driver/mysql" | 	_ "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/go-sql-driver/mysql" | ||||||
| 	gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1" | 	gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1" | ||||||
| 	"github.com/letsencrypt/boulder/core" | 	"github.com/letsencrypt/boulder/core" | ||||||
|  |  | ||||||
							
								
								
									
										19
									
								
								sa/model.go
								
								
								
								
							
							
						
						
									
										19
									
								
								sa/model.go
								
								
								
								
							|  | @ -35,6 +35,8 @@ type regModel struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // challModel is the description of a core.Challenge in the database
 | // challModel is the description of a core.Challenge in the database
 | ||||||
|  | //
 | ||||||
|  | // The Validation field is a stub; the column is only there for backward compatibility.
 | ||||||
| type challModel struct { | type challModel struct { | ||||||
| 	ID              int64  `db:"id"` | 	ID              int64  `db:"id"` | ||||||
| 	AuthorizationID string `db:"authorizationID"` | 	AuthorizationID string `db:"authorizationID"` | ||||||
|  | @ -45,7 +47,7 @@ type challModel struct { | ||||||
| 	Validated        *time.Time      `db:"validated"` | 	Validated        *time.Time      `db:"validated"` | ||||||
| 	Token            string          `db:"token"` | 	Token            string          `db:"token"` | ||||||
| 	TLS              *bool           `db:"tls"` | 	TLS              *bool           `db:"tls"` | ||||||
| 	Validation       []byte          `db:"validation"` | 	KeyAuthorization string          `db:"keyAuthorization"` | ||||||
| 	ValidationRecord []byte          `db:"validationRecord"` | 	ValidationRecord []byte          `db:"validationRecord"` | ||||||
| 	AccountKey       []byte          `db:"accountKey"` | 	AccountKey       []byte          `db:"accountKey"` | ||||||
| 
 | 
 | ||||||
|  | @ -99,11 +101,12 @@ func challengeToModel(c *core.Challenge, authID string) (*challModel, error) { | ||||||
| 		Token:           c.Token, | 		Token:           c.Token, | ||||||
| 		TLS:             c.TLS, | 		TLS:             c.TLS, | ||||||
| 	} | 	} | ||||||
| 	if c.Validation != nil { | 	if c.KeyAuthorization != nil { | ||||||
| 		cm.Validation = []byte(c.Validation.FullSerialize()) | 		kaString := c.KeyAuthorization.String() | ||||||
| 		if len(cm.Validation) > mediumBlobSize { | 		if len(kaString) > 255 { | ||||||
| 			return nil, fmt.Errorf("Validation object is too large to store in the database") | 			return nil, fmt.Errorf("Key authorization is too large to store in the database") | ||||||
| 		} | 		} | ||||||
|  | 		cm.KeyAuthorization = kaString | ||||||
| 	} | 	} | ||||||
| 	if c.Error != nil { | 	if c.Error != nil { | ||||||
| 		errJSON, err := json.Marshal(c.Error) | 		errJSON, err := json.Marshal(c.Error) | ||||||
|  | @ -147,12 +150,12 @@ func modelToChallenge(cm *challModel) (core.Challenge, error) { | ||||||
| 		Token:     cm.Token, | 		Token:     cm.Token, | ||||||
| 		TLS:       cm.TLS, | 		TLS:       cm.TLS, | ||||||
| 	} | 	} | ||||||
| 	if len(cm.Validation) > 0 { | 	if len(cm.KeyAuthorization) > 0 { | ||||||
| 		val, err := jose.ParseSigned(string(cm.Validation)) | 		ka, err := core.NewKeyAuthorizationFromString(cm.KeyAuthorization) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return core.Challenge{}, err | 			return core.Challenge{}, err | ||||||
| 		} | 		} | ||||||
| 		c.Validation = val | 		c.KeyAuthorization = &ka | ||||||
| 	} | 	} | ||||||
| 	if len(cm.Error) > 0 { | 	if len(cm.Error) > 0 { | ||||||
| 		var problem core.ProblemDetails | 		var problem core.ProblemDetails | ||||||
|  |  | ||||||
|  | @ -212,16 +212,16 @@ func (ssa *SQLStorageAuthority) GetAuthorization(id string) (authz core.Authoriz | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetLatestValidAuthorization gets the valid authorization with biggest expire date for a given domain and registrationId
 | // GetLatestValidAuthorization gets the valid authorization with biggest expire date for a given domain and registrationId
 | ||||||
| func (ssa *SQLStorageAuthority) GetLatestValidAuthorization(registrationId int64, identifier core.AcmeIdentifier) (authz core.Authorization, err error) { | func (ssa *SQLStorageAuthority) GetLatestValidAuthorization(registrationID int64, identifier core.AcmeIdentifier) (authz core.Authorization, err error) { | ||||||
| 	ident, err := json.Marshal(identifier) | 	ident, err := json.Marshal(identifier) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	var auth core.Authorization | 	var auth core.Authorization | ||||||
| 	err = ssa.dbMap.SelectOne(&auth, "SELECT id FROM authz "+ | 	err = ssa.dbMap.SelectOne(&auth, "SELECT id FROM authz "+ | ||||||
| 		"WHERE identifier = :identifier AND registrationID = :registrationId AND status = 'valid' "+ | 		"WHERE identifier = :identifier AND registrationID = :registrationID AND status = 'valid' "+ | ||||||
| 		"ORDER BY expires DESC LIMIT 1", | 		"ORDER BY expires DESC LIMIT 1", | ||||||
| 		map[string]interface{}{"identifier": string(ident), "registrationId": registrationId}) | 		map[string]interface{}{"identifier": string(ident), "registrationID": registrationID}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | @ -229,6 +229,8 @@ func (ssa *SQLStorageAuthority) GetLatestValidAuthorization(registrationId int64 | ||||||
| 	return ssa.GetAuthorization(auth.ID) | 	return ssa.GetAuthorization(auth.ID) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // TooManyCertificatesError indicates that the number of certificates returned by
 | ||||||
|  | // CountCertificates exceeded the hard-coded limit of 10,000 certificates.
 | ||||||
| type TooManyCertificatesError string | type TooManyCertificatesError string | ||||||
| 
 | 
 | ||||||
| func (t TooManyCertificatesError) Error() string { | func (t TooManyCertificatesError) Error() string { | ||||||
|  |  | ||||||
|  | @ -168,10 +168,10 @@ func TestAddAuthorization(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func CreateDomainAuth(t *testing.T, domainName string, sa *SQLStorageAuthority) (authz core.Authorization) { | func CreateDomainAuth(t *testing.T, domainName string, sa *SQLStorageAuthority) (authz core.Authorization) { | ||||||
| 	return CreateDomainAuthWithRegId(t, domainName, sa, 42) | 	return CreateDomainAuthWithRegID(t, domainName, sa, 42) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func CreateDomainAuthWithRegId(t *testing.T, domainName string, sa *SQLStorageAuthority, regID int64) (authz core.Authorization) { | func CreateDomainAuthWithRegID(t *testing.T, domainName string, sa *SQLStorageAuthority, regID int64) (authz core.Authorization) { | ||||||
| 
 | 
 | ||||||
| 	// create pending auth
 | 	// create pending auth
 | ||||||
| 	authz, err := sa.NewPendingAuthorization(core.Authorization{RegistrationID: regID, Challenges: []core.Challenge{core.Challenge{}}}) | 	authz, err := sa.NewPendingAuthorization(core.Authorization{RegistrationID: regID, Challenges: []core.Challenge{core.Challenge{}}}) | ||||||
|  | @ -212,7 +212,7 @@ func TestGetLatestValidAuthorizationBasic(t *testing.T) { | ||||||
| 	reg := satest.CreateWorkingRegistration(t, sa) | 	reg := satest.CreateWorkingRegistration(t, sa) | ||||||
| 
 | 
 | ||||||
| 	// authorize "example.org"
 | 	// authorize "example.org"
 | ||||||
| 	authz = CreateDomainAuthWithRegId(t, "example.org", sa, reg.ID) | 	authz = CreateDomainAuthWithRegID(t, "example.org", sa, reg.ID) | ||||||
| 
 | 
 | ||||||
| 	// finalize auth
 | 	// finalize auth
 | ||||||
| 	authz.Status = core.StatusValid | 	authz.Status = core.StatusValid | ||||||
|  | @ -243,7 +243,7 @@ func TestGetLatestValidAuthorizationMultiple(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	reg := satest.CreateWorkingRegistration(t, sa) | 	reg := satest.CreateWorkingRegistration(t, sa) | ||||||
| 	// create invalid authz
 | 	// create invalid authz
 | ||||||
| 	authz := CreateDomainAuthWithRegId(t, domain, sa, reg.ID) | 	authz := CreateDomainAuthWithRegID(t, domain, sa, reg.ID) | ||||||
| 	exp := time.Now().AddDate(0, 0, 10) // expire in 10 day
 | 	exp := time.Now().AddDate(0, 0, 10) // expire in 10 day
 | ||||||
| 	authz.Expires = &exp | 	authz.Expires = &exp | ||||||
| 	authz.Status = core.StatusInvalid | 	authz.Status = core.StatusInvalid | ||||||
|  | @ -255,7 +255,7 @@ func TestGetLatestValidAuthorizationMultiple(t *testing.T) { | ||||||
| 	test.AssertError(t, err, "Should not have found a valid auth for "+domain) | 	test.AssertError(t, err, "Should not have found a valid auth for "+domain) | ||||||
| 
 | 
 | ||||||
| 	// create valid auth
 | 	// create valid auth
 | ||||||
| 	authz = CreateDomainAuthWithRegId(t, domain, sa, reg.ID) | 	authz = CreateDomainAuthWithRegID(t, domain, sa, reg.ID) | ||||||
| 	exp = time.Now().AddDate(0, 0, 1) // expire in 1 day
 | 	exp = time.Now().AddDate(0, 0, 1) // expire in 1 day
 | ||||||
| 	authz.Expires = &exp | 	authz.Expires = &exp | ||||||
| 	authz.Status = core.StatusValid | 	authz.Status = core.StatusValid | ||||||
|  | @ -271,7 +271,7 @@ func TestGetLatestValidAuthorizationMultiple(t *testing.T) { | ||||||
| 	test.AssertEquals(t, authz.RegistrationID, reg.ID) | 	test.AssertEquals(t, authz.RegistrationID, reg.ID) | ||||||
| 
 | 
 | ||||||
| 	// create a newer auth
 | 	// create a newer auth
 | ||||||
| 	newAuthz := CreateDomainAuthWithRegId(t, domain, sa, reg.ID) | 	newAuthz := CreateDomainAuthWithRegID(t, domain, sa, reg.ID) | ||||||
| 	exp = time.Now().AddDate(0, 0, 2) // expire in 2 day
 | 	exp = time.Now().AddDate(0, 0, 2) // expire in 2 day
 | ||||||
| 	newAuthz.Expires = &exp | 	newAuthz.Expires = &exp | ||||||
| 	newAuthz.Status = core.StatusValid | 	newAuthz.Status = core.StatusValid | ||||||
|  |  | ||||||
							
								
								
									
										24
									
								
								test.sh
								
								
								
								
							
							
						
						
									
										24
									
								
								test.sh
								
								
								
								
							|  | @ -63,13 +63,23 @@ function run() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function run_and_comment() { | function run_and_comment() { | ||||||
|   if [ "x${TRAVIS}" = "x" ] || [ "${TRAVIS_PULL_REQUEST}" == "false" ] || [ ! -f "${GITHUB_SECRET_FILE}" ] ; then |   echo "$@" | ||||||
|     run "$@" |   result=$("$@" 2>&1) | ||||||
|  |   echo ${result} | ||||||
|  | 
 | ||||||
|  |   if [ "x${result}" == "x" ]; then | ||||||
|  |     update_status --state success | ||||||
|  |     echo "Success: $@" | ||||||
|   else |   else | ||||||
|     result=$(run "$@") |     FAILURE=1 | ||||||
|     local status=$? |     update_status --state failure | ||||||
|     # Only send a comment if exit code > 0 |     echo "[!] FAILURE: $@" | ||||||
|     if [ ${status} -ne 0 ] ; then |   fi | ||||||
|  | 
 | ||||||
|  |   # If this is a travis PR run, post a comment | ||||||
|  |   if [ "x${TRAVIS}" != "x" ] && [ "${TRAVIS_PULL_REQUEST}" != "false" ] && [ -f "${GITHUB_SECRET_FILE}" ] ; then | ||||||
|  |     # If the output is non-empty, post a comment and mark this as a failure | ||||||
|  |     if [ "x${result}" -ne "x" ] ; then | ||||||
|       echo $'```\n'${result}$'\n```' | github-pr-status --authfile $GITHUB_SECRET_FILE \ |       echo $'```\n'${result}$'\n```' | github-pr-status --authfile $GITHUB_SECRET_FILE \ | ||||||
|         --owner "letsencrypt" --repo "boulder" \ |         --owner "letsencrypt" --repo "boulder" \ | ||||||
|         comment --pr "${TRAVIS_PULL_REQUEST}" -b - |         comment --pr "${TRAVIS_PULL_REQUEST}" -b - | ||||||
|  | @ -157,7 +167,7 @@ fi | ||||||
| # | # | ||||||
| if [[ "$RUN" =~ "lint" ]] ; then | if [[ "$RUN" =~ "lint" ]] ; then | ||||||
|   start_context "test/golint" |   start_context "test/golint" | ||||||
|   [ -x "$(which golint)" ] && run golint ./... |   run_and_comment golint -min_confidence=0.81 ./... | ||||||
|   end_context #test/golint |   end_context #test/golint | ||||||
| fi | fi | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -134,9 +134,9 @@ | ||||||
|     "userAgent": "boulder", |     "userAgent": "boulder", | ||||||
|     "debugAddr": "localhost:8004", |     "debugAddr": "localhost:8004", | ||||||
|     "portConfig": { |     "portConfig": { | ||||||
|       "simpleHTTPPort": 5001, |       "httpPort": 5001, | ||||||
|       "simpleHTTPSPort": 5001, |       "httpsPort": 5001, | ||||||
|       "dvsniPort": 5001 |       "tlsPort": 5001 | ||||||
|     }, |     }, | ||||||
|     "maxConcurrentRPCServerRequests": 16 |     "maxConcurrentRPCServerRequests": 16 | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|  | @ -137,6 +137,12 @@ module.exports = { | ||||||
|     return forge.pki.certificateRequestToPem(c); |     return forge.pki.certificateRequestToPem(c); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|  |   thumbprint: function(publicKey) { | ||||||
|  |     // Only handling RSA keys
 | ||||||
|  |     input = bytesToBuffer('{"e":"'+ publicKey.e + '","kty":"RSA","n":"'+ publicKey.n +'"}'); | ||||||
|  |     return util.b64enc(crypto.createHash('sha256').update(input).digest()); | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|   ///// SIGNATURE GENERATION / VERIFICATION
 |   ///// SIGNATURE GENERATION / VERIFICATION
 | ||||||
| 
 | 
 | ||||||
|   generateSignature: function(keyPair, payload, nonce) { |   generateSignature: function(keyPair, payload, nonce) { | ||||||
|  |  | ||||||
|  | @ -20,7 +20,6 @@ var cryptoUtil = require("./crypto-util"); | ||||||
| var child_process = require('child_process'); | var child_process = require('child_process'); | ||||||
| var fs = require('fs'); | var fs = require('fs'); | ||||||
| var http = require('http'); | var http = require('http'); | ||||||
| var https = require('https'); |  | ||||||
| var inquirer = require("inquirer"); | var inquirer = require("inquirer"); | ||||||
| var request = require('request'); | var request = require('request'); | ||||||
| var url = require('url'); | var url = require('url'); | ||||||
|  | @ -103,7 +102,7 @@ var cliOptions = cli.parse({ | ||||||
| 
 | 
 | ||||||
| var state = { | var state = { | ||||||
|   certPrivateKey: null, |   certPrivateKey: null, | ||||||
|   accountPrivateKey: null, |   accountKeyPair: null, | ||||||
| 
 | 
 | ||||||
|   newRegistrationURL: cliOptions.newReg, |   newRegistrationURL: cliOptions.newReg, | ||||||
|   registrationURL: "", |   registrationURL: "", | ||||||
|  | @ -221,8 +220,8 @@ function makeAccountKeyPair(answers) { | ||||||
|       console.log(error); |       console.log(error); | ||||||
|       process.exit(1); |       process.exit(1); | ||||||
|     } |     } | ||||||
|     state.accountPrivateKey = cryptoUtil.importPemPrivateKey(fs.readFileSync("account-key.pem")); |     state.accountKeyPair = cryptoUtil.importPemPrivateKey(fs.readFileSync("account-key.pem")); | ||||||
|     state.acme = new Acme(state.accountPrivateKey); |     state.acme = new Acme(state.accountKeyPair); | ||||||
| 
 | 
 | ||||||
|     console.log(); |     console.log(); | ||||||
|     if (cliOptions.email) { |     if (cliOptions.email) { | ||||||
|  | @ -348,26 +347,20 @@ function getReadyToValidate(err, resp, body) { | ||||||
| 
 | 
 | ||||||
|   var authz = JSON.parse(body); |   var authz = JSON.parse(body); | ||||||
| 
 | 
 | ||||||
|   var simpleHttp = authz.challenges.filter(function(x) { return x.type == "simpleHttp"; }); |   var httpChallenges = authz.challenges.filter(function(x) { return x.type == "http-01"; }); | ||||||
|   if (simpleHttp.length == 0) { |   if (httpChallenges.length == 0) { | ||||||
|     console.log("The server didn't offer any challenges we can handle."); |     console.log("The server didn't offer any challenges we can handle."); | ||||||
|     process.exit(1); |     process.exit(1); | ||||||
|   } |   } | ||||||
|  |   var challenge = httpChallenges[0]; | ||||||
|  | 
 | ||||||
|  |   // Construct a key authorization for this token and key
 | ||||||
|  |   var thumbprint = cryptoUtil.thumbprint(state.accountKeyPair.publicKey); | ||||||
|  |   var keyAuthorization = challenge.token + "." + thumbprint; | ||||||
| 
 | 
 | ||||||
|   var challenge = simpleHttp[0]; |  | ||||||
|   var path = cryptoUtil.randomString(8) + ".txt"; |  | ||||||
|   var challengePath = ".well-known/acme-challenge/" + challenge.token; |   var challengePath = ".well-known/acme-challenge/" + challenge.token; | ||||||
|   state.responseURL = challenge["uri"]; |   state.responseURL = challenge["uri"]; | ||||||
|   state.path = path; |   state.path = challengePath; | ||||||
| 
 |  | ||||||
|   // Sign validation JWS
 |  | ||||||
|   var validationObject = JSON.stringify({ |  | ||||||
|     type: "simpleHttp", |  | ||||||
|     token: challenge.token, |  | ||||||
|     tls: true |  | ||||||
|   }) |  | ||||||
|   var validationJWS = cryptoUtil.generateSignature(state.accountPrivateKey, |  | ||||||
|                                                new Buffer(validationObject)) |  | ||||||
| 
 | 
 | ||||||
|   // For local, test-mode validation
 |   // For local, test-mode validation
 | ||||||
|   function httpResponder(req, response) { |   function httpResponder(req, response) { | ||||||
|  | @ -376,18 +369,16 @@ function getReadyToValidate(err, resp, body) { | ||||||
|     if ((host.split(/:/)[0] === state.domain || /localhost/.test(state.newRegistrationURL)) && |     if ((host.split(/:/)[0] === state.domain || /localhost/.test(state.newRegistrationURL)) && | ||||||
|         req.method === "GET" && |         req.method === "GET" && | ||||||
|         req.url == "/" + challengePath) { |         req.url == "/" + challengePath) { | ||||||
|       response.writeHead(200, {"Content-Type": "application/jose+json"}); |       console.log("Providing key authorization:", keyAuthorization); | ||||||
|       response.end(JSON.stringify(validationJWS)); |       response.writeHead(200, {"Content-Type": "application/json"}); | ||||||
|  |       response.end(keyAuthorization); | ||||||
|     } else { |     } else { | ||||||
|       console.log("Got invalid request for", req.method, host, req.url); |       console.log("Got invalid request for", req.method, host, req.url); | ||||||
|       response.writeHead(404, {"Content-Type": "text/plain"}); |       response.writeHead(404, {"Content-Type": "text/plain"}); | ||||||
|       response.end(""); |       response.end(""); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   state.httpServer = https.createServer({ |   state.httpServer = http.createServer(httpResponder) | ||||||
|     cert: fs.readFileSync("temp-cert.pem"), |  | ||||||
|     key: fs.readFileSync(state.keyFile) |  | ||||||
|   }, httpResponder) |  | ||||||
|   if (/localhost/.test(state.newRegistrationURL)) { |   if (/localhost/.test(state.newRegistrationURL)) { | ||||||
|     state.httpServer.listen(5001) |     state.httpServer.listen(5001) | ||||||
|   } else { |   } else { | ||||||
|  | @ -397,8 +388,7 @@ function getReadyToValidate(err, resp, body) { | ||||||
|   cli.spinner("Validating domain"); |   cli.spinner("Validating domain"); | ||||||
|   post(state.responseURL, { |   post(state.responseURL, { | ||||||
|     resource: "challenge", |     resource: "challenge", | ||||||
|     path: state.path, |     keyAuthorization: keyAuthorization, | ||||||
|     tls: true, |  | ||||||
|   }, ensureValidation); |   }, ensureValidation); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -46,9 +46,9 @@ type ValidationAuthorityImpl struct { | ||||||
| 	log          *blog.AuditLogger | 	log          *blog.AuditLogger | ||||||
| 	DNSResolver  core.DNSResolver | 	DNSResolver  core.DNSResolver | ||||||
| 	IssuerDomain string | 	IssuerDomain string | ||||||
| 	simpleHTTPPort  int | 	httpPort     int | ||||||
| 	simpleHTTPSPort int | 	httpsPort    int | ||||||
| 	dvsniPort       int | 	tlsPort      int | ||||||
| 	UserAgent    string | 	UserAgent    string | ||||||
| 	stats        statsd.Statter | 	stats        statsd.Statter | ||||||
| 	clk          clock.Clock | 	clk          clock.Clock | ||||||
|  | @ -57,9 +57,9 @@ type ValidationAuthorityImpl struct { | ||||||
| // PortConfig specifies what ports the VA should call to on the remote
 | // PortConfig specifies what ports the VA should call to on the remote
 | ||||||
| // host when performing its checks.
 | // host when performing its checks.
 | ||||||
| type PortConfig struct { | type PortConfig struct { | ||||||
| 	SimpleHTTPPort  int | 	HTTPPort  int | ||||||
| 	SimpleHTTPSPort int | 	HTTPSPort int | ||||||
| 	DVSNIPort       int | 	TLSPort   int | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewValidationAuthorityImpl constructs a new VA
 | // NewValidationAuthorityImpl constructs a new VA
 | ||||||
|  | @ -68,9 +68,9 @@ func NewValidationAuthorityImpl(pc *PortConfig, stats statsd.Statter, clk clock. | ||||||
| 	logger.Notice("Validation Authority Starting") | 	logger.Notice("Validation Authority Starting") | ||||||
| 	return &ValidationAuthorityImpl{ | 	return &ValidationAuthorityImpl{ | ||||||
| 		log:       logger, | 		log:       logger, | ||||||
| 		simpleHTTPPort:  pc.SimpleHTTPPort, | 		httpPort:  pc.HTTPPort, | ||||||
| 		simpleHTTPSPort: pc.SimpleHTTPSPort, | 		httpsPort: pc.HTTPSPort, | ||||||
| 		dvsniPort:       pc.DVSNIPort, | 		tlsPort:   pc.TLSPort, | ||||||
| 		stats:     stats, | 		stats:     stats, | ||||||
| 		clk:       clk, | 		clk:       clk, | ||||||
| 	} | 	} | ||||||
|  | @ -86,6 +86,7 @@ type verificationRequestEvent struct { | ||||||
| 	Error        string         `json:",omitempty"` | 	Error        string         `json:",omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
 | ||||||
| func verifyValidationJWS(validation *jose.JsonWebSignature, accountKey *jose.JsonWebKey, target map[string]interface{}) error { | func verifyValidationJWS(validation *jose.JsonWebSignature, accountKey *jose.JsonWebKey, target map[string]interface{}) error { | ||||||
| 	if len(validation.Signatures) > 1 { | 	if len(validation.Signatures) > 1 { | ||||||
| 		return fmt.Errorf("Too many signatures on validation JWS") | 		return fmt.Errorf("Too many signatures on validation JWS") | ||||||
|  | @ -178,7 +179,7 @@ func (d *dialer) Dial(_, _ string) (net.Conn, error) { | ||||||
| // resolveAndConstructDialer gets the prefered address using va.getAddr and returns
 | // resolveAndConstructDialer gets the prefered address using va.getAddr and returns
 | ||||||
| // the chosen address and dialer for that address and correct port.
 | // the chosen address and dialer for that address and correct port.
 | ||||||
| func (va *ValidationAuthorityImpl) resolveAndConstructDialer(name, defaultPort string) (dialer, *core.ProblemDetails) { | func (va *ValidationAuthorityImpl) resolveAndConstructDialer(name, defaultPort string) (dialer, *core.ProblemDetails) { | ||||||
| 	port := fmt.Sprintf("%d", va.simpleHTTPPort) | 	port := fmt.Sprintf("%d", va.httpPort) | ||||||
| 	if defaultPort != "" { | 	if defaultPort != "" { | ||||||
| 		port = defaultPort | 		port = defaultPort | ||||||
| 	} | 	} | ||||||
|  | @ -200,50 +201,37 @@ func (va *ValidationAuthorityImpl) resolveAndConstructDialer(name, defaultPort s | ||||||
| 
 | 
 | ||||||
| // Validation methods
 | // Validation methods
 | ||||||
| 
 | 
 | ||||||
| func (va *ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) { | func (va *ValidationAuthorityImpl) fetchHTTP(identifier core.AcmeIdentifier, path string, useTLS bool, input core.Challenge) ([]byte, core.Challenge, error) { | ||||||
|  | 	emptyBody := []byte{} | ||||||
| 	challenge := input | 	challenge := input | ||||||
| 
 | 
 | ||||||
| 	if identifier.Type != core.IdentifierDNS { |  | ||||||
| 		challenge.Status = core.StatusInvalid |  | ||||||
| 		challenge.Error = &core.ProblemDetails{ |  | ||||||
| 			Type:   core.MalformedProblem, |  | ||||||
| 			Detail: "Identifier type for SimpleHTTP was not DNS", |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		va.log.Debug(fmt.Sprintf("SimpleHTTP [%s] Identifier failure", identifier)) |  | ||||||
| 		return challenge, challenge.Error |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	host := identifier.Value | 	host := identifier.Value | ||||||
| 	var scheme string | 	scheme := "http" | ||||||
| 	var port int | 	port := va.httpPort | ||||||
| 	if input.TLS == nil || (input.TLS != nil && *input.TLS) { | 	if useTLS { | ||||||
| 		scheme = "https" | 		scheme = "https" | ||||||
| 		port = va.simpleHTTPSPort | 		port = va.httpsPort | ||||||
| 	} else { |  | ||||||
| 		scheme = "http" |  | ||||||
| 		port = va.simpleHTTPPort |  | ||||||
| 	} | 	} | ||||||
| 	portString := fmt.Sprintf("%d", port) | 	portString := strconv.Itoa(port) | ||||||
| 	hostPort := net.JoinHostPort(host, portString) | 	hostPort := net.JoinHostPort(host, portString) | ||||||
| 
 | 
 | ||||||
| 	url := &url.URL{ | 	url := &url.URL{ | ||||||
| 		Scheme: scheme, | 		Scheme: scheme, | ||||||
| 		Host:   hostPort, | 		Host:   hostPort, | ||||||
| 		Path:   fmt.Sprintf(".well-known/acme-challenge/%s", challenge.Token), | 		Path:   path, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
 | 	// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
 | ||||||
| 	va.log.Audit(fmt.Sprintf("Attempting to validate Simple%s for %s", strings.ToUpper(scheme), url)) | 	va.log.Audit(fmt.Sprintf("Attempting to validate %s for %s", challenge.Type, url)) | ||||||
| 	httpRequest, err := http.NewRequest("GET", url.String(), nil) | 	httpRequest, err := http.NewRequest("GET", url.String(), nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		challenge.Error = &core.ProblemDetails{ | 		challenge.Error = &core.ProblemDetails{ | ||||||
| 			Type:   core.MalformedProblem, | 			Type:   core.MalformedProblem, | ||||||
| 			Detail: "URL provided for SimpleHTTP was invalid", | 			Detail: "URL provided for HTTP was invalid", | ||||||
| 		} | 		} | ||||||
| 		va.log.Debug(fmt.Sprintf("SimpleHTTP [%s] HTTP failure: %s", identifier, err)) | 		va.log.Debug(fmt.Sprintf("%s [%s] HTTP failure: %s", challenge.Type, identifier, err)) | ||||||
| 		challenge.Status = core.StatusInvalid | 		challenge.Status = core.StatusInvalid | ||||||
| 		return challenge, err | 		return emptyBody, challenge, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if va.UserAgent != "" { | 	if va.UserAgent != "" { | ||||||
|  | @ -257,7 +245,7 @@ func (va *ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdenti | ||||||
| 	if prob != nil { | 	if prob != nil { | ||||||
| 		challenge.Status = core.StatusInvalid | 		challenge.Status = core.StatusInvalid | ||||||
| 		challenge.Error = prob | 		challenge.Error = prob | ||||||
| 		return challenge, prob | 		return emptyBody, challenge, prob | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	tr := &http.Transport{ | 	tr := &http.Transport{ | ||||||
|  | @ -303,7 +291,7 @@ func (va *ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdenti | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		tr.Dial = dialer.Dial | 		tr.Dial = dialer.Dial | ||||||
| 		va.log.Info(fmt.Sprintf("validateSimpleHTTP [%s] redirect from %q to %q [%s]", identifier, via[len(via)-1].URL.String(), req.URL.String(), dialer.record.AddressUsed)) | 		va.log.Info(fmt.Sprintf("%s [%s] redirect from %q to %q [%s]", challenge.Type, identifier, via[len(via)-1].URL.String(), req.URL.String(), dialer.record.AddressUsed)) | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	client := http.Client{ | 	client := http.Client{ | ||||||
|  | @ -319,7 +307,7 @@ func (va *ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdenti | ||||||
| 			Detail: fmt.Sprintf("Could not connect to %s", url), | 			Detail: fmt.Sprintf("Could not connect to %s", url), | ||||||
| 		} | 		} | ||||||
| 		va.log.Debug(strings.Join([]string{challenge.Error.Error(), err.Error()}, ": ")) | 		va.log.Debug(strings.Join([]string{challenge.Error.Error(), err.Error()}, ": ")) | ||||||
| 		return challenge, err | 		return emptyBody, challenge, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if httpResponse.StatusCode != 200 { | 	if httpResponse.StatusCode != 200 { | ||||||
|  | @ -330,18 +318,107 @@ func (va *ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdenti | ||||||
| 				url.String(), dialer.record.AddressUsed, httpResponse.StatusCode), | 				url.String(), dialer.record.AddressUsed, httpResponse.StatusCode), | ||||||
| 		} | 		} | ||||||
| 		err = challenge.Error | 		err = challenge.Error | ||||||
| 		return challenge, err | 		return emptyBody, challenge, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Read body & test
 | 	// Read body & test
 | ||||||
| 	body, readErr := ioutil.ReadAll(httpResponse.Body) | 	body, err := ioutil.ReadAll(httpResponse.Body) | ||||||
| 	if readErr != nil { | 	if err != nil { | ||||||
| 		challenge.Status = core.StatusInvalid | 		challenge.Status = core.StatusInvalid | ||||||
| 		challenge.Error = &core.ProblemDetails{ | 		challenge.Error = &core.ProblemDetails{ | ||||||
| 			Type:   core.UnauthorizedProblem, | 			Type:   core.UnauthorizedProblem, | ||||||
| 			Detail: fmt.Sprintf("Error reading HTTP response body"), | 			Detail: fmt.Sprintf("Error reading HTTP response body: %v", err), | ||||||
| 		} | 		} | ||||||
| 		return challenge, readErr | 		return emptyBody, challenge, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return body, challenge, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (va *ValidationAuthorityImpl) validateTLSWithZName(identifier core.AcmeIdentifier, input core.Challenge, zName string) (core.Challenge, error) { | ||||||
|  | 	challenge := input | ||||||
|  | 
 | ||||||
|  | 	addr, allAddrs, problem := va.getAddr(identifier.Value) | ||||||
|  | 	challenge.ValidationRecord = []core.ValidationRecord{ | ||||||
|  | 		core.ValidationRecord{ | ||||||
|  | 			Hostname:          identifier.Value, | ||||||
|  | 			AddressesResolved: allAddrs, | ||||||
|  | 			AddressUsed:       addr, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	if problem != nil { | ||||||
|  | 		challenge.Status = core.StatusInvalid | ||||||
|  | 		challenge.Error = problem | ||||||
|  | 		return challenge, challenge.Error | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Make a connection with SNI = nonceName
 | ||||||
|  | 	portString := strconv.Itoa(va.tlsPort) | ||||||
|  | 	hostPort := net.JoinHostPort(addr.String(), portString) | ||||||
|  | 	challenge.ValidationRecord[0].Port = portString | ||||||
|  | 	va.log.Notice(fmt.Sprintf("%s [%s] Attempting to validate for %s %s", challenge.Type, identifier, hostPort, zName)) | ||||||
|  | 	conn, err := tls.DialWithDialer(&net.Dialer{Timeout: validationTimeout}, "tcp", hostPort, &tls.Config{ | ||||||
|  | 		ServerName:         zName, | ||||||
|  | 		InsecureSkipVerify: true, | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		challenge.Status = core.StatusInvalid | ||||||
|  | 		challenge.Error = &core.ProblemDetails{ | ||||||
|  | 			Type:   parseHTTPConnError(err), | ||||||
|  | 			Detail: "Failed to connect to host for DVSNI challenge", | ||||||
|  | 		} | ||||||
|  | 		va.log.Debug(fmt.Sprintf("%s [%s] TLS Connection failure: %s", challenge.Type, identifier, err)) | ||||||
|  | 		return challenge, err | ||||||
|  | 	} | ||||||
|  | 	defer conn.Close() | ||||||
|  | 
 | ||||||
|  | 	// Check that zName is a dNSName SAN in the server's certificate
 | ||||||
|  | 	certs := conn.ConnectionState().PeerCertificates | ||||||
|  | 	if len(certs) == 0 { | ||||||
|  | 		challenge.Error = &core.ProblemDetails{ | ||||||
|  | 			Type:   core.UnauthorizedProblem, | ||||||
|  | 			Detail: "No certs presented for TLS SNI challenge", | ||||||
|  | 		} | ||||||
|  | 		challenge.Status = core.StatusInvalid | ||||||
|  | 		return challenge, challenge.Error | ||||||
|  | 	} | ||||||
|  | 	for _, name := range certs[0].DNSNames { | ||||||
|  | 		if subtle.ConstantTimeCompare([]byte(name), []byte(zName)) == 1 { | ||||||
|  | 			challenge.Status = core.StatusValid | ||||||
|  | 			return challenge, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	challenge.Error = &core.ProblemDetails{ | ||||||
|  | 		Type:   core.UnauthorizedProblem, | ||||||
|  | 		Detail: "Correct zName not found for TLS SNI challenge", | ||||||
|  | 	} | ||||||
|  | 	challenge.Status = core.StatusInvalid | ||||||
|  | 	return challenge, challenge.Error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
 | ||||||
|  | func (va *ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) { | ||||||
|  | 	challenge := input | ||||||
|  | 
 | ||||||
|  | 	if identifier.Type != core.IdentifierDNS { | ||||||
|  | 		challenge.Status = core.StatusInvalid | ||||||
|  | 		challenge.Error = &core.ProblemDetails{ | ||||||
|  | 			Type:   core.MalformedProblem, | ||||||
|  | 			Detail: "Identifier type for SimpleHTTP was not DNS", | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		va.log.Debug(fmt.Sprintf("SimpleHTTP [%s] Identifier failure", identifier)) | ||||||
|  | 		return challenge, challenge.Error | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Perform the fetch
 | ||||||
|  | 	path := fmt.Sprintf(".well-known/acme-challenge/%s", challenge.Token) | ||||||
|  | 	useTLS := (challenge.TLS == nil) || *challenge.TLS | ||||||
|  | 	body, challenge, err := va.fetchHTTP(identifier, path, useTLS, challenge) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return challenge, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Parse and verify JWS
 | 	// Parse and verify JWS
 | ||||||
|  | @ -381,6 +458,7 @@ func (va *ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdenti | ||||||
| 	return challenge, nil | 	return challenge, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this method
 | ||||||
| func (va *ValidationAuthorityImpl) validateDvsni(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) { | func (va *ValidationAuthorityImpl) validateDvsni(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) { | ||||||
| 	challenge := input | 	challenge := input | ||||||
| 
 | 
 | ||||||
|  | @ -417,67 +495,82 @@ func (va *ValidationAuthorityImpl) validateDvsni(identifier core.AcmeIdentifier, | ||||||
| 	h := sha256.New() | 	h := sha256.New() | ||||||
| 	h.Write([]byte(encodedSignature)) | 	h.Write([]byte(encodedSignature)) | ||||||
| 	Z := hex.EncodeToString(h.Sum(nil)) | 	Z := hex.EncodeToString(h.Sum(nil)) | ||||||
| 	ZName := fmt.Sprintf("%s.%s.%s", Z[:32], Z[32:], core.DVSNISuffix) | 	ZName := fmt.Sprintf("%s.%s.%s", Z[:32], Z[32:], core.TLSSNISuffix) | ||||||
| 
 | 
 | ||||||
| 	addr, allAddrs, problem := va.getAddr(identifier.Value) | 	return va.validateTLSWithZName(identifier, challenge, ZName) | ||||||
| 	challenge.ValidationRecord = []core.ValidationRecord{ | } | ||||||
| 		core.ValidationRecord{ | 
 | ||||||
| 			Hostname:          identifier.Value, | func (va *ValidationAuthorityImpl) validateHTTP01(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) { | ||||||
| 			AddressesResolved: allAddrs, | 	challenge := input | ||||||
| 			AddressUsed:       addr, | 
 | ||||||
| 		}, | 	if identifier.Type != core.IdentifierDNS { | ||||||
| 	} |  | ||||||
| 	if problem != nil { |  | ||||||
| 		challenge.Status = core.StatusInvalid | 		challenge.Status = core.StatusInvalid | ||||||
| 		challenge.Error = problem | 		challenge.Error = &core.ProblemDetails{ | ||||||
|  | 			Type:   core.MalformedProblem, | ||||||
|  | 			Detail: "Identifier type for HTTP validation was not DNS", | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		va.log.Debug(fmt.Sprintf("%s [%s] Identifier failure", challenge.Type, identifier)) | ||||||
| 		return challenge, challenge.Error | 		return challenge, challenge.Error | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Make a connection with SNI = nonceName
 | 	// Perform the fetch
 | ||||||
| 	portString := fmt.Sprintf("%d", va.dvsniPort) | 	path := fmt.Sprintf(".well-known/acme-challenge/%s", challenge.Token) | ||||||
| 	hostPort := net.JoinHostPort(addr.String(), portString) | 	body, challenge, err := va.fetchHTTP(identifier, path, false, challenge) | ||||||
| 	challenge.ValidationRecord[0].Port = portString |  | ||||||
| 	va.log.Notice(fmt.Sprintf("DVSNI [%s] Attempting to validate DVSNI for %s %s", |  | ||||||
| 		identifier, hostPort, ZName)) |  | ||||||
| 	conn, err := tls.DialWithDialer(&net.Dialer{Timeout: validationTimeout}, "tcp", hostPort, &tls.Config{ |  | ||||||
| 		ServerName:         ZName, |  | ||||||
| 		InsecureSkipVerify: true, |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		challenge.Status = core.StatusInvalid |  | ||||||
| 		challenge.Error = &core.ProblemDetails{ |  | ||||||
| 			Type:   parseHTTPConnError(err), |  | ||||||
| 			Detail: "Failed to connect to host for DVSNI challenge", |  | ||||||
| 		} |  | ||||||
| 		va.log.Debug(fmt.Sprintf("DVSNI [%s] TLS Connection failure: %s", identifier, err)) |  | ||||||
| 		return challenge, err | 		return challenge, err | ||||||
| 	} | 	} | ||||||
| 	defer conn.Close() |  | ||||||
| 
 | 
 | ||||||
| 	// Check that ZName is a dNSName SAN in the server's certificate
 | 	// Parse body as a key authorization object
 | ||||||
| 	certs := conn.ConnectionState().PeerCertificates | 	serverKeyAuthorization, err := core.NewKeyAuthorizationFromString(string(body)) | ||||||
| 	if len(certs) == 0 { | 	if err != nil { | ||||||
|  | 		err = fmt.Errorf("Error parsing key authorization file: %s", err.Error()) | ||||||
|  | 		va.log.Debug(err.Error()) | ||||||
|  | 		challenge.Status = core.StatusInvalid | ||||||
| 		challenge.Error = &core.ProblemDetails{ | 		challenge.Error = &core.ProblemDetails{ | ||||||
| 			Type:   core.UnauthorizedProblem, | 			Type:   core.UnauthorizedProblem, | ||||||
| 			Detail: "No certs presented for DVSNI challenge", | 			Detail: err.Error(), | ||||||
| 		} | 		} | ||||||
|  | 		return challenge, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Check that the account key for this challenge is authorized by this object
 | ||||||
|  | 	if !serverKeyAuthorization.Match(challenge.Token, challenge.AccountKey) { | ||||||
|  | 		err = fmt.Errorf("The key authorization file from the server did not match this challenge [%v] != [%v]", | ||||||
|  | 			challenge.KeyAuthorization.String(), string(body)) | ||||||
|  | 		va.log.Debug(err.Error()) | ||||||
| 		challenge.Status = core.StatusInvalid | 		challenge.Status = core.StatusInvalid | ||||||
| 		return challenge, challenge.Error | 		challenge.Error = &core.ProblemDetails{ | ||||||
|  | 			Type:   core.UnauthorizedProblem, | ||||||
|  | 			Detail: err.Error(), | ||||||
| 		} | 		} | ||||||
| 	for _, name := range certs[0].DNSNames { | 		return challenge, err | ||||||
| 		if subtle.ConstantTimeCompare([]byte(name), []byte(ZName)) == 1 { | 	} | ||||||
|  | 
 | ||||||
| 	challenge.Status = core.StatusValid | 	challenge.Status = core.StatusValid | ||||||
| 	return challenge, nil | 	return challenge, nil | ||||||
| 		} | } | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
|  | func (va *ValidationAuthorityImpl) validateTLSSNI01(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) { | ||||||
|  | 	challenge := input | ||||||
|  | 
 | ||||||
|  | 	if identifier.Type != "dns" { | ||||||
| 		challenge.Error = &core.ProblemDetails{ | 		challenge.Error = &core.ProblemDetails{ | ||||||
| 		Type:   core.UnauthorizedProblem, | 			Type:   core.MalformedProblem, | ||||||
| 		Detail: "Correct ZName not found for DVSNI challenge", | 			Detail: "Identifier type for TLS-SNI was not DNS", | ||||||
| 		} | 		} | ||||||
| 		challenge.Status = core.StatusInvalid | 		challenge.Status = core.StatusInvalid | ||||||
|  | 		va.log.Debug(fmt.Sprintf("TLS-SNI [%s] Identifier failure", identifier)) | ||||||
| 		return challenge, challenge.Error | 		return challenge, challenge.Error | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Compute the digest that will appear in the certificate
 | ||||||
|  | 	h := sha256.New() | ||||||
|  | 	h.Write([]byte(challenge.KeyAuthorization.String())) | ||||||
|  | 	Z := hex.EncodeToString(h.Sum(nil)) | ||||||
|  | 	ZName := fmt.Sprintf("%s.%s.%s", Z[:32], Z[32:], core.TLSSNISuffix) | ||||||
|  | 
 | ||||||
|  | 	return va.validateTLSWithZName(identifier, challenge, ZName) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // parseHTTPConnError returns the ACME ProblemType corresponding to an error
 | // parseHTTPConnError returns the ACME ProblemType corresponding to an error
 | ||||||
|  | @ -502,7 +595,7 @@ func parseHTTPConnError(err error) core.ProblemType { | ||||||
| 	return core.ConnectionProblem | 	return core.ConnectionProblem | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (va *ValidationAuthorityImpl) validateDNS(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) { | func (va *ValidationAuthorityImpl) validateDNS01(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) { | ||||||
| 	challenge := input | 	challenge := input | ||||||
| 
 | 
 | ||||||
| 	if identifier.Type != core.IdentifierDNS { | 	if identifier.Type != core.IdentifierDNS { | ||||||
|  | @ -515,24 +608,10 @@ func (va *ValidationAuthorityImpl) validateDNS(identifier core.AcmeIdentifier, i | ||||||
| 		return challenge, challenge.Error | 		return challenge, challenge.Error | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Check that JWS body is as expected
 | 	// Compute the digest of the key authorization file
 | ||||||
| 	// * "type" == "dvsni"
 | 	h := sha256.New() | ||||||
| 	// * "token" == challenge.token
 | 	h.Write([]byte(challenge.KeyAuthorization.String())) | ||||||
| 	target := map[string]interface{}{ | 	authorizedKeysDigest := hex.EncodeToString(h.Sum(nil)) | ||||||
| 		"type":  core.ChallengeTypeDNS, |  | ||||||
| 		"token": challenge.Token, |  | ||||||
| 	} |  | ||||||
| 	err := verifyValidationJWS(challenge.Validation, challenge.AccountKey, target) |  | ||||||
| 	if err != nil { |  | ||||||
| 		va.log.Debug(err.Error()) |  | ||||||
| 		challenge.Status = core.StatusInvalid |  | ||||||
| 		challenge.Error = &core.ProblemDetails{ |  | ||||||
| 			Type:   core.UnauthorizedProblem, |  | ||||||
| 			Detail: err.Error(), |  | ||||||
| 		} |  | ||||||
| 		return challenge, err |  | ||||||
| 	} |  | ||||||
| 	encodedSignature := core.B64enc(challenge.Validation.Signatures[0].Signature) |  | ||||||
| 
 | 
 | ||||||
| 	// Look for the required record in the DNS
 | 	// Look for the required record in the DNS
 | ||||||
| 	challengeSubdomain := fmt.Sprintf("%s.%s", core.DNSPrefix, identifier.Value) | 	challengeSubdomain := fmt.Sprintf("%s.%s", core.DNSPrefix, identifier.Value) | ||||||
|  | @ -548,7 +627,7 @@ func (va *ValidationAuthorityImpl) validateDNS(identifier core.AcmeIdentifier, i | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, element := range txts { | 	for _, element := range txts { | ||||||
| 		if subtle.ConstantTimeCompare([]byte(element), []byte(encodedSignature)) == 1 { | 		if subtle.ConstantTimeCompare([]byte(element), []byte(authorizedKeysDigest)) == 1 { | ||||||
| 			challenge.Status = core.StatusValid | 			challenge.Status = core.StatusValid | ||||||
| 			return challenge, nil | 			return challenge, nil | ||||||
| 		} | 		} | ||||||
|  | @ -583,11 +662,17 @@ func (va *ValidationAuthorityImpl) validate(authz core.Authorization, challengeI | ||||||
| 		vStart := va.clk.Now() | 		vStart := va.clk.Now() | ||||||
| 		switch authz.Challenges[challengeIndex].Type { | 		switch authz.Challenges[challengeIndex].Type { | ||||||
| 		case core.ChallengeTypeSimpleHTTP: | 		case core.ChallengeTypeSimpleHTTP: | ||||||
|  | 			// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this case
 | ||||||
| 			authz.Challenges[challengeIndex], err = va.validateSimpleHTTP(authz.Identifier, authz.Challenges[challengeIndex]) | 			authz.Challenges[challengeIndex], err = va.validateSimpleHTTP(authz.Identifier, authz.Challenges[challengeIndex]) | ||||||
| 		case core.ChallengeTypeDVSNI: | 		case core.ChallengeTypeDVSNI: | ||||||
|  | 			// TODO(https://github.com/letsencrypt/boulder/issues/894): Delete this case
 | ||||||
| 			authz.Challenges[challengeIndex], err = va.validateDvsni(authz.Identifier, authz.Challenges[challengeIndex]) | 			authz.Challenges[challengeIndex], err = va.validateDvsni(authz.Identifier, authz.Challenges[challengeIndex]) | ||||||
| 		case core.ChallengeTypeDNS: | 		case core.ChallengeTypeHTTP01: | ||||||
| 			authz.Challenges[challengeIndex], err = va.validateDNS(authz.Identifier, authz.Challenges[challengeIndex]) | 			authz.Challenges[challengeIndex], err = va.validateHTTP01(authz.Identifier, authz.Challenges[challengeIndex]) | ||||||
|  | 		case core.ChallengeTypeTLSSNI01: | ||||||
|  | 			authz.Challenges[challengeIndex], err = va.validateTLSSNI01(authz.Identifier, authz.Challenges[challengeIndex]) | ||||||
|  | 		case core.ChallengeTypeDNS01: | ||||||
|  | 			authz.Challenges[challengeIndex], err = va.validateDNS01(authz.Identifier, authz.Challenges[challengeIndex]) | ||||||
| 		} | 		} | ||||||
| 		va.stats.TimingDuration(fmt.Sprintf("VA.Validations.%s.%s", authz.Challenges[challengeIndex].Type, authz.Challenges[challengeIndex].Status), time.Since(vStart), 1.0) | 		va.stats.TimingDuration(fmt.Sprintf("VA.Validations.%s.%s", authz.Challenges[challengeIndex].Type, authz.Challenges[challengeIndex].Status), time.Since(vStart), 1.0) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -65,15 +65,21 @@ var ident = core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "localhost"} | ||||||
| 
 | 
 | ||||||
| var log = mocks.UseMockLog() | var log = mocks.UseMockLog() | ||||||
| 
 | 
 | ||||||
| const expectedToken = "THETOKEN" | // All paths that get assigned to tokens MUST be valid tokens
 | ||||||
| const pathWrongToken = "wrongtoken" | const expectedToken = "LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0" | ||||||
|  | const pathWrongToken = "i6lNAC4lOOLYCl-A08VJt9z_tKYvVk63Dumo8icsBjQ" | ||||||
| const path404 = "404" | const path404 = "404" | ||||||
| const pathFound = "302" | const pathFound = "GBq8SwWq3JsbREFdCamk5IX3KLsxW5ULeGs98Ajl_UM" | ||||||
| const pathMoved = "301" | const pathMoved = "5J4FIMrWNfmvHZo-QpKZngmuhqZGwRm21-oEgUDstJM" | ||||||
| const pathRedirectLookup = "re-lookup" |  | ||||||
| const pathRedirectLookupInvalid = "re-lookup-invalid" |  | ||||||
| const pathRedirectPort = "port-redirect" | const pathRedirectPort = "port-redirect" | ||||||
|  | const pathWait = "wait" | ||||||
|  | const pathWaitLong = "wait-long" | ||||||
|  | const pathReLookup = "7e-P57coLM7D3woNTp_xbJrtlkDYy6PWf3mSSbLwCr4" | ||||||
|  | const pathReLookupInvalid = "re-lookup-invalid" | ||||||
|  | const pathLooper = "looper" | ||||||
|  | const pathValid = "valid" | ||||||
| 
 | 
 | ||||||
|  | // TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this method
 | ||||||
| func createValidation(token string, enableTLS bool) string { | func createValidation(token string, enableTLS bool) string { | ||||||
| 	payload, _ := json.Marshal(map[string]interface{}{ | 	payload, _ := json.Marshal(map[string]interface{}{ | ||||||
| 		"type":  "simpleHttp", | 		"type":  "simpleHttp", | ||||||
|  | @ -85,6 +91,7 @@ func createValidation(token string, enableTLS bool) string { | ||||||
| 	return obj.FullSerialize() | 	return obj.FullSerialize() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this method
 | ||||||
| func simpleSrv(t *testing.T, token string, enableTLS bool) *httptest.Server { | func simpleSrv(t *testing.T, token string, enableTLS bool) *httptest.Server { | ||||||
| 	m := http.NewServeMux() | 	m := http.NewServeMux() | ||||||
| 
 | 
 | ||||||
|  | @ -110,22 +117,22 @@ func simpleSrv(t *testing.T, token string, enableTLS bool) *httptest.Server { | ||||||
| 				currentToken = pathFound | 				currentToken = pathFound | ||||||
| 			} | 			} | ||||||
| 			http.Redirect(w, r, pathMoved, 302) | 			http.Redirect(w, r, pathMoved, 302) | ||||||
| 		} else if strings.HasSuffix(r.URL.Path, "wait") { | 		} else if strings.HasSuffix(r.URL.Path, pathWait) { | ||||||
| 			t.Logf("SIMPLESRV: Got a wait req\n") | 			t.Logf("SIMPLESRV: Got a wait req\n") | ||||||
| 			time.Sleep(time.Second * 3) | 			time.Sleep(time.Second * 3) | ||||||
| 		} else if strings.HasSuffix(r.URL.Path, "wait-long") { | 		} else if strings.HasSuffix(r.URL.Path, pathWaitLong) { | ||||||
| 			t.Logf("SIMPLESRV: Got a wait-long req\n") | 			t.Logf("SIMPLESRV: Got a wait-long req\n") | ||||||
| 			time.Sleep(time.Second * 10) | 			time.Sleep(time.Second * 10) | ||||||
| 		} else if strings.HasSuffix(r.URL.Path, "re-lookup") { | 		} else if strings.HasSuffix(r.URL.Path, pathReLookup) { | ||||||
| 			t.Logf("SIMPLESRV: Got a redirect req to a valid hostname\n") | 			t.Logf("SIMPLESRV: Got a redirect req to a valid hostname\n") | ||||||
| 			if currentToken == defaultToken { | 			if currentToken == defaultToken { | ||||||
| 				currentToken = "re-lookup" | 				currentToken = pathReLookup | ||||||
| 			} | 			} | ||||||
| 			http.Redirect(w, r, "http://other.valid/path", 302) | 			http.Redirect(w, r, "http://other.valid/path", 302) | ||||||
| 		} else if strings.HasSuffix(r.URL.Path, "re-lookup-invalid") { | 		} else if strings.HasSuffix(r.URL.Path, pathReLookupInvalid) { | ||||||
| 			t.Logf("SIMPLESRV: Got a redirect req to a invalid hostname\n") | 			t.Logf("SIMPLESRV: Got a redirect req to a invalid hostname\n") | ||||||
| 			http.Redirect(w, r, "http://invalid.invalid/path", 302) | 			http.Redirect(w, r, "http://invalid.invalid/path", 302) | ||||||
| 		} else if strings.HasSuffix(r.URL.Path, "looper") { | 		} else if strings.HasSuffix(r.URL.Path, pathLooper) { | ||||||
| 			t.Logf("SIMPLESRV: Got a loop req\n") | 			t.Logf("SIMPLESRV: Got a loop req\n") | ||||||
| 			http.Redirect(w, r, r.URL.String(), 301) | 			http.Redirect(w, r, r.URL.String(), 301) | ||||||
| 		} else if strings.HasSuffix(r.URL.Path, pathRedirectPort) { | 		} else if strings.HasSuffix(r.URL.Path, pathRedirectPort) { | ||||||
|  | @ -174,6 +181,7 @@ func simpleSrv(t *testing.T, token string, enableTLS bool) *httptest.Server { | ||||||
| 	return server | 	return server | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this method
 | ||||||
| func dvsniSrv(t *testing.T, chall core.Challenge) *httptest.Server { | func dvsniSrv(t *testing.T, chall core.Challenge) *httptest.Server { | ||||||
| 	encodedSig := core.B64enc(chall.Validation.Signatures[0].Signature) | 	encodedSig := core.B64enc(chall.Validation.Signatures[0].Signature) | ||||||
| 	h := sha256.New() | 	h := sha256.New() | ||||||
|  | @ -221,17 +229,7 @@ func dvsniSrv(t *testing.T, chall core.Challenge) *httptest.Server { | ||||||
| 	return hs | 	return hs | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func brokenTLSSrv() *httptest.Server { | // TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this method
 | ||||||
| 	server := httptest.NewUnstartedServer(http.DefaultServeMux) |  | ||||||
| 	server.TLS = &tls.Config{ |  | ||||||
| 		GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) { |  | ||||||
| 			return nil, fmt.Errorf("Failing on purpose") |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	server.StartTLS() |  | ||||||
| 	return server |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestSimpleHttpTLS(t *testing.T) { | func TestSimpleHttpTLS(t *testing.T) { | ||||||
| 	chall := core.Challenge{ | 	chall := core.Challenge{ | ||||||
| 		Type:             core.ChallengeTypeSimpleHTTP, | 		Type:             core.ChallengeTypeSimpleHTTP, | ||||||
|  | @ -246,18 +244,19 @@ func TestSimpleHttpTLS(t *testing.T) { | ||||||
| 	port, err := getPort(hs) | 	port, err := getPort(hs) | ||||||
| 	test.AssertNotError(t, err, "failed to get test server port") | 	test.AssertNotError(t, err, "failed to get test server port") | ||||||
| 	stats, _ := statsd.NewNoopClient() | 	stats, _ := statsd.NewNoopClient() | ||||||
| 	va := NewValidationAuthorityImpl(&PortConfig{SimpleHTTPSPort: port}, stats, clock.Default()) | 	va := NewValidationAuthorityImpl(&PortConfig{HTTPSPort: port}, stats, clock.Default()) | ||||||
| 	va.DNSResolver = &mocks.MockDNS{} | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
| 
 | 
 | ||||||
| 	log.Clear() | 	log.Clear() | ||||||
| 	finChall, err := va.validateSimpleHTTP(ident, chall) | 	finChall, err := va.validateSimpleHTTP(ident, chall) | ||||||
| 	test.AssertEquals(t, finChall.Status, core.StatusValid) | 	test.AssertEquals(t, finChall.Status, core.StatusValid) | ||||||
| 	test.AssertNotError(t, err, "Error validating simpleHttp") | 	test.AssertNotError(t, err, "Error validating simpleHttp") | ||||||
| 	logs := log.GetAllMatching(`^\[AUDIT\] Attempting to validate SimpleHTTPS for `) | 	logs := log.GetAllMatching(`^\[AUDIT\] Attempting to validate simpleHttp for `) | ||||||
| 	test.AssertEquals(t, len(logs), 1) | 	test.AssertEquals(t, len(logs), 1) | ||||||
| 	test.AssertEquals(t, logs[0].Priority, syslog.LOG_NOTICE) | 	test.AssertEquals(t, logs[0].Priority, syslog.LOG_NOTICE) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this method
 | ||||||
| func TestSimpleHttp(t *testing.T) { | func TestSimpleHttp(t *testing.T) { | ||||||
| 	tls := false | 	tls := false | ||||||
| 	chall := core.Challenge{ | 	chall := core.Challenge{ | ||||||
|  | @ -288,16 +287,16 @@ func TestSimpleHttp(t *testing.T) { | ||||||
| 		badPort = goodPort - 1 | 		badPort = goodPort - 1 | ||||||
| 	} | 	} | ||||||
| 	stats, _ := statsd.NewNoopClient() | 	stats, _ := statsd.NewNoopClient() | ||||||
| 	va := NewValidationAuthorityImpl(&PortConfig{SimpleHTTPPort: badPort}, stats, clock.Default()) | 	va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: badPort}, stats, clock.Default()) | ||||||
| 	va.DNSResolver = &mocks.MockDNS{} | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
| 
 | 
 | ||||||
| 	invalidChall, err := va.validateSimpleHTTP(ident, chall) | 	invalidChall, err := va.validateSimpleHTTP(ident, chall) | ||||||
| 	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) | 	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) | ||||||
| 	test.AssertError(t, err, "Server's down; expected refusal. Where did we connect?") | 	test.AssertError(t, err, "Server's down; expected refusal. Where did we connect?") | ||||||
| 	test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem) | 	test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem) | ||||||
| 
 | 
 | ||||||
| 	va = NewValidationAuthorityImpl(&PortConfig{SimpleHTTPPort: goodPort}, stats, clock.Default()) | 	va = NewValidationAuthorityImpl(&PortConfig{HTTPPort: goodPort}, stats, clock.Default()) | ||||||
| 	va.DNSResolver = &mocks.MockDNS{} | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
| 	log.Clear() | 	log.Clear() | ||||||
| 	finChall, err := va.validateSimpleHTTP(ident, chall) | 	finChall, err := va.validateSimpleHTTP(ident, chall) | ||||||
| 	test.AssertEquals(t, finChall.Status, core.StatusValid) | 	test.AssertEquals(t, finChall.Status, core.StatusValid) | ||||||
|  | @ -327,15 +326,15 @@ func TestSimpleHttp(t *testing.T) { | ||||||
| 	finChall, err = va.validateSimpleHTTP(ident, chall) | 	finChall, err = va.validateSimpleHTTP(ident, chall) | ||||||
| 	test.AssertEquals(t, finChall.Status, core.StatusValid) | 	test.AssertEquals(t, finChall.Status, core.StatusValid) | ||||||
| 	test.AssertNotError(t, err, "Failed to follow 301 redirect") | 	test.AssertNotError(t, err, "Failed to follow 301 redirect") | ||||||
| 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/301" to ".*/valid"`)), 1) | 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1) | ||||||
| 
 | 
 | ||||||
| 	log.Clear() | 	log.Clear() | ||||||
| 	chall.Token = pathFound | 	chall.Token = pathFound | ||||||
| 	finChall, err = va.validateSimpleHTTP(ident, chall) | 	finChall, err = va.validateSimpleHTTP(ident, chall) | ||||||
| 	test.AssertEquals(t, finChall.Status, core.StatusValid) | 	test.AssertEquals(t, finChall.Status, core.StatusValid) | ||||||
| 	test.AssertNotError(t, err, "Failed to follow 302 redirect") | 	test.AssertNotError(t, err, "Failed to follow 302 redirect") | ||||||
| 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/302" to ".*/301"`)), 1) | 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathFound+`" to ".*/`+pathMoved+`"`)), 1) | ||||||
| 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/301" to ".*/valid"`)), 1) | 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1) | ||||||
| 
 | 
 | ||||||
| 	ipIdentifier := core.AcmeIdentifier{Type: core.IdentifierType("ip"), Value: "127.0.0.1"} | 	ipIdentifier := core.AcmeIdentifier{Type: core.IdentifierType("ip"), Value: "127.0.0.1"} | ||||||
| 	invalidChall, err = va.validateSimpleHTTP(ipIdentifier, chall) | 	invalidChall, err = va.validateSimpleHTTP(ipIdentifier, chall) | ||||||
|  | @ -360,6 +359,7 @@ func TestSimpleHttp(t *testing.T) { | ||||||
| 	test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem) | 	test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this method
 | ||||||
| func TestSimpleHttpRedirectLookup(t *testing.T) { | func TestSimpleHttpRedirectLookup(t *testing.T) { | ||||||
| 	tls := false | 	tls := false | ||||||
| 	chall := core.Challenge{ | 	chall := core.Challenge{ | ||||||
|  | @ -374,15 +374,15 @@ func TestSimpleHttpRedirectLookup(t *testing.T) { | ||||||
| 	port, err := getPort(hs) | 	port, err := getPort(hs) | ||||||
| 	test.AssertNotError(t, err, "failed to get test server port") | 	test.AssertNotError(t, err, "failed to get test server port") | ||||||
| 	stats, _ := statsd.NewNoopClient() | 	stats, _ := statsd.NewNoopClient() | ||||||
| 	va := NewValidationAuthorityImpl(&PortConfig{SimpleHTTPPort: port}, stats, clock.Default()) | 	va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: port}, stats, clock.Default()) | ||||||
| 	va.DNSResolver = &mocks.MockDNS{} | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
| 
 | 
 | ||||||
| 	log.Clear() | 	log.Clear() | ||||||
| 	chall.Token = pathMoved | 	chall.Token = pathMoved | ||||||
| 	finChall, err := va.validateSimpleHTTP(ident, chall) | 	finChall, err := va.validateSimpleHTTP(ident, chall) | ||||||
| 	test.AssertEquals(t, finChall.Status, core.StatusValid) | 	test.AssertEquals(t, finChall.Status, core.StatusValid) | ||||||
| 	test.AssertNotError(t, err, chall.Token) | 	test.AssertNotError(t, err, chall.Token) | ||||||
| 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/301" to ".*/valid"`)), 1) | 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1) | ||||||
| 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 2) | 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 2) | ||||||
| 
 | 
 | ||||||
| 	log.Clear() | 	log.Clear() | ||||||
|  | @ -390,12 +390,12 @@ func TestSimpleHttpRedirectLookup(t *testing.T) { | ||||||
| 	finChall, err = va.validateSimpleHTTP(ident, chall) | 	finChall, err = va.validateSimpleHTTP(ident, chall) | ||||||
| 	test.AssertEquals(t, finChall.Status, core.StatusValid) | 	test.AssertEquals(t, finChall.Status, core.StatusValid) | ||||||
| 	test.AssertNotError(t, err, chall.Token) | 	test.AssertNotError(t, err, chall.Token) | ||||||
| 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/302" to ".*/301"`)), 1) | 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathFound+`" to ".*/`+pathMoved+`"`)), 1) | ||||||
| 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/301" to ".*/valid"`)), 1) | 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1) | ||||||
| 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 3) | 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 3) | ||||||
| 
 | 
 | ||||||
| 	log.Clear() | 	log.Clear() | ||||||
| 	chall.Token = pathRedirectLookupInvalid | 	chall.Token = pathReLookupInvalid | ||||||
| 	finChall, err = va.validateSimpleHTTP(ident, chall) | 	finChall, err = va.validateSimpleHTTP(ident, chall) | ||||||
| 	test.AssertEquals(t, finChall.Status, core.StatusInvalid) | 	test.AssertEquals(t, finChall.Status, core.StatusInvalid) | ||||||
| 	test.AssertError(t, err, chall.Token) | 	test.AssertError(t, err, chall.Token) | ||||||
|  | @ -403,11 +403,11 @@ func TestSimpleHttpRedirectLookup(t *testing.T) { | ||||||
| 	test.AssertEquals(t, len(log.GetAllMatching(`No IPv4 addresses found for invalid.invalid`)), 1) | 	test.AssertEquals(t, len(log.GetAllMatching(`No IPv4 addresses found for invalid.invalid`)), 1) | ||||||
| 
 | 
 | ||||||
| 	log.Clear() | 	log.Clear() | ||||||
| 	chall.Token = pathRedirectLookup | 	chall.Token = pathReLookup | ||||||
| 	finChall, err = va.validateSimpleHTTP(ident, chall) | 	finChall, err = va.validateSimpleHTTP(ident, chall) | ||||||
| 	test.AssertEquals(t, finChall.Status, core.StatusValid) | 	test.AssertEquals(t, finChall.Status, core.StatusValid) | ||||||
| 	test.AssertNotError(t, err, chall.Token) | 	test.AssertNotError(t, err, chall.Token) | ||||||
| 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/re-lookup" to ".*other.valid/path"`)), 1) | 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathReLookup+`" to ".*other.valid/path"`)), 1) | ||||||
| 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1) | 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1) | ||||||
| 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for other.valid \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1) | 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for other.valid \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1) | ||||||
| 
 | 
 | ||||||
|  | @ -422,6 +422,7 @@ func TestSimpleHttpRedirectLookup(t *testing.T) { | ||||||
| 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for other.valid \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1) | 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for other.valid \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this method
 | ||||||
| func TestSimpleHttpRedirectLoop(t *testing.T) { | func TestSimpleHttpRedirectLoop(t *testing.T) { | ||||||
| 	tls := false | 	tls := false | ||||||
| 	chall := core.Challenge{ | 	chall := core.Challenge{ | ||||||
|  | @ -435,8 +436,8 @@ func TestSimpleHttpRedirectLoop(t *testing.T) { | ||||||
| 	port, err := getPort(hs) | 	port, err := getPort(hs) | ||||||
| 	test.AssertNotError(t, err, "failed to get test server port") | 	test.AssertNotError(t, err, "failed to get test server port") | ||||||
| 	stats, _ := statsd.NewNoopClient() | 	stats, _ := statsd.NewNoopClient() | ||||||
| 	va := NewValidationAuthorityImpl(&PortConfig{SimpleHTTPPort: port}, stats, clock.Default()) | 	va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: port}, stats, clock.Default()) | ||||||
| 	va.DNSResolver = &mocks.MockDNS{} | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
| 
 | 
 | ||||||
| 	log.Clear() | 	log.Clear() | ||||||
| 	finChall, err := va.validateSimpleHTTP(ident, chall) | 	finChall, err := va.validateSimpleHTTP(ident, chall) | ||||||
|  | @ -445,22 +446,7 @@ func TestSimpleHttpRedirectLoop(t *testing.T) { | ||||||
| 	fmt.Println(finChall) | 	fmt.Println(finChall) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func getPort(hs *httptest.Server) (int, error) { | // TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this method
 | ||||||
| 	url, err := url.Parse(hs.URL) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0, err |  | ||||||
| 	} |  | ||||||
| 	_, portString, err := net.SplitHostPort(url.Host) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0, err |  | ||||||
| 	} |  | ||||||
| 	port, err := strconv.ParseInt(portString, 10, 64) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0, err |  | ||||||
| 	} |  | ||||||
| 	return int(port), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestDvsni(t *testing.T) { | func TestDvsni(t *testing.T) { | ||||||
| 	chall := createChallenge(core.ChallengeTypeDVSNI) | 	chall := createChallenge(core.ChallengeTypeDVSNI) | ||||||
| 
 | 
 | ||||||
|  | @ -469,9 +455,9 @@ func TestDvsni(t *testing.T) { | ||||||
| 	test.AssertNotError(t, err, "failed to get test server port") | 	test.AssertNotError(t, err, "failed to get test server port") | ||||||
| 
 | 
 | ||||||
| 	stats, _ := statsd.NewNoopClient() | 	stats, _ := statsd.NewNoopClient() | ||||||
| 	va := NewValidationAuthorityImpl(&PortConfig{DVSNIPort: port}, stats, clock.Default()) | 	va := NewValidationAuthorityImpl(&PortConfig{TLSPort: port}, stats, clock.Default()) | ||||||
| 
 | 
 | ||||||
| 	va.DNSResolver = &mocks.MockDNS{} | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
| 
 | 
 | ||||||
| 	log.Clear() | 	log.Clear() | ||||||
| 	finChall, err := va.validateDvsni(ident, chall) | 	finChall, err := va.validateDvsni(ident, chall) | ||||||
|  | @ -523,15 +509,15 @@ func TestDvsni(t *testing.T) { | ||||||
| 	test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem) | 	test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestTLSError(t *testing.T) { | func TestDVSNIWithTLSError(t *testing.T) { | ||||||
| 	chall := createChallenge(core.ChallengeTypeDVSNI) | 	chall := createChallenge(core.ChallengeTypeDVSNI) | ||||||
| 	hs := brokenTLSSrv() | 	hs := brokenTLSSrv() | ||||||
| 
 | 
 | ||||||
| 	port, err := getPort(hs) | 	port, err := getPort(hs) | ||||||
| 	test.AssertNotError(t, err, "failed to get test server port") | 	test.AssertNotError(t, err, "failed to get test server port") | ||||||
| 	stats, _ := statsd.NewNoopClient() | 	stats, _ := statsd.NewNoopClient() | ||||||
| 	va := NewValidationAuthorityImpl(&PortConfig{DVSNIPort: port}, stats, clock.Default()) | 	va := NewValidationAuthorityImpl(&PortConfig{TLSPort: port}, stats, clock.Default()) | ||||||
| 	va.DNSResolver = &mocks.MockDNS{} | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
| 
 | 
 | ||||||
| 	invalidChall, err := va.validateDvsni(ident, chall) | 	invalidChall, err := va.validateDvsni(ident, chall) | ||||||
| 	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) | 	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) | ||||||
|  | @ -539,19 +525,403 @@ func TestTLSError(t *testing.T) { | ||||||
| 	test.AssertEquals(t, invalidChall.Error.Type, core.TLSProblem) | 	test.AssertEquals(t, invalidChall.Error.Type, core.TLSProblem) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestValidateHTTP(t *testing.T) { | func httpSrv(t *testing.T, token string) *httptest.Server { | ||||||
| 	tls := false | 	m := http.NewServeMux() | ||||||
| 	challHTTP := core.SimpleHTTPChallenge() |  | ||||||
| 	challHTTP.TLS = &tls |  | ||||||
| 	challHTTP.ValidationRecord = []core.ValidationRecord{} |  | ||||||
| 	challHTTP.AccountKey = accountKey |  | ||||||
| 
 | 
 | ||||||
| 	hs := simpleSrv(t, challHTTP.Token, tls) | 	defaultToken := token | ||||||
|  | 	currentToken := defaultToken | ||||||
|  | 
 | ||||||
|  | 	m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		if !strings.HasPrefix(r.Host, "localhost:") && r.Host != "other.valid" && r.Host != "other.valid:8080" { | ||||||
|  | 			t.Errorf("Bad Host header: " + r.Host) | ||||||
|  | 		} | ||||||
|  | 		if strings.HasSuffix(r.URL.Path, path404) { | ||||||
|  | 			t.Logf("HTTPSRV: Got a 404 req\n") | ||||||
|  | 			http.NotFound(w, r) | ||||||
|  | 		} else if strings.HasSuffix(r.URL.Path, pathMoved) { | ||||||
|  | 			t.Logf("HTTPSRV: Got a 301 redirect req\n") | ||||||
|  | 			if currentToken == defaultToken { | ||||||
|  | 				currentToken = pathMoved | ||||||
|  | 			} | ||||||
|  | 			http.Redirect(w, r, pathValid, 301) | ||||||
|  | 		} else if strings.HasSuffix(r.URL.Path, pathFound) { | ||||||
|  | 			t.Logf("HTTPSRV: Got a 302 redirect req\n") | ||||||
|  | 			if currentToken == defaultToken { | ||||||
|  | 				currentToken = pathFound | ||||||
|  | 			} | ||||||
|  | 			http.Redirect(w, r, pathMoved, 302) | ||||||
|  | 		} else if strings.HasSuffix(r.URL.Path, pathWait) { | ||||||
|  | 			t.Logf("HTTPSRV: Got a wait req\n") | ||||||
|  | 			time.Sleep(time.Second * 3) | ||||||
|  | 		} else if strings.HasSuffix(r.URL.Path, pathWaitLong) { | ||||||
|  | 			t.Logf("HTTPSRV: Got a wait-long req\n") | ||||||
|  | 			time.Sleep(time.Second * 10) | ||||||
|  | 		} else if strings.HasSuffix(r.URL.Path, pathReLookup) { | ||||||
|  | 			t.Logf("HTTPSRV: Got a redirect req to a valid hostname\n") | ||||||
|  | 			if currentToken == defaultToken { | ||||||
|  | 				currentToken = pathReLookup | ||||||
|  | 			} | ||||||
|  | 			http.Redirect(w, r, "http://other.valid/path", 302) | ||||||
|  | 		} else if strings.HasSuffix(r.URL.Path, pathReLookupInvalid) { | ||||||
|  | 			t.Logf("HTTPSRV: Got a redirect req to a invalid hostname\n") | ||||||
|  | 			http.Redirect(w, r, "http://invalid.invalid/path", 302) | ||||||
|  | 		} else if strings.HasSuffix(r.URL.Path, pathLooper) { | ||||||
|  | 			t.Logf("HTTPSRV: Got a loop req\n") | ||||||
|  | 			http.Redirect(w, r, r.URL.String(), 301) | ||||||
|  | 		} else if strings.HasSuffix(r.URL.Path, pathRedirectPort) { | ||||||
|  | 			t.Logf("HTTPSRV: Got a port redirect req\n") | ||||||
|  | 			http.Redirect(w, r, "http://other.valid:8080/path", 302) | ||||||
|  | 		} else { | ||||||
|  | 			t.Logf("HTTPSRV: Got a valid req\n") | ||||||
|  | 			t.Logf("HTTPSRV: Path = %s\n", r.URL.Path) | ||||||
|  | 
 | ||||||
|  | 			keyAuthz, _ := core.NewKeyAuthorization(currentToken, accountKey) | ||||||
|  | 			t.Logf("HTTPSRV: Key Authz = %s\n", keyAuthz.String()) | ||||||
|  | 
 | ||||||
|  | 			fmt.Fprint(w, keyAuthz.String()) | ||||||
|  | 			currentToken = defaultToken | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	server := httptest.NewUnstartedServer(m) | ||||||
|  | 	server.Start() | ||||||
|  | 	return server | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func tlssniSrv(t *testing.T, chall core.Challenge) *httptest.Server { | ||||||
|  | 	h := sha256.New() | ||||||
|  | 	h.Write([]byte(chall.KeyAuthorization.String())) | ||||||
|  | 	Z := hex.EncodeToString(h.Sum(nil)) | ||||||
|  | 	ZName := fmt.Sprintf("%s.%s.acme.invalid", Z[:32], Z[32:]) | ||||||
|  | 
 | ||||||
|  | 	template := &x509.Certificate{ | ||||||
|  | 		SerialNumber: big.NewInt(1337), | ||||||
|  | 		Subject: pkix.Name{ | ||||||
|  | 			Organization: []string{"tests"}, | ||||||
|  | 		}, | ||||||
|  | 		NotBefore: time.Now(), | ||||||
|  | 		NotAfter:  time.Now().AddDate(0, 0, 1), | ||||||
|  | 
 | ||||||
|  | 		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, | ||||||
|  | 		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, | ||||||
|  | 		BasicConstraintsValid: true, | ||||||
|  | 
 | ||||||
|  | 		DNSNames: []string{ZName}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey) | ||||||
|  | 	cert := &tls.Certificate{ | ||||||
|  | 		Certificate: [][]byte{certBytes}, | ||||||
|  | 		PrivateKey:  &TheKey, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	tlsConfig := &tls.Config{ | ||||||
|  | 		Certificates: []tls.Certificate{*cert}, | ||||||
|  | 		ClientAuth:   tls.NoClientCert, | ||||||
|  | 		GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { | ||||||
|  | 			if clientHello.ServerName != ZName { | ||||||
|  | 				time.Sleep(time.Second * 10) | ||||||
|  | 				return nil, nil | ||||||
|  | 			} | ||||||
|  | 			return cert, nil | ||||||
|  | 		}, | ||||||
|  | 		NextProtos: []string{"http/1.1"}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	hs := httptest.NewUnstartedServer(http.DefaultServeMux) | ||||||
|  | 	hs.TLS = tlsConfig | ||||||
|  | 	hs.StartTLS() | ||||||
|  | 	return hs | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestHttp(t *testing.T) { | ||||||
|  | 	chall := core.HTTPChallenge01(accountKey) | ||||||
|  | 	err := setChallengeToken(&chall, expectedToken) | ||||||
|  | 	test.AssertNotError(t, err, "Failed to complete HTTP challenge") | ||||||
|  | 
 | ||||||
|  | 	// NOTE: We do not attempt to shut down the server. The problem is that the
 | ||||||
|  | 	// "wait-long" handler sleeps for ten seconds, but this test finishes in less
 | ||||||
|  | 	// than that. So if we try to call hs.Close() at the end of the test, we'll be
 | ||||||
|  | 	// closing the test server while a request is still pending. Unfortunately,
 | ||||||
|  | 	// there appears to be an issue in httptest that trips Go's race detector when
 | ||||||
|  | 	// that happens, failing the test. So instead, we live with leaving the server
 | ||||||
|  | 	// around till the process exits.
 | ||||||
|  | 	// TODO(#661): add hs.Close back, see ticket for blocker
 | ||||||
|  | 	hs := httpSrv(t, chall.Token) | ||||||
|  | 
 | ||||||
|  | 	goodPort, err := getPort(hs) | ||||||
|  | 	test.AssertNotError(t, err, "failed to get test server port") | ||||||
|  | 
 | ||||||
|  | 	// Attempt to fail a challenge by telling the VA to connect to a port we are
 | ||||||
|  | 	// not listening on.
 | ||||||
|  | 	badPort := goodPort + 1 | ||||||
|  | 	if badPort == 65536 { | ||||||
|  | 		badPort = goodPort - 1 | ||||||
|  | 	} | ||||||
|  | 	stats, _ := statsd.NewNoopClient() | ||||||
|  | 	va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: badPort}, stats, clock.Default()) | ||||||
|  | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
|  | 
 | ||||||
|  | 	invalidChall, err := va.validateHTTP01(ident, chall) | ||||||
|  | 	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) | ||||||
|  | 	test.AssertError(t, err, "Server's down; expected refusal. Where did we connect?") | ||||||
|  | 	test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem) | ||||||
|  | 
 | ||||||
|  | 	va = NewValidationAuthorityImpl(&PortConfig{HTTPPort: goodPort}, stats, clock.Default()) | ||||||
|  | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
|  | 	log.Clear() | ||||||
|  | 	t.Logf("Trying to validate: %+v\n", chall) | ||||||
|  | 	finChall, err := va.validateHTTP01(ident, chall) | ||||||
|  | 	test.AssertEquals(t, finChall.Status, core.StatusValid) | ||||||
|  | 	test.AssertNotError(t, err, "Error validating http") | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`^\[AUDIT\] `)), 1) | ||||||
|  | 
 | ||||||
|  | 	log.Clear() | ||||||
|  | 	setChallengeToken(&chall, path404) | ||||||
|  | 	invalidChall, err = va.validateHTTP01(ident, chall) | ||||||
|  | 	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) | ||||||
|  | 	test.AssertError(t, err, "Should have found a 404 for the challenge.") | ||||||
|  | 	test.AssertEquals(t, invalidChall.Error.Type, core.UnauthorizedProblem) | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`^\[AUDIT\] `)), 1) | ||||||
|  | 
 | ||||||
|  | 	log.Clear() | ||||||
|  | 	setChallengeToken(&chall, pathWrongToken) | ||||||
|  | 	// The "wrong token" will actually be the expectedToken.  It's wrong
 | ||||||
|  | 	// because it doesn't match pathWrongToken.
 | ||||||
|  | 	invalidChall, err = va.validateHTTP01(ident, chall) | ||||||
|  | 	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) | ||||||
|  | 	test.AssertError(t, err, "Should have found the wrong token value.") | ||||||
|  | 	test.AssertEquals(t, invalidChall.Error.Type, core.UnauthorizedProblem) | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`^\[AUDIT\] `)), 1) | ||||||
|  | 
 | ||||||
|  | 	log.Clear() | ||||||
|  | 	setChallengeToken(&chall, pathMoved) | ||||||
|  | 	finChall, err = va.validateHTTP01(ident, chall) | ||||||
|  | 	test.AssertEquals(t, finChall.Status, core.StatusValid) | ||||||
|  | 	test.AssertNotError(t, err, "Failed to follow 301 redirect") | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1) | ||||||
|  | 
 | ||||||
|  | 	log.Clear() | ||||||
|  | 	setChallengeToken(&chall, pathFound) | ||||||
|  | 	finChall, err = va.validateHTTP01(ident, chall) | ||||||
|  | 	test.AssertEquals(t, finChall.Status, core.StatusValid) | ||||||
|  | 	test.AssertNotError(t, err, "Failed to follow 302 redirect") | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathFound+`" to ".*/`+pathMoved+`"`)), 1) | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1) | ||||||
|  | 
 | ||||||
|  | 	ipIdentifier := core.AcmeIdentifier{Type: core.IdentifierType("ip"), Value: "127.0.0.1"} | ||||||
|  | 	invalidChall, err = va.validateHTTP01(ipIdentifier, chall) | ||||||
|  | 	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) | ||||||
|  | 	test.AssertError(t, err, "IdentifierType IP shouldn't have worked.") | ||||||
|  | 	test.AssertEquals(t, invalidChall.Error.Type, core.MalformedProblem) | ||||||
|  | 
 | ||||||
|  | 	invalidChall, err = va.validateHTTP01(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "always.invalid"}, chall) | ||||||
|  | 	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) | ||||||
|  | 	test.AssertError(t, err, "Domain name is invalid.") | ||||||
|  | 	test.AssertEquals(t, invalidChall.Error.Type, core.UnknownHostProblem) | ||||||
|  | 
 | ||||||
|  | 	setChallengeToken(&chall, pathWaitLong) | ||||||
|  | 	started := time.Now() | ||||||
|  | 	invalidChall, err = va.validateHTTP01(ident, chall) | ||||||
|  | 	took := time.Since(started) | ||||||
|  | 	// Check that the HTTP connection times out after 5 seconds and doesn't block for 10 seconds
 | ||||||
|  | 	test.Assert(t, (took > (time.Second * 5)), "HTTP timed out before 5 seconds") | ||||||
|  | 	test.Assert(t, (took < (time.Second * 10)), "HTTP connection didn't timeout after 5 seconds") | ||||||
|  | 	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) | ||||||
|  | 	test.AssertError(t, err, "Connection should've timed out") | ||||||
|  | 	test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestHTTPRedirectLookup(t *testing.T) { | ||||||
|  | 	chall := core.HTTPChallenge01(accountKey) | ||||||
|  | 	err := setChallengeToken(&chall, expectedToken) | ||||||
|  | 	test.AssertNotError(t, err, "Failed to complete HTTP challenge") | ||||||
|  | 
 | ||||||
|  | 	hs := httpSrv(t, expectedToken) | ||||||
|  | 	defer hs.Close() | ||||||
| 	port, err := getPort(hs) | 	port, err := getPort(hs) | ||||||
| 	test.AssertNotError(t, err, "failed to get test server port") | 	test.AssertNotError(t, err, "failed to get test server port") | ||||||
| 	stats, _ := statsd.NewNoopClient() | 	stats, _ := statsd.NewNoopClient() | ||||||
| 	va := NewValidationAuthorityImpl(&PortConfig{SimpleHTTPPort: port}, stats, clock.Default()) | 	va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: port}, stats, clock.Default()) | ||||||
| 	va.DNSResolver = &mocks.MockDNS{} | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
|  | 
 | ||||||
|  | 	log.Clear() | ||||||
|  | 	setChallengeToken(&chall, pathMoved) | ||||||
|  | 	finChall, err := va.validateHTTP01(ident, chall) | ||||||
|  | 	test.AssertEquals(t, finChall.Status, core.StatusValid) | ||||||
|  | 	test.AssertNotError(t, err, chall.Token) | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1) | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 2) | ||||||
|  | 
 | ||||||
|  | 	log.Clear() | ||||||
|  | 	setChallengeToken(&chall, pathFound) | ||||||
|  | 	finChall, err = va.validateHTTP01(ident, chall) | ||||||
|  | 	test.AssertEquals(t, finChall.Status, core.StatusValid) | ||||||
|  | 	test.AssertNotError(t, err, chall.Token) | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathFound+`" to ".*/`+pathMoved+`"`)), 1) | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1) | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 3) | ||||||
|  | 
 | ||||||
|  | 	log.Clear() | ||||||
|  | 	setChallengeToken(&chall, pathReLookupInvalid) | ||||||
|  | 	finChall, err = va.validateHTTP01(ident, chall) | ||||||
|  | 	test.AssertEquals(t, finChall.Status, core.StatusInvalid) | ||||||
|  | 	test.AssertError(t, err, chall.Token) | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1) | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`No IPv4 addresses found for invalid.invalid`)), 1) | ||||||
|  | 
 | ||||||
|  | 	log.Clear() | ||||||
|  | 	setChallengeToken(&chall, pathReLookup) | ||||||
|  | 	finChall, err = va.validateHTTP01(ident, chall) | ||||||
|  | 	test.AssertEquals(t, finChall.Status, core.StatusValid) | ||||||
|  | 	test.AssertNotError(t, err, chall.Token) | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathReLookup+`" to ".*other.valid/path"`)), 1) | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1) | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for other.valid \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1) | ||||||
|  | 
 | ||||||
|  | 	log.Clear() | ||||||
|  | 	setChallengeToken(&chall, pathRedirectPort) | ||||||
|  | 	finChall, err = va.validateHTTP01(ident, chall) | ||||||
|  | 	fmt.Println(finChall.ValidationRecord) | ||||||
|  | 	test.AssertEquals(t, finChall.Status, core.StatusInvalid) | ||||||
|  | 	test.AssertError(t, err, chall.Token) | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/port-redirect" to ".*other.valid:8080/path"`)), 1) | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1) | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for other.valid \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestHTTPRedirectLoop(t *testing.T) { | ||||||
|  | 	chall := core.HTTPChallenge01(accountKey) | ||||||
|  | 	err := setChallengeToken(&chall, "looper") | ||||||
|  | 	test.AssertNotError(t, err, "Failed to complete HTTP challenge") | ||||||
|  | 
 | ||||||
|  | 	hs := httpSrv(t, expectedToken) | ||||||
|  | 	defer hs.Close() | ||||||
|  | 	port, err := getPort(hs) | ||||||
|  | 	test.AssertNotError(t, err, "failed to get test server port") | ||||||
|  | 	stats, _ := statsd.NewNoopClient() | ||||||
|  | 	va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: port}, stats, clock.Default()) | ||||||
|  | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
|  | 
 | ||||||
|  | 	log.Clear() | ||||||
|  | 	finChall, err := va.validateHTTP01(ident, chall) | ||||||
|  | 	test.AssertEquals(t, finChall.Status, core.StatusInvalid) | ||||||
|  | 	test.AssertError(t, err, chall.Token) | ||||||
|  | 	fmt.Println(finChall) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getPort(hs *httptest.Server) (int, error) { | ||||||
|  | 	url, err := url.Parse(hs.URL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  | 	_, portString, err := net.SplitHostPort(url.Host) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  | 	port, err := strconv.ParseInt(portString, 10, 64) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  | 	return int(port), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestTLSSNI(t *testing.T) { | ||||||
|  | 	chall := createChallenge(core.ChallengeTypeTLSSNI01) | ||||||
|  | 
 | ||||||
|  | 	hs := tlssniSrv(t, chall) | ||||||
|  | 	port, err := getPort(hs) | ||||||
|  | 	test.AssertNotError(t, err, "failed to get test server port") | ||||||
|  | 
 | ||||||
|  | 	stats, _ := statsd.NewNoopClient() | ||||||
|  | 	va := NewValidationAuthorityImpl(&PortConfig{TLSPort: port}, stats, clock.Default()) | ||||||
|  | 
 | ||||||
|  | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
|  | 
 | ||||||
|  | 	log.Clear() | ||||||
|  | 	finChall, err := va.validateTLSSNI01(ident, chall) | ||||||
|  | 	test.AssertEquals(t, finChall.Status, core.StatusValid) | ||||||
|  | 	test.AssertNotError(t, err, "") | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1) | ||||||
|  | 
 | ||||||
|  | 	log.Clear() | ||||||
|  | 	invalidChall, err := va.validateTLSSNI01(core.AcmeIdentifier{ | ||||||
|  | 		Type:  core.IdentifierType("ip"), | ||||||
|  | 		Value: net.JoinHostPort("127.0.0.1", fmt.Sprintf("%d", port)), | ||||||
|  | 	}, chall) | ||||||
|  | 	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) | ||||||
|  | 	test.AssertError(t, err, "IdentifierType IP shouldn't have worked.") | ||||||
|  | 	test.AssertEquals(t, invalidChall.Error.Type, core.MalformedProblem) | ||||||
|  | 
 | ||||||
|  | 	log.Clear() | ||||||
|  | 	invalidChall, err = va.validateTLSSNI01(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "always.invalid"}, chall) | ||||||
|  | 	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) | ||||||
|  | 	test.AssertError(t, err, "Domain name was supposed to be invalid.") | ||||||
|  | 	test.AssertEquals(t, invalidChall.Error.Type, core.UnknownHostProblem) | ||||||
|  | 
 | ||||||
|  | 	// Need to create a new authorized keys object to get an unknown SNI (from the signature value)
 | ||||||
|  | 	chall.Token = core.NewToken() | ||||||
|  | 	keyAuthorization, _ := core.NewKeyAuthorization(chall.Token, accountKey) | ||||||
|  | 	chall.KeyAuthorization = &keyAuthorization | ||||||
|  | 
 | ||||||
|  | 	log.Clear() | ||||||
|  | 	started := time.Now() | ||||||
|  | 	invalidChall, err = va.validateTLSSNI01(ident, chall) | ||||||
|  | 	took := time.Since(started) | ||||||
|  | 	// Check that the HTTP connection times out after 5 seconds and doesn't block for 10 seconds
 | ||||||
|  | 	test.Assert(t, (took > (time.Second * 5)), "HTTP timed out before 5 seconds") | ||||||
|  | 	test.Assert(t, (took < (time.Second * 10)), "HTTP connection didn't timeout after 5 seconds") | ||||||
|  | 	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) | ||||||
|  | 	test.AssertError(t, err, "Connection should've timed out") | ||||||
|  | 	test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem) | ||||||
|  | 	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1) | ||||||
|  | 
 | ||||||
|  | 	// Take down validation server and check that validation fails.
 | ||||||
|  | 	hs.Close() | ||||||
|  | 	invalidChall, err = va.validateTLSSNI01(ident, chall) | ||||||
|  | 	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) | ||||||
|  | 	test.AssertError(t, err, "Server's down; expected refusal. Where did we connect?") | ||||||
|  | 	test.AssertEquals(t, invalidChall.Error.Type, core.ConnectionProblem) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func brokenTLSSrv() *httptest.Server { | ||||||
|  | 	server := httptest.NewUnstartedServer(http.DefaultServeMux) | ||||||
|  | 	server.TLS = &tls.Config{ | ||||||
|  | 		GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) { | ||||||
|  | 			return nil, fmt.Errorf("Failing on purpose") | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	server.StartTLS() | ||||||
|  | 	return server | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestTLSError(t *testing.T) { | ||||||
|  | 	chall := createChallenge(core.ChallengeTypeTLSSNI01) | ||||||
|  | 	hs := brokenTLSSrv() | ||||||
|  | 
 | ||||||
|  | 	port, err := getPort(hs) | ||||||
|  | 	test.AssertNotError(t, err, "failed to get test server port") | ||||||
|  | 	stats, _ := statsd.NewNoopClient() | ||||||
|  | 	va := NewValidationAuthorityImpl(&PortConfig{TLSPort: port}, stats, clock.Default()) | ||||||
|  | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
|  | 
 | ||||||
|  | 	invalidChall, err := va.validateTLSSNI01(ident, chall) | ||||||
|  | 	test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) | ||||||
|  | 	test.AssertError(t, err, "What cert was used?") | ||||||
|  | 	test.AssertEquals(t, invalidChall.Error.Type, core.TLSProblem) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestValidateHTTP(t *testing.T) { | ||||||
|  | 	chall := core.HTTPChallenge01(accountKey) | ||||||
|  | 	err := setChallengeToken(&chall, core.NewToken()) | ||||||
|  | 	test.AssertNotError(t, err, "Failed to complete HTTP challenge") | ||||||
|  | 
 | ||||||
|  | 	hs := httpSrv(t, chall.Token) | ||||||
|  | 	port, err := getPort(hs) | ||||||
|  | 	test.AssertNotError(t, err, "failed to get test server port") | ||||||
|  | 	stats, _ := statsd.NewNoopClient() | ||||||
|  | 	va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: port}, stats, clock.Default()) | ||||||
|  | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
| 	mockRA := &MockRegistrationAuthority{} | 	mockRA := &MockRegistrationAuthority{} | ||||||
| 	va.RA = mockRA | 	va.RA = mockRA | ||||||
| 
 | 
 | ||||||
|  | @ -561,14 +931,14 @@ func TestValidateHTTP(t *testing.T) { | ||||||
| 		ID:             core.NewToken(), | 		ID:             core.NewToken(), | ||||||
| 		RegistrationID: 1, | 		RegistrationID: 1, | ||||||
| 		Identifier:     ident, | 		Identifier:     ident, | ||||||
| 		Challenges:     []core.Challenge{challHTTP}, | 		Challenges:     []core.Challenge{chall}, | ||||||
| 	} | 	} | ||||||
| 	va.validate(authz, 0) | 	va.validate(authz, 0) | ||||||
| 
 | 
 | ||||||
| 	test.AssertEquals(t, core.StatusValid, mockRA.lastAuthz.Challenges[0].Status) | 	test.AssertEquals(t, core.StatusValid, mockRA.lastAuthz.Challenges[0].Status) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // challengeType == "dvsni" or "dns", since they're the same
 | // challengeType == "tls-sni-00" or "dns-00", since they're the same
 | ||||||
| func createChallenge(challengeType string) core.Challenge { | func createChallenge(challengeType string) core.Challenge { | ||||||
| 	chall := core.Challenge{ | 	chall := core.Challenge{ | ||||||
| 		Type:             challengeType, | 		Type:             challengeType, | ||||||
|  | @ -578,27 +948,45 @@ func createChallenge(challengeType string) core.Challenge { | ||||||
| 		AccountKey:       accountKey, | 		AccountKey:       accountKey, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	keyAuthorization, _ := core.NewKeyAuthorization(chall.Token, accountKey) | ||||||
|  | 	chall.KeyAuthorization = &keyAuthorization | ||||||
|  | 
 | ||||||
|  | 	// TODO(https://github.com/letsencrypt/boulder/issues/894): Remove this block
 | ||||||
| 	validationPayload, _ := json.Marshal(map[string]interface{}{ | 	validationPayload, _ := json.Marshal(map[string]interface{}{ | ||||||
| 		"type":  chall.Type, | 		"type":  chall.Type, | ||||||
| 		"token": chall.Token, | 		"token": chall.Token, | ||||||
| 	}) | 	}) | ||||||
| 
 |  | ||||||
| 	signer, _ := jose.NewSigner(jose.RS256, &TheKey) | 	signer, _ := jose.NewSigner(jose.RS256, &TheKey) | ||||||
| 	chall.Validation, _ = signer.Sign(validationPayload, "") | 	chall.Validation, _ = signer.Sign(validationPayload, "") | ||||||
|  | 
 | ||||||
| 	return chall | 	return chall | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestValidateDvsni(t *testing.T) { | // setChallengeToken sets the token value both in the Token field and
 | ||||||
| 	chall := createChallenge(core.ChallengeTypeDVSNI) | // in the serialized KeyAuthorization object.
 | ||||||
| 	hs := dvsniSrv(t, chall) | func setChallengeToken(ch *core.Challenge, token string) (err error) { | ||||||
|  | 	ch.Token = token | ||||||
|  | 
 | ||||||
|  | 	keyAuthorization, err := core.NewKeyAuthorization(token, ch.AccountKey) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ch.KeyAuthorization = &keyAuthorization | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestValidateTLSSNI01(t *testing.T) { | ||||||
|  | 	chall := createChallenge(core.ChallengeTypeTLSSNI01) | ||||||
|  | 	hs := tlssniSrv(t, chall) | ||||||
| 	defer hs.Close() | 	defer hs.Close() | ||||||
| 
 | 
 | ||||||
| 	port, err := getPort(hs) | 	port, err := getPort(hs) | ||||||
| 	test.AssertNotError(t, err, "failed to get test server port") | 	test.AssertNotError(t, err, "failed to get test server port") | ||||||
| 
 | 
 | ||||||
| 	stats, _ := statsd.NewNoopClient() | 	stats, _ := statsd.NewNoopClient() | ||||||
| 	va := NewValidationAuthorityImpl(&PortConfig{DVSNIPort: port}, stats, clock.Default()) | 	va := NewValidationAuthorityImpl(&PortConfig{TLSPort: port}, stats, clock.Default()) | ||||||
| 	va.DNSResolver = &mocks.MockDNS{} | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
| 	mockRA := &MockRegistrationAuthority{} | 	mockRA := &MockRegistrationAuthority{} | ||||||
| 	va.RA = mockRA | 	va.RA = mockRA | ||||||
| 
 | 
 | ||||||
|  | @ -613,14 +1001,14 @@ func TestValidateDvsni(t *testing.T) { | ||||||
| 	test.AssertEquals(t, core.StatusValid, mockRA.lastAuthz.Challenges[0].Status) | 	test.AssertEquals(t, core.StatusValid, mockRA.lastAuthz.Challenges[0].Status) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestValidateDvsniNotSane(t *testing.T) { | func TestValidateTLSSNINotSane(t *testing.T) { | ||||||
| 	stats, _ := statsd.NewNoopClient() | 	stats, _ := statsd.NewNoopClient() | ||||||
| 	va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default()) // no calls made
 | 	va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default()) // no calls made
 | ||||||
| 	va.DNSResolver = &mocks.MockDNS{} | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
| 	mockRA := &MockRegistrationAuthority{} | 	mockRA := &MockRegistrationAuthority{} | ||||||
| 	va.RA = mockRA | 	va.RA = mockRA | ||||||
| 
 | 
 | ||||||
| 	chall := createChallenge(core.ChallengeTypeDVSNI) | 	chall := createChallenge(core.ChallengeTypeTLSSNI01) | ||||||
| 
 | 
 | ||||||
| 	chall.Token = "not sane" | 	chall.Token = "not sane" | ||||||
| 
 | 
 | ||||||
|  | @ -638,20 +1026,20 @@ func TestValidateDvsniNotSane(t *testing.T) { | ||||||
| func TestUpdateValidations(t *testing.T) { | func TestUpdateValidations(t *testing.T) { | ||||||
| 	stats, _ := statsd.NewNoopClient() | 	stats, _ := statsd.NewNoopClient() | ||||||
| 	va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default()) | 	va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default()) | ||||||
| 	va.DNSResolver = &mocks.MockDNS{} | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
| 	mockRA := &MockRegistrationAuthority{} | 	mockRA := &MockRegistrationAuthority{} | ||||||
| 	va.RA = mockRA | 	va.RA = mockRA | ||||||
| 
 | 
 | ||||||
| 	tls := false | 	chall := core.HTTPChallenge01(accountKey) | ||||||
| 	challHTTP := core.SimpleHTTPChallenge() | 	chall.ValidationRecord = []core.ValidationRecord{} | ||||||
| 	challHTTP.TLS = &tls | 	err := setChallengeToken(&chall, core.NewToken()) | ||||||
| 	challHTTP.ValidationRecord = []core.ValidationRecord{} | 	test.AssertNotError(t, err, "Failed to complete HTTP challenge") | ||||||
| 
 | 
 | ||||||
| 	var authz = core.Authorization{ | 	var authz = core.Authorization{ | ||||||
| 		ID:             core.NewToken(), | 		ID:             core.NewToken(), | ||||||
| 		RegistrationID: 1, | 		RegistrationID: 1, | ||||||
| 		Identifier:     ident, | 		Identifier:     ident, | ||||||
| 		Challenges:     []core.Challenge{challHTTP}, | 		Challenges:     []core.Challenge{chall}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	started := time.Now() | 	started := time.Now() | ||||||
|  | @ -694,7 +1082,7 @@ func TestCAAChecking(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	stats, _ := statsd.NewNoopClient() | 	stats, _ := statsd.NewNoopClient() | ||||||
| 	va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default()) | 	va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default()) | ||||||
| 	va.DNSResolver = &mocks.MockDNS{} | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
| 	va.IssuerDomain = "letsencrypt.org" | 	va.IssuerDomain = "letsencrypt.org" | ||||||
| 	for _, caaTest := range tests { | 	for _, caaTest := range tests { | ||||||
| 		present, valid, err := va.CheckCAARecords(core.AcmeIdentifier{Type: "dns", Value: caaTest.Domain}) | 		present, valid, err := va.CheckCAARecords(core.AcmeIdentifier{Type: "dns", Value: caaTest.Domain}) | ||||||
|  | @ -727,11 +1115,11 @@ func TestCAAChecking(t *testing.T) { | ||||||
| func TestDNSValidationFailure(t *testing.T) { | func TestDNSValidationFailure(t *testing.T) { | ||||||
| 	stats, _ := statsd.NewNoopClient() | 	stats, _ := statsd.NewNoopClient() | ||||||
| 	va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default()) | 	va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default()) | ||||||
| 	va.DNSResolver = &mocks.MockDNS{} | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
| 	mockRA := &MockRegistrationAuthority{} | 	mockRA := &MockRegistrationAuthority{} | ||||||
| 	va.RA = mockRA | 	va.RA = mockRA | ||||||
| 
 | 
 | ||||||
| 	chalDNS := createChallenge(core.ChallengeTypeDNS) | 	chalDNS := createChallenge(core.ChallengeTypeDNS01) | ||||||
| 
 | 
 | ||||||
| 	var authz = core.Authorization{ | 	var authz = core.Authorization{ | ||||||
| 		ID:             core.NewToken(), | 		ID:             core.NewToken(), | ||||||
|  | @ -753,7 +1141,7 @@ func TestDNSValidationInvalid(t *testing.T) { | ||||||
| 		Value: "790DB180-A274-47A4-855F-31C428CB1072", | 		Value: "790DB180-A274-47A4-855F-31C428CB1072", | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	chalDNS := core.DNSChallenge() | 	chalDNS := core.DNSChallenge01(accountKey) | ||||||
| 
 | 
 | ||||||
| 	var authz = core.Authorization{ | 	var authz = core.Authorization{ | ||||||
| 		ID:             core.NewToken(), | 		ID:             core.NewToken(), | ||||||
|  | @ -764,7 +1152,7 @@ func TestDNSValidationInvalid(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	stats, _ := statsd.NewNoopClient() | 	stats, _ := statsd.NewNoopClient() | ||||||
| 	va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default()) | 	va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default()) | ||||||
| 	va.DNSResolver = &mocks.MockDNS{} | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
| 	mockRA := &MockRegistrationAuthority{} | 	mockRA := &MockRegistrationAuthority{} | ||||||
| 	va.RA = mockRA | 	va.RA = mockRA | ||||||
| 
 | 
 | ||||||
|  | @ -778,17 +1166,17 @@ func TestDNSValidationInvalid(t *testing.T) { | ||||||
| func TestDNSValidationNotSane(t *testing.T) { | func TestDNSValidationNotSane(t *testing.T) { | ||||||
| 	stats, _ := statsd.NewNoopClient() | 	stats, _ := statsd.NewNoopClient() | ||||||
| 	va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default()) | 	va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default()) | ||||||
| 	va.DNSResolver = &mocks.MockDNS{} | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
| 	mockRA := &MockRegistrationAuthority{} | 	mockRA := &MockRegistrationAuthority{} | ||||||
| 	va.RA = mockRA | 	va.RA = mockRA | ||||||
| 
 | 
 | ||||||
| 	chal0 := core.DNSChallenge() | 	chal0 := core.DNSChallenge01(accountKey) | ||||||
| 	chal0.Token = "" | 	chal0.Token = "" | ||||||
| 
 | 
 | ||||||
| 	chal1 := core.DNSChallenge() | 	chal1 := core.DNSChallenge01(accountKey) | ||||||
| 	chal1.Token = "yfCBb-bRTLz8Wd1C0lTUQK3qlKj3-t2tYGwx5Hj7r_" | 	chal1.Token = "yfCBb-bRTLz8Wd1C0lTUQK3qlKj3-t2tYGwx5Hj7r_" | ||||||
| 
 | 
 | ||||||
| 	chal2 := core.DNSChallenge() | 	chal2 := core.DNSChallenge01(accountKey) | ||||||
| 	chal2.TLS = new(bool) | 	chal2.TLS = new(bool) | ||||||
| 	*chal2.TLS = true | 	*chal2.TLS = true | ||||||
| 
 | 
 | ||||||
|  | @ -809,11 +1197,11 @@ func TestDNSValidationNotSane(t *testing.T) { | ||||||
| func TestDNSValidationServFail(t *testing.T) { | func TestDNSValidationServFail(t *testing.T) { | ||||||
| 	stats, _ := statsd.NewNoopClient() | 	stats, _ := statsd.NewNoopClient() | ||||||
| 	va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default()) | 	va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default()) | ||||||
| 	va.DNSResolver = &mocks.MockDNS{} | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
| 	mockRA := &MockRegistrationAuthority{} | 	mockRA := &MockRegistrationAuthority{} | ||||||
| 	va.RA = mockRA | 	va.RA = mockRA | ||||||
| 
 | 
 | ||||||
| 	chalDNS := createChallenge(core.ChallengeTypeDNS) | 	chalDNS := createChallenge(core.ChallengeTypeDNS01) | ||||||
| 
 | 
 | ||||||
| 	badIdent := core.AcmeIdentifier{ | 	badIdent := core.AcmeIdentifier{ | ||||||
| 		Type:  core.IdentifierDNS, | 		Type:  core.IdentifierDNS, | ||||||
|  | @ -839,7 +1227,7 @@ func TestDNSValidationNoServer(t *testing.T) { | ||||||
| 	mockRA := &MockRegistrationAuthority{} | 	mockRA := &MockRegistrationAuthority{} | ||||||
| 	va.RA = mockRA | 	va.RA = mockRA | ||||||
| 
 | 
 | ||||||
| 	chalDNS := createChallenge(core.ChallengeTypeDNS) | 	chalDNS := createChallenge(core.ChallengeTypeDNS01) | ||||||
| 
 | 
 | ||||||
| 	var authz = core.Authorization{ | 	var authz = core.Authorization{ | ||||||
| 		ID:             core.NewToken(), | 		ID:             core.NewToken(), | ||||||
|  | @ -860,11 +1248,11 @@ func TestDNSValidationNoServer(t *testing.T) { | ||||||
| func TestDNSValidationLive(t *testing.T) { | func TestDNSValidationLive(t *testing.T) { | ||||||
| 	stats, _ := statsd.NewNoopClient() | 	stats, _ := statsd.NewNoopClient() | ||||||
| 	va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default()) | 	va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default()) | ||||||
| 	va.DNSResolver = &mocks.MockDNS{} | 	va.DNSResolver = &mocks.DNSResolver{} | ||||||
| 	mockRA := &MockRegistrationAuthority{} | 	mockRA := &MockRegistrationAuthority{} | ||||||
| 	va.RA = mockRA | 	va.RA = mockRA | ||||||
| 
 | 
 | ||||||
| 	goodChalDNS := core.DNSChallenge() | 	goodChalDNS := core.DNSChallenge01(accountKey) | ||||||
| 	// This token is set at _acme-challenge.good.bin.coffee
 | 	// This token is set at _acme-challenge.good.bin.coffee
 | ||||||
| 	goodChalDNS.Token = "yfCBb-bRTLz8Wd1C0lTUQK3qlKj3-t2tYGwx5Hj7r_w" | 	goodChalDNS.Token = "yfCBb-bRTLz8Wd1C0lTUQK3qlKj3-t2tYGwx5Hj7r_w" | ||||||
| 
 | 
 | ||||||
|  | @ -891,7 +1279,7 @@ func TestDNSValidationLive(t *testing.T) { | ||||||
| 		t.Logf("TestDNSValidationLive on Good did not succeed.") | 		t.Logf("TestDNSValidationLive on Good did not succeed.") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	badChalDNS := core.DNSChallenge() | 	badChalDNS := core.DNSChallenge01(accountKey) | ||||||
| 	// This token is NOT set at _acme-challenge.bad.bin.coffee
 | 	// This token is NOT set at _acme-challenge.bad.bin.coffee
 | ||||||
| 	badChalDNS.Token = "yfCBb-bRTLz8Wd1C0lTUQK3qlKj3-t2tYGwx5Hj7r_w" | 	badChalDNS.Token = "yfCBb-bRTLz8Wd1C0lTUQK3qlKj3-t2tYGwx5Hj7r_w" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -38,10 +38,14 @@ const ( | ||||||
| 	IssuerPath     = "/acme/issuer-cert" | 	IssuerPath     = "/acme/issuer-cert" | ||||||
| 	BuildIDPath    = "/build" | 	BuildIDPath    = "/build" | ||||||
| 
 | 
 | ||||||
| 	// Not included in net/http
 | 	// StatusRateLimited is not in net/http
 | ||||||
| 	StatusRateLimited = 429 | 	StatusRateLimited = 429 | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // WebFrontEndImpl provides all the logic for Boulder's web-facing interface,
 | ||||||
|  | // i.e., ACME.  Its members configure the paths for various ACME functions,
 | ||||||
|  | // plus a few other data items used in ACME.  Its methods are primarily handlers
 | ||||||
|  | // for HTTPS requests for the various ACME functions.
 | ||||||
| type WebFrontEndImpl struct { | type WebFrontEndImpl struct { | ||||||
| 	RA    core.RegistrationAuthority | 	RA    core.RegistrationAuthority | ||||||
| 	SA    core.StorageGetter | 	SA    core.StorageGetter | ||||||
|  | @ -113,7 +117,7 @@ func statusCodeFromError(err interface{}) int { | ||||||
| type requestEvent struct { | type requestEvent struct { | ||||||
| 	ID           string          `json:",omitempty"` | 	ID           string          `json:",omitempty"` | ||||||
| 	RealIP       string          `json:",omitempty"` | 	RealIP       string          `json:",omitempty"` | ||||||
| 	ForwardedFor string          `json:",omitempty"` | 	ClientAddr   string          `json:",omitempty"` | ||||||
| 	Endpoint     string          `json:",omitempty"` | 	Endpoint     string          `json:",omitempty"` | ||||||
| 	Method       string          `json:",omitempty"` | 	Method       string          `json:",omitempty"` | ||||||
| 	RequestTime  time.Time       `json:",omitempty"` | 	RequestTime  time.Time       `json:",omitempty"` | ||||||
|  | @ -302,6 +306,8 @@ func addCacheHeader(w http.ResponseWriter, age float64) { | ||||||
| 	w.Header().Add("Cache-Control", fmt.Sprintf("public, max-age=%.f", age)) | 	w.Header().Add("Cache-Control", fmt.Sprintf("public, max-age=%.f", age)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Directory is an HTTP request handler that simply provides the directory
 | ||||||
|  | // object stored in the WFE's DirectoryJSON member.
 | ||||||
| func (wfe *WebFrontEndImpl) Directory(response http.ResponseWriter, request *http.Request) { | func (wfe *WebFrontEndImpl) Directory(response http.ResponseWriter, request *http.Request) { | ||||||
| 	response.Header().Set("Content-Type", "application/json") | 	response.Header().Set("Content-Type", "application/json") | ||||||
| 	response.Write(wfe.DirectoryJSON) | 	response.Write(wfe.DirectoryJSON) | ||||||
|  | @ -553,8 +559,7 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques | ||||||
| 
 | 
 | ||||||
| 	// Use an explicitly typed variable. Otherwise `go vet' incorrectly complains
 | 	// Use an explicitly typed variable. Otherwise `go vet' incorrectly complains
 | ||||||
| 	// that reg.ID is a string being passed to %d.
 | 	// that reg.ID is a string being passed to %d.
 | ||||||
| 	var id int64 = reg.ID | 	regURL := fmt.Sprintf("%s%d", wfe.RegBase, reg.ID) | ||||||
| 	regURL := fmt.Sprintf("%s%d", wfe.RegBase, id) |  | ||||||
| 	responseBody, err := json.Marshal(reg) | 	responseBody, err := json.Marshal(reg) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		logEvent.Error = err.Error() | 		logEvent.Error = err.Error() | ||||||
|  | @ -730,13 +735,13 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(response http.ResponseWriter, requ | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (wfe *WebFrontEndImpl) logCsr(remoteAddr string, cr core.CertificateRequest, registration core.Registration) { | func (wfe *WebFrontEndImpl) logCsr(request *http.Request, cr core.CertificateRequest, registration core.Registration) { | ||||||
| 	var csrLog = struct { | 	var csrLog = struct { | ||||||
| 		RemoteAddr   string | 		ClientAddr   string | ||||||
| 		CsrBase64    []byte | 		CsrBase64    []byte | ||||||
| 		Registration core.Registration | 		Registration core.Registration | ||||||
| 	}{ | 	}{ | ||||||
| 		RemoteAddr:   remoteAddr, | 		ClientAddr:   getClientAddr(request), | ||||||
| 		CsrBase64:    cr.Bytes, | 		CsrBase64:    cr.Bytes, | ||||||
| 		Registration: registration, | 		Registration: registration, | ||||||
| 	} | 	} | ||||||
|  | @ -778,7 +783,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request | ||||||
| 		wfe.sendError(response, "Error unmarshaling certificate request", err, http.StatusBadRequest) | 		wfe.sendError(response, "Error unmarshaling certificate request", err, http.StatusBadRequest) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	wfe.logCsr(request.RemoteAddr, certificateRequest, reg) | 	wfe.logCsr(request, certificateRequest, reg) | ||||||
| 	// Check that the key in the CSR is good. This will also be checked in the CA
 | 	// Check that the key in the CSR is good. This will also be checked in the CA
 | ||||||
| 	// component, but we want to discard CSRs with bad keys as early as possible
 | 	// component, but we want to discard CSRs with bad keys as early as possible
 | ||||||
| 	// because (a) it's an easy check and we can save unnecessary requests and
 | 	// because (a) it's an easy check and we can save unnecessary requests and
 | ||||||
|  | @ -833,6 +838,8 @@ func (wfe *WebFrontEndImpl) NewCertificate(response http.ResponseWriter, request | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Challenge handles POST requests to challenge URLs.  Such requests are clients'
 | ||||||
|  | // responses to the server's challenges.
 | ||||||
| func (wfe *WebFrontEndImpl) Challenge( | func (wfe *WebFrontEndImpl) Challenge( | ||||||
| 	response http.ResponseWriter, | 	response http.ResponseWriter, | ||||||
| 	request *http.Request) { | 	request *http.Request) { | ||||||
|  | @ -1300,7 +1307,7 @@ func (wfe *WebFrontEndImpl) populateRequestEvent(request *http.Request) (logEven | ||||||
| 	logEvent = requestEvent{ | 	logEvent = requestEvent{ | ||||||
| 		ID:          core.NewToken(), | 		ID:          core.NewToken(), | ||||||
| 		RealIP:      request.Header.Get("X-Real-IP"), | 		RealIP:      request.Header.Get("X-Real-IP"), | ||||||
| 		ForwardedFor: request.Header.Get("X-Forwarded-For"), | 		ClientAddr:  getClientAddr(request), | ||||||
| 		Method:      request.Method, | 		Method:      request.Method, | ||||||
| 		RequestTime: time.Now(), | 		RequestTime: time.Now(), | ||||||
| 		Extra:       make(map[string]interface{}, 0), | 		Extra:       make(map[string]interface{}, 0), | ||||||
|  | @ -1310,3 +1317,14 @@ func (wfe *WebFrontEndImpl) populateRequestEvent(request *http.Request) (logEven | ||||||
| 	} | 	} | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // Comma-separated list of HTTP clients involved in making this
 | ||||||
|  | // request, starting with the original requestor and ending with the
 | ||||||
|  | // remote end of our TCP connection (which is typically our own
 | ||||||
|  | // proxy).
 | ||||||
|  | func getClientAddr(r *http.Request) string { | ||||||
|  | 	if xff := r.Header.Get("X-Forwarded-For"); xff != "" { | ||||||
|  | 		return xff + "," + r.RemoteAddr | ||||||
|  | 	} | ||||||
|  | 	return r.RemoteAddr | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -26,10 +26,9 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd" | 	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd" | ||||||
| 	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock" | 	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock" | ||||||
|  | 	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose" | ||||||
|  | 
 | ||||||
| 	"github.com/letsencrypt/boulder/cmd" | 	"github.com/letsencrypt/boulder/cmd" | ||||||
| 
 |  | ||||||
| 	jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose" |  | ||||||
| 
 |  | ||||||
| 	"github.com/letsencrypt/boulder/core" | 	"github.com/letsencrypt/boulder/core" | ||||||
| 	"github.com/letsencrypt/boulder/mocks" | 	"github.com/letsencrypt/boulder/mocks" | ||||||
| 	"github.com/letsencrypt/boulder/ra" | 	"github.com/letsencrypt/boulder/ra" | ||||||
|  | @ -172,7 +171,7 @@ func (ca *MockCA) RevokeCertificate(serial string, reasonCode core.RevocationCod | ||||||
| 
 | 
 | ||||||
| type MockPA struct{} | type MockPA struct{} | ||||||
| 
 | 
 | ||||||
| func (pa *MockPA) ChallengesFor(identifier core.AcmeIdentifier) (challenges []core.Challenge, combinations [][]int) { | func (pa *MockPA) ChallengesFor(identifier core.AcmeIdentifier, key *jose.JsonWebKey) (challenges []core.Challenge, combinations [][]int, err error) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -214,7 +213,7 @@ func setupWFE(t *testing.T) WebFrontEndImpl { | ||||||
| 	wfe.log.SyslogWriter = mocks.NewSyslogWriter() | 	wfe.log.SyslogWriter = mocks.NewSyslogWriter() | ||||||
| 
 | 
 | ||||||
| 	wfe.RA = &MockRegistrationAuthority{} | 	wfe.RA = &MockRegistrationAuthority{} | ||||||
| 	wfe.SA = &mocks.MockSA{} | 	wfe.SA = &mocks.StorageAuthority{} | ||||||
| 	wfe.stats, _ = statsd.NewNoopClient() | 	wfe.stats, _ = statsd.NewNoopClient() | ||||||
| 	wfe.SubscriberAgreementURL = agreementURL | 	wfe.SubscriberAgreementURL = agreementURL | ||||||
| 
 | 
 | ||||||
|  | @ -534,7 +533,7 @@ func TestIssueCertificate(t *testing.T) { | ||||||
| 	wfe := setupWFE(t) | 	wfe := setupWFE(t) | ||||||
| 	mux, err := wfe.Handler() | 	mux, err := wfe.Handler() | ||||||
| 	test.AssertNotError(t, err, "Problem setting up HTTP handlers") | 	test.AssertNotError(t, err, "Problem setting up HTTP handlers") | ||||||
| 	mockLog := wfe.log.SyslogWriter.(*mocks.MockSyslogWriter) | 	mockLog := wfe.log.SyslogWriter.(*mocks.SyslogWriter) | ||||||
| 
 | 
 | ||||||
| 	// The mock CA we use always returns the same test certificate, with a Not
 | 	// The mock CA we use always returns the same test certificate, with a Not
 | ||||||
| 	// Before of 2015-09-22. Since we're currently using a real RA instead of a
 | 	// Before of 2015-09-22. Since we're currently using a real RA instead of a
 | ||||||
|  | @ -549,10 +548,10 @@ func TestIssueCertificate(t *testing.T) { | ||||||
| 	// authorized, etc.
 | 	// authorized, etc.
 | ||||||
| 	stats, _ := statsd.NewNoopClient(nil) | 	stats, _ := statsd.NewNoopClient(nil) | ||||||
| 	ra := ra.NewRegistrationAuthorityImpl(fakeClock, wfe.log, stats, cmd.RateLimitConfig{}) | 	ra := ra.NewRegistrationAuthorityImpl(fakeClock, wfe.log, stats, cmd.RateLimitConfig{}) | ||||||
| 	ra.SA = &mocks.MockSA{} | 	ra.SA = &mocks.StorageAuthority{} | ||||||
| 	ra.CA = &MockCA{} | 	ra.CA = &MockCA{} | ||||||
| 	ra.PA = &MockPA{} | 	ra.PA = &MockPA{} | ||||||
| 	wfe.SA = &mocks.MockSA{} | 	wfe.SA = &mocks.StorageAuthority{} | ||||||
| 	wfe.RA = &ra | 	wfe.RA = &ra | ||||||
| 	responseWriter := httptest.NewRecorder() | 	responseWriter := httptest.NewRecorder() | ||||||
| 
 | 
 | ||||||
|  | @ -671,7 +670,7 @@ func TestChallenge(t *testing.T) { | ||||||
| 	wfe := setupWFE(t) | 	wfe := setupWFE(t) | ||||||
| 
 | 
 | ||||||
| 	wfe.RA = &MockRegistrationAuthority{} | 	wfe.RA = &MockRegistrationAuthority{} | ||||||
| 	wfe.SA = &mocks.MockSA{} | 	wfe.SA = &mocks.StorageAuthority{} | ||||||
| 	responseWriter := httptest.NewRecorder() | 	responseWriter := httptest.NewRecorder() | ||||||
| 
 | 
 | ||||||
| 	var key jose.JsonWebKey | 	var key jose.JsonWebKey | ||||||
|  | @ -707,7 +706,7 @@ func TestNewRegistration(t *testing.T) { | ||||||
| 	test.AssertNotError(t, err, "Problem setting up HTTP handlers") | 	test.AssertNotError(t, err, "Problem setting up HTTP handlers") | ||||||
| 
 | 
 | ||||||
| 	wfe.RA = &MockRegistrationAuthority{} | 	wfe.RA = &MockRegistrationAuthority{} | ||||||
| 	wfe.SA = &mocks.MockSA{} | 	wfe.SA = &mocks.StorageAuthority{} | ||||||
| 	wfe.stats, _ = statsd.NewNoopClient() | 	wfe.stats, _ = statsd.NewNoopClient() | ||||||
| 	wfe.SubscriberAgreementURL = agreementURL | 	wfe.SubscriberAgreementURL = agreementURL | ||||||
| 
 | 
 | ||||||
|  | @ -879,7 +878,7 @@ func makeRevokeRequestJSON() ([]byte, error) { | ||||||
| // registration when GetRegistrationByKey is called, and we want to get a
 | // registration when GetRegistrationByKey is called, and we want to get a
 | ||||||
| // NoSuchRegistrationError for tests that pass regCheck = false to verifyPOST.
 | // NoSuchRegistrationError for tests that pass regCheck = false to verifyPOST.
 | ||||||
| type mockSANoSuchRegistration struct { | type mockSANoSuchRegistration struct { | ||||||
| 	mocks.MockSA | 	mocks.StorageAuthority | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (msa mockSANoSuchRegistration) GetRegistrationByKey(jwk jose.JsonWebKey) (core.Registration, error) { | func (msa mockSANoSuchRegistration) GetRegistrationByKey(jwk jose.JsonWebKey) (core.Registration, error) { | ||||||
|  | @ -902,7 +901,7 @@ func TestRevokeCertificateCertKey(t *testing.T) { | ||||||
| 	test.AssertNotError(t, err, "Failed to make revokeRequestJSON") | 	test.AssertNotError(t, err, "Failed to make revokeRequestJSON") | ||||||
| 
 | 
 | ||||||
| 	wfe := setupWFE(t) | 	wfe := setupWFE(t) | ||||||
| 	wfe.SA = &mockSANoSuchRegistration{mocks.MockSA{}} | 	wfe.SA = &mockSANoSuchRegistration{mocks.StorageAuthority{}} | ||||||
| 	responseWriter := httptest.NewRecorder() | 	responseWriter := httptest.NewRecorder() | ||||||
| 
 | 
 | ||||||
| 	nonce, err := wfe.nonceService.Nonce() | 	nonce, err := wfe.nonceService.Nonce() | ||||||
|  | @ -992,7 +991,7 @@ func TestRevokeCertificateAlreadyRevoked(t *testing.T) { | ||||||
| 	wfe := setupWFE(t) | 	wfe := setupWFE(t) | ||||||
| 
 | 
 | ||||||
| 	wfe.RA = &MockRegistrationAuthority{} | 	wfe.RA = &MockRegistrationAuthority{} | ||||||
| 	wfe.SA = &mockSANoSuchRegistration{mocks.MockSA{}} | 	wfe.SA = &mockSANoSuchRegistration{mocks.StorageAuthority{}} | ||||||
| 	wfe.stats, _ = statsd.NewNoopClient() | 	wfe.stats, _ = statsd.NewNoopClient() | ||||||
| 	wfe.SubscriberAgreementURL = agreementURL | 	wfe.SubscriberAgreementURL = agreementURL | ||||||
| 	responseWriter := httptest.NewRecorder() | 	responseWriter := httptest.NewRecorder() | ||||||
|  | @ -1013,7 +1012,7 @@ func TestAuthorization(t *testing.T) { | ||||||
| 	test.AssertNotError(t, err, "Problem setting up HTTP handlers") | 	test.AssertNotError(t, err, "Problem setting up HTTP handlers") | ||||||
| 
 | 
 | ||||||
| 	wfe.RA = &MockRegistrationAuthority{} | 	wfe.RA = &MockRegistrationAuthority{} | ||||||
| 	wfe.SA = &mocks.MockSA{} | 	wfe.SA = &mocks.StorageAuthority{} | ||||||
| 	wfe.stats, _ = statsd.NewNoopClient() | 	wfe.stats, _ = statsd.NewNoopClient() | ||||||
| 	responseWriter := httptest.NewRecorder() | 	responseWriter := httptest.NewRecorder() | ||||||
| 
 | 
 | ||||||
|  | @ -1101,7 +1100,7 @@ func TestRegistration(t *testing.T) { | ||||||
| 	test.AssertNotError(t, err, "Problem setting up HTTP handlers") | 	test.AssertNotError(t, err, "Problem setting up HTTP handlers") | ||||||
| 
 | 
 | ||||||
| 	wfe.RA = &MockRegistrationAuthority{} | 	wfe.RA = &MockRegistrationAuthority{} | ||||||
| 	wfe.SA = &mocks.MockSA{} | 	wfe.SA = &mocks.StorageAuthority{} | ||||||
| 	wfe.stats, _ = statsd.NewNoopClient() | 	wfe.stats, _ = statsd.NewNoopClient() | ||||||
| 	wfe.SubscriberAgreementURL = agreementURL | 	wfe.SubscriberAgreementURL = agreementURL | ||||||
| 	responseWriter := httptest.NewRecorder() | 	responseWriter := httptest.NewRecorder() | ||||||
|  | @ -1192,7 +1191,7 @@ func TestTermsRedirect(t *testing.T) { | ||||||
| 	wfe := setupWFE(t) | 	wfe := setupWFE(t) | ||||||
| 
 | 
 | ||||||
| 	wfe.RA = &MockRegistrationAuthority{} | 	wfe.RA = &MockRegistrationAuthority{} | ||||||
| 	wfe.SA = &mocks.MockSA{} | 	wfe.SA = &mocks.StorageAuthority{} | ||||||
| 	wfe.stats, _ = statsd.NewNoopClient() | 	wfe.stats, _ = statsd.NewNoopClient() | ||||||
| 	wfe.SubscriberAgreementURL = agreementURL | 	wfe.SubscriberAgreementURL = agreementURL | ||||||
| 
 | 
 | ||||||
|  | @ -1228,59 +1227,64 @@ func TestGetCertificate(t *testing.T) { | ||||||
| 	wfe := setupWFE(t) | 	wfe := setupWFE(t) | ||||||
| 	wfe.CertCacheDuration = time.Second * 10 | 	wfe.CertCacheDuration = time.Second * 10 | ||||||
| 	wfe.CertNoCacheExpirationWindow = time.Hour * 24 * 7 | 	wfe.CertNoCacheExpirationWindow = time.Hour * 24 * 7 | ||||||
| 	wfe.SA = &mocks.MockSA{} | 	wfe.SA = &mocks.StorageAuthority{} | ||||||
| 
 | 
 | ||||||
| 	certPemBytes, _ := ioutil.ReadFile("test/178.crt") | 	certPemBytes, _ := ioutil.ReadFile("test/178.crt") | ||||||
| 	certBlock, _ := pem.Decode(certPemBytes) | 	certBlock, _ := pem.Decode(certPemBytes) | ||||||
| 
 | 
 | ||||||
| 	responseWriter := httptest.NewRecorder() | 	responseWriter := httptest.NewRecorder() | ||||||
| 
 | 
 | ||||||
|  | 	mockLog := wfe.log.SyslogWriter.(*mocks.SyslogWriter) | ||||||
|  | 	mockLog.Clear() | ||||||
|  | 
 | ||||||
| 	// Valid serial, cached
 | 	// Valid serial, cached
 | ||||||
| 	path, _ := url.Parse("/acme/cert/0000000000000000000000000000000000b2") | 	req, _ := http.NewRequest("GET", "/acme/cert/0000000000000000000000000000000000b2", nil) | ||||||
| 	wfe.Certificate(responseWriter, &http.Request{ | 	req.RemoteAddr = "192.168.0.1" | ||||||
| 		Method: "GET", | 	wfe.Certificate(responseWriter, req) | ||||||
| 		URL:    path, |  | ||||||
| 	}) |  | ||||||
| 	test.AssertEquals(t, responseWriter.Code, 200) | 	test.AssertEquals(t, responseWriter.Code, 200) | ||||||
| 	test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=10") | 	test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=10") | ||||||
| 	test.AssertEquals(t, responseWriter.Header().Get("Content-Type"), "application/pkix-cert") | 	test.AssertEquals(t, responseWriter.Header().Get("Content-Type"), "application/pkix-cert") | ||||||
| 	test.Assert(t, bytes.Compare(responseWriter.Body.Bytes(), certBlock.Bytes) == 0, "Certificates don't match") | 	test.Assert(t, bytes.Compare(responseWriter.Body.Bytes(), certBlock.Bytes) == 0, "Certificates don't match") | ||||||
| 
 | 
 | ||||||
|  | 	reqlogs := mockLog.GetAllMatching(`Successful request`) | ||||||
|  | 	test.AssertEquals(t, len(reqlogs), 1) | ||||||
|  | 	test.AssertEquals(t, reqlogs[0].Priority, syslog.LOG_INFO) | ||||||
|  | 	test.AssertContains(t, reqlogs[0].Message, `"ClientAddr":"192.168.0.1"`) | ||||||
|  | 
 | ||||||
| 	// Unused serial, no cache
 | 	// Unused serial, no cache
 | ||||||
|  | 	mockLog.Clear() | ||||||
| 	responseWriter = httptest.NewRecorder() | 	responseWriter = httptest.NewRecorder() | ||||||
| 	path, _ = url.Parse("/acme/cert/0000000000000000000000000000000000ff") | 	req, _ = http.NewRequest("GET", "/acme/cert/0000000000000000000000000000000000ff", nil) | ||||||
| 	wfe.Certificate(responseWriter, &http.Request{ | 	req.RemoteAddr = "192.168.0.1" | ||||||
| 		Method: "GET", | 	req.Header.Set("X-Forwarded-For", "192.168.99.99") | ||||||
| 		URL:    path, | 	wfe.Certificate(responseWriter, req) | ||||||
| 	}) | 	test.AssertEquals(t, responseWriter.Code, 404) | ||||||
|  | 	test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache") | ||||||
|  | 	test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`) | ||||||
|  | 
 | ||||||
|  | 	reqlogs = mockLog.GetAllMatching(`Terminated request`) | ||||||
|  | 	test.AssertEquals(t, len(reqlogs), 1) | ||||||
|  | 	test.AssertEquals(t, reqlogs[0].Priority, syslog.LOG_INFO) | ||||||
|  | 	test.AssertContains(t, reqlogs[0].Message, `"ClientAddr":"192.168.99.99,192.168.0.1"`) | ||||||
|  | 
 | ||||||
|  | 	// Invalid serial, no cache
 | ||||||
|  | 	responseWriter = httptest.NewRecorder() | ||||||
|  | 	req, _ = http.NewRequest("GET", "/acme/cert/nothex", nil) | ||||||
|  | 	wfe.Certificate(responseWriter, req) | ||||||
| 	test.AssertEquals(t, responseWriter.Code, 404) | 	test.AssertEquals(t, responseWriter.Code, 404) | ||||||
| 	test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache") | 	test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache") | ||||||
| 	test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`) | 	test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`) | ||||||
| 
 | 
 | ||||||
| 	// Invalid serial, no cache
 | 	// Invalid serial, no cache
 | ||||||
| 	responseWriter = httptest.NewRecorder() | 	responseWriter = httptest.NewRecorder() | ||||||
| 	path, _ = url.Parse("/acme/cert/nothex") | 	req, _ = http.NewRequest("GET", "/acme/cert/00000000000000", nil) | ||||||
| 	wfe.Certificate(responseWriter, &http.Request{ | 	wfe.Certificate(responseWriter, req) | ||||||
| 		Method: "GET", |  | ||||||
| 		URL:    path, |  | ||||||
| 	}) |  | ||||||
| 	test.AssertEquals(t, responseWriter.Code, 404) |  | ||||||
| 	test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache") |  | ||||||
| 	test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`) |  | ||||||
| 
 |  | ||||||
| 	// Invalid serial, no cache
 |  | ||||||
| 	responseWriter = httptest.NewRecorder() |  | ||||||
| 	path, _ = url.Parse("/acme/cert/00000000000000") |  | ||||||
| 	wfe.Certificate(responseWriter, &http.Request{ |  | ||||||
| 		Method: "GET", |  | ||||||
| 		URL:    path, |  | ||||||
| 	}) |  | ||||||
| 	test.AssertEquals(t, responseWriter.Code, 404) | 	test.AssertEquals(t, responseWriter.Code, 404) | ||||||
| 	test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache") | 	test.AssertEquals(t, responseWriter.Header().Get("Cache-Control"), "public, max-age=0, no-cache") | ||||||
| 	test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`) | 	test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Certificate not found"}`) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func assertCsrLogged(t *testing.T, mockLog *mocks.MockSyslogWriter) { | func assertCsrLogged(t *testing.T, mockLog *mocks.SyslogWriter) { | ||||||
| 	matches := mockLog.GetAllMatching("^\\[AUDIT\\] Certificate request JSON=") | 	matches := mockLog.GetAllMatching("^\\[AUDIT\\] Certificate request JSON=") | ||||||
| 	test.Assert(t, len(matches) == 1, | 	test.Assert(t, len(matches) == 1, | ||||||
| 		fmt.Sprintf("Incorrect number of certificate request log entries: %d", | 		fmt.Sprintf("Incorrect number of certificate request log entries: %d", | ||||||
|  | @ -1297,20 +1301,25 @@ func TestLogCsrPem(t *testing.T) { | ||||||
| 	err := json.Unmarshal([]byte(certificateRequestJSON), &certificateRequest) | 	err := json.Unmarshal([]byte(certificateRequestJSON), &certificateRequest) | ||||||
| 	test.AssertNotError(t, err, "Unable to parse certificateRequest") | 	test.AssertNotError(t, err, "Unable to parse certificateRequest") | ||||||
| 
 | 
 | ||||||
| 	mockSA := mocks.MockSA{} | 	mockSA := mocks.StorageAuthority{} | ||||||
| 	reg, err := mockSA.GetRegistration(789) | 	reg, err := mockSA.GetRegistration(789) | ||||||
| 	test.AssertNotError(t, err, "Unable to get registration") | 	test.AssertNotError(t, err, "Unable to get registration") | ||||||
| 
 | 
 | ||||||
| 	remoteAddr := "12.34.98.76" | 	req, err := http.NewRequest("GET", "http://[::1]/", nil) | ||||||
|  | 	test.AssertNotError(t, err, "NewRequest failed") | ||||||
|  | 	req.RemoteAddr = "12.34.98.76" | ||||||
|  | 	req.Header.Set("X-Forwarded-For", "10.0.0.1,172.16.0.1") | ||||||
| 
 | 
 | ||||||
| 	wfe.logCsr(remoteAddr, certificateRequest, reg) | 	mockLog := wfe.log.SyslogWriter.(*mocks.SyslogWriter) | ||||||
|  | 	mockLog.Clear() | ||||||
|  | 
 | ||||||
|  | 	wfe.logCsr(req, certificateRequest, reg) | ||||||
| 
 | 
 | ||||||
| 	mockLog := wfe.log.SyslogWriter.(*mocks.MockSyslogWriter) |  | ||||||
| 	matches := mockLog.GetAllMatching("Certificate request") | 	matches := mockLog.GetAllMatching("Certificate request") | ||||||
| 	test.Assert(t, len(matches) == 1, | 	test.Assert(t, len(matches) == 1, | ||||||
| 		"Incorrect number of certificate request log entries") | 		"Incorrect number of certificate request log entries") | ||||||
| 	test.AssertEquals(t, matches[0].Priority, syslog.LOG_NOTICE) | 	test.AssertEquals(t, matches[0].Priority, syslog.LOG_NOTICE) | ||||||
| 	test.AssertEquals(t, matches[0].Message, `[AUDIT] Certificate request JSON={"RemoteAddr":"12.34.98.76","CsrBase64":"MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAycX3ca+fViOuRWF38mssORISFxbJvspDfhPGRBZDxJ63NIqQzupB+6dp48xkcX7Z/KDaRJStcpJT2S0u33moNT4FHLklQBETLhExDk66cmlz6Xibp3LGZAwhWuec7wJoEwIgY8oq4rxihIyGq7HVIJoq9DqZGrUgfZMDeEJqbphukQOaXGEop7mD+eeu8+z5EVkB1LiJ6Yej6R8MAhVPHzG5fyOu6YVo6vY6QgwjRLfZHNj5XthxgPIEETZlUbiSoI6J19GYHvLURBTy5Ys54lYAPIGfNwcIBAH4gtH9FrYcDY68R22rp4iuxdvkf03ZWiT0F2W1y7/C9B2jayTzvQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAHd6Do9DIZ2hvdt1GwBXYjsqprZidT/DYOMfYcK17KlvdkFT58XrBH88ulLZ72NXEpiFMeTyzfs3XEyGq/Bbe7TBGVYZabUEh+LOskYwhgcOuThVN7tHnH5rhN+gb7cEdysjTb1QL+vOUwYgV75CB6PE5JVYK+cQsMIVvo0Kz4TpNgjJnWzbcH7h0mtvub+fCv92vBPjvYq8gUDLNrok6rbg05tdOJkXsF2G/W+Q6sf2Fvx0bK5JeH4an7P7cXF9VG9nd4sRt5zd+L3IcyvHVKxNhIJXZVH0AOqh/1YrKI9R0QKQiZCEy0xN1okPlcaIVaFhb7IKAHPxTI3r5f72LXY=","Registration":{"id":789,"key":{"kty":"RSA","n":"yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ","e":"AQAB"},"agreement":"http://example.invalid/terms"}}`) | 	test.AssertEquals(t, matches[0].Message, `[AUDIT] Certificate request JSON={"ClientAddr":"10.0.0.1,172.16.0.1,12.34.98.76","CsrBase64":"MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAycX3ca+fViOuRWF38mssORISFxbJvspDfhPGRBZDxJ63NIqQzupB+6dp48xkcX7Z/KDaRJStcpJT2S0u33moNT4FHLklQBETLhExDk66cmlz6Xibp3LGZAwhWuec7wJoEwIgY8oq4rxihIyGq7HVIJoq9DqZGrUgfZMDeEJqbphukQOaXGEop7mD+eeu8+z5EVkB1LiJ6Yej6R8MAhVPHzG5fyOu6YVo6vY6QgwjRLfZHNj5XthxgPIEETZlUbiSoI6J19GYHvLURBTy5Ys54lYAPIGfNwcIBAH4gtH9FrYcDY68R22rp4iuxdvkf03ZWiT0F2W1y7/C9B2jayTzvQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAHd6Do9DIZ2hvdt1GwBXYjsqprZidT/DYOMfYcK17KlvdkFT58XrBH88ulLZ72NXEpiFMeTyzfs3XEyGq/Bbe7TBGVYZabUEh+LOskYwhgcOuThVN7tHnH5rhN+gb7cEdysjTb1QL+vOUwYgV75CB6PE5JVYK+cQsMIVvo0Kz4TpNgjJnWzbcH7h0mtvub+fCv92vBPjvYq8gUDLNrok6rbg05tdOJkXsF2G/W+Q6sf2Fvx0bK5JeH4an7P7cXF9VG9nd4sRt5zd+L3IcyvHVKxNhIJXZVH0AOqh/1YrKI9R0QKQiZCEy0xN1okPlcaIVaFhb7IKAHPxTI3r5f72LXY=","Registration":{"id":789,"key":{"kty":"RSA","n":"yNWVhtYEKJR21y9xsHV-PD_bYwbXSeNuFal46xYxVfRL5mqha7vttvjB_vc7Xg2RvgCxHPCqoxgMPTzHrZT75LjCwIW2K_klBYN8oYvTwwmeSkAz6ut7ZxPv-nZaT5TJhGk0NT2kh_zSpdriEJ_3vW-mqxYbbBmpvHqsa1_zx9fSuHYctAZJWzxzUZXykbWMWQZpEiE0J4ajj51fInEzVn7VxV-mzfMyboQjujPh7aNJxAWSq4oQEJJDgWwSh9leyoJoPpONHxh5nEE5AjE01FkGICSxjpZsF-w8hOTI3XXohUdu29Se26k2B0PolDSuj0GIQU6-W9TdLXSjBb2SpQ","e":"AQAB"},"agreement":"http://example.invalid/terms"}}`) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestLengthRequired(t *testing.T) { | func TestLengthRequired(t *testing.T) { | ||||||
|  | @ -1325,7 +1334,7 @@ func TestLengthRequired(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type mockSADifferentStoredKey struct { | type mockSADifferentStoredKey struct { | ||||||
| 	mocks.MockSA | 	mocks.StorageAuthority | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (sa mockSADifferentStoredKey) GetRegistrationByKey(jwk jose.JsonWebKey) (core.Registration, error) { | func (sa mockSADifferentStoredKey) GetRegistrationByKey(jwk jose.JsonWebKey) (core.Registration, error) { | ||||||
|  | @ -1340,7 +1349,7 @@ func (sa mockSADifferentStoredKey) GetRegistrationByKey(jwk jose.JsonWebKey) (co | ||||||
| 
 | 
 | ||||||
| func TestVerifyPOSTUsesStoredKey(t *testing.T) { | func TestVerifyPOSTUsesStoredKey(t *testing.T) { | ||||||
| 	wfe := setupWFE(t) | 	wfe := setupWFE(t) | ||||||
| 	wfe.SA = &mockSADifferentStoredKey{mocks.MockSA{}} | 	wfe.SA = &mockSADifferentStoredKey{mocks.StorageAuthority{}} | ||||||
| 	// signRequest signs with test1Key, but our special mock returns a
 | 	// signRequest signs with test1Key, but our special mock returns a
 | ||||||
| 	// registration with test2Key
 | 	// registration with test2Key
 | ||||||
| 	_, _, _, err := wfe.verifyPOST(makePostRequest(signRequest(t, `{"resource":"foo"}`, &wfe.nonceService)), true, "foo") | 	_, _, _, err := wfe.verifyPOST(makePostRequest(signRequest(t, `{"resource":"foo"}`, &wfe.nonceService)), true, "foo") | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue