2023-01-27 13:37:20 -08:00
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
2022-11-07 21:26:26 -08:00
//go:build !js
package ipnlocal
import (
"bytes"
"context"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io"
"log"
2024-06-05 14:37:31 -07:00
randv2 "math/rand/v2"
2022-11-07 21:26:26 -08:00
"net"
"os"
"path/filepath"
"runtime"
2023-08-16 22:09:53 -07:00
"slices"
2022-11-07 21:26:26 -08:00
"strings"
"sync"
"time"
2023-03-23 17:21:41 -07:00
"tailscale.com/atomicfile"
2022-11-07 21:26:26 -08:00
"tailscale.com/envknob"
2023-02-01 06:25:48 -08:00
"tailscale.com/hostinfo"
"tailscale.com/ipn"
2022-11-07 21:26:26 -08:00
"tailscale.com/ipn/ipnstate"
2023-03-22 15:10:04 -07:00
"tailscale.com/ipn/store"
"tailscale.com/ipn/store/mem"
2025-01-21 15:30:55 -08:00
"tailscale.com/net/bakedroots"
2025-01-27 03:07:21 +00:00
"tailscale.com/tempfork/acme"
2022-11-07 21:26:26 -08:00
"tailscale.com/types/logger"
2023-11-16 12:15:39 -08:00
"tailscale.com/util/testenv"
2022-11-07 21:26:26 -08:00
"tailscale.com/version"
"tailscale.com/version/distro"
)
// Process-wide cache. (A new *Handler is created per connection,
// effectively per request)
var (
// acmeMu guards all ACME operations, so concurrent requests
// for certs don't slam ACME. The first will go through and
// populate the on-disk cache and the rest should use that.
acmeMu sync . Mutex
2023-07-27 12:29:40 -07:00
renewMu sync . Mutex // lock order: acmeMu before renewMu
renewCertAt = map [ string ] time . Time { }
2022-11-07 21:26:26 -08:00
)
// certDir returns (creating if needed) the directory in which cached
// cert keypairs are stored.
func ( b * LocalBackend ) certDir ( ) ( string , error ) {
d := b . TailscaleVarRoot ( )
// As a workaround for Synology DSM6 not having a "var" directory, use the
// app's "etc" directory (on a small partition) to hold certs at least.
// See https://github.com/tailscale/tailscale/issues/4060#issuecomment-1186592251
if d == "" && runtime . GOOS == "linux" && distro . Get ( ) == distro . Synology && distro . DSMVersion ( ) == 6 {
d = "/var/packages/Tailscale/etc" // base; we append "certs" below
}
if d == "" {
return "" , errors . New ( "no TailscaleVarRoot" )
}
full := filepath . Join ( d , "certs" )
if err := os . MkdirAll ( full , 0700 ) ; err != nil {
return "" , err
}
return full , nil
}
var acmeDebug = envknob . RegisterBool ( "TS_DEBUG_ACME" )
2023-07-27 12:29:40 -07:00
// GetCertPEM gets the TLSCertKeyPair for domain, either from cache or via the
// ACME process. ACME process is used for new domain certs, existing expired
// certs or existing certs that should get renewed due to upcoming expiry.
//
2023-10-12 15:52:41 -07:00
// If a cert is expired, it will be renewed synchronously otherwise it will be
// renewed asynchronously.
func ( b * LocalBackend ) GetCertPEM ( ctx context . Context , domain string ) ( * TLSCertKeyPair , error ) {
2024-07-19 11:35:22 -05:00
return b . GetCertPEMWithValidity ( ctx , domain , 0 )
}
// GetCertPEMWithValidity gets the TLSCertKeyPair for domain, either from cache
// or via the ACME process. ACME process is used for new domain certs, existing
// expired certs or existing certs that should get renewed sooner than
// minValidity.
//
// If a cert is expired, or expires sooner than minValidity, it will be renewed
// synchronously. Otherwise it will be renewed asynchronously.
func ( b * LocalBackend ) GetCertPEMWithValidity ( ctx context . Context , domain string , minValidity time . Duration ) ( * TLSCertKeyPair , error ) {
2022-11-07 21:26:26 -08:00
if ! validLookingCertDomain ( domain ) {
return nil , errors . New ( "invalid domain" )
}
logf := logger . WithPrefix ( b . logf , fmt . Sprintf ( "cert(%q): " , domain ) )
2023-07-27 15:41:31 -04:00
now := b . clock . Now ( )
2022-11-07 21:26:26 -08:00
traceACME := func ( v any ) {
if ! acmeDebug ( ) {
return
}
j , _ := json . MarshalIndent ( v , "" , "\t" )
log . Printf ( "acme %T: %s" , v , j )
}
2023-03-22 15:09:30 -07:00
cs , err := b . getCertStore ( )
if err != nil {
return nil , err
}
if pair , err := getCertPEMCached ( cs , domain , now ) ; err == nil {
2025-03-13 14:14:03 +00:00
if envknob . IsCertShareReadOnlyMode ( ) {
return pair , nil
}
2023-10-12 15:52:41 -07:00
// If we got here, we have a valid unexpired cert.
// Check whether we should start an async renewal.
2024-07-19 11:35:22 -05:00
shouldRenew , err := b . shouldStartDomainRenewal ( cs , domain , now , pair , minValidity )
if err != nil {
2023-06-02 10:16:56 -04:00
logf ( "error checking for certificate renewal: %v" , err )
2024-07-19 11:35:22 -05:00
// Renewal check failed, but the current cert is valid and not
// expired, so it's safe to return.
return pair , nil
}
if ! shouldRenew {
return pair , nil
}
if minValidity == 0 {
2022-11-07 21:26:26 -08:00
logf ( "starting async renewal" )
2024-07-19 11:35:22 -05:00
// Start renewal in the background, return current valid cert.
2025-03-13 14:14:03 +00:00
b . goTracker . Go ( func ( ) { getCertPEM ( context . Background ( ) , b , cs , logf , traceACME , domain , now , minValidity ) } )
2024-07-19 11:35:22 -05:00
return pair , nil
2022-11-07 21:26:26 -08:00
}
2024-07-19 11:35:22 -05:00
// If the caller requested a specific validity duration, fall through
// to synchronous renewal to fulfill that.
logf ( "starting sync renewal" )
2022-11-07 21:26:26 -08:00
}
2025-03-13 14:14:03 +00:00
if envknob . IsCertShareReadOnlyMode ( ) {
return nil , fmt . Errorf ( "retrieving cached TLS certificate failed and cert store is configured in read-only mode, not attempting to issue a new certificate: %w" , err )
}
pair , err := getCertPEM ( ctx , b , cs , logf , traceACME , domain , now , minValidity )
2022-11-07 21:26:26 -08:00
if err != nil {
logf ( "getCertPEM: %v" , err )
return nil , err
}
return pair , nil
}
2023-10-12 15:52:41 -07:00
// shouldStartDomainRenewal reports whether the domain's cert should be renewed
// based on the current time, the cert's expiry, and the ARI check.
2024-07-19 11:35:22 -05:00
func ( b * LocalBackend ) shouldStartDomainRenewal ( cs certStore , domain string , now time . Time , pair * TLSCertKeyPair , minValidity time . Duration ) ( bool , error ) {
if minValidity != 0 {
cert , err := pair . parseCertificate ( )
if err != nil {
return false , fmt . Errorf ( "parsing certificate: %w" , err )
}
return cert . NotAfter . Sub ( now ) < minValidity , nil
}
2022-11-07 21:26:26 -08:00
renewMu . Lock ( )
defer renewMu . Unlock ( )
2023-07-27 12:29:40 -07:00
if renewAt , ok := renewCertAt [ domain ] ; ok {
return now . After ( renewAt ) , nil
2022-11-07 21:26:26 -08:00
}
2023-06-02 10:16:56 -04:00
2023-07-27 12:29:40 -07:00
renewTime , err := b . domainRenewalTimeByARI ( cs , pair )
2023-07-13 14:29:59 -07:00
if err != nil {
// Log any ARI failure and fall back to checking for renewal by expiry.
b . logf ( "acme: ARI check failed: %v; falling back to expiry-based check" , err )
2023-07-27 12:29:40 -07:00
renewTime , err = b . domainRenewalTimeByExpiry ( pair )
if err != nil {
return false , err
}
2023-07-13 14:29:59 -07:00
}
2023-07-27 12:29:40 -07:00
renewCertAt [ domain ] = renewTime
return now . After ( renewTime ) , nil
2023-07-13 14:29:59 -07:00
}
2023-07-27 12:29:40 -07:00
func ( b * LocalBackend ) domainRenewed ( domain string ) {
renewMu . Lock ( )
defer renewMu . Unlock ( )
delete ( renewCertAt , domain )
}
func ( b * LocalBackend ) domainRenewalTimeByExpiry ( pair * TLSCertKeyPair ) ( time . Time , error ) {
2024-07-19 11:35:22 -05:00
cert , err := pair . parseCertificate ( )
2023-06-02 10:16:56 -04:00
if err != nil {
2023-07-27 12:29:40 -07:00
return time . Time { } , fmt . Errorf ( "parsing certificate: %w" , err )
2023-06-02 10:16:56 -04:00
}
certLifetime := cert . NotAfter . Sub ( cert . NotBefore )
if certLifetime < 0 {
2023-07-27 12:29:40 -07:00
return time . Time { } , fmt . Errorf ( "negative certificate lifetime %v" , certLifetime )
2023-06-02 10:16:56 -04:00
}
// Per https://github.com/tailscale/tailscale/issues/8204, check
// whether we're more than 2/3 of the way through the certificate's
// lifetime, which is the officially-recommended best practice by Let's
// Encrypt.
renewalDuration := certLifetime * 2 / 3
renewAt := cert . NotBefore . Add ( renewalDuration )
2023-07-27 12:29:40 -07:00
return renewAt , nil
2023-02-01 06:25:48 -08:00
}
2023-07-27 12:29:40 -07:00
func ( b * LocalBackend ) domainRenewalTimeByARI ( cs certStore , pair * TLSCertKeyPair ) ( time . Time , error ) {
2023-07-13 14:29:59 -07:00
var blocks [ ] * pem . Block
rest := pair . CertPEM
for len ( rest ) > 0 {
var block * pem . Block
block , rest = pem . Decode ( rest )
if block == nil {
2023-07-27 12:29:40 -07:00
return time . Time { } , fmt . Errorf ( "parsing certificate PEM" )
2023-07-13 14:29:59 -07:00
}
blocks = append ( blocks , block )
}
2024-06-04 14:52:54 -07:00
if len ( blocks ) < 1 {
2023-07-27 12:29:40 -07:00
return time . Time { } , fmt . Errorf ( "could not parse certificate chain from certStore, got %d PEM block(s)" , len ( blocks ) )
2023-07-13 14:29:59 -07:00
}
ac , err := acmeClient ( cs )
if err != nil {
2023-07-27 12:29:40 -07:00
return time . Time { } , err
2023-07-13 14:29:59 -07:00
}
ctx , cancel := context . WithTimeout ( b . ctx , 5 * time . Second )
defer cancel ( )
2024-06-04 14:52:54 -07:00
ri , err := ac . FetchRenewalInfo ( ctx , blocks [ 0 ] . Bytes )
2023-07-13 14:29:59 -07:00
if err != nil {
2023-07-27 12:29:40 -07:00
return time . Time { } , fmt . Errorf ( "failed to fetch renewal info from ACME server: %w" , err )
2023-07-13 14:29:59 -07:00
}
if acmeDebug ( ) {
b . logf ( "acme: ARI response: %+v" , ri )
}
// Select a random time in the suggested window and renew if that time has
// passed. Time is randomized per recommendation in
// https://datatracker.ietf.org/doc/draft-ietf-acme-ari/
start , end := ri . SuggestedWindow . Start , ri . SuggestedWindow . End
2024-06-05 14:37:31 -07:00
renewTime := start . Add ( randv2 . N ( end . Sub ( start ) ) )
2023-07-27 12:29:40 -07:00
return renewTime , nil
2023-07-13 14:29:59 -07:00
}
2023-02-01 06:25:48 -08:00
// certStore provides a way to perist and retrieve TLS certificates.
// As of 2023-02-01, we use store certs in directories on disk everywhere
// except on Kubernetes, where we use the state store.
type certStore interface {
// Read returns the cert and key for domain, if they exist and are valid
// for now. If they're expired, it returns errCertExpired.
// If they don't exist, it returns ipn.ErrStateNotExist.
Read ( domain string , now time . Time ) ( * TLSCertKeyPair , error )
2023-03-22 15:09:30 -07:00
// ACMEKey returns the value previously stored via WriteACMEKey.
// It is a PEM encoded ECDSA key.
ACMEKey ( ) ( [ ] byte , error )
// WriteACMEKey stores the provided PEM encoded ECDSA key.
WriteACMEKey ( [ ] byte ) error
2025-02-27 14:41:05 -08:00
// WriteTLSCertAndKey writes the cert and key for domain.
WriteTLSCertAndKey ( domain string , cert , key [ ] byte ) error
2023-02-01 06:25:48 -08:00
}
var errCertExpired = errors . New ( "cert expired" )
2023-11-16 12:15:39 -08:00
var testX509Roots * x509 . CertPool // set non-nil by tests
2023-03-22 15:09:30 -07:00
func ( b * LocalBackend ) getCertStore ( ) ( certStore , error ) {
2023-03-22 15:10:04 -07:00
switch b . store . ( type ) {
case * store . FileStore :
case * mem . Store :
default :
if hostinfo . GetEnvType ( ) == hostinfo . Kubernetes {
// We're running in Kubernetes with a custom StateStore,
// use that instead of the cert directory.
// TODO(maisem): expand this to other environments?
return certStateStore { StateStore : b . store } , nil
}
}
2023-03-22 15:09:30 -07:00
dir , err := b . certDir ( )
if err != nil {
return nil , err
}
2023-11-16 12:15:39 -08:00
if testX509Roots != nil && ! testenv . InTest ( ) {
panic ( "use of test hook outside of tests" )
}
return certFileStore { dir : dir , testRoots : testX509Roots } , nil
2023-02-01 06:25:48 -08:00
}
// certFileStore implements certStore by storing the cert & key files in the named directory.
2023-02-07 14:34:04 -08:00
type certFileStore struct {
dir string
// This field allows a test to override the CA root(s) for certificate
// verification. If nil the default system pool is used.
testRoots * x509 . CertPool
}
2023-02-01 06:25:48 -08:00
2023-03-22 15:09:30 -07:00
const acmePEMName = "acme-account.key.pem"
func ( f certFileStore ) ACMEKey ( ) ( [ ] byte , error ) {
pemName := filepath . Join ( f . dir , acmePEMName )
v , err := os . ReadFile ( pemName )
if err != nil {
if os . IsNotExist ( err ) {
return nil , ipn . ErrStateNotExist
}
return nil , err
}
return v , nil
}
func ( f certFileStore ) WriteACMEKey ( b [ ] byte ) error {
pemName := filepath . Join ( f . dir , acmePEMName )
2023-03-23 17:21:41 -07:00
return atomicfile . WriteFile ( pemName , b , 0600 )
2023-03-22 15:09:30 -07:00
}
2023-02-01 06:25:48 -08:00
func ( f certFileStore ) Read ( domain string , now time . Time ) ( * TLSCertKeyPair , error ) {
2023-02-07 14:34:04 -08:00
certPEM , err := os . ReadFile ( certFile ( f . dir , domain ) )
2023-02-01 06:25:48 -08:00
if err != nil {
if os . IsNotExist ( err ) {
return nil , ipn . ErrStateNotExist
}
return nil , err
}
2023-02-07 14:34:04 -08:00
keyPEM , err := os . ReadFile ( keyFile ( f . dir , domain ) )
2023-02-01 06:25:48 -08:00
if err != nil {
if os . IsNotExist ( err ) {
return nil , ipn . ErrStateNotExist
}
return nil , err
}
2023-02-07 14:34:04 -08:00
if ! validCertPEM ( domain , keyPEM , certPEM , f . testRoots , now ) {
2023-02-01 06:25:48 -08:00
return nil , errCertExpired
}
return & TLSCertKeyPair { CertPEM : certPEM , KeyPEM : keyPEM , Cached : true } , nil
}
func ( f certFileStore ) WriteCert ( domain string , cert [ ] byte ) error {
2023-03-23 17:21:41 -07:00
return atomicfile . WriteFile ( certFile ( f . dir , domain ) , cert , 0644 )
2023-02-01 06:25:48 -08:00
}
func ( f certFileStore ) WriteKey ( domain string , key [ ] byte ) error {
2023-03-23 17:21:41 -07:00
return atomicfile . WriteFile ( keyFile ( f . dir , domain ) , key , 0600 )
2023-02-01 06:25:48 -08:00
}
2025-02-27 14:41:05 -08:00
func ( f certFileStore ) WriteTLSCertAndKey ( domain string , cert , key [ ] byte ) error {
if err := f . WriteKey ( domain , key ) ; err != nil {
return err
}
return f . WriteCert ( domain , cert )
}
2023-02-01 06:25:48 -08:00
// certStateStore implements certStore by storing the cert & key files in an ipn.StateStore.
type certStateStore struct {
ipn . StateStore
2023-02-07 14:34:04 -08:00
// This field allows a test to override the CA root(s) for certificate
// verification. If nil the default system pool is used.
testRoots * x509 . CertPool
2023-02-01 06:25:48 -08:00
}
2025-03-13 14:14:03 +00:00
// TLSCertKeyReader is an interface implemented by state stores where it makes
// sense to read the TLS cert and key in a single operation that can be
// distinguished from generic state value reads. Currently this is only implemented
// by the kubestore.Store, which, in some cases, need to read cert and key from a
// non-cached TLS Secret.
type TLSCertKeyReader interface {
ReadTLSCertAndKey ( domain string ) ( [ ] byte , [ ] byte , error )
}
2023-02-01 06:25:48 -08:00
func ( s certStateStore ) Read ( domain string , now time . Time ) ( * TLSCertKeyPair , error ) {
2025-03-13 14:14:03 +00:00
// If we're using a store that supports atomic reads, use that
if kr , ok := s . StateStore . ( TLSCertKeyReader ) ; ok {
cert , key , err := kr . ReadTLSCertAndKey ( domain )
if err != nil {
return nil , err
}
if ! validCertPEM ( domain , key , cert , s . testRoots , now ) {
return nil , errCertExpired
}
return & TLSCertKeyPair { CertPEM : cert , KeyPEM : key , Cached : true } , nil
}
// Otherwise fall back to separate reads
2023-02-01 06:25:48 -08:00
certPEM , err := s . ReadState ( ipn . StateKey ( domain + ".crt" ) )
if err != nil {
return nil , err
}
keyPEM , err := s . ReadState ( ipn . StateKey ( domain + ".key" ) )
if err != nil {
return nil , err
}
2023-02-07 14:34:04 -08:00
if ! validCertPEM ( domain , keyPEM , certPEM , s . testRoots , now ) {
2023-02-01 06:25:48 -08:00
return nil , errCertExpired
}
return & TLSCertKeyPair { CertPEM : certPEM , KeyPEM : keyPEM , Cached : true } , nil
}
func ( s certStateStore ) WriteCert ( domain string , cert [ ] byte ) error {
2023-08-04 07:55:59 -07:00
return ipn . WriteState ( s . StateStore , ipn . StateKey ( domain + ".crt" ) , cert )
2023-02-01 06:25:48 -08:00
}
func ( s certStateStore ) WriteKey ( domain string , key [ ] byte ) error {
2023-08-04 07:55:59 -07:00
return ipn . WriteState ( s . StateStore , ipn . StateKey ( domain + ".key" ) , key )
2022-11-07 21:26:26 -08:00
}
2023-03-22 15:09:30 -07:00
func ( s certStateStore ) ACMEKey ( ) ( [ ] byte , error ) {
return s . ReadState ( ipn . StateKey ( acmePEMName ) )
}
func ( s certStateStore ) WriteACMEKey ( key [ ] byte ) error {
2023-08-04 07:55:59 -07:00
return ipn . WriteState ( s . StateStore , ipn . StateKey ( acmePEMName ) , key )
2023-03-22 15:09:30 -07:00
}
2025-02-27 14:41:05 -08:00
// TLSCertKeyWriter is an interface implemented by state stores that can write the TLS
// cert and key in a single atomic operation. Currently this is only implemented
// by the kubestore.StoreKube.
type TLSCertKeyWriter interface {
WriteTLSCertAndKey ( domain string , cert , key [ ] byte ) error
}
// WriteTLSCertAndKey writes the TLS cert and key for domain to the current
// LocalBackend's StateStore.
func ( s certStateStore ) WriteTLSCertAndKey ( domain string , cert , key [ ] byte ) error {
// If we're using a store that supports atomic writes, use that.
if aw , ok := s . StateStore . ( TLSCertKeyWriter ) ; ok {
return aw . WriteTLSCertAndKey ( domain , cert , key )
}
// Otherwise fall back to separate writes for cert and key.
if err := s . WriteKey ( domain , key ) ; err != nil {
return err
}
return s . WriteCert ( domain , cert )
}
2022-11-07 21:26:26 -08:00
// TLSCertKeyPair is a TLS public and private key, and whether they were obtained
// from cache or freshly obtained.
type TLSCertKeyPair struct {
CertPEM [ ] byte // public key, in PEM form
KeyPEM [ ] byte // private key, in PEM form
Cached bool // whether result came from cache
}
2024-07-19 11:35:22 -05:00
func ( kp TLSCertKeyPair ) parseCertificate ( ) ( * x509 . Certificate , error ) {
block , _ := pem . Decode ( kp . CertPEM )
if block == nil {
return nil , fmt . Errorf ( "error parsing certificate PEM" )
}
if block . Type != "CERTIFICATE" {
return nil , fmt . Errorf ( "PEM block is %q, not a CERTIFICATE" , block . Type )
}
return x509 . ParseCertificate ( block . Bytes )
}
2022-11-07 21:26:26 -08:00
func keyFile ( dir , domain string ) string { return filepath . Join ( dir , domain + ".key" ) }
func certFile ( dir , domain string ) string { return filepath . Join ( dir , domain + ".crt" ) }
2023-10-12 15:52:41 -07:00
// getCertPEMCached returns a non-nil keyPair if a cached keypair for domain
// exists on disk in dir that is valid at the provided now time.
//
2023-02-01 06:25:48 -08:00
// If the keypair is expired, it returns errCertExpired.
// If the keypair doesn't exist, it returns ipn.ErrStateNotExist.
2023-03-22 15:09:30 -07:00
func getCertPEMCached ( cs certStore , domain string , now time . Time ) ( p * TLSCertKeyPair , err error ) {
2022-11-07 21:26:26 -08:00
if ! validLookingCertDomain ( domain ) {
// Before we read files from disk using it, validate it's halfway
// reasonable looking.
2023-02-01 06:25:48 -08:00
return nil , fmt . Errorf ( "invalid domain %q" , domain )
2022-11-07 21:26:26 -08:00
}
2023-03-22 15:09:30 -07:00
return cs . Read ( domain , now )
2022-11-07 21:26:26 -08:00
}
2025-03-13 14:14:03 +00:00
// getCertPem checks if a cert needs to be renewed and if so, renews it.
// It can be overridden in tests.
var getCertPEM = func ( ctx context . Context , b * LocalBackend , cs certStore , logf logger . Logf , traceACME func ( any ) , domain string , now time . Time , minValidity time . Duration ) ( * TLSCertKeyPair , error ) {
2022-11-07 21:26:26 -08:00
acmeMu . Lock ( )
defer acmeMu . Unlock ( )
2023-07-27 12:29:40 -07:00
// In case this method was triggered multiple times in parallel (when
// serving incoming requests), check whether one of the other goroutines
// already renewed the cert before us.
2023-03-22 15:09:30 -07:00
if p , err := getCertPEMCached ( cs , domain , now ) ; err == nil {
2023-07-27 12:29:40 -07:00
// shouldStartDomainRenewal caches its result so it's OK to call this
// frequently.
2024-07-19 11:35:22 -05:00
shouldRenew , err := b . shouldStartDomainRenewal ( cs , domain , now , p , minValidity )
2023-07-27 12:29:40 -07:00
if err != nil {
logf ( "error checking for certificate renewal: %v" , err )
} else if ! shouldRenew {
return p , nil
}
2023-02-01 06:25:48 -08:00
} else if ! errors . Is ( err , ipn . ErrStateNotExist ) && ! errors . Is ( err , errCertExpired ) {
return nil , err
2022-11-07 21:26:26 -08:00
}
2023-07-13 14:29:59 -07:00
ac , err := acmeClient ( cs )
2022-11-07 21:26:26 -08:00
if err != nil {
2023-07-13 14:29:59 -07:00
return nil , err
2022-11-07 21:26:26 -08:00
}
2025-03-11 07:09:46 -07:00
if ! isDefaultDirectoryURL ( ac . DirectoryURL ) {
logf ( "acme: using Directory URL %q" , ac . DirectoryURL )
}
2022-11-07 21:26:26 -08:00
a , err := ac . GetReg ( ctx , "" /* pre-RFC param */ )
switch {
case err == nil :
// Great, already registered.
logf ( "already had ACME account." )
case err == acme . ErrNoAccount :
a , err = ac . Register ( ctx , new ( acme . Account ) , acme . AcceptTOS )
if err == acme . ErrAccountAlreadyExists {
// Potential race. Double check.
a , err = ac . GetReg ( ctx , "" /* pre-RFC param */ )
}
if err != nil {
return nil , fmt . Errorf ( "acme.Register: %w" , err )
}
logf ( "registered ACME account." )
traceACME ( a )
default :
return nil , fmt . Errorf ( "acme.GetReg: %w" , err )
}
if a . Status != acme . StatusValid {
return nil , fmt . Errorf ( "unexpected ACME account status %q" , a . Status )
}
// Before hitting LetsEncrypt, see if this is a domain that Tailscale will do DNS challenges for.
st := b . StatusWithoutPeers ( )
if err := checkCertDomain ( st , domain ) ; err != nil {
return nil , err
}
order , err := ac . AuthorizeOrder ( ctx , [ ] acme . AuthzID { { Type : "dns" , Value : domain } } )
if err != nil {
return nil , err
}
traceACME ( order )
for _ , aurl := range order . AuthzURLs {
az , err := ac . GetAuthorization ( ctx , aurl )
if err != nil {
return nil , err
}
traceACME ( az )
for _ , ch := range az . Challenges {
if ch . Type == "dns-01" {
rec , err := ac . DNS01ChallengeRecord ( ch . Token )
if err != nil {
return nil , err
}
key := "_acme-challenge." + domain
2023-05-05 12:19:25 -07:00
// Do a best-effort lookup to see if we've already created this DNS name
// in a previous attempt. Don't burn too much time on it, though. Worst
// case we ask the server to create something that already exists.
2022-11-07 21:26:26 -08:00
var resolver net . Resolver
2023-05-05 12:19:25 -07:00
lookupCtx , lookupCancel := context . WithTimeout ( ctx , 500 * time . Millisecond )
txts , _ := resolver . LookupTXT ( lookupCtx , key )
lookupCancel ( )
if slices . Contains ( txts , rec ) {
logf ( "TXT record already existed" )
} else {
2022-11-07 21:26:26 -08:00
logf ( "starting SetDNS call..." )
err = b . SetDNS ( ctx , key , rec )
if err != nil {
return nil , fmt . Errorf ( "SetDNS %q => %q: %w" , key , rec , err )
}
logf ( "did SetDNS" )
}
chal , err := ac . Accept ( ctx , ch )
if err != nil {
return nil , fmt . Errorf ( "Accept: %v" , err )
}
traceACME ( chal )
break
}
}
}
orderURI := order . URI
order , err = ac . WaitOrder ( ctx , orderURI )
if err != nil {
if ctx . Err ( ) != nil {
return nil , ctx . Err ( )
}
if oe , ok := err . ( * acme . OrderError ) ; ok {
logf ( "acme: WaitOrder: OrderError status %q" , oe . Status )
} else {
logf ( "acme: WaitOrder error: %v" , err )
}
return nil , err
}
traceACME ( order )
certPrivKey , err := ecdsa . GenerateKey ( elliptic . P256 ( ) , rand . Reader )
if err != nil {
return nil , err
}
var privPEM bytes . Buffer
if err := encodeECDSAKey ( & privPEM , certPrivKey ) ; err != nil {
return nil , err
}
csr , err := certRequest ( certPrivKey , domain , nil )
if err != nil {
return nil , err
}
logf ( "requesting cert..." )
2025-01-24 17:04:26 +00:00
traceACME ( csr )
2022-11-07 21:26:26 -08:00
der , _ , err := ac . CreateOrderCert ( ctx , order . FinalizeURL , csr , true )
if err != nil {
return nil , fmt . Errorf ( "CreateOrder: %v" , err )
}
logf ( "got cert" )
var certPEM bytes . Buffer
for _ , b := range der {
pb := & pem . Block { Type : "CERTIFICATE" , Bytes : b }
if err := pem . Encode ( & certPEM , pb ) ; err != nil {
return nil , err
}
}
2025-02-27 14:41:05 -08:00
if err := cs . WriteTLSCertAndKey ( domain , certPEM . Bytes ( ) , privPEM . Bytes ( ) ) ; err != nil {
2022-11-07 21:26:26 -08:00
return nil , err
}
2023-07-27 12:29:40 -07:00
b . domainRenewed ( domain )
2022-11-07 21:26:26 -08:00
return & TLSCertKeyPair { CertPEM : certPEM . Bytes ( ) , KeyPEM : privPEM . Bytes ( ) } , nil
}
// certRequest generates a CSR for the given common name cn and optional SANs.
2025-01-24 17:04:26 +00:00
func certRequest ( key crypto . Signer , name string , ext [ ] pkix . Extension ) ( [ ] byte , error ) {
2022-11-07 21:26:26 -08:00
req := & x509 . CertificateRequest {
2025-01-24 17:04:26 +00:00
Subject : pkix . Name { CommonName : name } ,
DNSNames : [ ] string { name } ,
2022-11-07 21:26:26 -08:00
ExtraExtensions : ext ,
}
return x509 . CreateCertificateRequest ( rand . Reader , req , key )
}
func encodeECDSAKey ( w io . Writer , key * ecdsa . PrivateKey ) error {
b , err := x509 . MarshalECPrivateKey ( key )
if err != nil {
return err
}
pb := & pem . Block { Type : "EC PRIVATE KEY" , Bytes : b }
return pem . Encode ( w , pb )
}
// parsePrivateKey is a copy of x/crypto/acme's parsePrivateKey.
//
// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates
// PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys.
// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three.
//
// Inspired by parsePrivateKey in crypto/tls/tls.go.
func parsePrivateKey ( der [ ] byte ) ( crypto . Signer , error ) {
if key , err := x509 . ParsePKCS1PrivateKey ( der ) ; err == nil {
return key , nil
}
if key , err := x509 . ParsePKCS8PrivateKey ( der ) ; err == nil {
switch key := key . ( type ) {
case * rsa . PrivateKey :
return key , nil
case * ecdsa . PrivateKey :
return key , nil
default :
return nil , errors . New ( "acme/autocert: unknown private key type in PKCS#8 wrapping" )
}
}
if key , err := x509 . ParseECPrivateKey ( der ) ; err == nil {
return key , nil
}
return nil , errors . New ( "acme/autocert: failed to parse private key" )
}
2023-03-22 15:09:30 -07:00
func acmeKey ( cs certStore ) ( crypto . Signer , error ) {
if v , err := cs . ACMEKey ( ) ; err == nil {
2022-11-07 21:26:26 -08:00
priv , _ := pem . Decode ( v )
if priv == nil || ! strings . Contains ( priv . Type , "PRIVATE" ) {
return nil , errors . New ( "acme/autocert: invalid account key found in cache" )
}
return parsePrivateKey ( priv . Bytes )
2023-12-05 10:32:35 -05:00
} else if ! errors . Is ( err , ipn . ErrStateNotExist ) {
2023-03-22 15:09:30 -07:00
return nil , err
2022-11-07 21:26:26 -08:00
}
privKey , err := ecdsa . GenerateKey ( elliptic . P256 ( ) , rand . Reader )
if err != nil {
return nil , err
}
var pemBuf bytes . Buffer
if err := encodeECDSAKey ( & pemBuf , privKey ) ; err != nil {
return nil , err
}
2023-03-22 15:09:30 -07:00
if err := cs . WriteACMEKey ( pemBuf . Bytes ( ) ) ; err != nil {
2022-11-07 21:26:26 -08:00
return nil , err
}
return privKey , nil
}
2023-07-13 14:29:59 -07:00
func acmeClient ( cs certStore ) ( * acme . Client , error ) {
key , err := acmeKey ( cs )
if err != nil {
return nil , fmt . Errorf ( "acmeKey: %w" , err )
}
// Note: if we add support for additional ACME providers (other than
// LetsEncrypt), we should make sure that they support ARI extension (see
// shouldStartDomainRenewalARI).
return & acme . Client {
2025-01-25 00:29:00 +00:00
Key : key ,
UserAgent : "tailscaled/" + version . Long ( ) ,
DirectoryURL : envknob . String ( "TS_DEBUG_ACME_DIRECTORY_URL" ) ,
2023-07-13 14:29:59 -07:00
} , nil
}
2023-02-07 14:34:04 -08:00
// validCertPEM reports whether the given certificate is valid for domain at now.
//
// If roots != nil, it is used instead of the system root pool. This is meant
2025-01-21 15:30:55 -08:00
// to support testing; production code should pass roots == nil.
2023-02-07 14:34:04 -08:00
func validCertPEM ( domain string , keyPEM , certPEM [ ] byte , roots * x509 . CertPool , now time . Time ) bool {
2022-11-07 21:26:26 -08:00
if len ( keyPEM ) == 0 || len ( certPEM ) == 0 {
return false
}
tlsCert , err := tls . X509KeyPair ( certPEM , keyPEM )
if err != nil {
return false
}
2023-02-07 14:34:04 -08:00
2022-11-07 21:26:26 -08:00
var leaf * x509 . Certificate
intermediates := x509 . NewCertPool ( )
for i , certDER := range tlsCert . Certificate {
cert , err := x509 . ParseCertificate ( certDER )
if err != nil {
return false
}
if i == 0 {
leaf = cert
} else {
intermediates . AddCert ( cert )
}
}
2025-01-21 15:30:55 -08:00
return validateLeaf ( leaf , intermediates , domain , now , roots )
}
// validateLeaf is a helper for [validCertPEM].
//
// If called with roots == nil, it will use the system root pool as well as the
// baked-in roots. If non-nil, only those roots are used.
func validateLeaf ( leaf * x509 . Certificate , intermediates * x509 . CertPool , domain string , now time . Time , roots * x509 . CertPool ) bool {
2022-11-07 21:26:26 -08:00
if leaf == nil {
return false
}
2025-01-21 15:30:55 -08:00
_ , err := leaf . Verify ( x509 . VerifyOptions {
2022-11-07 21:26:26 -08:00
DNSName : domain ,
CurrentTime : now ,
2023-02-07 14:34:04 -08:00
Roots : roots ,
2022-11-07 21:26:26 -08:00
Intermediates : intermediates ,
} )
2025-01-21 15:30:55 -08:00
if err != nil && roots == nil {
// If validation failed and they specified nil for roots (meaning to use
// the system roots), then give it another chance to validate using the
// binary's baked-in roots (LetsEncrypt). See tailscale/tailscale#14690.
return validateLeaf ( leaf , intermediates , domain , now , bakedroots . Get ( ) )
}
2025-03-11 07:09:46 -07:00
if err == nil {
return true
}
// When pointed at a non-prod ACME server, we don't expect to have the CA
// in our system or baked-in roots. Verify only throws UnknownAuthorityError
// after first checking the leaf cert's expiry, hostnames etc, so we know
// that the only reason for an error is to do with constructing a full chain.
// Allow this error so that cert caching still works in testing environments.
if errors . As ( err , & x509 . UnknownAuthorityError { } ) {
acmeURL := envknob . String ( "TS_DEBUG_ACME_DIRECTORY_URL" )
if ! isDefaultDirectoryURL ( acmeURL ) {
return true
}
}
return false
}
func isDefaultDirectoryURL ( u string ) bool {
return u == "" || u == acme . LetsEncryptURL
2022-11-07 21:26:26 -08:00
}
// validLookingCertDomain reports whether name looks like a valid domain name that
// we might be able to get a cert for.
//
// It's a light check primarily for double checking before it's used
// as part of a filesystem path. The actual validation happens in checkCertDomain.
func validLookingCertDomain ( name string ) bool {
if name == "" ||
strings . Contains ( name , ".." ) ||
strings . ContainsAny ( name , ":/\\\x00" ) ||
! strings . Contains ( name , "." ) {
return false
}
return true
}
func checkCertDomain ( st * ipnstate . Status , domain string ) error {
if domain == "" {
return errors . New ( "missing domain name" )
}
for _ , d := range st . CertDomains {
if d == domain {
return nil
}
}
2024-02-17 06:27:30 -08:00
if len ( st . CertDomains ) == 0 {
2022-11-07 21:26:26 -08:00
return errors . New ( "your Tailscale account does not support getting TLS certs" )
}
2024-02-17 06:27:30 -08:00
return fmt . Errorf ( "invalid domain %q; must be one of %q" , domain , st . CertDomains )
2022-11-07 21:26:26 -08:00
}