func setupTLS(ws *webserver.Server, config *serverinit.Config, listen string) { cert, key := config.OptionalString("TLSCertFile", ""), config.OptionalString("TLSKeyFile", "") if !config.OptionalBool("https", true) { return } if (cert != "") != (key != "") { exitf("TLSCertFile and TLSKeyFile must both be either present or absent") } defCert := osutil.DefaultTLSCert() defKey := osutil.DefaultTLSKey() if cert == defCert && key == defKey { _, err1 := os.Stat(cert) _, err2 := os.Stat(key) if err1 != nil || err2 != nil { if os.IsNotExist(err1) || os.IsNotExist(err2) { if err := genSelfTLS(listen); err != nil { exitf("Could not generate self-signed TLS cert: %q", err) } } else { exitf("Could not stat cert or key: %q, %q", err1, err2) } } } if cert == "" && key == "" { err := genSelfTLS(listen) if err != nil { exitf("Could not generate self signed creds: %q", err) } cert = defCert key = defKey } data, err := ioutil.ReadFile(cert) if err != nil { exitf("Failed to read pem certificate: %s", err) } block, _ := pem.Decode(data) if block == nil { exitf("Failed to decode pem certificate") } certif, err := x509.ParseCertificate(block.Bytes) if err != nil { exitf("Failed to parse certificate: %v", err) } sig := misc.SHA256Prefix(certif.Raw) log.Printf("TLS enabled, with SHA-256 certificate fingerprint: %v", sig) ws.SetTLS(cert, key) }
// DialFunc returns the adequate dial function, depending on // whether SSL is required, the client's config has some trusted // certs, and we're on android. // If the client's config has some trusted certs, the server's // certificate will be checked against those in the config after // the TLS handshake. func (c *Client) DialFunc() func(network, addr string) (net.Conn, error) { trustedCerts := c.getTrustedCerts() if !c.useTLS() || (!c.InsecureTLS && len(trustedCerts) == 0) { // No TLS, or TLS with normal/full verification if android.OnAndroid() { return func(network, addr string) (net.Conn, error) { return android.Dial(network, addr) } } return nil } return func(network, addr string) (net.Conn, error) { var conn *tls.Conn var err error if android.OnAndroid() { con, err := android.Dial(network, addr) if err != nil { return nil, err } conn = tls.Client(con, &tls.Config{InsecureSkipVerify: true}) if err = conn.Handshake(); err != nil { return nil, err } } else { conn, err = tls.Dial(network, addr, &tls.Config{InsecureSkipVerify: true}) if err != nil { return nil, err } } if c.InsecureTLS { return conn, nil } certs := conn.ConnectionState().PeerCertificates if certs == nil || len(certs) < 1 { return nil, errors.New("Could not get server's certificate from the TLS connection.") } sig := misc.SHA256Prefix(certs[0].Raw) for _, v := range trustedCerts { if v == sig { return conn, nil } } return nil, fmt.Errorf("Server's certificate %v is not in the trusted list", sig) } }
// 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 genSelfTLS(listen string) error { priv, err := rsa.GenerateKey(rand.Reader, 1024) if err != nil { return fmt.Errorf("failed to generate private key: %s", err) } now := time.Now() hostname, _, err := net.SplitHostPort(listen) if err != nil { return fmt.Errorf("splitting listen failed: %q", err) } // TODO(mpl): if no host is specified in the listening address // (e.g ":3179") we'll end up in this case, and the self-signed // will have "localhost" as a CommonName. But I don't think // there's anything we can do about it. Maybe warn... if hostname == "" { hostname = "localhost" } template := x509.Certificate{ SerialNumber: new(big.Int).SetInt64(0), Subject: pkix.Name{ CommonName: hostname, Organization: []string{hostname}, }, NotBefore: now.Add(-5 * time.Minute).UTC(), NotAfter: now.AddDate(1, 0, 0).UTC(), SubjectKeyId: []byte{1, 2, 3, 4}, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, IsCA: true, BasicConstraintsValid: true, } derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) if err != nil { return fmt.Errorf("Failed to create certificate: %s", err) } defCert := osutil.DefaultTLSCert() defKey := osutil.DefaultTLSKey() certOut, err := os.Create(defCert) if err != nil { return fmt.Errorf("failed to open %s for writing: %s", defCert, err) } pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) certOut.Close() log.Printf("written %s\n", defCert) cert, err := x509.ParseCertificate(derBytes) if err != nil { return fmt.Errorf("Failed to parse certificate: %v", err) } sig := misc.SHA256Prefix(cert.Raw) hint := "You must add this certificate's fingerprint to your client's trusted certs list to use it. Like so:\n" + `"trustedCerts": ["` + sig + `"],` log.Printf(hint) keyOut, err := os.OpenFile(defKey, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return fmt.Errorf("failed to open %s for writing:", defKey, err) } pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) keyOut.Close() log.Printf("written %s\n", defKey) return nil }