├── .github └── FUNDING.yml ├── LICENSE ├── README.md ├── ca.go ├── cert.go ├── cli └── mad │ ├── .gitignore │ ├── build.sh │ └── main.go ├── go.mod ├── go.sum ├── install.go ├── install_darwin.go └── install_windows.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: txthinking 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015-present Cloud https://www.txthinking.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mad 2 | 3 | Generate root CA and derivative certificate for any domains and any IPs 4 | 5 | ❤️ A project by [txthinking.com](https://www.txthinking.com) 6 | 7 | ### Install via [Nami](https://github.com/txthinking/nami) 8 | 9 | nami install mad 10 | 11 | ### Usage 12 | 13 | ``` 14 | NAME: 15 | Mad - Generate root CA and derivative certificate for any domains and any IPs 16 | 17 | USAGE: 18 | mad [global options] command [command options] [arguments...] 19 | 20 | VERSION: 21 | 20210401 22 | 23 | AUTHOR: 24 | Cloud 25 | 26 | COMMANDS: 27 | ca Generate CA 28 | cert Generate certificate 29 | install Install ROOT CA 30 | help, h Shows a list of commands or help for one command 31 | 32 | GLOBAL OPTIONS: 33 | --help, -h show help (default: false) 34 | --version, -v print the version (default: false) 35 | ``` 36 | 37 | ### Example 38 | 39 | Generate root CA 40 | 41 | ``` 42 | mad ca --ca ./ca.pem --key ./ca_key.pem 43 | ``` 44 | 45 | Generate cert for `localhost` 46 | 47 | ``` 48 | mad cert --ca ./ca.pem --ca_key ./ca_key.pem --cert ./localhost_cert.pem --key ./localhost_cert_key.pem --domain localhost 49 | ``` 50 | 51 | ## License 52 | 53 | Licensed under The MIT License 54 | -------------------------------------------------------------------------------- /ca.go: -------------------------------------------------------------------------------- 1 | package mad 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/sha1" 7 | "crypto/x509" 8 | "crypto/x509/pkix" 9 | "encoding/asn1" 10 | "encoding/pem" 11 | "math/big" 12 | "os" 13 | "time" 14 | ) 15 | 16 | type Ca struct { 17 | C *x509.Certificate 18 | CaPEM []byte 19 | KeyPEM []byte 20 | } 21 | 22 | func NewCa(Organization, OrganizationalUnit, CommonName string, start, end time.Time) *Ca { 23 | var l []string 24 | if Organization != "" { 25 | l = []string{Organization} 26 | } 27 | var l1 []string 28 | if OrganizationalUnit != "" { 29 | l1 = []string{OrganizationalUnit} 30 | } 31 | c := &x509.Certificate{ 32 | Subject: pkix.Name{ 33 | Organization: l, 34 | OrganizationalUnit: l1, 35 | CommonName: CommonName, 36 | }, 37 | NotBefore: start, 38 | NotAfter: end, 39 | IsCA: true, 40 | KeyUsage: x509.KeyUsageCertSign, 41 | BasicConstraintsValid: true, 42 | MaxPathLenZero: true, 43 | } 44 | return &Ca{ 45 | C: c, 46 | } 47 | } 48 | 49 | func (c *Ca) Create() error { 50 | p, err := rsa.GenerateKey(rand.Reader, 4096) 51 | if err != nil { 52 | return err 53 | } 54 | pub := p.Public() 55 | 56 | b, err := x509.MarshalPKIXPublicKey(pub) 57 | if err != nil { 58 | return err 59 | } 60 | var spki struct { 61 | Algorithm pkix.AlgorithmIdentifier 62 | SubjectPublicKey asn1.BitString 63 | } 64 | if _, err := asn1.Unmarshal(b, &spki); err != nil { 65 | return err 66 | } 67 | skid := sha1.Sum(spki.SubjectPublicKey.Bytes) 68 | c.C.SubjectKeyId = skid[:] 69 | 70 | sn, err := rand.Int(rand.Reader, big.NewInt(0).Lsh(big.NewInt(1), 128)) 71 | if err != nil { 72 | return err 73 | } 74 | c.C.SerialNumber = sn 75 | 76 | b, err = x509.CreateCertificate(rand.Reader, c.C, c.C, pub, p) 77 | if err != nil { 78 | return err 79 | } 80 | c.CaPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: b}) 81 | // c.KeyPEM = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(p)}) 82 | b, err = x509.MarshalPKCS8PrivateKey(p) 83 | if err != nil { 84 | return err 85 | } 86 | c.KeyPEM = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: b}) 87 | return nil 88 | } 89 | 90 | func (c *Ca) Ca() []byte { 91 | return c.CaPEM 92 | } 93 | 94 | func (c *Ca) Key() []byte { 95 | return c.KeyPEM 96 | } 97 | 98 | func (c *Ca) SaveToFile(ca, key string) error { 99 | f, err := os.Create(ca) 100 | if err != nil { 101 | return err 102 | } 103 | if _, err := f.Write(c.CaPEM); err != nil { 104 | return err 105 | } 106 | if err := f.Close(); err != nil { 107 | return err 108 | } 109 | f, err = os.Create(key) 110 | if err != nil { 111 | return err 112 | } 113 | if _, err := f.Write(c.KeyPEM); err != nil { 114 | return err 115 | } 116 | if err := f.Close(); err != nil { 117 | return err 118 | } 119 | return nil 120 | } 121 | -------------------------------------------------------------------------------- /cert.go: -------------------------------------------------------------------------------- 1 | package mad 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/sha1" 7 | "crypto/tls" 8 | "crypto/x509" 9 | "crypto/x509/pkix" 10 | "encoding/asn1" 11 | "encoding/pem" 12 | "math/big" 13 | "net" 14 | "os" 15 | "time" 16 | ) 17 | 18 | type Cert struct { 19 | CaPEM []byte 20 | CaKeyPEM []byte 21 | C *x509.Certificate 22 | CertPEM []byte 23 | KeyPEM []byte 24 | } 25 | 26 | func NewCert(caPEM, caKeyPEM []byte, Organization, OrganizationalUnit string, start, end time.Time) *Cert { 27 | var l []string 28 | if Organization != "" { 29 | l = []string{Organization} 30 | } 31 | var l1 []string 32 | if OrganizationalUnit != "" { 33 | l1 = []string{OrganizationalUnit} 34 | } 35 | c := &x509.Certificate{ 36 | Subject: pkix.Name{ 37 | Organization: l, 38 | OrganizationalUnit: l1, 39 | }, 40 | NotBefore: start, 41 | NotAfter: end, 42 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 43 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 44 | BasicConstraintsValid: true, 45 | } 46 | return &Cert{ 47 | CaPEM: caPEM, 48 | CaKeyPEM: caKeyPEM, 49 | C: c, 50 | } 51 | } 52 | 53 | func (c *Cert) SetIPAddresses(ips []net.IP) { 54 | c.C.IPAddresses = ips 55 | c.C.Subject.CommonName = ips[0].String() 56 | } 57 | 58 | func (c *Cert) SetDNSNames(domains []string) { 59 | c.C.DNSNames = domains 60 | c.C.Subject.CommonName = domains[0] 61 | } 62 | 63 | func (c *Cert) SetCommonName(commonName string) { 64 | c.C.Subject.CommonName = commonName 65 | } 66 | 67 | func (c *Cert) Create() error { 68 | tc, err := tls.X509KeyPair(c.CaPEM, c.CaKeyPEM) 69 | if err != nil { 70 | return err 71 | } 72 | ca, err := x509.ParseCertificate(tc.Certificate[0]) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | p, err := rsa.GenerateKey(rand.Reader, 4096) 78 | if err != nil { 79 | return err 80 | } 81 | pub := p.Public() 82 | 83 | b, err := x509.MarshalPKIXPublicKey(pub) 84 | if err != nil { 85 | return err 86 | } 87 | var spki struct { 88 | Algorithm pkix.AlgorithmIdentifier 89 | SubjectPublicKey asn1.BitString 90 | } 91 | if _, err := asn1.Unmarshal(b, &spki); err != nil { 92 | return err 93 | } 94 | skid := sha1.Sum(spki.SubjectPublicKey.Bytes) 95 | c.C.SubjectKeyId = skid[:] 96 | 97 | sn, err := rand.Int(rand.Reader, big.NewInt(0).Lsh(big.NewInt(1), 128)) 98 | if err != nil { 99 | return err 100 | } 101 | c.C.SerialNumber = sn 102 | 103 | b, err = x509.CreateCertificate(rand.Reader, c.C, ca, pub, tc.PrivateKey) 104 | if err != nil { 105 | return err 106 | } 107 | c.CertPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: b}) 108 | // c.KeyPEM = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(p)}) 109 | b, err = x509.MarshalPKCS8PrivateKey(p) 110 | if err != nil { 111 | return err 112 | } 113 | c.KeyPEM = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: b}) 114 | return nil 115 | } 116 | 117 | func (c *Cert) Cert() []byte { 118 | return c.CertPEM 119 | } 120 | 121 | func (c *Cert) Key() []byte { 122 | return c.KeyPEM 123 | } 124 | 125 | func (c *Cert) SaveToFile(cert, key string) error { 126 | f, err := os.Create(cert) 127 | if err != nil { 128 | return err 129 | } 130 | if _, err := f.Write(c.CertPEM); err != nil { 131 | return err 132 | } 133 | if err := f.Close(); err != nil { 134 | return err 135 | } 136 | f, err = os.Create(key) 137 | if err != nil { 138 | return err 139 | } 140 | if _, err := f.Write(c.KeyPEM); err != nil { 141 | return err 142 | } 143 | if err := f.Close(); err != nil { 144 | return err 145 | } 146 | return nil 147 | } 148 | -------------------------------------------------------------------------------- /cli/mad/.gitignore: -------------------------------------------------------------------------------- 1 | mad 2 | -------------------------------------------------------------------------------- /cli/mad/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# -ne 1 ]; then 4 | echo "./build.sh version" 5 | exit 6 | fi 7 | 8 | mkdir _ 9 | 10 | CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -trimpath -ldflags="-w -s" -o _/mad_darwin_arm64 11 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -trimpath -ldflags="-w -s" -o _/mad_darwin_amd64 12 | CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -trimpath -ldflags="-w -s" -o _/mad_freebsd_386 13 | CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -trimpath -ldflags="-w -s" -o _/mad_freebsd_amd64 14 | CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -trimpath -ldflags="-w -s" -o _/mad_linux_386 15 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-w -s" -o _/mad_linux_amd64 16 | CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath -ldflags="-w -s" -o _/mad_linux_arm64 17 | CGO_ENABLED=0 GOOS=netbsd GOARCH=386 go build -trimpath -ldflags="-w -s" -o _/mad_netbsd_386 18 | CGO_ENABLED=0 GOOS=netbsd GOARCH=amd64 go build -trimpath -ldflags="-w -s" -o _/mad_netbsd_amd64 19 | CGO_ENABLED=0 GOOS=openbsd GOARCH=386 go build -trimpath -ldflags="-w -s" -o _/mad_openbsd_386 20 | CGO_ENABLED=0 GOOS=openbsd GOARCH=amd64 go build -trimpath -ldflags="-w -s" -o _/mad_openbsd_amd64 21 | CGO_ENABLED=0 GOOS=openbsd GOARCH=arm64 go build -trimpath -ldflags="-w -s" -o _/mad_openbsd_arm64 22 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags="-w -s" -o _/mad_windows_amd64.exe 23 | CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -trimpath -ldflags="-w -s" -o _/mad_windows_386.exe 24 | 25 | nami release github.com/txthinking/mad $1 _ 26 | 27 | rm -rf _ 28 | -------------------------------------------------------------------------------- /cli/mad/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "net" 7 | "os" 8 | "time" 9 | 10 | "github.com/txthinking/mad" 11 | "github.com/urfave/cli/v2" 12 | ) 13 | 14 | func main() { 15 | app := cli.NewApp() 16 | app.Name = "mad" 17 | app.Version = "20240923" 18 | app.Usage = "Generate root CA and derivative certificate for any domains and any IPs" 19 | app.Authors = []*cli.Author{ 20 | { 21 | Name: "Cloud", 22 | Email: "cloud@txthinking.com", 23 | }, 24 | } 25 | app.Commands = []*cli.Command{ 26 | &cli.Command{ 27 | Name: "ca", 28 | Usage: "Generate CA", 29 | Flags: []cli.Flag{ 30 | &cli.StringFlag{ 31 | Name: "ca", 32 | Usage: "CA file which will be created or overwritten", 33 | Value: "ca.pem", 34 | }, 35 | &cli.StringFlag{ 36 | Name: "key", 37 | Usage: "Key file which will be created or overwritten", 38 | Value: "ca.key.pem", 39 | }, 40 | &cli.StringFlag{ 41 | Name: "organization", 42 | Value: "github.com/txthinking/mad", 43 | }, 44 | &cli.StringFlag{ 45 | Name: "organizationUnit", 46 | Value: "github.com/txthinking/mad", 47 | }, 48 | &cli.StringFlag{ 49 | Name: "commonName", 50 | Value: "github.com/txthinking/mad", 51 | }, 52 | &cli.StringFlag{ 53 | Name: "start", 54 | Usage: "Certificate valid start time, such as: '2024-09-22T13:07:38+08:00'. If empty, it is the current time", 55 | }, 56 | &cli.StringFlag{ 57 | Name: "end", 58 | Usage: "Certificate valid end time, such as: '2024-09-22T13:07:38+08:00'. If empty, it is start time add 10 years", 59 | }, 60 | &cli.BoolFlag{ 61 | Name: "install", 62 | Usage: "Install immediately after creation", 63 | }, 64 | }, 65 | Action: func(c *cli.Context) error { 66 | var err error 67 | start := time.Now() 68 | if c.String("start") != "" { 69 | start, err = time.Parse(time.RFC3339, c.String("start")) 70 | if err != nil { 71 | return err 72 | } 73 | } 74 | end := start.AddDate(10, 0, 0) 75 | if c.String("end") != "" { 76 | end, err = time.Parse(time.RFC3339, c.String("end")) 77 | if err != nil { 78 | return err 79 | } 80 | } 81 | ca := mad.NewCa(c.String("organization"), c.String("organizationUnit"), c.String("commonName"), start, end) 82 | if err := ca.Create(); err != nil { 83 | return err 84 | } 85 | if err := ca.SaveToFile(c.String("ca"), c.String("key")); err != nil { 86 | return err 87 | } 88 | if c.Bool("install") { 89 | if err := mad.Install(c.String("ca")); err != nil { 90 | return err 91 | } 92 | } 93 | return nil 94 | }, 95 | }, 96 | &cli.Command{ 97 | Name: "cert", 98 | Usage: "Generate certificate", 99 | Flags: []cli.Flag{ 100 | &cli.StringFlag{ 101 | Name: "ca", 102 | Usage: "ROOT CA file path", 103 | Value: "ca.pem", 104 | }, 105 | &cli.StringFlag{ 106 | Name: "ca_key", 107 | Usage: "Deprecated, please use --caKey", 108 | }, 109 | &cli.StringFlag{ 110 | Name: "caKey", 111 | Usage: "ROOT Key file path", 112 | Value: "ca.key.pem", 113 | }, 114 | &cli.StringFlag{ 115 | Name: "cert", 116 | Usage: "Certificate file which will be created or overwritten", 117 | Value: "cert.pem", 118 | }, 119 | &cli.StringFlag{ 120 | Name: "key", 121 | Usage: "Certificate key file which will be created or overwritten", 122 | Value: "cert.key.pem", 123 | }, 124 | &cli.StringFlag{ 125 | Name: "organization", 126 | Value: "github.com/txthinking/mad", 127 | }, 128 | &cli.StringFlag{ 129 | Name: "organizationUnit", 130 | Value: "github.com/txthinking/mad", 131 | }, 132 | &cli.StringSliceFlag{ 133 | Name: "ip", 134 | Usage: "IP address", 135 | }, 136 | &cli.StringSliceFlag{ 137 | Name: "domain", 138 | Usage: "Domain name", 139 | }, 140 | &cli.StringFlag{ 141 | Name: "commonName", 142 | Usage: "If empty, the first domain or IP will be used", 143 | }, 144 | &cli.StringFlag{ 145 | Name: "start", 146 | Usage: "Certificate valid start time, such as: '2024-09-22T13:07:38+08:00'. If empty, it is the current time", 147 | }, 148 | &cli.StringFlag{ 149 | Name: "end", 150 | Usage: "Certificate valid end time, such as: '2024-09-22T13:07:38+08:00'. If empty, it is start time add 10 years", 151 | }, 152 | }, 153 | Action: func(c *cli.Context) error { 154 | ca, err := os.ReadFile(c.String("ca")) 155 | if err != nil { 156 | return err 157 | } 158 | var caKey []byte 159 | if c.String("ca_key") != "" { 160 | caKey, err = os.ReadFile(c.String("ca_key")) 161 | if err != nil { 162 | return err 163 | } 164 | } else { 165 | caKey, err = os.ReadFile(c.String("caKey")) 166 | if err != nil { 167 | return err 168 | } 169 | } 170 | start := time.Now() 171 | if c.String("start") != "" { 172 | start, err = time.Parse(time.RFC3339, c.String("start")) 173 | if err != nil { 174 | return err 175 | } 176 | } 177 | end := start.AddDate(10, 0, 0) 178 | if c.String("end") != "" { 179 | end, err = time.Parse(time.RFC3339, c.String("end")) 180 | if err != nil { 181 | return err 182 | } 183 | } 184 | cert := mad.NewCert(ca, caKey, c.String("organization"), c.String("organizationUnit"), start, end) 185 | ips := make([]net.IP, 0) 186 | for _, v := range c.StringSlice("ip") { 187 | ip := net.ParseIP(v) 188 | if ip == nil { 189 | return errors.New(v + " is not an IP") 190 | } 191 | ips = append(ips, ip) 192 | } 193 | if len(ips) > 0 { 194 | cert.SetIPAddresses(ips) 195 | } 196 | if len(c.StringSlice("domain")) > 0 { 197 | cert.SetDNSNames(c.StringSlice("domain")) 198 | } 199 | if c.String("commonName") != "" { 200 | cert.SetCommonName(c.String("commonName")) 201 | } 202 | if err := cert.Create(); err != nil { 203 | return err 204 | } 205 | if err := cert.SaveToFile(c.String("cert"), c.String("key")); err != nil { 206 | return err 207 | } 208 | return nil 209 | }, 210 | }, 211 | &cli.Command{ 212 | Name: "install", 213 | Usage: "Install ROOT CA", 214 | Flags: []cli.Flag{ 215 | &cli.StringFlag{ 216 | Name: "ca", 217 | Usage: "CA file which will be installed", 218 | Value: "ca.pem", 219 | }, 220 | }, 221 | Action: func(c *cli.Context) error { 222 | if err := mad.Install(c.String("ca")); err != nil { 223 | return err 224 | } 225 | return nil 226 | }, 227 | }, 228 | } 229 | if err := app.Run(os.Args); err != nil { 230 | log.Fatal(err) 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/txthinking/mad 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/urfave/cli v1.22.5 7 | github.com/urfave/cli/v2 v2.27.2 // indirect 8 | gopkg.in/yaml.v2 v2.2.3 // indirect 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 3 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= 4 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 5 | github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= 6 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= 10 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 11 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 12 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 13 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 14 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 15 | github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= 16 | github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 17 | github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= 18 | github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= 19 | github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= 20 | github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= 21 | github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= 22 | github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= 23 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 24 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 25 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 26 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 27 | -------------------------------------------------------------------------------- /install.go: -------------------------------------------------------------------------------- 1 | //go:build !darwin && !windows 2 | 3 | package mad 4 | 5 | import "errors" 6 | 7 | func Install(ca string) error { 8 | return errors.New(` 9 | We cannot automate the certificate installation for you. 10 | Different Linux distributions have different installation methods, the following is an example for Ubuntu: 11 | 12 | https://ubuntu.com/server/docs/security-trust-store 13 | 14 | sudo apt-get install -y ca-certificates 15 | sudo cp ~/.nami/bin/ca.pem /usr/local/share/ca-certificates/ca.crt 16 | sudo update-ca-certificates 17 | 18 | If you are using a different distribution, please refer to the relevant official documentation. 19 | `) 20 | } 21 | -------------------------------------------------------------------------------- /install_darwin.go: -------------------------------------------------------------------------------- 1 | package mad 2 | 3 | import "os/exec" 4 | 5 | func Install(ca string) error { 6 | cmd := exec.Command("sh", "-c", "sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "+ca) 7 | if err := cmd.Run(); err != nil { 8 | return err 9 | } 10 | return nil 11 | } 12 | -------------------------------------------------------------------------------- /install_windows.go: -------------------------------------------------------------------------------- 1 | package mad 2 | 3 | import "os/exec" 4 | 5 | func Install(ca string) error { 6 | cmd := exec.Command("sh", "-c", "certutil -addstore -f \"ROOT\" "+ca) 7 | if err := cmd.Run(); err != nil { 8 | return err 9 | } 10 | return nil 11 | } 12 | --------------------------------------------------------------------------------