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

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  SPDX-License-Identifier: Apache-2.0
     4  */
     5  
     6  package hkdf
     7  
     8  import (
     9  	"crypto/cipher"
    10  	"crypto/sha256"
    11  	"encoding/base64"
    12  	"fmt"
    13  	"hash"
    14  	"io"
    15  
    16  	"github.com/google/tink/go/subtle/random"
    17  	"golang.org/x/crypto/hkdf"
    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 hkdf provides an hkdf implementation of secretlock as a masterlock.
    25  // See golang.org/x/crypto/hkdf/hkdf.go for IETF reference.
    26  // The IETF RFC in question is RFC 5869. It mentions the following paragraph in the introduction about NIST documents:
    27  // " Note that some existing KDF specifications, such as NIST Special
    28  //   Publication 800-56A [800-56A], NIST Special Publication 800-108
    29  //   [800-108] and IEEE Standard 1363a-2004 [1363a], either only consider
    30  //   the second stage (expanding a pseudorandom key), or do not explicitly
    31  //   differentiate between the "extract" and "expand" stages, often
    32  //   resulting in design shortcomings.  The goal of this specification is
    33  //   to accommodate a wide range of KDF requirements while minimizing the
    34  //   assumptions about the underlying hash function.  The "extract-then-
    35  //   expand" paradigm supports well this goal (see [HKDF-paper] for more
    36  //   information about the design rationale). "
    37  
    38  type masterLockHKDF struct {
    39  	h    func() hash.Hash
    40  	salt []byte
    41  	aead cipher.AEAD
    42  }
    43  
    44  // NewMasterLock is responsible for encrypting/decrypting with a master key expanded from a passphrase using HKDF
    45  // using `passphrase`, hash function `h`, `salt`.
    46  // The salt is optional and can be set to nil.
    47  // This implementation must not be used directly in Aries framework. It should be passed in
    48  // as the second argument to local secret lock service constructor:
    49  // `local.NewService(masterKeyReader io.Reader, secLock secretlock.Service)`.
    50  func NewMasterLock(passphrase string, h func() hash.Hash, salt []byte) (secretlock.Service, error) {
    51  	if passphrase == "" {
    52  		return nil, fmt.Errorf("passphrase is empty")
    53  	}
    54  
    55  	if h == nil {
    56  		return nil, fmt.Errorf("hash is nil")
    57  	}
    58  
    59  	size := h().Size()
    60  	if size > sha256.Size { // AEAD cipher requires at most sha256.Size
    61  		return nil, fmt.Errorf("hash size not supported")
    62  	}
    63  
    64  	// expand an encryption key from passphrase
    65  	expander := hkdf.New(h, []byte(passphrase), salt, nil)
    66  
    67  	masterKey := make([]byte, size)
    68  
    69  	_, err := io.ReadFull(expander, masterKey)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	aead, err := cipherutil.CreateAESCipher(masterKey)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	return &masterLockHKDF{
    80  		h:    h,
    81  		salt: salt,
    82  		aead: aead,
    83  	}, nil
    84  }
    85  
    86  // Encrypt a master key in req
    87  //
    88  //	(keyURI is used for remote locks, it is ignored by this implementation)
    89  func (m *masterLockHKDF) Encrypt(keyURI string, req *secretlock.EncryptRequest) (*secretlock.EncryptResponse, error) {
    90  	nonce := random.GetRandomBytes(uint32(m.aead.NonceSize()))
    91  	ct := m.aead.Seal(nil, nonce, []byte(req.Plaintext), []byte(req.AdditionalAuthenticatedData))
    92  	ct = append(nonce, ct...)
    93  
    94  	return &secretlock.EncryptResponse{
    95  		Ciphertext: base64.URLEncoding.EncodeToString(ct),
    96  	}, nil
    97  }
    98  
    99  // Decrypt a master key in req
   100  // (keyURI is used for remote locks, it is ignored by this implementation).
   101  func (m *masterLockHKDF) Decrypt(keyURI string, req *secretlock.DecryptRequest) (*secretlock.DecryptResponse, error) {
   102  	ct, err := base64.URLEncoding.DecodeString(req.Ciphertext)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	nonceSize := uint32(m.aead.NonceSize())
   108  
   109  	// ensure ciphertext contains more than nonce+ciphertext (result from Encrypt())
   110  	if len(ct) <= int(nonceSize) {
   111  		return nil, fmt.Errorf("invalid request")
   112  	}
   113  
   114  	nonce := ct[0:nonceSize]
   115  	ct = ct[nonceSize:]
   116  
   117  	pt, err := m.aead.Open(nil, nonce, ct, []byte(req.AdditionalAuthenticatedData))
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	return &secretlock.DecryptResponse{Plaintext: string(pt)}, nil
   123  }