From 6aab4fb696c064a15986da36b2e2489235651d6e Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 11 Nov 2022 07:27:59 -0800 Subject: [PATCH] cmd/tailscale/cli: start making cert output support pkcs12 (p12) output If the --key-file output filename ends in ".pfx" or ".p12", use pkcs12 format. This might not be working entirely correctly yet but might be enough for others to help out or experiment. Updates #2928 Updates #5011 Change-Id: I62eb0eeaa293b9fd5e27b97b9bc476c23dd27cf6 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/cli/cert.go | 45 ++++++++++++++++++++++++++++++++++---- cmd/tailscale/depaware.txt | 3 +++ go.mod | 2 +- go.sum | 6 ++--- 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/cmd/tailscale/cli/cert.go b/cmd/tailscale/cli/cert.go index ba4679378..8a03965d5 100644 --- a/cmd/tailscale/cli/cert.go +++ b/cmd/tailscale/cli/cert.go @@ -7,7 +7,9 @@ package cli import ( "bytes" "context" + "crypto/rand" "crypto/tls" + "crypto/x509" "errors" "flag" "fmt" @@ -17,6 +19,7 @@ import ( "strings" "github.com/peterbourgon/ff/v3/ffcli" + "software.sslmate.com/src/go-pkcs12" "tailscale.com/atomicfile" "tailscale.com/ipn" "tailscale.com/version" @@ -130,17 +133,25 @@ func runCert(ctx context.Context, args []string) error { } } } - if certArgs.keyFile != "" { - keyChanged, err := writeIfChanged(certArgs.keyFile, keyPEM, 0600) + if dst := certArgs.keyFile; dst != "" { + contents := keyPEM + if isPKCS12(dst) { + var err error + contents, err = convertToPKCS12(certPEM, keyPEM) + if err != nil { + return err + } + } + keyChanged, err := writeIfChanged(dst, contents, 0600) if err != nil { return err } if certArgs.keyFile != "-" { macWarn() if keyChanged { - printf("Wrote private key to %v\n", certArgs.keyFile) + printf("Wrote private key to %v\n", dst) } else { - printf("Private key unchanged at %v\n", certArgs.keyFile) + printf("Private key unchanged at %v\n", dst) } } } @@ -160,3 +171,29 @@ func writeIfChanged(filename string, contents []byte, mode os.FileMode) (changed } return true, nil } + +func isPKCS12(dst string) bool { + return strings.HasSuffix(dst, ".p12") || strings.HasSuffix(dst, ".pfx") +} + +func convertToPKCS12(certPEM, keyPEM []byte) ([]byte, error) { + cert, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + return nil, err + } + var certs []*x509.Certificate + for _, c := range cert.Certificate { + cert, err := x509.ParseCertificate(c) + if err != nil { + return nil, err + } + certs = append(certs, cert) + } + if len(certs) == 0 { + return nil, errors.New("no certs") + } + // TODO(bradfitz): I'm not sure this is right yet. The goal was to make this + // work for https://github.com/tailscale/tailscale/issues/2928 but I'm still + // fighting Windows. + return pkcs12.Encode(rand.Reader, cert.PrivateKey, certs[0], certs[1:], "" /* no password */) +} diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index f6b9778be..5772e88fd 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -37,6 +37,8 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep nhooyr.io/websocket from tailscale.com/derp/derphttp+ nhooyr.io/websocket/internal/errd from nhooyr.io/websocket nhooyr.io/websocket/internal/xsync from nhooyr.io/websocket + software.sslmate.com/src/go-pkcs12 from tailscale.com/cmd/tailscale/cli + software.sslmate.com/src/go-pkcs12/internal/rc2 from software.sslmate.com/src/go-pkcs12 tailscale.com from tailscale.com/version tailscale.com/atomicfile from tailscale.com/ipn+ tailscale.com/client/tailscale from tailscale.com/cmd/tailscale/cli+ @@ -119,6 +121,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep golang.org/x/crypto/hkdf from crypto/tls+ golang.org/x/crypto/nacl/box from tailscale.com/types/key golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box + golang.org/x/crypto/pbkdf2 from software.sslmate.com/src/go-pkcs12 golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+ golang.org/x/net/bpf from github.com/mdlayher/netlink+ golang.org/x/net/dns/dnsmessage from net+ diff --git a/go.mod b/go.mod index 2805c6892..2bd53cb49 100644 --- a/go.mod +++ b/go.mod @@ -73,6 +73,7 @@ require ( inet.af/peercred v0.0.0-20210906144145-0893ea02156a inet.af/wf v0.0.0-20220728202103-50d96caab2f6 nhooyr.io/websocket v1.8.7 + software.sslmate.com/src/go-pkcs12 v0.2.0 ) require ( @@ -281,5 +282,4 @@ require ( mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect mvdan.cc/unparam v0.0.0-20211002134041-24922b6997ca // indirect - software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78 // indirect ) diff --git a/go.sum b/go.sum index e423daedf..9eb09a67a 100644 --- a/go.sum +++ b/go.sum @@ -1250,7 +1250,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= @@ -1259,6 +1258,7 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1870,5 +1870,5 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= software.sslmate.com/src/go-pkcs12 v0.0.0-20180114231543-2291e8f0f237/go.mod h1:/xvNRWUqm0+/ZMiF4EX00vrSCMsE4/NHb+Pt3freEeQ= -software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78 h1:SqYE5+A2qvRhErbsXFfUEUmpWEKxxRSMgGLkvRAFOV4= -software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78/go.mod h1:B7Wf0Ya4DHF9Yw+qfZuJijQYkWicqDa+79Ytmmq3Kjg= +software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE= +software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ=