From 4fc3a1146e2b1fccab5a46f987ea29ceea4a7363 Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Mon, 4 May 2015 18:43:18 -0700 Subject: [PATCH] VA tests, WFE tests, plus WFE NewRegistration empty payload fix --- va/validation-authority_test.go | 184 ++++++++++++++++++++++++++++++++ wfe/web-front-end.go | 9 ++ wfe/web-front-end_test.go | 106 ++++++++++++++++++ 3 files changed, 299 insertions(+) diff --git a/va/validation-authority_test.go b/va/validation-authority_test.go index 0df108d16..7f4d2b9e7 100644 --- a/va/validation-authority_test.go +++ b/va/validation-authority_test.go @@ -4,3 +4,187 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. package va + +import ( + "testing" + "net" + "net/http" + "fmt" + "strings" + "math/big" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "crypto/sha256" + "time" + + "github.com/letsencrypt/boulder/core" + "github.com/letsencrypt/boulder/test" + "github.com/letsencrypt/boulder/jose" +) + +func bigIntFromB64(b64 string) *big.Int { + bytes, _ := jose.B64dec(b64) + x := big.NewInt(0) + x.SetBytes(bytes) + return x +} + +func intFromB64(b64 string) int { + return int(bigIntFromB64(b64).Int64()) +} + +var n *big.Int = bigIntFromB64("n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw") +var e int = intFromB64("AQAB") +var d *big.Int = bigIntFromB64("bWUC9B-EFRIo8kpGfh0ZuyGPvMNKvYWNtB_ikiH9k20eT-O1q_I78eiZkpXxXQ0UTEs2LsNRS-8uJbvQ-A1irkwMSMkK1J3XTGgdrhCku9gRldY7sNA_AKZGh-Q661_42rINLRCe8W-nZ34ui_qOfkLnK9QWDDqpaIsA-bMwWWSDFu2MUBYwkHTMEzLYGqOe04noqeq1hExBTHBOBdkMXiuFhUq1BU6l-DqEiWxqg82sXt2h-LMnT3046AOYJoRioz75tSUQfGCshWTBnP5uDjd18kKhyv07lhfSJdrPdM5Plyl21hsFf4L_mHCuoFau7gdsPfHPxxjVOcOpBrQzwQ") +var p *big.Int = bigIntFromB64("uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0s7pFc") +var q *big.Int = bigIntFromB64("uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0s7pFc") + +var TheKey rsa.PrivateKey = rsa.PrivateKey{ + PublicKey: rsa.PublicKey{N: n, E: e}, + D: d, + Primes: []*big.Int{p, q}, +} + +var ident core.AcmeIdentifier = core.AcmeIdentifier{Type: core.IdentifierType("dns"), Value: "localhost"} + +func simpleSrv(token string) { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if strings.HasSuffix(r.URL.Path, "404") { + http.NotFound(w, r) + } + fmt.Fprintf(w, "%s", token) + }) + http.ListenAndServe("localhost:5001", nil) +} + +func dvsniSrv(t *testing.T, R, S []byte, waitChan chan bool) { + RS := append(R, S...) + z := sha256.Sum256(RS) + zName := fmt.Sprintf("%064x.acme.invalid", z) + 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) { + fmt.Println(clientHello) + return cert, nil + }, + NextProtos: []string{"http/1.1"}, + } + + httpsServer := &http.Server{Addr: "localhost:443"} + conn, err := net.Listen("tcp", httpsServer.Addr) + if err != nil { + waitChan <- true + t.Fatalf("wat %s", err) + } + tlsListener := tls.NewListener(conn, tlsConfig) + waitChan <- true + t.Fatalf("%s", httpsServer.Serve(tlsListener)) +} + +func TestSimpleHttps(t *testing.T) { + va := NewValidationAuthorityImpl(true) + + chall := core.Challenge{Path: "test", Token: "THETOKEN"} + + invalidChall := va.validateSimpleHTTPS(ident, chall) + test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) + + go simpleSrv("THETOKEN") + + finChall := va.validateSimpleHTTPS(ident, chall) + test.AssertEquals(t, finChall.Status, core.StatusValid) + + chall.Path = "404" + invalidChall = va.validateSimpleHTTPS(ident, chall) + test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) +} + +func TestDvsni(t *testing.T) { + va := NewValidationAuthorityImpl(false) + + a := []byte{1,2,3,4,5,6,7,8,9,0} + ba := core.B64enc(a) + chall := core.Challenge{Path: "test", R: ba, S: ba} + + invalidChall := va.validateDvsni(ident, chall) + test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) + + waitChan := make(chan bool, 1) + go dvsniSrv(t, a, a, waitChan) + <-waitChan + + finChall := va.validateDvsni(ident, chall) + test.AssertEquals(t, finChall.Status, core.StatusValid) + + chall.R = ba[5:] + invalidChall = va.validateDvsni(ident, chall) + test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) +} + +type MockRegistrationAuthority struct{} + +func (ra *MockRegistrationAuthority) NewRegistration(reg core.Registration, jwk jose.JsonWebKey) (core.Registration, error) { + return reg, nil +} + +func (ra *MockRegistrationAuthority) NewAuthorization(authz core.Authorization, jwk jose.JsonWebKey) (core.Authorization, error) { + return authz, nil +} + +func (ra *MockRegistrationAuthority) NewCertificate(req core.CertificateRequest, jwk jose.JsonWebKey) (core.Certificate, error) { + return core.Certificate{}, nil +} + +func (ra *MockRegistrationAuthority) UpdateRegistration(reg core.Registration, updated core.Registration) (core.Registration, error) { + return reg, nil +} + +func (ra *MockRegistrationAuthority) UpdateAuthorization(authz core.Authorization, foo int, challenge core.Challenge) (core.Authorization, error) { + return authz, nil +} + +func (ra *MockRegistrationAuthority) RevokeCertificate(cert x509.Certificate) error { + return nil +} + +func (ra *MockRegistrationAuthority) OnValidationUpdate(authz core.Authorization) { +} + +func TestValidate(t *testing.T) { + va := NewValidationAuthorityImpl(false) + va.RA = &MockRegistrationAuthority{} + + a := []byte{1,2,3,4,5,6,7,8,9,0} + ba := core.B64enc(a) + Dchall := core.Challenge{Path: "test", R: ba, S: ba} + Schall := core.Challenge{Path: "test", Token: "THETOKEN"} + + authz := core.Authorization{Identifier: ident, Challenges: []core.Challenge{Dchall, Schall}} + + va.validate(authz) +} diff --git a/wfe/web-front-end.go b/wfe/web-front-end.go index 533f68d57..12ba64a16 100644 --- a/wfe/web-front-end.go +++ b/wfe/web-front-end.go @@ -104,6 +104,8 @@ func (wfe *WebFrontEndImpl) Index(response http.ResponseWriter, request *http.Re response.Header().Set("Content-Type", "text/html") } +type emptyJson struct {} + func verifyPOST(request *http.Request) ([]byte, jose.JsonWebKey, error) { zeroKey := jose.JsonWebKey{} @@ -182,6 +184,13 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques return } + // Don't allow an empty payload {} + var empty emptyJson + if err = json.Unmarshal(body, &empty); err == nil { + wfe.sendError(response, "Empty payload", http.StatusBadRequest) + return + } + var init core.Registration err = json.Unmarshal(body, &init) if err != nil { diff --git a/wfe/web-front-end_test.go b/wfe/web-front-end_test.go index 1f7b69cc3..40c538bc7 100644 --- a/wfe/web-front-end_test.go +++ b/wfe/web-front-end_test.go @@ -278,3 +278,109 @@ func TestChallenge(t *testing.T) { t, responseWriter.Body.String(), "{\"type\":\"dns\",\"uri\":\"/acme/authz/asdf?challenge=foo\"}") } + +func TestRegistration(t *testing.T) { + wfe := NewWebFrontEndImpl() + wfe.RA = &MockRegistrationAuthority{} + responseWriter := httptest.NewRecorder() + + var key jose.JsonWebKey + err := json.Unmarshal([]byte(`{ + "e": "AQAB", + "kty": "RSA", + "n": "tSwgy3ORGvc7YJI9B2qqkelZRUC6F1S5NwXFvM4w5-M0TsxbFsH5UH6adigV0jzsDJ5imAechcSoOhAh9POceCbPN1sTNwLpNbOLiQQ7RD5mY_pSUHWXNmS9R4NZ3t2fQAzPeW7jOfF0LKuJRGkekx6tXP1uSnNibgpJULNc4208dgBaCHo3mvaE2HV2GmVl1yxwWX5QZZkGQGjNDZYnjFfa2DKVvFs0QbAk21ROm594kAxlRlMMrvqlf24Eq4ERO0ptzpZgm_3j_e4hGRD39gJS7kAzK-j2cacFQ5Qi2Y6wZI2p-FCq_wiYsfEAIkATPBiLKl_6d_Jfcvs_impcXQ" + }`), &key) + test.AssertNotError(t, err, "Could not unmarshal testing key") + + // GET instead of POST should be rejected + wfe.NewRegistration(responseWriter, &http.Request{ + Method: "GET", + }) + test.AssertEquals(t, responseWriter.Body.String(), "{\"detail\":\"Method not allowed\"}") + + // POST, but no body. + responseWriter.Body.Reset() + wfe.NewRegistration(responseWriter, &http.Request{ + Method: "POST", + }) + test.AssertEquals(t, responseWriter.Body.String(), "{\"detail\":\"Unable to read/verify body: No body on POST\"}") + + // POST, but body that isn't valid JWS + responseWriter.Body.Reset() + wfe.NewRegistration(responseWriter, &http.Request{ + Method: "POST", + Body: makeBody("hi"), + }) + test.AssertEquals(t, responseWriter.Body.String(), "{\"detail\":\"Unable to read/verify body: invalid character 'h' looking for beginning of value\"}") + + // POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON. + responseWriter.Body.Reset() + wfe.NewRegistration(responseWriter, &http.Request{ + Method: "POST", + Body: makeBody(` +{ + "header": { + "alg": "RS256", + "jwk": { + "e": "AQAB", + "kty": "RSA", + "n": "tSwgy3ORGvc7YJI9B2qqkelZRUC6F1S5NwXFvM4w5-M0TsxbFsH5UH6adigV0jzsDJ5imAechcSoOhAh9POceCbPN1sTNwLpNbOLiQQ7RD5mY_pSUHWXNmS9R4NZ3t2fQAzPeW7jOfF0LKuJRGkekx6tXP1uSnNibgpJULNc4208dgBaCHo3mvaE2HV2GmVl1yxwWX5QZZkGQGjNDZYnjFfa2DKVvFs0QbAk21ROm594kAxlRlMMrvqlf24Eq4ERO0ptzpZgm_3j_e4hGRD39gJS7kAzK-j2cacFQ5Qi2Y6wZI2p-FCq_wiYsfEAIkATPBiLKl_6d_Jfcvs_impcXQ" + } + }, + "payload": "Zm9vCg", + "signature": "hRt2eYqBd_MyMRNIh8PEIACoFtmBi7BHTLBaAhpSU6zyDAFdEBaX7us4VB9Vo1afOL03Q8iuoRA0AT4akdV_mQTAQ_jhTcVOAeXPr0tB8b8Q11UPQ0tXJYmU4spAW2SapJIvO50ntUaqU05kZd0qw8-noH1Lja-aNnU-tQII4iYVvlTiRJ5g8_CADsvJqOk6FcHuo2mG643TRnhkAxUtazvHyIHeXMxydMMSrpwUwzMtln4ZJYBNx4QGEq6OhpAD_VSp-w8Lq5HOwGQoNs0bPxH1SGrArt67LFQBfjlVr94E1sn26p4vigXm83nJdNhWAMHHE9iV67xN-r29LT-FjA" +} + `), + }) + test.AssertEquals(t, + responseWriter.Body.String(), + "{\"detail\":\"Error unmarshaling JSON\"}") + + // Same signed body, but payload modified by one byte, breaking signature. + // should fail JWS verification. + responseWriter.Body.Reset() + wfe.NewRegistration(responseWriter, &http.Request{ + Method: "POST", + Body: makeBody(` + { + "header": { + "alg": "RS256", + "jwk": { + "e": "AQAB", + "kty": "RSA", + "n": "vd7rZIoTLEe-z1_8G1FcXSw9CQFEJgV4g9V277sER7yx5Qjz_Pkf2YVth6wwwFJEmzc0hoKY-MMYFNwBE4hQHw" + } + }, + "payload": "xm9vCg", + "signature": "RjUQ679fxJgeAJlxqgvDP_sfGZnJ-1RgWF2qmcbnBWljs6h1qp63pLnJOl13u81bP_bCSjaWkelGG8Ymx_X-aQ" + } + `), + }) + test.AssertEquals(t, + responseWriter.Body.String(), + "{\"detail\":\"Unable to read/verify body: crypto/rsa: verification error\"}") + + // Valid, signed JWS body, payload is '{}' + responseWriter.Body.Reset() + wfe.NewRegistration(responseWriter, &http.Request{ + Method: "POST", + Body: makeBody(` + { + "header": { + "alg": "RS256", + "jwk": { + "e": "AQAB", + "kty": "RSA", + "n": "tSwgy3ORGvc7YJI9B2qqkelZRUC6F1S5NwXFvM4w5-M0TsxbFsH5UH6adigV0jzsDJ5imAechcSoOhAh9POceCbPN1sTNwLpNbOLiQQ7RD5mY_pSUHWXNmS9R4NZ3t2fQAzPeW7jOfF0LKuJRGkekx6tXP1uSnNibgpJULNc4208dgBaCHo3mvaE2HV2GmVl1yxwWX5QZZkGQGjNDZYnjFfa2DKVvFs0QbAk21ROm594kAxlRlMMrvqlf24Eq4ERO0ptzpZgm_3j_e4hGRD39gJS7kAzK-j2cacFQ5Qi2Y6wZI2p-FCq_wiYsfEAIkATPBiLKl_6d_Jfcvs_impcXQ" + } + }, + "payload": "e30K", + "signature": "JXYA_pin91Bc5oz5I6dqCNNWDrBaYTB31EnWorrj4JEFRaidafC9mpLDLLA9jR9kX_Vy2bA5b6pPpXVKm0w146a0L551OdL8JrrLka9q6LypQdDLLQa76XD03hSBOFcC-Oo5FLPa3WRWS1fQ37hYAoLxtS3isWXMIq_4Onx5bq8bwKyu-3E3fRb_lzIZ8hTIWwcblCTOfufUe6AoK4m6MfBjz0NGhyyk4lEZZw6Sttm2VuZo3xmWoRTJEyJG5AOJ6fkNJ9iQQ1kVhMr0ZZ7NVCaOZAnxrwv2sCjY6R3f4HuEVe1yzT75Mq2IuXq-tadGyFujvUxF6BWHCulbEnss7g" + } + `), + }) + test.AssertEquals(t, + responseWriter.Body.String(), + "{\"detail\":\"Empty payload\"}") + +}