2019-11-19 10:00:20 -07:00
package main
import (
"crypto/rand"
"flag"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"strings"
"time"
2021-02-11 17:53:25 -07:00
"github.com/skip2/go-qrcode"
2019-11-19 10:00:20 -07:00
"github.com/slackhq/nebula/cert"
2019-12-09 17:41:27 -07:00
"golang.org/x/crypto/curve25519"
2019-11-19 10:00:20 -07:00
)
type signFlags struct {
set * flag . FlagSet
caKeyPath * string
caCertPath * string
name * string
ip * string
duration * time . Duration
inPubPath * string
outKeyPath * string
outCertPath * string
2021-02-11 17:53:25 -07:00
outQRPath * string
2019-11-19 10:00:20 -07:00
groups * string
subnets * string
}
func newSignFlags ( ) * signFlags {
sf := signFlags { set : flag . NewFlagSet ( "sign" , flag . ContinueOnError ) }
sf . set . Usage = func ( ) { }
sf . caKeyPath = sf . set . String ( "ca-key" , "ca.key" , "Optional: path to the signing CA key" )
sf . caCertPath = sf . set . String ( "ca-crt" , "ca.crt" , "Optional: path to the signing CA cert" )
sf . name = sf . set . String ( "name" , "" , "Required: name of the cert, usually a hostname" )
2021-12-07 20:40:30 -07:00
sf . ip = sf . set . String ( "ip" , "" , "Required: ipv4 address and network in CIDR notation to assign the cert" )
2020-01-06 11:51:29 -07:00
sf . duration = sf . set . Duration ( "duration" , 0 , "Optional: how long the cert should be valid for. The default is 1 second before the signing cert expires. Valid time units are seconds: \"s\", minutes: \"m\", hours: \"h\"" )
2019-11-19 10:00:20 -07:00
sf . inPubPath = sf . set . String ( "in-pub" , "" , "Optional (if out-key not set): path to read a previously generated public key" )
sf . outKeyPath = sf . set . String ( "out-key" , "" , "Optional (if in-pub not set): path to write the private key to" )
sf . outCertPath = sf . set . String ( "out-crt" , "" , "Optional: path to write the certificate to" )
2021-02-11 17:53:25 -07:00
sf . outQRPath = sf . set . String ( "out-qr" , "" , "Optional: output a qr code image (png) of the certificate" )
2019-11-19 10:00:20 -07:00
sf . groups = sf . set . String ( "groups" , "" , "Optional: comma separated list of groups" )
2021-12-07 20:40:30 -07:00
sf . subnets = sf . set . String ( "subnets" , "" , "Optional: comma separated list of ipv4 address and network in CIDR notation. Subnets this cert can serve for" )
2019-11-19 10:00:20 -07:00
return & sf
}
func signCert ( args [ ] string , out io . Writer , errOut io . Writer ) error {
sf := newSignFlags ( )
err := sf . set . Parse ( args )
if err != nil {
return err
}
if err := mustFlagString ( "ca-key" , sf . caKeyPath ) ; err != nil {
return err
}
if err := mustFlagString ( "ca-crt" , sf . caCertPath ) ; err != nil {
return err
}
if err := mustFlagString ( "name" , sf . name ) ; err != nil {
return err
}
if err := mustFlagString ( "ip" , sf . ip ) ; err != nil {
return err
}
if * sf . inPubPath != "" && * sf . outKeyPath != "" {
return newHelpErrorf ( "cannot set both -in-pub and -out-key" )
}
rawCAKey , err := ioutil . ReadFile ( * sf . caKeyPath )
if err != nil {
return fmt . Errorf ( "error while reading ca-key: %s" , err )
}
caKey , _ , err := cert . UnmarshalEd25519PrivateKey ( rawCAKey )
if err != nil {
return fmt . Errorf ( "error while parsing ca-key: %s" , err )
}
rawCACert , err := ioutil . ReadFile ( * sf . caCertPath )
if err != nil {
return fmt . Errorf ( "error while reading ca-crt: %s" , err )
}
caCert , _ , err := cert . UnmarshalNebulaCertificateFromPEM ( rawCACert )
if err != nil {
return fmt . Errorf ( "error while parsing ca-crt: %s" , err )
}
2021-10-01 10:43:33 -06:00
if err := caCert . VerifyPrivateKey ( caKey ) ; err != nil {
return fmt . Errorf ( "refusing to sign, root certificate does not match private key" )
}
2019-11-19 10:00:20 -07:00
issuer , err := caCert . Sha256Sum ( )
if err != nil {
return fmt . Errorf ( "error while getting -ca-crt fingerprint: %s" , err )
}
if caCert . Expired ( time . Now ( ) ) {
return fmt . Errorf ( "ca certificate is expired" )
}
// if no duration is given, expire one second before the root expires
if * sf . duration <= 0 {
* sf . duration = time . Until ( caCert . Details . NotAfter ) - time . Second * 1
}
ip , ipNet , err := net . ParseCIDR ( * sf . ip )
if err != nil {
return newHelpErrorf ( "invalid ip definition: %s" , err )
}
2021-12-07 20:40:30 -07:00
if ip . To4 ( ) == nil {
return newHelpErrorf ( "invalid ip definition: can only be ipv4, have %s" , * sf . ip )
}
2019-11-19 10:00:20 -07:00
ipNet . IP = ip
groups := [ ] string { }
if * sf . groups != "" {
for _ , rg := range strings . Split ( * sf . groups , "," ) {
g := strings . TrimSpace ( rg )
if g != "" {
groups = append ( groups , g )
}
}
}
subnets := [ ] * net . IPNet { }
if * sf . subnets != "" {
for _ , rs := range strings . Split ( * sf . subnets , "," ) {
rs := strings . Trim ( rs , " " )
if rs != "" {
_ , s , err := net . ParseCIDR ( rs )
if err != nil {
return newHelpErrorf ( "invalid subnet definition: %s" , err )
}
2021-12-07 20:40:30 -07:00
if s . IP . To4 ( ) == nil {
return newHelpErrorf ( "invalid subnet definition: can only be ipv4, have %s" , rs )
}
2019-11-19 10:00:20 -07:00
subnets = append ( subnets , s )
}
}
}
var pub , rawPriv [ ] byte
if * sf . inPubPath != "" {
rawPub , err := ioutil . ReadFile ( * sf . inPubPath )
if err != nil {
return fmt . Errorf ( "error while reading in-pub: %s" , err )
}
pub , _ , err = cert . UnmarshalX25519PublicKey ( rawPub )
if err != nil {
return fmt . Errorf ( "error while parsing in-pub: %s" , err )
}
} else {
pub , rawPriv = x25519Keypair ( )
}
nc := cert . NebulaCertificate {
Details : cert . NebulaCertificateDetails {
Name : * sf . name ,
Ips : [ ] * net . IPNet { ipNet } ,
Groups : groups ,
Subnets : subnets ,
NotBefore : time . Now ( ) ,
NotAfter : time . Now ( ) . Add ( * sf . duration ) ,
PublicKey : pub ,
IsCA : false ,
Issuer : issuer ,
} ,
}
2019-12-17 18:59:21 -07:00
if err := nc . CheckRootConstrains ( caCert ) ; err != nil {
return fmt . Errorf ( "refusing to sign, root certificate constraints violated: %s" , err )
}
2019-11-19 10:00:20 -07:00
if * sf . outKeyPath == "" {
* sf . outKeyPath = * sf . name + ".key"
}
if * sf . outCertPath == "" {
* sf . outCertPath = * sf . name + ".crt"
}
if _ , err := os . Stat ( * sf . outCertPath ) ; err == nil {
return fmt . Errorf ( "refusing to overwrite existing cert: %s" , * sf . outCertPath )
}
err = nc . Sign ( caKey )
if err != nil {
return fmt . Errorf ( "error while signing: %s" , err )
}
if * sf . inPubPath == "" {
2019-12-09 17:41:27 -07:00
if _ , err := os . Stat ( * sf . outKeyPath ) ; err == nil {
return fmt . Errorf ( "refusing to overwrite existing key: %s" , * sf . outKeyPath )
}
2019-11-19 10:00:20 -07:00
err = ioutil . WriteFile ( * sf . outKeyPath , cert . MarshalX25519PrivateKey ( rawPriv ) , 0600 )
if err != nil {
return fmt . Errorf ( "error while writing out-key: %s" , err )
}
}
b , err := nc . MarshalToPEM ( )
if err != nil {
return fmt . Errorf ( "error while marshalling certificate: %s" , err )
}
err = ioutil . WriteFile ( * sf . outCertPath , b , 0600 )
if err != nil {
return fmt . Errorf ( "error while writing out-crt: %s" , err )
}
2021-02-11 17:53:25 -07:00
if * sf . outQRPath != "" {
b , err = qrcode . Encode ( string ( b ) , qrcode . Medium , - 5 )
if err != nil {
return fmt . Errorf ( "error while generating qr code: %s" , err )
}
err = ioutil . WriteFile ( * sf . outQRPath , b , 0600 )
if err != nil {
return fmt . Errorf ( "error while writing out-qr: %s" , err )
}
}
2019-11-19 10:00:20 -07:00
return nil
}
func x25519Keypair ( ) ( [ ] byte , [ ] byte ) {
2021-10-12 10:03:43 -06:00
privkey := make ( [ ] byte , 32 )
if _ , err := io . ReadFull ( rand . Reader , privkey ) ; err != nil {
2019-11-19 10:00:20 -07:00
panic ( err )
}
2021-10-12 10:03:43 -06:00
pubkey , err := curve25519 . X25519 ( privkey , curve25519 . Basepoint )
if err != nil {
panic ( err )
}
return pubkey , privkey
2019-11-19 10:00:20 -07:00
}
func signSummary ( ) string {
return "sign <flags>: create and sign a certificate"
}
func signHelp ( out io . Writer ) {
sf := newSignFlags ( )
out . Write ( [ ] byte ( "Usage of " + os . Args [ 0 ] + " " + signSummary ( ) + "\n" ) )
sf . set . SetOutput ( out )
sf . set . PrintDefaults ( )
}