github.com/slackhq/nebula@v1.9.0/cmd/nebula-cert/ca.go (about) 1 package main 2 3 import ( 4 "crypto/ecdsa" 5 "crypto/elliptic" 6 "crypto/rand" 7 "flag" 8 "fmt" 9 "io" 10 "math" 11 "net" 12 "os" 13 "strings" 14 "time" 15 16 "github.com/skip2/go-qrcode" 17 "github.com/slackhq/nebula/cert" 18 "golang.org/x/crypto/ed25519" 19 ) 20 21 type caFlags struct { 22 set *flag.FlagSet 23 name *string 24 duration *time.Duration 25 outKeyPath *string 26 outCertPath *string 27 outQRPath *string 28 groups *string 29 ips *string 30 subnets *string 31 argonMemory *uint 32 argonIterations *uint 33 argonParallelism *uint 34 encryption *bool 35 36 curve *string 37 } 38 39 func newCaFlags() *caFlags { 40 cf := caFlags{set: flag.NewFlagSet("ca", flag.ContinueOnError)} 41 cf.set.Usage = func() {} 42 cf.name = cf.set.String("name", "", "Required: name of the certificate authority") 43 cf.duration = cf.set.Duration("duration", time.Duration(time.Hour*8760), "Optional: amount of time the certificate should be valid for. Valid time units are seconds: \"s\", minutes: \"m\", hours: \"h\"") 44 cf.outKeyPath = cf.set.String("out-key", "ca.key", "Optional: path to write the private key to") 45 cf.outCertPath = cf.set.String("out-crt", "ca.crt", "Optional: path to write the certificate to") 46 cf.outQRPath = cf.set.String("out-qr", "", "Optional: output a qr code image (png) of the certificate") 47 cf.groups = cf.set.String("groups", "", "Optional: comma separated list of groups. This will limit which groups subordinate certs can use") 48 cf.ips = cf.set.String("ips", "", "Optional: comma separated list of ipv4 address and network in CIDR notation. This will limit which ipv4 addresses and networks subordinate certs can use for ip addresses") 49 cf.subnets = cf.set.String("subnets", "", "Optional: comma separated list of ipv4 address and network in CIDR notation. This will limit which ipv4 addresses and networks subordinate certs can use in subnets") 50 cf.argonMemory = cf.set.Uint("argon-memory", 2*1024*1024, "Optional: Argon2 memory parameter (in KiB) used for encrypted private key passphrase") 51 cf.argonParallelism = cf.set.Uint("argon-parallelism", 4, "Optional: Argon2 parallelism parameter used for encrypted private key passphrase") 52 cf.argonIterations = cf.set.Uint("argon-iterations", 1, "Optional: Argon2 iterations parameter used for encrypted private key passphrase") 53 cf.encryption = cf.set.Bool("encrypt", false, "Optional: prompt for passphrase and write out-key in an encrypted format") 54 cf.curve = cf.set.String("curve", "25519", "EdDSA/ECDSA Curve (25519, P256)") 55 return &cf 56 } 57 58 func parseArgonParameters(memory uint, parallelism uint, iterations uint) (*cert.Argon2Parameters, error) { 59 if memory <= 0 || memory > math.MaxUint32 { 60 return nil, newHelpErrorf("-argon-memory must be be greater than 0 and no more than %d KiB", uint32(math.MaxUint32)) 61 } 62 if parallelism <= 0 || parallelism > math.MaxUint8 { 63 return nil, newHelpErrorf("-argon-parallelism must be be greater than 0 and no more than %d", math.MaxUint8) 64 } 65 if iterations <= 0 || iterations > math.MaxUint32 { 66 return nil, newHelpErrorf("-argon-iterations must be be greater than 0 and no more than %d", uint32(math.MaxUint32)) 67 } 68 69 return cert.NewArgon2Parameters(uint32(memory), uint8(parallelism), uint32(iterations)), nil 70 } 71 72 func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error { 73 cf := newCaFlags() 74 err := cf.set.Parse(args) 75 if err != nil { 76 return err 77 } 78 79 if err := mustFlagString("name", cf.name); err != nil { 80 return err 81 } 82 if err := mustFlagString("out-key", cf.outKeyPath); err != nil { 83 return err 84 } 85 if err := mustFlagString("out-crt", cf.outCertPath); err != nil { 86 return err 87 } 88 var kdfParams *cert.Argon2Parameters 89 if *cf.encryption { 90 if kdfParams, err = parseArgonParameters(*cf.argonMemory, *cf.argonParallelism, *cf.argonIterations); err != nil { 91 return err 92 } 93 } 94 95 if *cf.duration <= 0 { 96 return &helpError{"-duration must be greater than 0"} 97 } 98 99 var groups []string 100 if *cf.groups != "" { 101 for _, rg := range strings.Split(*cf.groups, ",") { 102 g := strings.TrimSpace(rg) 103 if g != "" { 104 groups = append(groups, g) 105 } 106 } 107 } 108 109 var ips []*net.IPNet 110 if *cf.ips != "" { 111 for _, rs := range strings.Split(*cf.ips, ",") { 112 rs := strings.Trim(rs, " ") 113 if rs != "" { 114 ip, ipNet, err := net.ParseCIDR(rs) 115 if err != nil { 116 return newHelpErrorf("invalid ip definition: %s", err) 117 } 118 if ip.To4() == nil { 119 return newHelpErrorf("invalid ip definition: can only be ipv4, have %s", rs) 120 } 121 122 ipNet.IP = ip 123 ips = append(ips, ipNet) 124 } 125 } 126 } 127 128 var subnets []*net.IPNet 129 if *cf.subnets != "" { 130 for _, rs := range strings.Split(*cf.subnets, ",") { 131 rs := strings.Trim(rs, " ") 132 if rs != "" { 133 _, s, err := net.ParseCIDR(rs) 134 if err != nil { 135 return newHelpErrorf("invalid subnet definition: %s", err) 136 } 137 if s.IP.To4() == nil { 138 return newHelpErrorf("invalid subnet definition: can only be ipv4, have %s", rs) 139 } 140 subnets = append(subnets, s) 141 } 142 } 143 } 144 145 var passphrase []byte 146 if *cf.encryption { 147 for i := 0; i < 5; i++ { 148 out.Write([]byte("Enter passphrase: ")) 149 passphrase, err = pr.ReadPassword() 150 151 if err == ErrNoTerminal { 152 return fmt.Errorf("out-key must be encrypted interactively") 153 } else if err != nil { 154 return fmt.Errorf("error reading passphrase: %s", err) 155 } 156 157 if len(passphrase) > 0 { 158 break 159 } 160 } 161 162 if len(passphrase) == 0 { 163 return fmt.Errorf("no passphrase specified, remove -encrypt flag to write out-key in plaintext") 164 } 165 } 166 167 var curve cert.Curve 168 var pub, rawPriv []byte 169 switch *cf.curve { 170 case "25519", "X25519", "Curve25519", "CURVE25519": 171 curve = cert.Curve_CURVE25519 172 pub, rawPriv, err = ed25519.GenerateKey(rand.Reader) 173 if err != nil { 174 return fmt.Errorf("error while generating ed25519 keys: %s", err) 175 } 176 case "P256": 177 var key *ecdsa.PrivateKey 178 curve = cert.Curve_P256 179 key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 180 if err != nil { 181 return fmt.Errorf("error while generating ecdsa keys: %s", err) 182 } 183 184 // ecdh.PrivateKey lets us get at the encoded bytes, even though 185 // we aren't using ECDH here. 186 eKey, err := key.ECDH() 187 if err != nil { 188 return fmt.Errorf("error while converting ecdsa key: %s", err) 189 } 190 rawPriv = eKey.Bytes() 191 pub = eKey.PublicKey().Bytes() 192 } 193 194 nc := cert.NebulaCertificate{ 195 Details: cert.NebulaCertificateDetails{ 196 Name: *cf.name, 197 Groups: groups, 198 Ips: ips, 199 Subnets: subnets, 200 NotBefore: time.Now(), 201 NotAfter: time.Now().Add(*cf.duration), 202 PublicKey: pub, 203 IsCA: true, 204 Curve: curve, 205 }, 206 } 207 208 if _, err := os.Stat(*cf.outKeyPath); err == nil { 209 return fmt.Errorf("refusing to overwrite existing CA key: %s", *cf.outKeyPath) 210 } 211 212 if _, err := os.Stat(*cf.outCertPath); err == nil { 213 return fmt.Errorf("refusing to overwrite existing CA cert: %s", *cf.outCertPath) 214 } 215 216 err = nc.Sign(curve, rawPriv) 217 if err != nil { 218 return fmt.Errorf("error while signing: %s", err) 219 } 220 221 var b []byte 222 if *cf.encryption { 223 b, err = cert.EncryptAndMarshalSigningPrivateKey(curve, rawPriv, passphrase, kdfParams) 224 if err != nil { 225 return fmt.Errorf("error while encrypting out-key: %s", err) 226 } 227 } else { 228 b = cert.MarshalSigningPrivateKey(curve, rawPriv) 229 } 230 231 err = os.WriteFile(*cf.outKeyPath, b, 0600) 232 if err != nil { 233 return fmt.Errorf("error while writing out-key: %s", err) 234 } 235 236 b, err = nc.MarshalToPEM() 237 if err != nil { 238 return fmt.Errorf("error while marshalling certificate: %s", err) 239 } 240 241 err = os.WriteFile(*cf.outCertPath, b, 0600) 242 if err != nil { 243 return fmt.Errorf("error while writing out-crt: %s", err) 244 } 245 246 if *cf.outQRPath != "" { 247 b, err = qrcode.Encode(string(b), qrcode.Medium, -5) 248 if err != nil { 249 return fmt.Errorf("error while generating qr code: %s", err) 250 } 251 252 err = os.WriteFile(*cf.outQRPath, b, 0600) 253 if err != nil { 254 return fmt.Errorf("error while writing out-qr: %s", err) 255 } 256 } 257 258 return nil 259 } 260 261 func caSummary() string { 262 return "ca <flags>: create a self signed certificate authority" 263 } 264 265 func caHelp(out io.Writer) { 266 cf := newCaFlags() 267 out.Write([]byte("Usage of " + os.Args[0] + " " + caSummary() + "\n")) 268 cf.set.SetOutput(out) 269 cf.set.PrintDefaults() 270 }