// Using the given client and interactor (or interaction.Auto if nil), register // the client account if it does not already exist. // // The interactor is used to prompt for terms of service agreement, if // agreement has not already been obtained. An e. mail address is prompted for. func AssistedUpsertRegistration(cl *acmeapi.Client, interactor interaction.Interactor, ctx context.Context) error { interactor = defaultInteraction(interactor) email := "" for { err := cl.AgreeRegistration(ctx) if err != nil { if e, ok := err.(*acmeapi.AgreementError); ok { res, err := interactor.Prompt(&interaction.Challenge{ Title: "Terms of Service Agreement Required", YesLabel: "I Agree", NoLabel: "Cancel", ResponseType: interaction.RTYesNo, UniqueID: "acme-agreement:" + e.URI, Prompt: "Do you agree to the Terms of Service?", Body: fmt.Sprintf(`You must agree to the terms of service at the following URL to continue: %s Do you agree to the terms of service set out in the above document?`, e.URI), }) if err != nil { return err } if !res.Cancelled { if email == "" { email, err = getEmail(interactor) if err != nil { return err } if email == "-" { return fmt.Errorf("e. mail input cancelled") } } if cl.AccountInfo.AgreementURIs == nil { cl.AccountInfo.AgreementURIs = map[string]struct{}{} } cl.AccountInfo.AgreementURIs[e.URI] = struct{}{} if email != "" { cl.AccountInfo.ContactURIs = []string{"mailto:" + email} } continue } } } return err } }
func (r *reconcile) revokeCertificateInner(c *storage.Certificate) error { if len(c.Certificates) == 0 { return fmt.Errorf("no certificates in certificate to revoke: %v", c) } endCertificate := c.Certificates[0] crt, err := x509.ParseCertificate(endCertificate) if err != nil { return err } // Get the endpoint which issued the certificate. endpoint, err := acmeendpoints.CertificateToEndpoint(r.getGenericClient(), crt, context.TODO()) if err != nil { return fmt.Errorf("could not map certificate %v to endpoint: %v", c, err) } // In order to revoke a certificate, one needs either the private key of the // certificate, or the account key with authorizations for all names on the // certificate. Try and find the private key first. var client *acmeapi.Client var revocationKey crypto.PrivateKey if c.Key != nil { revocationKey = c.Key.PrivateKey client = r.getClientForDirectoryURL(endpoint.DirectoryURL) } if revocationKey == nil { acct, err := r.getAccountByDirectoryURL(endpoint.DirectoryURL) if err != nil { return err } client = r.getClientForAccount(acct) // If we have no private key for the certificate, obtain all necessary // authorizations. err = r.getRevocationAuthorizations(acct, crt) if err != nil { return err } } return client.Revoke(endCertificate, revocationKey, context.TODO()) }
// The Let's Encrypt state directory format keeps certificates but not their // URLs. Since boulder uses the serial number to form the URL, we can // reconstruct the URL. But since not even the provider association is stored, // we have to guess. func determineLECertificateURL(certFilename string) (string, error) { b, err := ioutil.ReadFile(certFilename) if err != nil { return "", err } certs, err := acmeutils.LoadCertificates(b) if err != nil { return "", err } if len(certs) == 0 { return "", fmt.Errorf("no certs") } c, err := x509.ParseCertificate(certs[0]) if err != nil { return "", err } sn := fmt.Sprintf("%036x", c.SerialNumber) for u := range knownProviderURLs { certURL, err := convertBoulderProviderURLToCertificateURL(u, sn) if err != nil { continue } cl := acmeapi.Client{ DirectoryURL: u, } crt := acmeapi.Certificate{ URI: certURL, } err = cl.LoadCertificate(&crt, context.TODO()) if err != nil { continue } return certURL, nil } return "", fmt.Errorf("cannot find certificate URL for %#v (serial %#v)", certFilename, sn) }
// Given a certificate, tries to determine the certificate URL and definite endpoint. func CertificateToEndpointURL(cl *acmeapi.Client, cert *x509.Certificate, ctx context.Context) (*Endpoint, string, error) { es, certain, err := CertificateToEndpoints(cert) if err != nil { return nil, "", err } for _, e := range es { if e.certificateURLTemplate == nil { continue } var b bytes.Buffer err = e.certificateURLTemplate.Execute(&b, map[string]interface{}{ "Certificate": cert, }) if err != nil { return nil, "", err } u := b.String() if !certain { // Check that this is the right endpoint via an HTTP request. acrt := acmeapi.Certificate{ URI: u, } err := cl.LoadCertificate(&acrt, ctx) if err != nil { continue } // check that the certificate DER matches if !bytes.Equal(acrt.Certificate, cert.Raw) { continue } } return e, u, nil } return nil, "", ErrNotFound }
// Completes a given challenge, polling it until it is complete. Can be // cancelled using ctx. // // dnsName is the hostname which is being authorized. webPaths and priorKeyFunc // are passed to responders. // // The return value indicates whether the whole authorization has been invalidated // (set to "failed" status) as a result of an error. In this case a new authorization // must be created. func CompleteChallenge(c *acmeapi.Client, ch *acmeapi.Challenge, dnsName string, ccfg responder.ChallengeConfig, ctx context.Context) (invalidated bool, err error) { log.Debugf("attempting challenge type %s", ch.Type) var certs [][]byte for _, c := range ch.Certs { certs = append(certs, c) } r, err := responder.New(responder.Config{ Type: ch.Type, Token: ch.Token, AccountKey: c.AccountKey, Hostname: dnsName, AcceptableCertificates: certs, ChallengeConfig: ccfg, }) if err != nil { log.Debuge(err, "challenge instantiation failed") return false, err } err = r.Start() if err != nil { log.Debuge(err, "challenge start failed") return false, err } defer r.Stop() err = c.RespondToChallenge(ch, r.Validation(), r.ValidationSigningKey(), ctx) if err != nil { return false /* ??? */, err } b := denet.Backoff{ InitialDelay: 5 * time.Second, MaxDelay: 30 * time.Second, } for { log.Debug("waiting to poll challenge") select { case <-ctx.Done(): return true, ctx.Err() case <-r.RequestDetectedChan(): log.Debug("request detected") case <-time.After(b.NextDelay()): } log.Debug("querying challenge status") err := c.WaitLoadChallenge(ch, ctx) if err != nil { return false, err } if ch.Status.Final() { log.Debug("challenge now in final state") break } } if ch.Status != "valid" { return true, fmt.Errorf("challenge failed with status %#v", ch.Status) } return false, nil }
func (s *Store) downloadCertificate(c *Certificate) error { log.Debugf("downloading certificate %v", c) col := s.db.Collection("certs/" + c.ID()) if col == nil { return fmt.Errorf("cannot get collection") } cl := acmeapi.Client{} crt := acmeapi.Certificate{ URI: c.URL, } err := cl.WaitForCertificate(&crt, context.TODO()) if err != nil { return err } if len(crt.Certificate) == 0 { return fmt.Errorf("nil certificate?") } fcert, err := col.Create("cert") if err != nil { return err } defer fcert.CloseAbort() fchain, err := col.Create("chain") if err != nil { return err } defer fchain.CloseAbort() ffullchain, err := col.Create("fullchain") if err != nil { return err } defer ffullchain.CloseAbort() err = pem.Encode(io.MultiWriter(fcert, ffullchain), &pem.Block{ Type: "CERTIFICATE", Bytes: crt.Certificate, }) if err != nil { return err } for _, ec := range crt.ExtraCertificates { err = pem.Encode(io.MultiWriter(fchain, ffullchain), &pem.Block{ Type: "CERTIFICATE", Bytes: ec, }) if err != nil { return err } } fcert.Close() fchain.Close() ffullchain.Close() c.Certificates = nil c.Certificates = append(c.Certificates, crt.Certificate) c.Certificates = append(c.Certificates, crt.ExtraCertificates...) c.Cached = true return nil }