Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 0 additions & 66 deletions ra/ra.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/json"
"errors"
"fmt"
"net/url"
"os"
"slices"
"strconv"
Expand Down Expand Up @@ -42,7 +40,6 @@ import (
"github.com/letsencrypt/boulder/issuance"
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/metrics"
"github.com/letsencrypt/boulder/policy"
"github.com/letsencrypt/boulder/probs"
pubpb "github.com/letsencrypt/boulder/publisher/proto"
rapb "github.com/letsencrypt/boulder/ra/proto"
Expand Down Expand Up @@ -515,69 +512,6 @@ func (ra *RegistrationAuthorityImpl) NewRegistration(ctx context.Context, reques
return res, nil
}

// validateContacts checks the provided list of contacts, returning an error if
// any are not acceptable. Unacceptable contacts lists include:
// * An empty list
// * A list has more than maxContactsPerReg contacts
// * A list containing an empty contact
// * A list containing a contact that does not parse as a URL
// * A list containing a contact that has a URL scheme other than mailto
// * A list containing a mailto contact that contains hfields
// * A list containing a contact that has non-ascii characters
// * A list containing a contact that doesn't pass `policy.ValidEmail`
func (ra *RegistrationAuthorityImpl) validateContacts(contacts []string) error {
if len(contacts) == 0 {
return nil // Nothing to validate
}
if ra.maxContactsPerReg > 0 && len(contacts) > ra.maxContactsPerReg {
return berrors.MalformedError(
"too many contacts provided: %d > %d",
len(contacts),
ra.maxContactsPerReg,
)
}

for _, contact := range contacts {
if contact == "" {
return berrors.InvalidEmailError("empty contact")
}
parsed, err := url.Parse(contact)
if err != nil {
return berrors.InvalidEmailError("unparsable contact")
}
if parsed.Scheme != "mailto" {
return berrors.UnsupportedContactError("only contact scheme 'mailto:' is supported")
}
if parsed.RawQuery != "" || contact[len(contact)-1] == '?' {
return berrors.InvalidEmailError("contact email contains a question mark")
}
if parsed.Fragment != "" || contact[len(contact)-1] == '#' {
return berrors.InvalidEmailError("contact email contains a '#'")
}
if !core.IsASCII(contact) {
return berrors.InvalidEmailError("contact email contains non-ASCII characters")
}
err = policy.ValidEmail(parsed.Opaque)
if err != nil {
return err
}
}

// NOTE(@cpu): For historical reasons (</3) we store ACME account contact
// information de-normalized in a fixed size `contact` field on the
// `registrations` table. At the time of writing this field is VARCHAR(191)
// That means the largest marshalled JSON value we can store is 191 bytes.
const maxContactBytes = 191
if jsonBytes, err := json.Marshal(contacts); err != nil {
return fmt.Errorf("failed to marshal reg.Contact to JSON: %w", err)
} else if len(jsonBytes) >= maxContactBytes {
return berrors.InvalidEmailError(
"too many/too long contact(s). Please use shorter or fewer email addresses")
}

return nil
}

// matchesCSR tests the contents of a generated certificate to make sure
// that the PublicKey, CommonName, and identifiers match those provided in
// the CSR that was used to generate the certificate. It also checks the
Expand Down
77 changes: 0 additions & 77 deletions ra/ra_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,83 +390,6 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, sapb.StorageAutho
return dummyVA, sa, ra, rlSource, fc, registration, cleanUp
}

func TestValidateContacts(t *testing.T) {
_, _, ra, _, _, _, cleanUp := initAuthorities(t)
defer cleanUp()

ansible := "ansible:earth.sol.milkyway.laniakea/letsencrypt"
validEmail := "mailto:admin@email.com"
otherValidEmail := "mailto:other-admin@email.com"
malformedEmail := "mailto:admin.com"
nonASCII := "mailto:señor@email.com"
unparsable := "mailto:a@email.com, b@email.com"
forbidden := "mailto:a@example.org"

err := ra.validateContacts([]string{})
test.AssertNotError(t, err, "No Contacts")

err = ra.validateContacts([]string{validEmail, otherValidEmail})
test.AssertError(t, err, "Too Many Contacts")

err = ra.validateContacts([]string{validEmail})
test.AssertNotError(t, err, "Valid Email")

err = ra.validateContacts([]string{malformedEmail})
test.AssertError(t, err, "Malformed Email")

err = ra.validateContacts([]string{ansible})
test.AssertError(t, err, "Unknown scheme")

err = ra.validateContacts([]string{""})
test.AssertError(t, err, "Empty URL")

err = ra.validateContacts([]string{nonASCII})
test.AssertError(t, err, "Non ASCII email")

err = ra.validateContacts([]string{unparsable})
test.AssertError(t, err, "Unparsable email")

err = ra.validateContacts([]string{forbidden})
test.AssertError(t, err, "Forbidden email")

err = ra.validateContacts([]string{"mailto:admin@localhost"})
test.AssertError(t, err, "Forbidden email")

err = ra.validateContacts([]string{"mailto:admin@example.not.a.iana.suffix"})
test.AssertError(t, err, "Forbidden email")

err = ra.validateContacts([]string{"mailto:admin@1.2.3.4"})
test.AssertError(t, err, "Forbidden email")

err = ra.validateContacts([]string{"mailto:admin@[1.2.3.4]"})
test.AssertError(t, err, "Forbidden email")

err = ra.validateContacts([]string{"mailto:admin@a.com?no-reminder-emails"})
test.AssertError(t, err, "No hfields in email")

err = ra.validateContacts([]string{"mailto:example@a.com?"})
test.AssertError(t, err, "No hfields in email")

err = ra.validateContacts([]string{"mailto:example@a.com#"})
test.AssertError(t, err, "No fragment")

err = ra.validateContacts([]string{"mailto:example@a.com#optional"})
test.AssertError(t, err, "No fragment")

// The registrations.contact field is VARCHAR(191). 175 'a' characters plus
// the prefix "mailto:" and the suffix "@a.com" makes exactly 191 bytes of
// encoded JSON. The correct size to hit our maximum DB field length.
var longStringBuf strings.Builder
longStringBuf.WriteString("mailto:")
for range 175 {
longStringBuf.WriteRune('a')
}
longStringBuf.WriteString("@a.com")

err = ra.validateContacts([]string{longStringBuf.String()})
test.AssertError(t, err, "Too long contacts")
}

func TestNewRegistration(t *testing.T) {
_, sa, ra, _, _, _, cleanUp := initAuthorities(t)
defer cleanUp()
Expand Down