2019-11-19 17:00:20 +00:00
package main
import (
2023-05-04 17:50:23 -04:00
"crypto/ecdh"
2019-11-19 17:00:20 +00:00
"crypto/rand"
2025-03-06 11:28:26 -06:00
"errors"
2019-11-19 17:00:20 +00:00
"flag"
"fmt"
"io"
2024-10-10 18:00:22 -05:00
"net/netip"
2019-11-19 17:00:20 +00:00
"os"
"strings"
"time"
2021-02-11 18:53:25 -06:00
"github.com/skip2/go-qrcode"
2019-11-19 17:00:20 +00:00
"github.com/slackhq/nebula/cert"
2024-09-09 17:51:58 -04:00
"github.com/slackhq/nebula/pkclient"
2019-12-09 16:41:27 -08:00
"golang.org/x/crypto/curve25519"
2019-11-19 17:00:20 +00:00
)
type signFlags struct {
2025-03-06 11:28:26 -06:00
set * flag . FlagSet
version * uint
caKeyPath * string
caCertPath * string
name * string
networks * string
unsafeNetworks * string
duration * time . Duration
inPubPath * string
outKeyPath * string
outCertPath * string
outQRPath * string
groups * string
p11url * string
// Deprecated options
ip * string
subnets * string
2019-11-19 17:00:20 +00:00
}
func newSignFlags ( ) * signFlags {
sf := signFlags { set : flag . NewFlagSet ( "sign" , flag . ContinueOnError ) }
sf . set . Usage = func ( ) { }
2025-11-20 13:27:27 -06:00
sf . version = sf . set . Uint ( "version" , 0 , "Optional: version of the certificate format to use. The default is to match the version of the signing CA" )
2019-11-19 17:00:20 +00:00
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" )
2025-03-06 11:28:26 -06:00
sf . networks = sf . set . String ( "networks" , "" , "Required: comma separated list of ip address and network in CIDR notation to assign to this cert" )
sf . unsafeNetworks = sf . set . String ( "unsafe-networks" , "" , "Optional: comma separated list of ip address and network in CIDR notation. Unsafe networks this cert can route for" )
2020-01-06 19:51:29 +01: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 17:00:20 +00: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 18:53:25 -06:00
sf . outQRPath = sf . set . String ( "out-qr" , "" , "Optional: output a qr code image (png) of the certificate" )
2019-11-19 17:00:20 +00:00
sf . groups = sf . set . String ( "groups" , "" , "Optional: comma separated list of groups" )
2024-09-09 17:51:58 -04:00
sf . p11url = p11Flag ( sf . set )
2025-03-06 11:28:26 -06:00
sf . ip = sf . set . String ( "ip" , "" , "Deprecated, see -networks" )
sf . subnets = sf . set . String ( "subnets" , "" , "Deprecated, see -unsafe-networks" )
2019-11-19 17:00:20 +00:00
return & sf
}
2023-04-03 13:59:38 -04:00
func signCert ( args [ ] string , out io . Writer , errOut io . Writer , pr PasswordReader ) error {
2019-11-19 17:00:20 +00:00
sf := newSignFlags ( )
err := sf . set . Parse ( args )
if err != nil {
return err
}
2024-09-09 17:51:58 -04:00
isP11 := len ( * sf . p11url ) > 0
if ! isP11 {
if err := mustFlagString ( "ca-key" , sf . caKeyPath ) ; err != nil {
return err
}
2019-11-19 17:00:20 +00:00
}
if err := mustFlagString ( "ca-crt" , sf . caCertPath ) ; err != nil {
return err
}
if err := mustFlagString ( "name" , sf . name ) ; err != nil {
return err
}
2024-09-09 17:51:58 -04:00
if ! isP11 && * sf . inPubPath != "" && * sf . outKeyPath != "" {
2019-11-19 17:00:20 +00:00
return newHelpErrorf ( "cannot set both -in-pub and -out-key" )
}
2025-03-06 11:28:26 -06:00
var v4Networks [ ] netip . Prefix
var v6Networks [ ] netip . Prefix
if * sf . networks == "" && * sf . ip != "" {
// Pull up deprecated -ip flag if needed
* sf . networks = * sf . ip
}
if len ( * sf . networks ) == 0 {
return newHelpErrorf ( "-networks is required" )
}
version := cert . Version ( * sf . version )
if version != 0 && version != cert . Version1 && version != cert . Version2 {
return newHelpErrorf ( "-version must be either %v or %v" , cert . Version1 , cert . Version2 )
}
2023-05-04 17:50:23 -04:00
var curve cert . Curve
var caKey [ ] byte
2024-10-10 18:00:22 -05:00
2024-09-09 17:51:58 -04:00
if ! isP11 {
var rawCAKey [ ] byte
rawCAKey , err := os . ReadFile ( * sf . caKeyPath )
2024-10-10 18:00:22 -05:00
2024-09-09 17:51:58 -04:00
if err != nil {
return fmt . Errorf ( "error while reading ca-key: %s" , err )
}
2023-04-03 13:59:38 -04:00
2024-09-09 17:51:58 -04:00
// naively attempt to decode the private key as though it is not encrypted
2024-10-10 18:00:22 -05:00
caKey , _ , curve , err = cert . UnmarshalSigningPrivateKeyFromPEM ( rawCAKey )
2025-03-06 11:28:26 -06:00
if errors . Is ( err , cert . ErrPrivateKeyEncrypted ) {
2024-09-09 17:51:58 -04:00
var passphrase [ ] byte
2025-11-17 21:41:08 +01:00
passphrase = [ ] byte ( os . Getenv ( "NEBULA_CA_PASSPHRASE" ) )
if len ( passphrase ) == 0 {
// ask for a passphrase until we get one
for i := 0 ; i < 5 ; i ++ {
out . Write ( [ ] byte ( "Enter passphrase: " ) )
passphrase , err = pr . ReadPassword ( )
if errors . Is ( err , ErrNoTerminal ) {
return fmt . Errorf ( "ca-key is encrypted and must be decrypted interactively" )
} else if err != nil {
return fmt . Errorf ( "error reading password: %s" , err )
}
if len ( passphrase ) > 0 {
break
}
2024-09-09 17:51:58 -04:00
}
2025-11-17 21:41:08 +01:00
if len ( passphrase ) == 0 {
return fmt . Errorf ( "cannot open encrypted ca-key without passphrase" )
2024-09-09 17:51:58 -04:00
}
}
curve , caKey , _ , err = cert . DecryptAndUnmarshalSigningPrivateKey ( passphrase , rawCAKey )
if err != nil {
return fmt . Errorf ( "error while parsing encrypted ca-key: %s" , err )
}
} else if err != nil {
return fmt . Errorf ( "error while parsing ca-key: %s" , err )
2023-04-03 13:59:38 -04:00
}
2019-11-19 17:00:20 +00:00
}
2023-10-31 22:35:13 +08:00
rawCACert , err := os . ReadFile ( * sf . caCertPath )
2019-11-19 17:00:20 +00:00
if err != nil {
return fmt . Errorf ( "error while reading ca-crt: %s" , err )
}
2024-10-10 18:00:22 -05:00
caCert , _ , err := cert . UnmarshalCertificateFromPEM ( rawCACert )
2019-11-19 17:00:20 +00:00
if err != nil {
return fmt . Errorf ( "error while parsing ca-crt: %s" , err )
}
2024-09-09 17:51:58 -04:00
if ! isP11 {
if err := caCert . VerifyPrivateKey ( curve , caKey ) ; err != nil {
return fmt . Errorf ( "refusing to sign, root certificate does not match private key" )
}
2021-10-01 12:43:33 -04:00
}
2019-11-19 17:00:20 +00:00
if caCert . Expired ( time . Now ( ) ) {
return fmt . Errorf ( "ca certificate is expired" )
}
2025-11-20 13:27:27 -06:00
if version == 0 {
version = caCert . Version ( )
}
2019-11-19 17:00:20 +00:00
// if no duration is given, expire one second before the root expires
if * sf . duration <= 0 {
2024-10-10 18:00:22 -05:00
* sf . duration = time . Until ( caCert . NotAfter ( ) ) - time . Second * 1
2019-11-19 17:00:20 +00:00
}
2025-03-06 11:28:26 -06:00
if * sf . networks != "" {
for _ , rs := range strings . Split ( * sf . networks , "," ) {
rs := strings . Trim ( rs , " " )
if rs != "" {
n , err := netip . ParsePrefix ( rs )
if err != nil {
return newHelpErrorf ( "invalid -networks definition: %s" , rs )
}
2019-11-19 17:00:20 +00:00
2025-03-06 11:28:26 -06:00
if n . Addr ( ) . Is4 ( ) {
v4Networks = append ( v4Networks , n )
} else {
v6Networks = append ( v6Networks , n )
}
2019-11-19 17:00:20 +00:00
}
}
}
2025-03-06 11:28:26 -06:00
var v4UnsafeNetworks [ ] netip . Prefix
var v6UnsafeNetworks [ ] netip . Prefix
if * sf . unsafeNetworks == "" && * sf . subnets != "" {
// Pull up deprecated -subnets flag if needed
* sf . unsafeNetworks = * sf . subnets
}
if * sf . unsafeNetworks != "" {
for _ , rs := range strings . Split ( * sf . unsafeNetworks , "," ) {
2019-11-19 17:00:20 +00:00
rs := strings . Trim ( rs , " " )
if rs != "" {
2025-03-06 11:28:26 -06:00
n , err := netip . ParsePrefix ( rs )
2019-11-19 17:00:20 +00:00
if err != nil {
2025-03-06 11:28:26 -06:00
return newHelpErrorf ( "invalid -unsafe-networks definition: %s" , rs )
2019-11-19 17:00:20 +00:00
}
2025-03-06 11:28:26 -06:00
if n . Addr ( ) . Is4 ( ) {
v4UnsafeNetworks = append ( v4UnsafeNetworks , n )
} else {
v6UnsafeNetworks = append ( v6UnsafeNetworks , n )
2021-12-07 21:40:30 -06:00
}
2025-03-06 11:28:26 -06:00
}
}
}
var groups [ ] string
if * sf . groups != "" {
for _ , rg := range strings . Split ( * sf . groups , "," ) {
g := strings . TrimSpace ( rg )
if g != "" {
groups = append ( groups , g )
2019-11-19 17:00:20 +00:00
}
}
}
var pub , rawPriv [ ] byte
2024-09-09 17:51:58 -04:00
var p11Client * pkclient . PKClient
if isP11 {
curve = cert . Curve_P256
p11Client , err = pkclient . FromUrl ( * sf . p11url )
if err != nil {
return fmt . Errorf ( "error while creating PKCS#11 client: %w" , err )
}
defer func ( client * pkclient . PKClient ) {
_ = client . Close ( )
} ( p11Client )
}
2019-11-19 17:00:20 +00:00
if * sf . inPubPath != "" {
2024-09-09 17:51:58 -04:00
var pubCurve cert . Curve
2023-10-31 22:35:13 +08:00
rawPub , err := os . ReadFile ( * sf . inPubPath )
2019-11-19 17:00:20 +00:00
if err != nil {
return fmt . Errorf ( "error while reading in-pub: %s" , err )
}
2024-10-10 18:00:22 -05:00
pub , _ , pubCurve , err = cert . UnmarshalPublicKeyFromPEM ( rawPub )
2019-11-19 17:00:20 +00:00
if err != nil {
return fmt . Errorf ( "error while parsing in-pub: %s" , err )
}
2023-05-04 17:50:23 -04:00
if pubCurve != curve {
return fmt . Errorf ( "curve of in-pub does not match ca" )
}
2024-09-09 17:51:58 -04:00
} else if isP11 {
pub , err = p11Client . GetPubKey ( )
if err != nil {
return fmt . Errorf ( "error while getting public key with PKCS#11: %w" , err )
}
2019-11-19 17:00:20 +00:00
} else {
2023-05-04 17:50:23 -04:00
pub , rawPriv = newKeypair ( curve )
2019-11-19 17:00:20 +00: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 )
}
2025-03-06 11:28:26 -06:00
var crts [ ] cert . Certificate
2024-10-10 18:00:22 -05:00
2025-03-06 11:28:26 -06:00
notBefore := time . Now ( )
notAfter := notBefore . Add ( * sf . duration )
2025-11-20 13:27:27 -06:00
switch version {
case cert . Version1 :
// Make sure we have only one ipv4 address
2025-03-06 11:28:26 -06:00
if len ( v4Networks ) != 1 {
return newHelpErrorf ( "invalid -networks definition: v1 certificates can only have a single ipv4 address" )
2024-10-10 18:00:22 -05:00
}
2025-03-06 11:28:26 -06:00
2025-11-20 13:27:27 -06:00
if len ( v6Networks ) > 0 {
return newHelpErrorf ( "invalid -networks definition: v1 certificates can only contain ipv4 addresses" )
}
2025-03-06 11:28:26 -06:00
2025-11-20 13:27:27 -06:00
if len ( v6UnsafeNetworks ) > 0 {
return newHelpErrorf ( "invalid -unsafe-networks definition: v1 certificates can only contain ipv4 addresses" )
2025-03-06 11:28:26 -06:00
}
t := & cert . TBSCertificate {
Version : cert . Version1 ,
Name : * sf . name ,
Networks : [ ] netip . Prefix { v4Networks [ 0 ] } ,
Groups : groups ,
UnsafeNetworks : v4UnsafeNetworks ,
NotBefore : notBefore ,
NotAfter : notAfter ,
PublicKey : pub ,
IsCA : false ,
Curve : curve ,
}
var nc cert . Certificate
if p11Client == nil {
nc , err = t . Sign ( caCert , curve , caKey )
if err != nil {
return fmt . Errorf ( "error while signing: %w" , err )
}
} else {
nc , err = t . SignWith ( caCert , curve , p11Client . SignASN1 )
if err != nil {
return fmt . Errorf ( "error while signing with PKCS#11: %w" , err )
}
2024-10-10 18:00:22 -05:00
}
2025-03-06 11:28:26 -06:00
crts = append ( crts , nc )
2025-11-20 13:27:27 -06:00
case cert . Version2 :
2025-03-06 11:28:26 -06:00
t := & cert . TBSCertificate {
Version : cert . Version2 ,
Name : * sf . name ,
Networks : append ( v4Networks , v6Networks ... ) ,
Groups : groups ,
UnsafeNetworks : append ( v4UnsafeNetworks , v6UnsafeNetworks ... ) ,
NotBefore : notBefore ,
NotAfter : notAfter ,
PublicKey : pub ,
IsCA : false ,
Curve : curve ,
}
var nc cert . Certificate
if p11Client == nil {
nc , err = t . Sign ( caCert , curve , caKey )
if err != nil {
return fmt . Errorf ( "error while signing: %w" , err )
}
} else {
nc , err = t . SignWith ( caCert , curve , p11Client . SignASN1 )
if err != nil {
return fmt . Errorf ( "error while signing with PKCS#11: %w" , err )
}
}
crts = append ( crts , nc )
2025-11-20 13:27:27 -06:00
default :
// this should be unreachable
return fmt . Errorf ( "invalid version: %d" , version )
2024-10-10 18:00:22 -05:00
}
2024-09-09 17:51:58 -04:00
if ! isP11 && * sf . inPubPath == "" {
2019-12-09 16:41:27 -08:00
if _ , err := os . Stat ( * sf . outKeyPath ) ; err == nil {
return fmt . Errorf ( "refusing to overwrite existing key: %s" , * sf . outKeyPath )
}
2024-10-10 18:00:22 -05:00
err = os . WriteFile ( * sf . outKeyPath , cert . MarshalPrivateKeyToPEM ( curve , rawPriv ) , 0600 )
2019-11-19 17:00:20 +00:00
if err != nil {
return fmt . Errorf ( "error while writing out-key: %s" , err )
}
}
2025-03-06 11:28:26 -06:00
var b [ ] byte
for _ , c := range crts {
sb , err := c . MarshalPEM ( )
if err != nil {
return fmt . Errorf ( "error while marshalling certificate: %s" , err )
}
b = append ( b , sb ... )
2019-11-19 17:00:20 +00:00
}
2023-10-31 22:35:13 +08:00
err = os . WriteFile ( * sf . outCertPath , b , 0600 )
2019-11-19 17:00:20 +00:00
if err != nil {
return fmt . Errorf ( "error while writing out-crt: %s" , err )
}
2021-02-11 18:53:25 -06: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 )
}
2023-10-31 22:35:13 +08:00
err = os . WriteFile ( * sf . outQRPath , b , 0600 )
2021-02-11 18:53:25 -06:00
if err != nil {
return fmt . Errorf ( "error while writing out-qr: %s" , err )
}
}
2019-11-19 17:00:20 +00:00
return nil
}
2023-05-04 17:50:23 -04:00
func newKeypair ( curve cert . Curve ) ( [ ] byte , [ ] byte ) {
switch curve {
case cert . Curve_CURVE25519 :
return x25519Keypair ( )
case cert . Curve_P256 :
return p256Keypair ( )
default :
return nil , nil
}
}
2019-11-19 17:00:20 +00:00
func x25519Keypair ( ) ( [ ] byte , [ ] byte ) {
2021-10-12 18:03:43 +02:00
privkey := make ( [ ] byte , 32 )
if _ , err := io . ReadFull ( rand . Reader , privkey ) ; err != nil {
2019-11-19 17:00:20 +00:00
panic ( err )
}
2021-10-12 18:03:43 +02:00
pubkey , err := curve25519 . X25519 ( privkey , curve25519 . Basepoint )
if err != nil {
panic ( err )
}
return pubkey , privkey
2019-11-19 17:00:20 +00:00
}
2023-05-04 17:50:23 -04:00
func p256Keypair ( ) ( [ ] byte , [ ] byte ) {
privkey , err := ecdh . P256 ( ) . GenerateKey ( rand . Reader )
if err != nil {
panic ( err )
}
pubkey := privkey . PublicKey ( )
return pubkey . Bytes ( ) , privkey . Bytes ( )
}
2019-11-19 17:00:20 +00: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 ( )
}