VA tests, WFE tests, plus WFE NewRegistration empty payload fix

This commit is contained in:
Roland Shoemaker 2015-05-04 18:43:18 -07:00
parent 957fff4f48
commit 4fc3a1146e
3 changed files with 299 additions and 0 deletions

View File

@ -4,3 +4,187 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
package va 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)
}

View File

@ -104,6 +104,8 @@ func (wfe *WebFrontEndImpl) Index(response http.ResponseWriter, request *http.Re
response.Header().Set("Content-Type", "text/html") response.Header().Set("Content-Type", "text/html")
} }
type emptyJson struct {}
func verifyPOST(request *http.Request) ([]byte, jose.JsonWebKey, error) { func verifyPOST(request *http.Request) ([]byte, jose.JsonWebKey, error) {
zeroKey := jose.JsonWebKey{} zeroKey := jose.JsonWebKey{}
@ -182,6 +184,13 @@ func (wfe *WebFrontEndImpl) NewRegistration(response http.ResponseWriter, reques
return 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 var init core.Registration
err = json.Unmarshal(body, &init) err = json.Unmarshal(body, &init)
if err != nil { if err != nil {

View File

@ -278,3 +278,109 @@ func TestChallenge(t *testing.T) {
t, responseWriter.Body.String(), t, responseWriter.Body.String(),
"{\"type\":\"dns\",\"uri\":\"/acme/authz/asdf?challenge=foo\"}") "{\"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\"}")
}