github.com/trustbloc/kms-go@v1.1.2/secretlock/local/local_secret_lock.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  SPDX-License-Identifier: Apache-2.0
     4  */
     5  
     6  package local
     7  
     8  import (
     9  	"crypto/cipher"
    10  	"encoding/base64"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"log"
    15  	"os"
    16  
    17  	"github.com/google/tink/go/subtle/random"
    18  
    19  	"github.com/trustbloc/kms-go/spi/secretlock"
    20  
    21  	cipherutil "github.com/trustbloc/kms-go/secretlock/local/internal/cipher"
    22  )
    23  
    24  // package local provides a local secret lock service. The user must create a master key and store it
    25  // in a local file or an environment variable prior to using this service.
    26  //
    27  // The user has the option to encrypt the master key using hkdf.NewMasterLock(passphrase, hash func(), salt)
    28  // found in the sub package masterlock/hkdf. There's also the option of using pbkdf2.NewMasterLock() instead of hkdf
    29  // which is located under masterlock/pbkdf2.
    30  //
    31  // This lock services uses the NIST approved AES-GCM 256 bit encryption as per NIST SP 800-38D.
    32  //
    33  // The user can then call either:
    34  //		MasterKeyFromPath(path) or
    35  //		MasterKeyFromEnv(envPrefix, keyURI)
    36  // to get an io.Reader instance needed to read the master key and create a keys Lock service.
    37  //
    38  // The content of the master key reader may be either raw bytes or base64URL encoded (by masterlock if protected or
    39  // manually if not). Base64URL encoding is useful when setting a master key in an environment variable as some OSs may
    40  // reject setting env variables with binary data as value. The service will attempt to base64URL decode the content of
    41  // reader first and if it fails, will try to create the service with the raw (binary) content.
    42  //
    43  // To get the lock service, call:
    44  // 		NewService(reader, secLock)
    45  // where reader is the instance returned from one of the MasterKeyFrom..() functions mentioned above
    46  // and secLock which is the masterKey lock used to encrypt/decrypt the master key. If secLock is nil
    47  // then the masterKey content in reader will be used as-is without being decrypted. The keys however are always
    48  // encrypted using the read masterKey.
    49  
    50  var logger = log.New(os.Stderr, " [kms-go/lock] ", log.Ldate|log.Ltime|log.LUTC)
    51  
    52  const masterKeyLen = 512
    53  
    54  // Lock is a secret lock service responsible for encrypting keys using a master key.
    55  type Lock struct {
    56  	aead cipher.AEAD
    57  }
    58  
    59  // NewService creates a new instance of local secret lock service using a master key in masterKeyReader.
    60  // If the masterKey is not protected (secLock=nil) this function will attempt to base64 URL Decode the
    61  // content of masterKeyReader and if it fails, then will attempt to create a secret lock cipher with the raw key as is.
    62  func NewService(masterKeyReader io.Reader, secLock secretlock.Service) (secretlock.Service, error) {
    63  	masterKeyData := make([]byte, masterKeyLen)
    64  
    65  	if masterKeyReader == nil {
    66  		return nil, fmt.Errorf("masterKeyReader is nil")
    67  	}
    68  
    69  	n, err := masterKeyReader.Read(masterKeyData)
    70  	if err != nil {
    71  		if !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF) {
    72  			return nil, err
    73  		}
    74  	}
    75  
    76  	if n == 0 {
    77  		return nil, fmt.Errorf("masterKeyReader is empty")
    78  	}
    79  
    80  	var masterKey []byte
    81  
    82  	// if secLock not empty, then masterKeyData is encrypted (protected), let's decrypt it first.
    83  	if secLock != nil {
    84  		decResponse, e := secLock.Decrypt("", &secretlock.DecryptRequest{
    85  			Ciphertext: string(masterKeyData[:n]),
    86  		})
    87  		if e != nil {
    88  			return nil, e
    89  		}
    90  
    91  		masterKey = []byte(decResponse.Plaintext)
    92  	} else {
    93  		// masterKeyData is not encrypted, base64URL decode it
    94  		masterKey, err = base64.URLEncoding.DecodeString(string(masterKeyData[:n]))
    95  		if err != nil {
    96  			// attempt to create a service using the key content from reader as is
    97  
    98  			masterKey = make([]byte, n)
    99  
   100  			// copy masterKey read from reader directly
   101  			copy(masterKey, masterKeyData)
   102  		}
   103  	}
   104  
   105  	// finally create the cipher to be used by the lock service
   106  	aead, err := cipherutil.CreateAESCipher(masterKey)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	return &Lock{aead: aead}, nil
   112  }
   113  
   114  // Encrypt a key in req using master key in the local secret lock service
   115  // (keyURI is used for remote locks, it is ignored by this implementation).
   116  func (s *Lock) Encrypt(keyURI string, req *secretlock.EncryptRequest) (*secretlock.EncryptResponse, error) {
   117  	nonce := random.GetRandomBytes(uint32(s.aead.NonceSize()))
   118  	ct := s.aead.Seal(nil, nonce, []byte(req.Plaintext), []byte(req.AdditionalAuthenticatedData))
   119  	ct = append(nonce, ct...)
   120  
   121  	return &secretlock.EncryptResponse{
   122  		Ciphertext: base64.URLEncoding.EncodeToString(ct),
   123  	}, nil
   124  }
   125  
   126  // Decrypt a key in req using master key in the local secret lock service
   127  // (keyURI is used for remote locks, it is ignored by this implementation).
   128  func (s *Lock) Decrypt(keyURI string, req *secretlock.DecryptRequest) (*secretlock.DecryptResponse, error) {
   129  	ct, err := base64.URLEncoding.DecodeString(req.Ciphertext)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  
   134  	nonceSize := uint32(s.aead.NonceSize())
   135  
   136  	// ensure ciphertext contains more than nonce+ciphertext (result from Encrypt())
   137  	if len(ct) <= int(nonceSize) {
   138  		return nil, fmt.Errorf("invalid request")
   139  	}
   140  
   141  	nonce := ct[0:nonceSize]
   142  	ct = ct[nonceSize:]
   143  
   144  	pt, err := s.aead.Open(nil, nonce, ct, []byte(req.AdditionalAuthenticatedData))
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	return &secretlock.DecryptResponse{Plaintext: string(pt)}, nil
   150  }