Changes to core, sa, policy

This commit is contained in:
Richard Barnes 2015-09-28 10:51:55 -04:00
parent c44962d077
commit c1c3d1e871
6 changed files with 256 additions and 63 deletions

View File

@ -29,9 +29,34 @@ func newChallenge(challengeType string, accountKey *jose.JsonWebKey) (Challenge,
}, nil
}
//----- BEGIN TO DELETE -----
// SimpleHTTPChallenge constructs a random HTTP challenge
func SimpleHTTPChallenge(accountKey *jose.JsonWebKey) (Challenge, error) {
chall, err := newChallenge(ChallengeTypeSimpleHTTP, accountKey)
tls := true
return Challenge{
Type: ChallengeTypeSimpleHTTP,
Status: StatusPending,
Token: NewToken(),
TLS: &tls,
AccountKey: accountKey,
}, nil
}
// DvsniChallenge constructs a random DVSNI challenge
func DvsniChallenge(accountKey *jose.JsonWebKey) (Challenge, error) {
return Challenge{
Type: ChallengeTypeDVSNI,
Status: StatusPending,
Token: NewToken(),
AccountKey: accountKey,
}, nil
}
//----- END TO DELETE -----
// HTTPChallenge constructs a random http-00 challenge
func HTTPChallenge_00(accountKey *jose.JsonWebKey) (Challenge, error) {
chall, err := newChallenge(ChallengeTypeHTTP_00, accountKey)
if err != nil {
return Challenge{}, err
}
@ -41,12 +66,12 @@ func SimpleHTTPChallenge(accountKey *jose.JsonWebKey) (Challenge, error) {
return chall, nil
}
// DvsniChallenge constructs a random DVSNI challenge
func DvsniChallenge(accountKey *jose.JsonWebKey) (Challenge, error) {
return newChallenge(ChallengeTypeDVSNI, accountKey)
// DvsniChallenge constructs a random tls-sni-00 challenge
func TLSSNIChallenge_00(accountKey *jose.JsonWebKey) (Challenge, error) {
return newChallenge(ChallengeTypeTLSSNI_00, accountKey)
}
// DNSChallenge constructs a random DNS challenge
func DNSChallenge(accountKey *jose.JsonWebKey) (Challenge, error) {
return newChallenge(ChallengeTypeDNS, accountKey)
func DNSChallenge_00(accountKey *jose.JsonWebKey) (Challenge, error) {
return newChallenge(ChallengeTypeDNS_00, accountKey)
}

View File

@ -90,7 +90,9 @@ const (
const (
ChallengeTypeSimpleHTTP = "simpleHttp"
ChallengeTypeDVSNI = "dvsni"
ChallengeTypeDNS = "dns"
ChallengeTypeHTTP_00 = "http-00"
ChallengeTypeTLSSNI_00 = "tls-sni-00"
ChallengeTypeDNS_00 = "dns-00"
)
// The suffix appended to pseudo-domain names in DVSNI challenges
@ -235,13 +237,16 @@ type Challenge struct {
// A URI to which a response can be POSTed
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"`
// Used by simpleHTTP challenges
// Used by simpleHttp challenges
TLS *bool `json:"tls,omitempty"`
// Used by simpleHTTP, dns, and dvsni challenges
// Used by dvsni challenges
Validation *jose.JsonWebSignature `json:"validation,omitempty"`
// Used by http-00, tls-sni-00, and dns-00 challenges
AuthorizedKey JSONBuffer `json:"authorizedKey,omitempty"`
// Contains information about URLs used or redirected to and IPs resolved and
@ -282,14 +287,18 @@ func (ch Challenge) RecordsSane() bool {
}
switch ch.Type {
case ChallengeTypeSimpleHTTP:
case ChallengeTypeSimpleHTTP: // TO DELETE
fallthrough // TO DELETE
case ChallengeTypeHTTP_00:
for _, rec := range ch.ValidationRecord {
if rec.URL == "" || rec.Hostname == "" || rec.Port == "" || rec.AddressUsed == nil ||
len(rec.AddressesResolved) == 0 {
return false
}
}
case ChallengeTypeDVSNI:
case ChallengeTypeDVSNI: // TO DELETE
fallthrough // TO DELETE
case ChallengeTypeTLSSNI_00:
if len(ch.ValidationRecord) > 1 {
return false
}
@ -300,16 +309,112 @@ func (ch Challenge) RecordsSane() bool {
ch.ValidationRecord[0].AddressUsed == nil || len(ch.ValidationRecord[0].AddressesResolved) == 0 {
return false
}
case ChallengeTypeDNS:
case ChallengeTypeDNS_00:
// Nothing for now
}
return true
}
//----- BEGIN TO DELETE -----
// IsLegacy returns true if the challenge is of a legacy type (i.e., one defined
// before draft-ietf-acme-acme-00)
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.
func (ch Challenge) LegacyIsSane(completed bool) bool {
if !ch.IsLegacy() {
return false
}
if ch.Status != StatusPending {
return false
}
if ch.AccountKey == nil {
return false
}
switch ch.Type {
case ChallengeTypeSimpleHTTP:
// check extra fields aren't used
if ch.Validation != nil {
return false
}
if completed && ch.TLS == nil {
return false
}
// check token is present, corrent length, and contains b64 encoded string
if ch.Token == "" || len(ch.Token) != 43 {
return false
}
if _, err := B64dec(ch.Token); err != nil {
return false
}
case ChallengeTypeDVSNI:
// check extra fields aren't used
if ch.TLS != nil {
return false
}
// check token is present, corrent length, and contains b64 encoded string
if ch.Token == "" || len(ch.Token) != 43 {
return false
}
if _, err := B64dec(ch.Token); err != nil {
return false
}
// If completed, check that there's a validation object
if completed && ch.Validation == nil {
return false
}
default:
return false
}
return true
}
// 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 '.'
func (ch Challenge) LegacyMergeResponse(resp Challenge) Challenge {
switch ch.Type {
case ChallengeTypeSimpleHTTP:
// For simpleHttp, only "tls" is client-provided
// If "tls" is not provided, default to "true"
if resp.TLS != nil {
ch.TLS = resp.TLS
} else {
ch.TLS = new(bool)
*ch.TLS = true
}
case ChallengeTypeDVSNI:
// For dvsni and dns, only "validation" is client-provided
if resp.Validation != nil {
ch.Validation = resp.Validation
}
}
return ch
}
//----- END TO DELETE -----
// 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 {
if ch.IsLegacy() { // TO DELETE
return ch.LegacyIsSane(completed) // TO DELETE
} // TO DELETE
if ch.Status != StatusPending {
return false
}
@ -347,51 +452,24 @@ func (ch Challenge) IsSane(completed bool) bool {
}
}
// TODO(rlb): Remove this whole switch once the TLS option is gone.
switch ch.Type {
case ChallengeTypeSimpleHTTP:
if completed && ch.TLS == nil {
return false
}
case ChallengeTypeDVSNI:
// Same as DNS
fallthrough
case ChallengeTypeDNS:
// check extra fields aren't used
if ch.TLS != nil {
return false
}
default:
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 {
switch ch.Type {
case ChallengeTypeSimpleHTTP:
// For simpleHttp, only "tls" is client-provided
// If "tls" is not provided, default to "true"
if resp.TLS != nil {
ch.TLS = resp.TLS
} else {
ch.TLS = new(bool)
*ch.TLS = true
}
if ch.IsLegacy() { // TO DELETE
return ch.LegacyMergeResponse(resp) // TO DELETE
} // TO DELETE
// For dvsni and dns (as well as simpleHttp), the client echoes back
// the "token" value provided in the authorizedKey object. The caller
// should use IsSane to verify that the "token" field in the challenge
// matches the corresponding field in the authorized key.
// The only client-provided field is the token, and all current challenge types
// use it.
switch ch.Type {
case ChallengeTypeHTTP_00:
fallthrough
case ChallengeTypeDVSNI:
case ChallengeTypeTLSSNI_00:
fallthrough
case ChallengeTypeDNS:
case ChallengeTypeDNS_00:
ch.Token = resp.Token
}

View File

@ -91,6 +91,71 @@ func TestRecordSanityCheck(t *testing.T) {
test.Assert(t, !chall.RecordsSane(), "Record should not be sane")
}
//-----BEGIN TO DELETE-----
func TestChallengeSanityCheck_Legacy(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")
types := []string{ChallengeTypeSimpleHTTP, ChallengeTypeDVSNI}
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 = ""
test.Assert(t, !chall.IsSane(false), "IsSane should be false")
chall.Token = "notlongenough"
test.Assert(t, !chall.IsSane(false), "IsSane should be false")
chall.Token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+o!"
test.Assert(t, !chall.IsSane(false), "IsSane should be false")
chall.Token = "KQqLsiS5j0CONR_eUXTUSUDNVaHODtc-0pD6ACif7U4"
test.Assert(t, chall.IsSane(false), "IsSane should be true")
// Post-completion tests differ by type
if challengeType == ChallengeTypeSimpleHTTP {
tls := true
chall.TLS = &tls
chall.ValidationRecord = []ValidationRecord{ValidationRecord{
URL: "",
Hostname: "localhost",
Port: "80",
AddressesResolved: []net.IP{net.IP{127, 0, 0, 1}},
AddressUsed: net.IP{127, 0, 0, 1},
}}
test.Assert(t, chall.IsSane(true), "IsSane should be true")
} else if challengeType == ChallengeTypeDVSNI {
chall.Validation = new(jose.JsonWebSignature)
if challengeType == ChallengeTypeDVSNI {
chall.ValidationRecord = []ValidationRecord{ValidationRecord{
Hostname: "localhost",
Port: "80",
AddressesResolved: []net.IP{net.IP{127, 0, 0, 1}},
AddressUsed: net.IP{127, 0, 0, 1},
}}
} else {
chall.ValidationRecord = []ValidationRecord{}
}
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")
}
//-----END TO DELETE-----
func TestChallengeSanityCheck(t *testing.T) {
// Make a temporary account key
var accountKey *jose.JsonWebKey
@ -108,7 +173,7 @@ func TestChallengeSanityCheck(t *testing.T) {
jsonAK, err := json.Marshal(ak)
test.AssertNotError(t, err, "Error marshaling authorized key")
types := []string{ChallengeTypeSimpleHTTP, ChallengeTypeDVSNI, ChallengeTypeDNS}
types := []string{ChallengeTypeHTTP_00, ChallengeTypeTLSSNI_00, ChallengeTypeDNS_00}
for _, challengeType := range types {
chall := Challenge{
Type: challengeType,
@ -123,15 +188,8 @@ func TestChallengeSanityCheck(t *testing.T) {
chall.AuthorizedKey = jsonAK
test.Assert(t, chall.IsSane(false), "IsSane should be true")
// Post-completion tests differ by type
chall.Token = ak.Token
if challengeType == ChallengeTypeSimpleHTTP {
tls := true
chall.TLS = &tls
test.Assert(t, chall.IsSane(true), "IsSane should be true")
} else if challengeType == ChallengeTypeDVSNI || challengeType == ChallengeTypeDNS {
test.Assert(t, chall.IsSane(true), "IsSane should be true")
}
test.Assert(t, chall.IsSane(true), "IsSane should be true")
}
chall := Challenge{Type: "bogus", Status: StatusPending}

View File

@ -178,6 +178,7 @@ func (pa PolicyAuthorityImpl) WillingToIssue(id core.AcmeIdentifier) error {
//
// Note: Current implementation is static, but future versions may not be.
func (pa PolicyAuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier, accountKey *jose.JsonWebKey) (challenges []core.Challenge, combinations [][]int, err error) {
//-----BEGIN TO DELETE-----
simpleHTTP, err := core.SimpleHTTPChallenge(accountKey)
if err != nil {
return
@ -187,8 +188,19 @@ func (pa PolicyAuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier, acco
if err != nil {
return
}
//-----END TO DELETE-----
challenges = []core.Challenge{simpleHTTP, dvsni}
combinations = [][]int{[]int{0}, []int{1}}
http00, err := core.HTTPChallenge_00(accountKey)
if err != nil {
return
}
tlssni00, err := core.TLSSNIChallenge_00(accountKey)
if err != nil {
return
}
challenges = []core.Challenge{simpleHTTP, dvsni, http00, tlssni00} // TO UPDATE
combinations = [][]int{[]int{0}, []int{1}, []int{2}, []int{3}} // TO UPDATE
return
}

View File

@ -186,11 +186,18 @@ func TestChallengesFor(t *testing.T) {
t.Errorf("Error generating challenges: %v", err)
}
if len(challenges) != 2 || challenges[0].Type != core.ChallengeTypeSimpleHTTP ||
challenges[1].Type != core.ChallengeTypeDVSNI {
//-----BEGIN TO UPDATE-----
if len(challenges) != 4 ||
challenges[0].Type != core.ChallengeTypeSimpleHTTP ||
challenges[1].Type != core.ChallengeTypeDVSNI ||
challenges[2].Type != core.ChallengeTypeHTTP_00 ||
challenges[3].Type != core.ChallengeTypeTLSSNI_00 {
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")
}
//-----END TO UPDATE-----
}

View File

@ -95,6 +95,12 @@ func challengeToModel(c *core.Challenge, authID string) (*challModel, error) {
Token: c.Token,
TLS: c.TLS,
}
if c.Validation != nil {
cm.Validation = []byte(c.Validation.FullSerialize())
if len(cm.Validation) > mediumBlobSize {
return nil, fmt.Errorf("Validation object is too large to store in the database")
}
}
if c.AuthorizedKey != nil {
cm.AuthorizedKey = []byte(c.AuthorizedKey)
if len(cm.AuthorizedKey) > mediumBlobSize {
@ -144,6 +150,13 @@ func modelToChallenge(cm *challModel) (core.Challenge, error) {
TLS: cm.TLS,
AuthorizedKey: core.JSONBuffer(cm.AuthorizedKey),
}
if len(cm.Validation) > 0 {
val, err := jose.ParseSigned(string(cm.Validation))
if err != nil {
return core.Challenge{}, err
}
c.Validation = val
}
if len(cm.Error) > 0 {
var problem core.ProblemDetails
err := json.Unmarshal(cm.Error, &problem)