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 }