github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/ca/keyreadwriter.go (about)

     1  package ca
     2  
     3  import (
     4  	"crypto/x509"
     5  	"encoding/pem"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"strconv"
    10  	"strings"
    11  	"sync"
    12  
    13  	"crypto/tls"
    14  
    15  	"github.com/docker/swarmkit/ca/keyutils"
    16  	"github.com/docker/swarmkit/ca/pkcs8"
    17  	"github.com/docker/swarmkit/ioutils"
    18  	"github.com/pkg/errors"
    19  )
    20  
    21  const (
    22  	// keyPerms are the permissions used to write the TLS keys
    23  	keyPerms = 0600
    24  	// certPerms are the permissions used to write TLS certificates
    25  	certPerms = 0644
    26  	// versionHeader is the TLS PEM key header that contains the KEK version
    27  	versionHeader = "kek-version"
    28  )
    29  
    30  // PEMKeyHeaders is an interface for something that needs to know about PEM headers
    31  // when reading or writing TLS keys in order to keep them updated with the latest
    32  // KEK.
    33  type PEMKeyHeaders interface {
    34  
    35  	// UnmarshalHeaders loads the headers map given the current KEK
    36  	UnmarshalHeaders(map[string]string, KEKData) (PEMKeyHeaders, error)
    37  
    38  	// MarshalHeaders returns a header map given the current KEK
    39  	MarshalHeaders(KEKData) (map[string]string, error)
    40  
    41  	// UpdateKEK gets called whenever KeyReadWriter gets a KEK update.  This allows the
    42  	// PEMKeyHeaders to optionally update any internal state.  It should return an
    43  	// updated (if needed) versino of itself.
    44  	UpdateKEK(KEKData, KEKData) PEMKeyHeaders
    45  }
    46  
    47  // KeyReader reads a TLS cert and key from disk
    48  type KeyReader interface {
    49  
    50  	// Read reads and returns the certificate and the key PEM bytes that are on disk
    51  	Read() ([]byte, []byte, error)
    52  
    53  	// Target returns a string representation of where the cert data is being read from
    54  	Target() string
    55  }
    56  
    57  // KeyWriter writes a TLS key and cert to disk
    58  type KeyWriter interface {
    59  
    60  	// Write accepts a certificate and key in PEM format, as well as an optional KEKData object.
    61  	// If there is a current KEK, the key is encrypted using the current KEK.  If the KEKData object
    62  	// is provided (not nil), the key will be encrypted using the new KEK instead, and the current
    63  	// KEK in memory will be replaced by the provided KEKData.  The reason to allow changing key
    64  	// material and KEK in a single step, as opposed to two steps, is to prevent the key material
    65  	// from being written unencrypted or with an old KEK in the first place (when a node gets a
    66  	// certificate from the CA, it will also request the current KEK so it won't have to immediately
    67  	// do a KEK rotation after getting the key).
    68  	Write([]byte, []byte, *KEKData) error
    69  
    70  	// ViewAndUpdateHeaders is a function that reads and updates the headers of the key in a single
    71  	// transaction (e.g. within a lock).  It accepts a callback function which will be passed the
    72  	// current header management object, and which must return a new, updated, or same header
    73  	// management object.  KeyReadWriter then performs the following actions:
    74  	//    - uses the old header management object and the current KEK to deserialize/decrypt
    75  	//      the existing PEM headers
    76  	//    - uses the new header management object and the current KEK to to reserialize/encrypt
    77  	//      the PEM headers
    78  	//    - writes the new PEM headers, as well as the key material, unchanged, to disk
    79  	ViewAndUpdateHeaders(func(PEMKeyHeaders) (PEMKeyHeaders, error)) error
    80  
    81  	// ViewAndRotateKEK is a function that just re-encrypts the TLS key and headers in a single
    82  	// transaction (e.g. within a lock).  It accepts a callback unction which will be passed the
    83  	// current KEK and the current headers management object, and which should return a new
    84  	// KEK and header management object.  KeyReadWriter then performs the following actions:
    85  	//    - uses the old KEK and header management object to deserialize/decrypt the
    86  	//      TLS key and PEM headers
    87  	//    - uses the new KEK and header management object to serialize/encrypt the TLS key
    88  	//      and PEM headers
    89  	//    - writes the new PEM headers and newly encrypted TLS key to disk
    90  	ViewAndRotateKEK(func(KEKData, PEMKeyHeaders) (KEKData, PEMKeyHeaders, error)) error
    91  
    92  	// GetCurrentState returns the current header management object and the current KEK.
    93  	GetCurrentState() (PEMKeyHeaders, KEKData)
    94  
    95  	// Target returns a string representation of where the cert data is being read from
    96  	Target() string
    97  }
    98  
    99  // KEKData provides an optional update to the kek when writing.  The structure
   100  // is needed so that we can tell the difference between "do not encrypt anymore"
   101  // and there is "no update".
   102  type KEKData struct {
   103  	KEK     []byte
   104  	Version uint64
   105  }
   106  
   107  // ErrInvalidKEK means that we cannot decrypt the TLS key for some reason
   108  type ErrInvalidKEK struct {
   109  	Wrapped error
   110  }
   111  
   112  func (e ErrInvalidKEK) Error() string {
   113  	return e.Wrapped.Error()
   114  }
   115  
   116  // KeyReadWriter is an object that knows how to read and write TLS keys and certs to disk,
   117  // optionally encrypted and optionally updating PEM headers.  It should be the only object which
   118  // can write the TLS key, to ensure that writes are serialized and that the TLS key, the
   119  // KEK (key encrypting key), and any headers which need to be written are never out of sync.
   120  // It accepts a PEMKeyHeaders object, which is used to serialize/encrypt and deserialize/decrypt
   121  // the PEM headers when given the current headers and the current KEK.
   122  type KeyReadWriter struct {
   123  
   124  	// This lock is held whenever a key is read from or written to disk, or whenever the internal
   125  	// state of the KeyReadWriter (such as the KEK, the key formatter, or the PEM header management
   126  	// object changes.)
   127  	mu sync.Mutex
   128  
   129  	kekData      KEKData
   130  	paths        CertPaths
   131  	headersObj   PEMKeyHeaders
   132  	keyFormatter keyutils.Formatter
   133  }
   134  
   135  // NewKeyReadWriter creates a new KeyReadWriter
   136  func NewKeyReadWriter(paths CertPaths, kek []byte, headersObj PEMKeyHeaders) *KeyReadWriter {
   137  	return &KeyReadWriter{
   138  		kekData:      KEKData{KEK: kek},
   139  		paths:        paths,
   140  		headersObj:   headersObj,
   141  		keyFormatter: keyutils.Default,
   142  	}
   143  }
   144  
   145  // SetKeyFormatter sets the keyformatter with which to encrypt and decrypt keys
   146  func (k *KeyReadWriter) SetKeyFormatter(kf keyutils.Formatter) {
   147  	k.mu.Lock()
   148  	defer k.mu.Unlock()
   149  	k.keyFormatter = kf
   150  }
   151  
   152  // Migrate checks to see if a temporary key file exists.  Older versions of
   153  // swarmkit wrote temporary keys instead of temporary certificates, so
   154  // migrate that temporary key if it exists.  We want to write temporary certificates,
   155  // instead of temporary keys, because we may need to periodically re-encrypt the
   156  // keys and modify the headers, and it's easier to have a single canonical key
   157  // location than two possible key locations.
   158  func (k *KeyReadWriter) Migrate() error {
   159  	tmpPaths := k.genTempPaths()
   160  	keyBytes, err := ioutil.ReadFile(tmpPaths.Key)
   161  	if err != nil {
   162  		return nil // no key?  no migration
   163  	}
   164  
   165  	// it does exist - no need to decrypt, because previous versions of swarmkit
   166  	// which supported this temporary key did not support encrypting TLS keys
   167  	cert, err := ioutil.ReadFile(k.paths.Cert)
   168  	if err != nil {
   169  		return os.RemoveAll(tmpPaths.Key) // no cert?  no migration
   170  	}
   171  
   172  	// nope, this does not match the cert
   173  	if _, err = tls.X509KeyPair(cert, keyBytes); err != nil {
   174  		return os.RemoveAll(tmpPaths.Key)
   175  	}
   176  
   177  	return os.Rename(tmpPaths.Key, k.paths.Key)
   178  }
   179  
   180  // Read will read a TLS cert and key from the given paths
   181  func (k *KeyReadWriter) Read() ([]byte, []byte, error) {
   182  	k.mu.Lock()
   183  	defer k.mu.Unlock()
   184  	keyBlock, err := k.readKey()
   185  	if err != nil {
   186  		return nil, nil, err
   187  	}
   188  
   189  	if version, ok := keyBlock.Headers[versionHeader]; ok {
   190  		if versionInt, err := strconv.ParseUint(version, 10, 64); err == nil {
   191  			k.kekData.Version = versionInt
   192  		}
   193  	}
   194  	delete(keyBlock.Headers, versionHeader)
   195  
   196  	if k.headersObj != nil {
   197  		newHeaders, err := k.headersObj.UnmarshalHeaders(keyBlock.Headers, k.kekData)
   198  		if err != nil {
   199  			return nil, nil, errors.Wrap(err, "unable to read TLS key headers")
   200  		}
   201  		k.headersObj = newHeaders
   202  	}
   203  
   204  	keyBytes := pem.EncodeToMemory(keyBlock)
   205  	cert, err := ioutil.ReadFile(k.paths.Cert)
   206  	// The cert is written to a temporary file first, then the key, and then
   207  	// the cert gets renamed - so, if interrupted, it's possible to end up with
   208  	// a cert that only exists in the temporary location.
   209  	switch {
   210  	case err == nil:
   211  		_, err = tls.X509KeyPair(cert, keyBytes)
   212  	case os.IsNotExist(err): //continue to try temp location
   213  		break
   214  	default:
   215  		return nil, nil, err
   216  	}
   217  
   218  	// either the cert doesn't exist, or it doesn't match the key - try the temp file, if it exists
   219  	if err != nil {
   220  		var tempErr error
   221  		tmpPaths := k.genTempPaths()
   222  		cert, tempErr = ioutil.ReadFile(tmpPaths.Cert)
   223  		if tempErr != nil {
   224  			return nil, nil, err // return the original error
   225  		}
   226  		if _, tempErr := tls.X509KeyPair(cert, keyBytes); tempErr != nil {
   227  			os.RemoveAll(tmpPaths.Cert) // nope, it doesn't match either - remove and return the original error
   228  			return nil, nil, err
   229  		}
   230  		os.Rename(tmpPaths.Cert, k.paths.Cert) // try to move the temp cert back to the regular location
   231  
   232  	}
   233  
   234  	return cert, keyBytes, nil
   235  }
   236  
   237  // ViewAndRotateKEK re-encrypts the key with a new KEK
   238  func (k *KeyReadWriter) ViewAndRotateKEK(cb func(KEKData, PEMKeyHeaders) (KEKData, PEMKeyHeaders, error)) error {
   239  	k.mu.Lock()
   240  	defer k.mu.Unlock()
   241  
   242  	updatedKEK, updatedHeaderObj, err := cb(k.kekData, k.headersObj)
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	keyBlock, err := k.readKey()
   248  	if err != nil {
   249  		return err
   250  	}
   251  
   252  	return k.writeKey(keyBlock, updatedKEK, updatedHeaderObj)
   253  }
   254  
   255  // ViewAndUpdateHeaders updates the header manager, and updates any headers on the existing key
   256  func (k *KeyReadWriter) ViewAndUpdateHeaders(cb func(PEMKeyHeaders) (PEMKeyHeaders, error)) error {
   257  	k.mu.Lock()
   258  	defer k.mu.Unlock()
   259  
   260  	pkh, err := cb(k.headersObj)
   261  	if err != nil {
   262  		return err
   263  	}
   264  
   265  	keyBlock, err := k.readKeyblock()
   266  	if err != nil {
   267  		return err
   268  	}
   269  
   270  	headers := make(map[string]string)
   271  	if pkh != nil {
   272  		var err error
   273  		headers, err = pkh.MarshalHeaders(k.kekData)
   274  		if err != nil {
   275  			return err
   276  		}
   277  	}
   278  	// we WANT any original encryption headers
   279  	for key, value := range keyBlock.Headers {
   280  		normalizedKey := strings.TrimSpace(strings.ToLower(key))
   281  		if normalizedKey == "proc-type" || normalizedKey == "dek-info" {
   282  			headers[key] = value
   283  		}
   284  	}
   285  	headers[versionHeader] = strconv.FormatUint(k.kekData.Version, 10)
   286  	keyBlock.Headers = headers
   287  
   288  	if err = ioutils.AtomicWriteFile(k.paths.Key, pem.EncodeToMemory(keyBlock), keyPerms); err != nil {
   289  		return err
   290  	}
   291  	k.headersObj = pkh
   292  	return nil
   293  }
   294  
   295  // GetCurrentState returns the current KEK data, including version
   296  func (k *KeyReadWriter) GetCurrentState() (PEMKeyHeaders, KEKData) {
   297  	k.mu.Lock()
   298  	defer k.mu.Unlock()
   299  	return k.headersObj, k.kekData
   300  }
   301  
   302  // Write attempts write a cert and key to text.  This can also optionally update
   303  // the KEK while writing, if an updated KEK is provided.  If the pointer to the
   304  // update KEK is nil, then we don't update. If the updated KEK itself is nil,
   305  // then we update the KEK to be nil (data should be unencrypted).
   306  func (k *KeyReadWriter) Write(certBytes, plaintextKeyBytes []byte, kekData *KEKData) error {
   307  	k.mu.Lock()
   308  	defer k.mu.Unlock()
   309  
   310  	// current assumption is that the cert and key will be in the same directory
   311  	if err := os.MkdirAll(filepath.Dir(k.paths.Key), 0755); err != nil {
   312  		return err
   313  	}
   314  
   315  	// Ensure that we will have a keypair on disk at all times by writing the cert to a
   316  	// temp path first.  This is because we want to have only a single copy of the key
   317  	// for rotation and header modification.
   318  	tmpPaths := k.genTempPaths()
   319  	if err := ioutils.AtomicWriteFile(tmpPaths.Cert, certBytes, certPerms); err != nil {
   320  		return err
   321  	}
   322  
   323  	keyBlock, _ := pem.Decode(plaintextKeyBytes)
   324  	if keyBlock == nil {
   325  		return errors.New("invalid PEM-encoded private key")
   326  	}
   327  
   328  	if kekData == nil {
   329  		kekData = &k.kekData
   330  	}
   331  	pkh := k.headersObj
   332  	if k.headersObj != nil {
   333  		pkh = k.headersObj.UpdateKEK(k.kekData, *kekData)
   334  	}
   335  
   336  	if err := k.writeKey(keyBlock, *kekData, pkh); err != nil {
   337  		return err
   338  	}
   339  	return os.Rename(tmpPaths.Cert, k.paths.Cert)
   340  }
   341  
   342  func (k *KeyReadWriter) genTempPaths() CertPaths {
   343  	return CertPaths{
   344  		Key:  filepath.Join(filepath.Dir(k.paths.Key), "."+filepath.Base(k.paths.Key)),
   345  		Cert: filepath.Join(filepath.Dir(k.paths.Cert), "."+filepath.Base(k.paths.Cert)),
   346  	}
   347  }
   348  
   349  // Target returns a string representation of this KeyReadWriter, namely where
   350  // it is writing to
   351  func (k *KeyReadWriter) Target() string {
   352  	return k.paths.Cert
   353  }
   354  
   355  func (k *KeyReadWriter) readKeyblock() (*pem.Block, error) {
   356  	key, err := ioutil.ReadFile(k.paths.Key)
   357  	if err != nil {
   358  		return nil, err
   359  	}
   360  
   361  	// Decode the PEM private key
   362  	keyBlock, _ := pem.Decode(key)
   363  	if keyBlock == nil {
   364  		return nil, errors.New("invalid PEM-encoded private key")
   365  	}
   366  
   367  	return keyBlock, nil
   368  }
   369  
   370  // readKey returns the decrypted key pem bytes, and enforces the KEK if applicable
   371  // (writes it back with the correct encryption if it is not correctly encrypted)
   372  func (k *KeyReadWriter) readKey() (*pem.Block, error) {
   373  	keyBlock, err := k.readKeyblock()
   374  	if err != nil {
   375  		return nil, err
   376  	}
   377  
   378  	if !keyutils.IsEncryptedPEMBlock(keyBlock) {
   379  		return keyBlock, nil
   380  	}
   381  
   382  	// If it's encrypted, we can't read without a passphrase (we're assuming
   383  	// empty passphrases are invalid)
   384  	if k.kekData.KEK == nil {
   385  		return nil, ErrInvalidKEK{Wrapped: x509.IncorrectPasswordError}
   386  	}
   387  
   388  	derBytes, err := k.keyFormatter.DecryptPEMBlock(keyBlock, k.kekData.KEK)
   389  	if err == keyutils.ErrFIPSUnsupportedKeyFormat {
   390  		return nil, err
   391  	} else if err != nil {
   392  		return nil, ErrInvalidKEK{Wrapped: err}
   393  	}
   394  
   395  	// change header only if its pkcs8
   396  	if keyBlock.Type == "ENCRYPTED PRIVATE KEY" {
   397  		keyBlock.Type = "PRIVATE KEY"
   398  	}
   399  
   400  	// remove encryption PEM headers
   401  	headers := make(map[string]string)
   402  	mergePEMHeaders(headers, keyBlock.Headers)
   403  
   404  	return &pem.Block{
   405  		Type:    keyBlock.Type, // the key type doesn't change
   406  		Bytes:   derBytes,
   407  		Headers: headers,
   408  	}, nil
   409  }
   410  
   411  // writeKey takes an unencrypted keyblock and, if the kek is not nil, encrypts it before
   412  // writing it to disk.  If the kek is nil, writes it to disk unencrypted.
   413  func (k *KeyReadWriter) writeKey(keyBlock *pem.Block, kekData KEKData, pkh PEMKeyHeaders) error {
   414  	if kekData.KEK != nil {
   415  		encryptedPEMBlock, err := k.keyFormatter.EncryptPEMBlock(keyBlock.Bytes, kekData.KEK)
   416  		if err != nil {
   417  			return err
   418  		}
   419  		if !keyutils.IsEncryptedPEMBlock(encryptedPEMBlock) {
   420  			return errors.New("unable to encrypt key - invalid PEM file produced")
   421  		}
   422  		keyBlock = encryptedPEMBlock
   423  	}
   424  
   425  	if pkh != nil {
   426  		headers, err := pkh.MarshalHeaders(kekData)
   427  		if err != nil {
   428  			return err
   429  		}
   430  		mergePEMHeaders(keyBlock.Headers, headers)
   431  	}
   432  	keyBlock.Headers[versionHeader] = strconv.FormatUint(kekData.Version, 10)
   433  
   434  	if err := ioutils.AtomicWriteFile(k.paths.Key, pem.EncodeToMemory(keyBlock), keyPerms); err != nil {
   435  		return err
   436  	}
   437  	k.kekData = kekData
   438  	k.headersObj = pkh
   439  	return nil
   440  }
   441  
   442  // DowngradeKey converts the PKCS#8 key to PKCS#1 format and save it
   443  func (k *KeyReadWriter) DowngradeKey() error {
   444  	_, key, err := k.Read()
   445  	if err != nil {
   446  		return err
   447  	}
   448  
   449  	oldBlock, _ := pem.Decode(key)
   450  	if oldBlock == nil {
   451  		return errors.New("invalid PEM-encoded private key")
   452  	}
   453  
   454  	// stop if the key is already downgraded to pkcs1
   455  	if !keyutils.IsPKCS8(oldBlock.Bytes) {
   456  		return errors.New("key is already downgraded to PKCS#1")
   457  	}
   458  
   459  	eckey, err := pkcs8.ConvertToECPrivateKeyPEM(key)
   460  	if err != nil {
   461  		return err
   462  	}
   463  
   464  	newBlock, _ := pem.Decode(eckey)
   465  	if newBlock == nil {
   466  		return errors.New("invalid PEM-encoded private key")
   467  	}
   468  
   469  	if k.kekData.KEK != nil {
   470  		newBlock, err = k.keyFormatter.EncryptPEMBlock(newBlock.Bytes, k.kekData.KEK)
   471  		if err != nil {
   472  			return err
   473  		}
   474  	}
   475  
   476  	// add kek-version header back to the new key
   477  	newBlock.Headers[versionHeader] = strconv.FormatUint(k.kekData.Version, 10)
   478  	mergePEMHeaders(newBlock.Headers, oldBlock.Headers)
   479  
   480  	// do not use krw.Write as it will convert the key to pkcs8
   481  	return ioutils.AtomicWriteFile(k.paths.Key, pem.EncodeToMemory(newBlock), keyPerms)
   482  }
   483  
   484  // merges one set of PEM headers onto another, excepting for key encryption value
   485  // "proc-type" and "dek-info"
   486  func mergePEMHeaders(original, newSet map[string]string) {
   487  	for key, value := range newSet {
   488  		normalizedKey := strings.TrimSpace(strings.ToLower(key))
   489  		if normalizedKey != "proc-type" && normalizedKey != "dek-info" {
   490  			original[key] = value
   491  		}
   492  	}
   493  }