-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #88 from local-deploy/DL-T-97
Generating a self-signed CA certificate and installing it
Showing
19 changed files
with
834 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
#!/bin/bash | ||
set -e | ||
|
||
mkdir -p "/etc/dl/config-files" | ||
mkdir -p "/usr/share/zsh/vendor-completions" | ||
install -m 0755 -d /etc/apt/keyrings | ||
install -m 0755 -d /etc/dl/config-files | ||
install -m 0755 -d /usr/share/zsh/vendor-completions |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package command | ||
|
||
import ( | ||
"github.com/pterm/pterm" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
) | ||
|
||
var certCmd = &cobra.Command{ | ||
Use: "cert", | ||
Short: "CA certificate management", | ||
Long: `Generating and (un)installing a root certificate in Firefox and/or Chrome/Chromium browsers.`, | ||
ValidArgs: []string{"install", "i", "uninstall", "delete"}, | ||
} | ||
|
||
func certCommand() *cobra.Command { | ||
certCmd.AddCommand( | ||
installCertCommand(), | ||
uninstallCertCommand(), | ||
) | ||
return certCmd | ||
} | ||
|
||
func storeCertConfig(status bool) { | ||
viper.Set("ca", status) | ||
err := viper.WriteConfig() | ||
if err != nil { | ||
pterm.FgRed.Println(err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package command | ||
|
||
import ( | ||
"context" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/local-deploy/dl/helper" | ||
"github.com/local-deploy/dl/utils/cert" | ||
"github.com/pterm/pterm" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var reinstallCert bool | ||
|
||
func installCertCommand() *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "install", | ||
Aliases: []string{"i"}, | ||
Short: "Installing CA certificate", | ||
Long: `Generating a self-signed CA certificate and installing it in Firefox and/or Chrome/Chromium browsers.`, | ||
Run: func(cmd *cobra.Command, args []string) { | ||
installCertRun() | ||
}, | ||
ValidArgs: []string{"--reinstall", "-r"}, | ||
} | ||
cmd.Flags().BoolVarP(&reinstallCert, "reinstall", "r", false, "Reinstall certificate") | ||
return cmd | ||
} | ||
|
||
func installCertRun() { | ||
certutilPath, err := helper.CertutilPath() | ||
if err != nil { | ||
pterm.FgRed.Printfln("Error: %s", err) | ||
return | ||
} | ||
|
||
err = helper.CreateDirectory(filepath.Join(helper.CertDir(), "conf")) | ||
if err != nil { | ||
pterm.FgRed.Printfln("Error: %s \n", err) | ||
os.Exit(1) | ||
} | ||
|
||
c := &cert.Cert{ | ||
CertutilPath: certutilPath, | ||
CaFileName: cert.CaRootName, | ||
CaFileKeyName: cert.CaRootKeyName, | ||
CaPath: helper.CertDir(), | ||
} | ||
|
||
if reinstallCert { | ||
err = c.LoadCA() | ||
if err != nil { | ||
pterm.FgRed.Printfln("Error: %s", err) | ||
return | ||
} | ||
c.Uninstall() | ||
helper.RemoveFilesInPath(filepath.Join(helper.CertDir(), "conf")) | ||
helper.RemoveFilesInPath(helper.CertDir()) | ||
} | ||
|
||
_, err = os.Stat(filepath.Join(helper.CertDir(), cert.CaRootName)) | ||
if err != nil { | ||
err := c.CreateCA() | ||
if err != nil { | ||
pterm.FgRed.Printfln("Error: %s", err) | ||
return | ||
} | ||
} | ||
|
||
err = c.LoadCA() | ||
if err != nil { | ||
pterm.FgRed.Printfln("Error: %s", err) | ||
return | ||
} | ||
|
||
if c.Check() { | ||
pterm.FgGreen.Println("The local CA is already installed in the browsers trust store!") | ||
} else if c.Install() { | ||
storeCertConfig(true) | ||
pterm.FgGreen.Println("The local CA is now installed in the browsers trust store (requires browser restart)!") | ||
|
||
// Restart traefik | ||
source = "traefik" | ||
ctx := context.Background() | ||
_ = downServiceRun(ctx) | ||
err = upServiceRun(ctx) | ||
if err != nil { | ||
pterm.FgYellow.Println("Restart services for changes to take effect: dl service recreate") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package command | ||
|
||
import ( | ||
"path/filepath" | ||
|
||
"github.com/local-deploy/dl/helper" | ||
"github.com/local-deploy/dl/utils/cert" | ||
"github.com/pterm/pterm" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
) | ||
|
||
func uninstallCertCommand() *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "uninstall", | ||
Short: "Removing CA certificate", | ||
Long: `Removing a self-signed CA certificate from the Firefox and/or Chrome/Chromium browsers.`, | ||
Run: func(cmd *cobra.Command, args []string) { | ||
uninstallCertRun() | ||
}, | ||
} | ||
return cmd | ||
} | ||
|
||
func uninstallCertRun() { | ||
certutilPath, err := helper.CertutilPath() | ||
if err != nil { | ||
pterm.FgRed.Printfln("Error: %s", err) | ||
return | ||
} | ||
|
||
c := &cert.Cert{ | ||
CertutilPath: certutilPath, | ||
CaFileName: cert.CaRootName, | ||
CaFileKeyName: cert.CaRootKeyName, | ||
CaPath: helper.CertDir(), | ||
} | ||
|
||
err = c.LoadCA() | ||
if err != nil { | ||
pterm.FgRed.Printfln("Error: %s", err) | ||
return | ||
} | ||
|
||
ca := viper.GetBool("ca") | ||
if !ca { | ||
pterm.FgYellow.Println("The local CA is not installed") | ||
return | ||
} | ||
|
||
c.Uninstall() | ||
|
||
helper.RemoveFilesInPath(filepath.Join(helper.CertDir(), "conf")) | ||
helper.RemoveFilesInPath(helper.CertDir()) | ||
|
||
storeCertConfig(false) | ||
pterm.FgYellow.Println("The local CA is now uninstalled from the browsers trust store!") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package project | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/local-deploy/dl/helper" | ||
"github.com/local-deploy/dl/utils/cert" | ||
"github.com/pterm/pterm" | ||
"gopkg.in/yaml.v3" | ||
) | ||
|
||
type tlsStruct struct { | ||
TSL certStruct `yaml:"tls"` | ||
} | ||
|
||
type certStruct struct { | ||
Certificates []certFileStruct `yaml:"certificates"` | ||
} | ||
|
||
type certFileStruct struct { | ||
CertFile string `yaml:"certFile"` | ||
KeyFile string `yaml:"keyFile"` | ||
} | ||
|
||
// CreateCert create a certificate and key for the project | ||
func CreateCert() { | ||
certutilPath, err := helper.CertutilPath() | ||
if err != nil { | ||
pterm.FgRed.Printfln("Error: %s", err) | ||
return | ||
} | ||
|
||
c := &cert.Cert{ | ||
CertutilPath: certutilPath, | ||
CaFileName: cert.CaRootName, | ||
CaFileKeyName: cert.CaRootKeyName, | ||
CaPath: helper.CertDir(), | ||
} | ||
|
||
err = c.LoadCA() | ||
if err != nil { | ||
pterm.FgRed.Printfln("Error: %s", err) | ||
return | ||
} | ||
|
||
// ~/.config/dl/certs/site | ||
certDir := filepath.Join(helper.CertDir(), Env.GetString("NETWORK_NAME")) | ||
_ = helper.CreateDirectory(certDir) | ||
|
||
err = c.MakeCert([]string{ | ||
Env.GetString("LOCAL_DOMAIN"), | ||
Env.GetString("NIP_DOMAIN"), | ||
}, Env.GetString("NETWORK_NAME")) | ||
if err != nil { | ||
pterm.FgRed.Printfln("Error: %s", err) | ||
} | ||
|
||
tls := tlsStruct{ | ||
TSL: certStruct{ | ||
Certificates: []certFileStruct{ | ||
{ | ||
CertFile: "/certs/" + Env.GetString("NETWORK_NAME") + "/cert.pem", | ||
KeyFile: "/certs/" + Env.GetString("NETWORK_NAME") + "/key.pem", | ||
}, | ||
}}} | ||
|
||
yamlData, err := yaml.Marshal(&tls) | ||
if err != nil { | ||
fmt.Println(err) | ||
return | ||
} | ||
|
||
// ~/.config/dl/certs/conf/site.localhost.yaml | ||
err = os.WriteFile(filepath.Join(helper.CertDir(), "conf", Env.GetString("NETWORK_NAME")+".yaml"), yamlData, 0600) | ||
if err != nil { | ||
pterm.FgRed.Printfln("failed to create config certificate file: %s", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,267 @@ | ||
package cert | ||
|
||
import ( | ||
"crypto" | ||
"crypto/rand" | ||
"crypto/rsa" | ||
"crypto/sha1" //nolint:gosec | ||
"crypto/x509" | ||
"crypto/x509/pkix" | ||
"encoding/asn1" | ||
"encoding/pem" | ||
"errors" | ||
"fmt" | ||
"math/big" | ||
"net" | ||
"net/url" | ||
"os" | ||
"path/filepath" | ||
"time" | ||
|
||
"github.com/local-deploy/dl/helper" | ||
"github.com/pterm/pterm" | ||
) | ||
|
||
// CaRootName certificate file name | ||
const CaRootName = "rootCA.pem" | ||
|
||
// CaRootKeyName certificate key file name | ||
const CaRootKeyName = "rootCA-key.pem" | ||
|
||
// Cert certificate structure | ||
type Cert struct { | ||
CertutilPath string | ||
CaFileName string | ||
CaFileKeyName string | ||
CaPath string | ||
CaCert *x509.Certificate | ||
CaKey crypto.PrivateKey | ||
|
||
keyFile, certFile string | ||
} | ||
|
||
// LoadCA certificate reading | ||
func (c *Cert) LoadCA() error { | ||
if !pathExists(filepath.Join(c.CaPath, c.CaFileName)) { | ||
return nil | ||
} | ||
|
||
certPEMBlock, err := os.ReadFile(filepath.Join(c.CaPath, c.CaFileName)) | ||
if err != nil { | ||
return fmt.Errorf("failed to read the CA certificate: %w", err) | ||
} | ||
certDERBlock, _ := pem.Decode(certPEMBlock) | ||
if certDERBlock == nil || certDERBlock.Type != "CERTIFICATE" { | ||
return errors.New("failed to read the CA certificate: unexpected content") | ||
} | ||
c.CaCert, err = x509.ParseCertificate(certDERBlock.Bytes) | ||
if err != nil { | ||
return fmt.Errorf("failed to parse the CA certificate: %w", err) | ||
} | ||
|
||
if !pathExists(filepath.Join(c.CaPath, c.CaFileKeyName)) { | ||
return nil | ||
} | ||
|
||
keyPEMBlock, err := os.ReadFile(filepath.Join(c.CaPath, c.CaFileKeyName)) | ||
if err != nil { | ||
return fmt.Errorf("failed to read the CA key: %w", err) | ||
} | ||
keyDERBlock, _ := pem.Decode(keyPEMBlock) | ||
if keyDERBlock == nil || keyDERBlock.Type != "PRIVATE KEY" { | ||
return errors.New("failed to read the CA key: unexpected content") | ||
} | ||
c.CaKey, err = x509.ParsePKCS8PrivateKey(keyDERBlock.Bytes) | ||
if err != nil { | ||
return fmt.Errorf("failed to parse the CA key: %w", err) | ||
} | ||
return nil | ||
} | ||
|
||
// CreateCA creating a root certificate | ||
func (c *Cert) CreateCA() error { | ||
privateKey, err := c.generateKey(true) | ||
if err != nil { | ||
return fmt.Errorf("failed to generate the CA key: %w", err) | ||
} | ||
publicKey := privateKey.(crypto.Signer).Public() | ||
|
||
pkixPublicKey, err := x509.MarshalPKIXPublicKey(publicKey) | ||
if err != nil { | ||
return fmt.Errorf("failed to encode public key: %w", err) | ||
} | ||
|
||
var keyIdentifier struct { | ||
Algorithm pkix.AlgorithmIdentifier | ||
SubjectPublicKey asn1.BitString | ||
} | ||
_, err = asn1.Unmarshal(pkixPublicKey, &keyIdentifier) | ||
if err != nil { | ||
return fmt.Errorf("failed to decode public key: %w", err) | ||
} | ||
|
||
checksum := sha1.Sum(keyIdentifier.SubjectPublicKey.Bytes) //nolint:gosec | ||
|
||
template := &x509.Certificate{ | ||
SerialNumber: randomSerialNumber(), | ||
Subject: pkix.Name{ | ||
Organization: []string{"Local Deploy CA"}, | ||
OrganizationalUnit: []string{"DL Certificate Authority"}, | ||
CommonName: "DL Certificate", | ||
}, | ||
SubjectKeyId: checksum[:], | ||
NotAfter: time.Now().AddDate(10, 0, 0), | ||
NotBefore: time.Now(), | ||
KeyUsage: x509.KeyUsageCertSign, | ||
BasicConstraintsValid: true, | ||
IsCA: true, | ||
MaxPathLenZero: true, | ||
} | ||
|
||
certificate, err := x509.CreateCertificate(rand.Reader, template, template, publicKey, privateKey) | ||
if err != nil { | ||
return fmt.Errorf("failed to generate CA certificate: %w", err) | ||
} | ||
|
||
pkcs8PrivateKey, err := x509.MarshalPKCS8PrivateKey(privateKey) | ||
if err != nil { | ||
return fmt.Errorf("failed to encode CA key: %w", err) | ||
} | ||
err = os.WriteFile(filepath.Join(c.CaPath, c.CaFileKeyName), pem.EncodeToMemory( | ||
&pem.Block{Type: "PRIVATE KEY", Bytes: pkcs8PrivateKey}), 0400) | ||
if err != nil { | ||
return fmt.Errorf("failed to save CA key: %w", err) | ||
} | ||
|
||
err = os.WriteFile(filepath.Join(c.CaPath, c.CaFileName), pem.EncodeToMemory( //nolint:gosec | ||
&pem.Block{Type: "CERTIFICATE", Bytes: certificate}), 0644) | ||
if err != nil { | ||
return fmt.Errorf("failed to save CA certificate: %w", err) | ||
} | ||
return nil | ||
} | ||
|
||
// MakeCert Create certificates for domains | ||
func (c *Cert) MakeCert(hosts []string, path string) error { | ||
if c.CaKey == nil { | ||
return fmt.Errorf("can't create new certificates because the CA key (%s) is missing", c.CaFileKeyName) | ||
} | ||
|
||
privateKey, err := c.generateKey(false) | ||
if err != nil { | ||
return fmt.Errorf("failed to generate certificate key: %w", err) | ||
} | ||
publicKey := privateKey.(crypto.Signer).Public() | ||
expiration := time.Now().AddDate(2, 3, 0) | ||
|
||
dir, _ := os.Getwd() | ||
template := &x509.Certificate{ | ||
SerialNumber: randomSerialNumber(), | ||
Subject: pkix.Name{ | ||
Organization: []string{filepath.Base(dir) + " development certificate"}, | ||
OrganizationalUnit: []string{filepath.Base(dir) + " Certificate"}, | ||
}, | ||
|
||
NotBefore: time.Now(), NotAfter: expiration, | ||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, | ||
} | ||
|
||
for _, h := range hosts { | ||
if ip := net.ParseIP(h); ip != nil { | ||
template.IPAddresses = append(template.IPAddresses, ip) | ||
} else if uriName, err := url.Parse(h); err == nil && uriName.Scheme != "" && uriName.Host != "" { | ||
template.URIs = append(template.URIs, uriName) | ||
} else { | ||
template.DNSNames = append(template.DNSNames, h) | ||
} | ||
} | ||
|
||
if len(template.IPAddresses) > 0 || len(template.DNSNames) > 0 || len(template.URIs) > 0 { | ||
template.ExtKeyUsage = append(template.ExtKeyUsage, x509.ExtKeyUsageServerAuth) | ||
} | ||
|
||
cert, err := x509.CreateCertificate(rand.Reader, template, c.CaCert, publicKey, c.CaKey) | ||
if err != nil { | ||
return fmt.Errorf("failed to generate certificate: %w", err) | ||
} | ||
|
||
certFile, keyFile := c.fileNames() | ||
|
||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert}) | ||
pkcs8PrivateKey, err := x509.MarshalPKCS8PrivateKey(privateKey) | ||
if err != nil { | ||
return fmt.Errorf("failed to encode certificate key: %w", err) | ||
} | ||
privatePEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: pkcs8PrivateKey}) | ||
|
||
if certFile == keyFile { | ||
err = os.WriteFile(filepath.Join(helper.CertDir(), path, keyFile), append(certPEM, privatePEM...), 0600) | ||
if err != nil { | ||
return fmt.Errorf("failed to save certificate and key: %w", err) | ||
} | ||
} else { | ||
err = os.WriteFile(filepath.Join(helper.CertDir(), path, certFile), certPEM, 0644) //nolint:gosec | ||
if err != nil { | ||
return fmt.Errorf("failed to save certificate: %w", err) | ||
} | ||
err = os.WriteFile(filepath.Join(helper.CertDir(), path, keyFile), privatePEM, 0600) | ||
if err != nil { | ||
return fmt.Errorf("failed to save certificate key: %w", err) | ||
} | ||
} | ||
|
||
// TODO: add debug | ||
|
||
// if certFile == keyFile { | ||
// log.Printf("\nThe certificate and key are at \"%s\"\n\n", certFile) | ||
// } else { | ||
// log.Printf("\nThe certificate is at \"%s\" and the key at \"%s\"\n\n", certFile, keyFile) | ||
// } | ||
// | ||
// log.Printf("It will expire on %s\n\n", expiration.Format("2 January 2006")) | ||
return nil | ||
} | ||
|
||
func (c *Cert) fileNames() (certFile, keyFile string) { | ||
certFile = "./cert.pem" | ||
if c.certFile != "" { | ||
certFile = c.certFile | ||
} | ||
keyFile = "./key.pem" | ||
if c.keyFile != "" { | ||
keyFile = c.keyFile | ||
} | ||
|
||
return | ||
} | ||
|
||
func (c *Cert) generateKey(rootCA bool) (crypto.PrivateKey, error) { | ||
if rootCA { | ||
return rsa.GenerateKey(rand.Reader, 3072) | ||
} | ||
return rsa.GenerateKey(rand.Reader, 2048) | ||
} | ||
|
||
func (c *Cert) verifyCert() bool { | ||
_, err := c.CaCert.Verify(x509.VerifyOptions{}) | ||
return err == nil | ||
} | ||
|
||
func (c *Cert) caUniqueName() string { | ||
return "DL development CA " + c.CaCert.SerialNumber.String() | ||
} | ||
|
||
func randomSerialNumber() *big.Int { | ||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) | ||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) | ||
if err != nil { | ||
pterm.FgRed.Printfln("failed to generate serial number: %s", err) | ||
os.Exit(1) | ||
} | ||
return serialNumber | ||
} | ||
|
||
func pathExists(path string) bool { | ||
_, err := os.Stat(path) | ||
return err == nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package cert | ||
|
||
import ( | ||
"bytes" | ||
"os" | ||
"os/exec" | ||
"os/user" | ||
"path/filepath" | ||
"sync" | ||
|
||
"github.com/local-deploy/dl/helper" | ||
"github.com/pterm/pterm" | ||
) | ||
|
||
var ( | ||
nssDBs = []string{ | ||
filepath.Join(os.Getenv("HOME"), ".pki/nssdb"), | ||
filepath.Join(os.Getenv("HOME"), "snap/chromium/current/.pki/nssdb"), // Snapcraft | ||
"/etc/pki/nssdb", // CentOS 7 | ||
} | ||
firefoxProfiles = []string{ | ||
os.Getenv("HOME") + "/.mozilla/firefox/*", | ||
os.Getenv("HOME") + "/snap/firefox/common/.mozilla/firefox/*", | ||
} | ||
firefoxPaths = []string{ | ||
"/usr/bin/firefox", | ||
"/usr/bin/firefox-nightly", | ||
"/usr/bin/firefox-developer-edition", | ||
"/snap/firefox", | ||
"/Applications/Firefox.app", | ||
"/Applications/FirefoxDeveloperEdition.app", | ||
"/Applications/Firefox Developer Edition.app", | ||
"/Applications/Firefox Nightly.app", | ||
} | ||
) | ||
|
||
func hasBrowser() bool { | ||
allPaths := append(append([]string{}, nssDBs...), firefoxPaths...) | ||
for _, path := range allPaths { | ||
if pathExists(path) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// Check if the certificate is installed | ||
func (c *Cert) Check() bool { | ||
success := true | ||
if c.forEachProfile(func(profile string) { | ||
err := exec.Command(c.CertutilPath, "-V", "-d", profile, "-u", "L", "-n", c.caUniqueName()).Run() //nolint:gosec | ||
if err != nil { | ||
success = false | ||
} | ||
}) == 0 { | ||
success = false | ||
} | ||
return success | ||
} | ||
|
||
// Install certificate installation | ||
func (c *Cert) Install() bool { | ||
if c.forEachProfile(func(profile string) { | ||
cmd := exec.Command(c.CertutilPath, "-A", "-d", profile, "-t", "C,,", "-n", c.caUniqueName(), "-i", filepath.Join(c.CaPath, c.CaFileName)) //nolint:gosec | ||
out, err := execCertutil(cmd) | ||
if err != nil { | ||
pterm.FgRed.Printfln("Error: failed to execute \"%s\": %s\n\n%s\n", "certutil -A -d "+profile, err, out) | ||
os.Exit(1) | ||
} | ||
}) == 0 { | ||
pterm.FgRed.Println("Error: no browsers security databases found") | ||
return false | ||
} | ||
if !c.Check() { | ||
pterm.FgRed.Println("Installing in browsers failed. Please report the issue with details about your environment at https://github.com/local-deploy/dl/issues/new") | ||
pterm.FgYellow.Println("Note that if you never started browsers, you need to do that at least once.") | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
// Uninstall deleting certificate | ||
func (c *Cert) Uninstall() { | ||
c.forEachProfile(func(profile string) { | ||
err := exec.Command(c.CertutilPath, "-V", "-d", profile, "-u", "L", "-n", c.caUniqueName()).Run() //nolint:gosec | ||
if err != nil { | ||
return | ||
} | ||
cmd := exec.Command(c.CertutilPath, "-D", "-d", profile, "-n", c.caUniqueName()) //nolint:gosec | ||
out, err := execCertutil(cmd) | ||
if err != nil { | ||
pterm.FgRed.Printfln("Error: failed to execute \"%s\": %s\n\n%s\n", "certutil -D -d "+profile, err, out) | ||
} | ||
}) | ||
} | ||
|
||
func execCertutil(cmd *exec.Cmd) ([]byte, error) { | ||
out, err := cmd.CombinedOutput() | ||
if err != nil && bytes.Contains(out, []byte("SEC_ERROR_READ_ONLY")) { | ||
origArgs := cmd.Args[1:] | ||
cmd = commandWithSudo(cmd.Path) | ||
cmd.Args = append(cmd.Args, origArgs...) | ||
out, err = cmd.CombinedOutput() | ||
} | ||
return out, err | ||
} | ||
|
||
func (c *Cert) forEachProfile(f func(profile string)) (found int) { | ||
var profiles []string | ||
profiles = append(profiles, nssDBs...) | ||
for _, ff := range firefoxProfiles { | ||
pp, _ := filepath.Glob(ff) | ||
profiles = append(profiles, pp...) | ||
} | ||
for _, profile := range profiles { | ||
if stat, err := os.Stat(profile); err != nil || !stat.IsDir() { | ||
continue | ||
} | ||
if pathExists(filepath.Join(profile, "cert9.db")) { | ||
f("sql:" + profile) | ||
found++ | ||
} else if pathExists(filepath.Join(profile, "cert8.db")) { | ||
f("dbm:" + profile) | ||
found++ | ||
} | ||
} | ||
return | ||
} | ||
|
||
var sudoWarningOnce sync.Once | ||
|
||
func commandWithSudo(cmd ...string) *exec.Cmd { | ||
u, err := user.Current() | ||
if err == nil && u.Uid == "0" { | ||
return exec.Command(cmd[0], cmd[1:]...) //nolint:gosec | ||
} | ||
if !helper.BinaryExists("sudo") { | ||
sudoWarningOnce.Do(func() { | ||
pterm.FgRed.Println(`Warning: "sudo" is not available, and dl is not running as root. The (un)install operation might fail.️`) | ||
}) | ||
return exec.Command(cmd[0], cmd[1:]...) //nolint:gosec | ||
} | ||
|
||
userName := "user" | ||
if u != nil && len(u.Username) > 0 { | ||
userName = u.Username | ||
} | ||
|
||
return exec.Command("sudo", append([]string{"--prompt=[sudo] password for " + userName + ":", "--"}, cmd...)...) //nolint:gosec | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters