// 1) We do not want to force the user to buy a cert. // 2) We still want our client (camput) to be able to // verify the cert's authenticity. // 3) We want to avoid MITM attacks and warnings in // the browser. // Using a simple self-signed won't do because of 3), // as Chrome offers no way to set a self-signed as // trusted when importing it. (same on android). // We could have created a self-signed CA (that we // would import in the browsers) and create another // cert (signed by that CA) which would be the one // used in camlistore. // We're doing even simpler: create a self-signed // CA and directly use it as a self-signed cert // (and install it as a CA in the browsers). // 2) is satisfied by doing our own checks, // See pkg/client func setupTLS(ws *webserver.Server, config *serverinit.Config, hostname string) { cert, key := config.OptionalString("httpsCert", ""), config.OptionalString("httpsKey", "") if !config.OptionalBool("https", true) { return } if (cert != "") != (key != "") { exitf("httpsCert and httpsKey must both be either present or absent") } defCert := osutil.DefaultTLSCert() defKey := osutil.DefaultTLSKey() const hint = "You must add this certificate's fingerprint to your client's trusted certs list to use it. Like so:\n\"trustedCerts\": [\"%s\"]," if cert == defCert && key == defKey { _, err1 := wkfs.Stat(cert) _, err2 := wkfs.Stat(key) if err1 != nil || err2 != nil { if os.IsNotExist(err1) || os.IsNotExist(err2) { sig, err := httputil.GenSelfTLSFiles(hostname, defCert, defKey) if err != nil { exitf("Could not generate self-signed TLS cert: %q", err) } log.Printf(hint, sig) } else { exitf("Could not stat cert or key: %q, %q", err1, err2) } } } // Always generate new certificates if the config's httpsCert and httpsKey are empty. if cert == "" && key == "" { sig, err := httputil.GenSelfTLSFiles(hostname, defCert, defKey) if err != nil { exitf("Could not generate self signed creds: %q", err) } log.Printf(hint, sig) cert = defCert key = defKey } data, err := wkfs.ReadFile(cert) if err != nil { exitf("Failed to read pem certificate: %s", err) } sig, err := httputil.CertFingerprint(data) if err != nil { exitf("certificate error: %v", err) } log.Printf("TLS enabled, with SHA-256 certificate fingerprint: %v", sig) ws.SetTLS(cert, key) }
// serverKeyId returns the public gpg key id ("identity" field) // from the user's server config , if any. // It returns the empty string otherwise. func serverKeyId() string { serverConfigFile := osutil.UserServerConfigPath() if _, err := wkfs.Stat(serverConfigFile); err != nil { if os.IsNotExist(err) { return "" } log.Fatalf("Could not stat %v: %v", serverConfigFile, err) } obj, err := jsonconfig.ReadFile(serverConfigFile) if err != nil { return "" } keyId, ok := obj["identity"].(string) if !ok { return "" } return keyId }
// loadConfig returns the server's parsed config file, locating it using the provided arg. // // The arg may be of the form: // - empty, to mean automatic (will write a default high-level config if // no cloud config is available) // - a filepath absolute or relative to the user's configuration directory, // - a URL func loadConfig(arg string) (conf *serverinit.Config, isNewConfig bool, err error) { if strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://") { contents, err := slurpURL(arg, 256<<10) if err != nil { return nil, false, err } conf, err = serverinit.Load(contents) return conf, false, err } var absPath string switch { case arg == "": absPath = osutil.UserServerConfigPath() _, err = wkfs.Stat(absPath) if err != nil { if !os.IsNotExist(err) { return } conf, err = serverinit.DefaultEnvConfig() if err != nil || conf != nil { return } err = wkfs.MkdirAll(osutil.CamliConfigDir(), 0700) if err != nil { return } log.Printf("Generating template config file %s", absPath) if err = serverinit.WriteDefaultConfigFile(absPath, sqlite.CompiledIn()); err == nil { isNewConfig = true } } case filepath.IsAbs(arg): absPath = arg default: absPath = filepath.Join(osutil.CamliConfigDir(), arg) } conf, err = serverinit.LoadFile(absPath) return }
func getOrMakeKeyring() (keyID, secRing string, err error) { secRing = osutil.SecretRingFile() _, err = wkfs.Stat(secRing) switch { case err == nil: keyID, err = jsonsign.KeyIdFromRing(secRing) if err != nil { err = fmt.Errorf("Could not find any keyID in file %q: %v", secRing, err) return } log.Printf("Re-using identity with keyID %q found in file %s", keyID, secRing) case os.IsNotExist(err): keyID, err = jsonsign.GenerateNewSecRing(secRing) if err != nil { err = fmt.Errorf("Could not generate new secRing at file %q: %v", secRing, err) return } log.Printf("Generated new identity with keyID %q in file %s", keyID, secRing) default: err = fmt.Errorf("Could not stat secret ring %q: %v", secRing, err) } return }
// lazy config parsing when there's a known client already. // The client c may be nil. func (c *Client) parseConfig() { if android.OnAndroid() { panic("parseConfig should never have been called on Android") } if configDisabled { panic("parseConfig should never have been called with CAMLI_DISABLE_CLIENT_CONFIG_FILE set") } configPath := osutil.UserClientConfigPath() if _, err := wkfs.Stat(configPath); os.IsNotExist(err) { if c != nil && c.isSharePrefix { return } errMsg := fmt.Sprintf("Client configuration file %v does not exist. See 'camput init' to generate it.", configPath) if keyId := serverKeyId(); keyId != "" { hint := fmt.Sprintf("\nThe key id %v was found in the server config %v, so you might want:\n'camput init -gpgkey %v'", keyId, osutil.UserServerConfigPath(), keyId) errMsg += hint } log.Fatal(errMsg) } // TODO: instead of using jsonconfig, we could read the file, and unmarshall into the structs that we now have in pkg/types/clientconfig. But we'll have to add the old fields (before the name changes, and before the multi-servers change) to the structs as well for our gracefull conversion/error messages to work. conf, err := osutil.NewJSONConfigParser().ReadFile(configPath) if err != nil { log.Fatal(err.Error()) } cfg := jsonconfig.Obj(conf) if singleServerAuth := cfg.OptionalString("auth", ""); singleServerAuth != "" { newConf, err := convertToMultiServers(cfg) if err != nil { log.Print(err) } else { cfg = newConf } } config = &clientconfig.Config{ Identity: cfg.OptionalString("identity", ""), IdentitySecretRing: cfg.OptionalString("identitySecretRing", ""), IgnoredFiles: cfg.OptionalList("ignoredFiles"), } serversList := make(map[string]*clientconfig.Server) servers := cfg.OptionalObject("servers") for alias, vei := range servers { // An alias should never be confused with a host name, // so we forbid anything looking like one. if isURLOrHostPort(alias) { log.Fatalf("Server alias %q looks like a hostname; \".\" or \";\" are not allowed.", alias) } serverMap, ok := vei.(map[string]interface{}) if !ok { log.Fatalf("entry %q in servers section is a %T, want an object", alias, vei) } serverConf := jsonconfig.Obj(serverMap) server := &clientconfig.Server{ Server: cleanServer(serverConf.OptionalString("server", "")), Auth: serverConf.OptionalString("auth", ""), IsDefault: serverConf.OptionalBool("default", false), TrustedCerts: serverConf.OptionalList("trustedCerts"), } if err := serverConf.Validate(); err != nil { log.Fatalf("Error in servers section of config file for server %q: %v", alias, err) } serversList[alias] = server } config.Servers = serversList if err := cfg.Validate(); err != nil { printConfigChangeHelp(cfg) log.Fatalf("Error in config file: %v", err) } }
// If cert/key files are specified, and found, use them. // If cert/key files are specified, not found, and the default values, generate // them (self-signed CA used as a cert), and use them. // If cert/key files are not specified, use Let's Encrypt. func setupTLS(ws *webserver.Server, config *serverinit.Config, hostname string) { cert, key := config.OptionalString("httpsCert", ""), config.OptionalString("httpsKey", "") if !config.OptionalBool("https", true) { return } if (cert != "") != (key != "") { exitf("httpsCert and httpsKey must both be either present or absent") } defCert := osutil.DefaultTLSCert() defKey := osutil.DefaultTLSKey() const hint = "You must add this certificate's fingerprint to your client's trusted certs list to use it. Like so:\n\"trustedCerts\": [\"%s\"]," if cert == defCert && key == defKey { _, err1 := wkfs.Stat(cert) _, err2 := wkfs.Stat(key) if err1 != nil || err2 != nil { if os.IsNotExist(err1) || os.IsNotExist(err2) { sig, err := httputil.GenSelfTLSFiles(hostname, defCert, defKey) if err != nil { exitf("Could not generate self-signed TLS cert: %q", err) } log.Printf(hint, sig) } else { exitf("Could not stat cert or key: %q, %q", err1, err2) } } } if cert == "" && key == "" { // Use Let's Encrypt if no files are specified, and we have a usable hostname. if netutil.IsFQDN(hostname) { m := autocert.Manager{ Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist(hostname), Cache: autocert.DirCache(osutil.DefaultLetsEncryptCache()), } log.Print("TLS enabled, with Let's Encrypt") ws.SetTLS(webserver.TLSSetup{ CertManager: m.GetCertificate, }) return } // Otherwise generate new certificates sig, err := httputil.GenSelfTLSFiles(hostname, defCert, defKey) if err != nil { exitf("Could not generate self signed creds: %q", err) } log.Printf(hint, sig) cert = defCert key = defKey } data, err := wkfs.ReadFile(cert) if err != nil { exitf("Failed to read pem certificate: %s", err) } sig, err := httputil.CertFingerprint(data) if err != nil { exitf("certificate error: %v", err) } log.Printf("TLS enabled, with SHA-256 certificate fingerprint: %v", sig) ws.SetTLS(webserver.TLSSetup{ CertFile: cert, KeyFile: key, }) }