github.com/trustbloc/kms-go@v1.1.2/kms/webkms/crypto_box_test.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package webkms
     8  
     9  import (
    10  	"crypto/ed25519"
    11  	"crypto/rand"
    12  	"encoding/json"
    13  	"errors"
    14  	"fmt"
    15  	"io/ioutil"
    16  	"net/http"
    17  	"strings"
    18  	"testing"
    19  
    20  	"github.com/google/tink/go/subtle/random"
    21  	"github.com/stretchr/testify/require"
    22  	"golang.org/x/crypto/nacl/box"
    23  
    24  	"github.com/trustbloc/kms-go/util/cryptoutil"
    25  
    26  	mockkms "github.com/trustbloc/kms-go/mock/kms"
    27  )
    28  
    29  func TestNewRemoteCryptoBox(t *testing.T) {
    30  	hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
    31  
    32  	server, url, client := CreateMockHTTPServerAndClient(t, hf)
    33  	defaultKeystoreURL := fmt.Sprintf("%s/%s", strings.ReplaceAll(KeystoreEndpoint,
    34  		"{serverEndpoint}", url), defaultKeyStoreID)
    35  
    36  	defer func() {
    37  		e := server.Close()
    38  		require.NoError(t, e)
    39  	}()
    40  
    41  	t.Run("create new cryptoBox with non remote kms implementation", func(t *testing.T) {
    42  		_, err := NewCryptoBox(&mockkms.KeyManager{})
    43  		require.EqualError(t, err, "cannot use parameter argument as KMS")
    44  	})
    45  
    46  	remoteKMS := New(defaultKeystoreURL, client)
    47  
    48  	_, err := NewCryptoBox(remoteKMS)
    49  	require.NoError(t, err)
    50  }
    51  
    52  func TestSealAndSealOpen(t *testing.T) {
    53  	recPubKey, recPrivKey, err := ed25519.GenerateKey(rand.Reader)
    54  
    55  	hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    56  		err = processPOSTSealOpenRequest(w, r, recPubKey, recPrivKey)
    57  		require.NoError(t, err)
    58  	})
    59  
    60  	server, url, client := CreateMockHTTPServerAndClient(t, hf)
    61  	defaultKeystoreURL := fmt.Sprintf("%s/%s", strings.ReplaceAll(KeystoreEndpoint,
    62  		"{serverEndpoint}", url), defaultKeyStoreID)
    63  
    64  	defer func() {
    65  		e := server.Close()
    66  		require.NoError(t, e)
    67  	}()
    68  
    69  	rKMS := New(defaultKeystoreURL, client)
    70  
    71  	cryptoBox, err := NewCryptoBox(rKMS)
    72  	require.NoError(t, err)
    73  
    74  	payload := []byte("loremipsum")
    75  	recEncPub, err := cryptoutil.PublicEd25519toCurve25519(recPubKey)
    76  	require.NoError(t, err)
    77  
    78  	cipherText, err := cryptoBox.Seal(payload, recEncPub, rand.Reader)
    79  	require.NoError(t, err)
    80  
    81  	decPayload, err := cryptoBox.SealOpen(cipherText, recPubKey)
    82  	require.NoError(t, err)
    83  	require.EqualValues(t, payload, decPayload)
    84  
    85  	t.Run("SealOpen Post fail", func(t *testing.T) {
    86  		blankClient := &http.Client{}
    87  		rKMS1 := New(defaultKeystoreURL, blankClient)
    88  
    89  		cBox, e := NewCryptoBox(rKMS1)
    90  		require.NoError(t, e)
    91  
    92  		_, e = cBox.SealOpen([]byte("mock cipherText"), recPubKey)
    93  		require.Contains(t, e.Error(), "posting SealOpen failed ")
    94  	})
    95  
    96  	t.Run("SealOpen API error", func(t *testing.T) {
    97  		_hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    98  			w.WriteHeader(http.StatusInternalServerError)
    99  			_, err = w.Write([]byte(`{"errMessage": "api error msg"}`))
   100  			require.NoError(t, err)
   101  		})
   102  
   103  		srv, _url, _client := CreateMockHTTPServerAndClient(t, _hf)
   104  
   105  		defer func() { require.NoError(t, srv.Close()) }()
   106  
   107  		tmpKMS := New(_url, _client)
   108  
   109  		var cBox *CryptoBox
   110  		cBox, err = NewCryptoBox(tmpKMS)
   111  		require.NoError(t, err)
   112  
   113  		_, err = cBox.SealOpen([]byte("mock cipherText"), recPubKey)
   114  		require.Contains(t, err.Error(), "api error msg")
   115  	})
   116  
   117  	t.Run("SealOpen fail to Marshal/UnMarshal", func(t *testing.T) {
   118  		rKMS.marshalFunc = failingMarshal
   119  		_, err = cryptoBox.SealOpen(cipherText, recPubKey)
   120  		require.Contains(t, err.Error(), "failed to marshal SealOpen request")
   121  		require.Contains(t, err.Error(), errFailingMarshal.Error())
   122  
   123  		rKMS.marshalFunc = json.Marshal
   124  		rKMS.unmarshalFunc = failingUnmarshal
   125  		_, err = cryptoBox.SealOpen(cipherText, recPubKey)
   126  		require.Contains(t, err.Error(), "unmarshal plaintext for SealOpen failed")
   127  		require.Contains(t, err.Error(), errFailingUnmarshal.Error())
   128  	})
   129  }
   130  
   131  // nolint:gocyclo // test code
   132  func processPOSTSealOpenRequest(w http.ResponseWriter, r *http.Request, recipientPubKey ed25519.PublicKey,
   133  	recipientPrivKey ed25519.PrivateKey) error {
   134  	if valid := validateHTTPMethod(w, r); !valid {
   135  		return errors.New("http method invalid")
   136  	}
   137  
   138  	if valid := validatePostPayload(r, w); !valid {
   139  		return errors.New("http request body invalid")
   140  	}
   141  
   142  	destination := "https://" + r.Host + r.URL.Path
   143  
   144  	// nolint:nestif // test code
   145  	if strings.LastIndex(r.URL.Path, unwrapURL) == len(r.URL.Path)-len(unwrapURL) {
   146  		reqBody, err := ioutil.ReadAll(r.Body)
   147  		if err != nil {
   148  			return fmt.Errorf("read ciphertext request for SealOpen failed [%s, %w]", destination, err)
   149  		}
   150  
   151  		defer closeResponseBody(r.Body, "MockServer-SealOpen")
   152  
   153  		httpReq := &sealOpenReq{}
   154  
   155  		err = json.Unmarshal(reqBody, httpReq)
   156  		if err != nil {
   157  			return fmt.Errorf("unmarshal SealOpen failed [%s, %w]", destination, err)
   158  		}
   159  
   160  		pkBytes := make([]byte, ed25519.PrivateKeySize)
   161  		copy(pkBytes, recipientPrivKey)
   162  
   163  		recipientEncPriv, err := cryptoutil.SecretEd25519toCurve25519(pkBytes)
   164  		if err != nil {
   165  			return fmt.Errorf("failed to convert Ed25519 to Curve25519 private key for SealOpen [%s, %w]",
   166  				destination, err)
   167  		}
   168  
   169  		cipherText := httpReq.Ciphertext
   170  
   171  		var (
   172  			epk  [cryptoutil.Curve25519KeySize]byte
   173  			priv [cryptoutil.Curve25519KeySize]byte
   174  		)
   175  
   176  		copy(epk[:], cipherText[:cryptoutil.Curve25519KeySize])
   177  		copy(priv[:], recipientEncPriv)
   178  
   179  		recEncPub, err := cryptoutil.PublicEd25519toCurve25519(recipientPubKey)
   180  		if err != nil {
   181  			return fmt.Errorf("sealOpen: failed to convert pub Ed25519 to X25519 key: %w", err)
   182  		}
   183  
   184  		nonce, err := cryptoutil.Nonce(epk[:], recEncPub)
   185  		if err != nil {
   186  			return err
   187  		}
   188  
   189  		out, success := box.Open(nil, cipherText[cryptoutil.Curve25519KeySize:], nonce, &epk, &priv)
   190  		if !success {
   191  			return errors.New("failed to unpack")
   192  		}
   193  
   194  		resp := &sealOpenResp{
   195  			Plaintext: out,
   196  		}
   197  
   198  		mResp, err := json.Marshal(resp)
   199  		if err != nil {
   200  			return err
   201  		}
   202  
   203  		_, err = w.Write(mResp)
   204  		if err != nil {
   205  			return err
   206  		}
   207  	}
   208  
   209  	return nil
   210  }
   211  
   212  func TestEasyAndEasyOpen(t *testing.T) {
   213  	recPubKey, recPrivKey, err := ed25519.GenerateKey(rand.Reader)
   214  	require.NoError(t, err)
   215  
   216  	senderPubKey, senderPrivKey, err := ed25519.GenerateKey(rand.Reader)
   217  	require.NoError(t, err)
   218  
   219  	hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   220  		err = processPOSTEasyOpenRequest(w, r, recPrivKey, senderPrivKey)
   221  		require.NoError(t, err)
   222  	})
   223  
   224  	server, url, client := CreateMockHTTPServerAndClient(t, hf)
   225  	defaultKeystoreURL := fmt.Sprintf("%s/%s", strings.ReplaceAll(KeystoreEndpoint,
   226  		"{serverEndpoint}", url), defaultKeyStoreID)
   227  
   228  	defer func() {
   229  		e := server.Close()
   230  		require.NoError(t, e)
   231  	}()
   232  
   233  	rKMS := New(defaultKeystoreURL, client)
   234  
   235  	cryptoBox, err := NewCryptoBox(rKMS)
   236  	require.NoError(t, err)
   237  
   238  	payload := []byte("loremipsum")
   239  	nonce := random.GetRandomBytes(uint32(cryptoutil.NonceSize))
   240  
   241  	recEncPub, err := cryptoutil.PublicEd25519toCurve25519(recPubKey)
   242  	require.NoError(t, err)
   243  
   244  	cipherText, err := cryptoBox.Easy(payload, nonce, recEncPub, defaultKID)
   245  	require.NoError(t, err)
   246  
   247  	senderEncPub, err := cryptoutil.PublicEd25519toCurve25519(senderPubKey)
   248  	require.NoError(t, err)
   249  
   250  	decPayload, err := cryptoBox.EasyOpen(cipherText, nonce, senderEncPub, recPubKey)
   251  	require.NoError(t, err)
   252  	require.EqualValues(t, payload, decPayload)
   253  
   254  	t.Run("Easy/EasyOpen Post fail", func(t *testing.T) {
   255  		blankClient := &http.Client{}
   256  		rKMS1 := New(defaultKeystoreURL, blankClient)
   257  
   258  		cBox, e := NewCryptoBox(rKMS1)
   259  		require.NoError(t, e)
   260  
   261  		_, e = cBox.Easy(payload, nonce, recEncPub, defaultKID)
   262  		require.Contains(t, e.Error(), "posting Easy request failed ")
   263  
   264  		_, e = cBox.EasyOpen([]byte("mock cipherText"), []byte("mock nonce"), senderEncPub, recPubKey)
   265  		require.Contains(t, e.Error(), "posting EasyOpen failed ")
   266  	})
   267  
   268  	t.Run("Easy/EasyOpen API error", func(t *testing.T) {
   269  		_hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   270  			w.WriteHeader(http.StatusInternalServerError)
   271  			_, err = w.Write([]byte(`{"errMessage": "api error msg"}`))
   272  			require.NoError(t, err)
   273  		})
   274  
   275  		srv, _url, _client := CreateMockHTTPServerAndClient(t, _hf)
   276  
   277  		defer func() { require.NoError(t, srv.Close()) }()
   278  
   279  		tmpKMS := New(_url, _client)
   280  
   281  		var cBox *CryptoBox
   282  		cBox, err = NewCryptoBox(tmpKMS)
   283  		require.NoError(t, err)
   284  
   285  		_, err = cBox.Easy(payload, nonce, recEncPub, defaultKID)
   286  		require.Contains(t, err.Error(), "api error msg")
   287  
   288  		_, err = cBox.EasyOpen(cipherText, nonce, senderEncPub, recPubKey)
   289  		require.Contains(t, err.Error(), "api error msg")
   290  	})
   291  
   292  	t.Run("Easy/EasyOpen fail to Marshal/UnMarshal", func(t *testing.T) {
   293  		rKMS.marshalFunc = failingMarshal
   294  		_, err = cryptoBox.Easy(payload, nonce, recEncPub, defaultKID)
   295  		require.Contains(t, err.Error(), "failed to marshal Easy request")
   296  		require.Contains(t, err.Error(), errFailingMarshal.Error())
   297  
   298  		_, err = cryptoBox.EasyOpen([]byte("mock cipherText"), []byte("mock nonce"), senderEncPub, recPubKey)
   299  		require.Contains(t, err.Error(), "failed to marshal EasyOpen request")
   300  		require.Contains(t, err.Error(), errFailingMarshal.Error())
   301  
   302  		rKMS.marshalFunc = json.Marshal
   303  		rKMS.unmarshalFunc = failingUnmarshal
   304  		_, err = cryptoBox.Easy(payload, nonce, recEncPub, defaultKID)
   305  		require.Contains(t, err.Error(), "unmarshal ciphertext for Easy failed")
   306  		require.Contains(t, err.Error(), errFailingUnmarshal.Error())
   307  
   308  		_, err = cryptoBox.EasyOpen(cipherText, nonce, senderEncPub, recPubKey)
   309  		require.Contains(t, err.Error(), "unmarshal plaintext for EasyOpen failed")
   310  		require.Contains(t, err.Error(), errFailingUnmarshal.Error())
   311  	})
   312  }
   313  
   314  // nolint:gocyclo // test code
   315  func processPOSTEasyOpenRequest(w http.ResponseWriter, r *http.Request, recPrivKey, sPrivKey ed25519.PrivateKey) error {
   316  	if valid := validateHTTPMethod(w, r); !valid {
   317  		return errors.New("http method invalid")
   318  	}
   319  
   320  	if valid := validatePostPayload(r, w); !valid {
   321  		return errors.New("http request body invalid")
   322  	}
   323  
   324  	destination := "https://" + r.Host + r.URL.Path
   325  
   326  	// nolint:nestif // test code
   327  	if strings.LastIndex(r.URL.Path, wrapURL) == len(r.URL.Path)-len(wrapURL) {
   328  		reqBody, err := ioutil.ReadAll(r.Body)
   329  		if err != nil {
   330  			return fmt.Errorf("read ciphertext request for EasyOpen failed [%s, %w]", destination, err)
   331  		}
   332  
   333  		defer closeResponseBody(r.Body, "MockServer-Easy")
   334  
   335  		httpReq := &easyReq{}
   336  
   337  		err = json.Unmarshal(reqBody, httpReq)
   338  		if err != nil {
   339  			return fmt.Errorf("unmarshal EasyOpen failed [%s, %w]", destination, err)
   340  		}
   341  
   342  		payload := httpReq.Payload
   343  		nonceReq := httpReq.Nonce
   344  		recEncPub := httpReq.TheirPub
   345  
   346  		var (
   347  			recPubBytes [cryptoutil.Curve25519KeySize]byte
   348  			priv        [cryptoutil.Curve25519KeySize]byte
   349  			nonceBytes  [cryptoutil.NonceSize]byte
   350  		)
   351  
   352  		senderEncPriv, err := cryptoutil.SecretEd25519toCurve25519(sPrivKey)
   353  		if err != nil {
   354  			return fmt.Errorf("failed to convert Ed25519 to Curve25519 private key for Easy [%s, %w]",
   355  				destination, err)
   356  		}
   357  
   358  		copy(priv[:], senderEncPriv)
   359  		copy(recPubBytes[:], recEncPub)
   360  		copy(nonceBytes[:], nonceReq)
   361  
   362  		out := box.Seal(nil, payload, &nonceBytes, &recPubBytes, &priv)
   363  
   364  		resp := &easyResp{
   365  			Ciphertext: out,
   366  		}
   367  
   368  		mResp, err := json.Marshal(resp)
   369  		if err != nil {
   370  			return err
   371  		}
   372  
   373  		_, err = w.Write(mResp)
   374  		if err != nil {
   375  			return err
   376  		}
   377  	}
   378  
   379  	// nolint:nestif // test code
   380  	if strings.LastIndex(r.URL.Path, unwrapURL) == len(r.URL.Path)-len(unwrapURL) {
   381  		reqBody, err := ioutil.ReadAll(r.Body)
   382  		if err != nil {
   383  			return fmt.Errorf("read ciphertext request for EasyOpen failed [%s, %w]", destination, err)
   384  		}
   385  
   386  		defer closeResponseBody(r.Body, "MockServer-EasyOpen")
   387  
   388  		httpReq := &easyOpenReq{}
   389  
   390  		err = json.Unmarshal(reqBody, httpReq)
   391  		if err != nil {
   392  			return fmt.Errorf("unmarshal EasyOpen failed [%s, %w]", destination, err)
   393  		}
   394  
   395  		pkBytes := make([]byte, ed25519.PrivateKeySize)
   396  		copy(pkBytes, recPrivKey)
   397  
   398  		recipientEncPriv, err := cryptoutil.SecretEd25519toCurve25519(pkBytes)
   399  		if err != nil {
   400  			return fmt.Errorf("failed to convert Ed25519 to Curve25519 private key for EasyOpen [%s, %w]",
   401  				destination, err)
   402  		}
   403  
   404  		cipherText := httpReq.Ciphertext
   405  		senderPubKey := httpReq.TheirPub
   406  		nonceReq := httpReq.Nonce
   407  
   408  		var (
   409  			senderPubKeyBytes [cryptoutil.Curve25519KeySize]byte
   410  			priv              [cryptoutil.Curve25519KeySize]byte
   411  			nonce             [cryptoutil.NonceSize]byte
   412  		)
   413  
   414  		copy(senderPubKeyBytes[:], senderPubKey)
   415  		copy(priv[:], recipientEncPriv)
   416  		copy(nonce[:], nonceReq)
   417  
   418  		out, success := box.Open(nil, cipherText, &nonce, &senderPubKeyBytes, &priv)
   419  
   420  		if !success {
   421  			return errors.New("failed to unpack")
   422  		}
   423  
   424  		resp := &easyOpenResp{
   425  			Plaintext: out,
   426  		}
   427  
   428  		mResp, err := json.Marshal(resp)
   429  		if err != nil {
   430  			return err
   431  		}
   432  
   433  		_, err = w.Write(mResp)
   434  		if err != nil {
   435  			return err
   436  		}
   437  	}
   438  
   439  	return nil
   440  }