github.com/trustbloc/kms-go@v1.1.2/crypto/webkms/remotecrypto.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  	"bytes"
    11  	"crypto/sha256"
    12  	"encoding/json"
    13  	"errors"
    14  	"fmt"
    15  	"io"
    16  	"io/ioutil"
    17  	"log"
    18  	"net/http"
    19  	"os"
    20  	"strings"
    21  	"time"
    22  
    23  	"github.com/bluele/gcache"
    24  	"github.com/google/tink/go/keyset"
    25  
    26  	cryptoapi "github.com/trustbloc/kms-go/spi/crypto"
    27  
    28  	"github.com/trustbloc/kms-go/crypto/tinkcrypto/primitive/composite/keyio"
    29  	webkmsimpl "github.com/trustbloc/kms-go/kms/webkms"
    30  )
    31  
    32  const (
    33  	logPrefix = " [kms-go/crypto/webkms] "
    34  )
    35  
    36  var errorLogger = log.New(os.Stderr, logPrefix, log.Ldate|log.Ltime|log.LUTC)
    37  var debugLogger = log.New(io.Discard, logPrefix, log.Ldate|log.Ltime|log.LUTC)
    38  
    39  // SetDebugOutput used to set output of debug logs.
    40  func SetDebugOutput(out io.Writer) {
    41  	debugLogger.SetOutput(out)
    42  }
    43  
    44  // HTTPClient interface for the http client.
    45  type HTTPClient interface {
    46  	Do(req *http.Request) (*http.Response, error)
    47  }
    48  
    49  type encryptReq struct {
    50  	Message        []byte `json:"message"`
    51  	AssociatedData []byte `json:"associated_data,omitempty"`
    52  }
    53  
    54  type encryptResp struct {
    55  	Ciphertext []byte `json:"ciphertext"`
    56  	Nonce      []byte `json:"nonce"`
    57  }
    58  
    59  type decryptReq struct {
    60  	Ciphertext     []byte `json:"ciphertext"`
    61  	AssociatedData []byte `json:"associated_data,omitempty"`
    62  	Nonce          []byte `json:"nonce"`
    63  }
    64  
    65  type decryptResp struct {
    66  	Plaintext []byte `json:"plaintext"`
    67  }
    68  
    69  type signReq struct {
    70  	Message []byte `json:"message"`
    71  }
    72  
    73  type signResp struct {
    74  	Signature []byte `json:"signature"`
    75  }
    76  
    77  type signMultiReq struct {
    78  	Messages [][]byte `json:"messages"`
    79  }
    80  
    81  type deriveProofReq struct {
    82  	Messages        [][]byte `json:"messages"`
    83  	Signature       []byte   `json:"signature"`
    84  	Nonce           []byte   `json:"nonce"`
    85  	RevealedIndexes []int    `json:"revealed_indexes"`
    86  }
    87  
    88  type deriveProofResp struct {
    89  	Proof []byte `json:"proof"`
    90  }
    91  
    92  type verifyReq struct {
    93  	Signature []byte `json:"signature"`
    94  	Message   []byte `json:"message"`
    95  }
    96  
    97  type verifyProofReq struct {
    98  	Proof    []byte   `json:"proof"`
    99  	Messages [][]byte `json:"messages"`
   100  	Nonce    []byte   `json:"nonce"`
   101  }
   102  
   103  type verifyMultiReq struct {
   104  	Signature []byte   `json:"signature"`
   105  	Messages  [][]byte `json:"messages"`
   106  }
   107  
   108  type computeMACReq struct {
   109  	Data []byte `json:"data"`
   110  }
   111  
   112  type computeMACResp struct {
   113  	MAC []byte `json:"mac"`
   114  }
   115  
   116  type verifyMACReq struct {
   117  	MAC  []byte `json:"mac"`
   118  	Data []byte `json:"data"`
   119  }
   120  
   121  // wrapKeyReq serializable WrapKey request.
   122  type wrapKeyReq struct {
   123  	CEK             []byte               `json:"cek"`
   124  	APU             []byte               `json:"apu"`
   125  	APV             []byte               `json:"apv"`
   126  	RecipientPubKey *cryptoapi.PublicKey `json:"recipient_pub_key"`
   127  	Tag             []byte               `json:"tag,omitempty"`
   128  }
   129  
   130  // wrapKeyResp serializable WrapKey response.
   131  type wrapKeyResp struct {
   132  	cryptoapi.RecipientWrappedKey
   133  }
   134  
   135  // unwrapKeyReq serializable UnwrapKey request.
   136  type unwrapKeyReq struct {
   137  	WrappedKey   cryptoapi.RecipientWrappedKey `json:"wrapped_key"`
   138  	SenderPubKey *cryptoapi.PublicKey          `json:"sender_pub_key,omitempty"`
   139  	Tag          []byte                        `json:"tag,omitempty"`
   140  }
   141  
   142  // unwrapKeyResp serializable UnwrapKey response.
   143  type unwrapKeyResp struct {
   144  	Key []byte `json:"key"`
   145  }
   146  
   147  type marshalFunc func(interface{}) ([]byte, error)
   148  
   149  type unmarshalFunc func([]byte, interface{}) error
   150  
   151  // RemoteCrypto implementation of kms.KeyManager api.
   152  type RemoteCrypto struct {
   153  	httpClient    HTTPClient
   154  	keystoreURL   string
   155  	marshalFunc   marshalFunc
   156  	unmarshalFunc unmarshalFunc
   157  	opts          *webkmsimpl.Opts
   158  }
   159  
   160  const (
   161  	keysURI       = "/keys"
   162  	encryptURI    = "/encrypt"
   163  	decryptURI    = "/decrypt"
   164  	signURI       = "/sign"
   165  	verifyURI     = "/verify"
   166  	computeMACURI = "/computemac"
   167  	verifyMACURI  = "/verifymac"
   168  	wrapURI       = "/wrap"
   169  	unwrapURI     = "/unwrap"
   170  
   171  	// multi signatures/selective disclosure crypto (eg BBS+) endpoints.
   172  	signMultiURI   = "/signmulti"
   173  	verifyMultiURI = "/verifymulti"
   174  	deriveProofURI = "/deriveproof"
   175  	verifyProofURI = "/verifyproof"
   176  )
   177  
   178  // New creates a new remoteCrypto instance using http client connecting to keystoreURL.
   179  func New(keystoreURL string, client HTTPClient, opts ...webkmsimpl.Opt) *RemoteCrypto {
   180  	rOpts := webkmsimpl.NewOpt()
   181  
   182  	for _, opt := range opts {
   183  		opt(rOpts)
   184  	}
   185  
   186  	return &RemoteCrypto{
   187  		httpClient:    client,
   188  		keystoreURL:   keystoreURL,
   189  		marshalFunc:   json.Marshal,
   190  		unmarshalFunc: json.Unmarshal,
   191  		opts:          rOpts,
   192  	}
   193  }
   194  
   195  func (r *RemoteCrypto) postHTTPRequest(destination string, mReq []byte) (*http.Response, error) {
   196  	return r.doHTTPRequest(http.MethodPost, destination, mReq)
   197  }
   198  
   199  func (r *RemoteCrypto) doHTTPRequest(method, destination string, mReq []byte) (*http.Response, error) {
   200  	start := time.Now()
   201  
   202  	var body io.Reader
   203  
   204  	if mReq != nil {
   205  		body = bytes.NewBuffer(mReq)
   206  	}
   207  
   208  	httpReq, err := http.NewRequest(method, destination, body)
   209  	if err != nil {
   210  		return nil, fmt.Errorf("build request error: %w", err)
   211  	}
   212  
   213  	if method == http.MethodPost {
   214  		httpReq.Header.Set("Content-Type", webkmsimpl.ContentType)
   215  	}
   216  
   217  	if r.opts.HeadersFunc != nil {
   218  		httpHeaders, e := r.opts.HeadersFunc(httpReq)
   219  		if e != nil {
   220  			return nil, fmt.Errorf("add optional request headers error: %w", e)
   221  		}
   222  
   223  		if httpHeaders != nil {
   224  			httpReq.Header = httpHeaders.Clone()
   225  		}
   226  	}
   227  
   228  	resp, err := r.httpClient.Do(httpReq)
   229  
   230  	debugLogger.Printf("  HTTP %s %s call duration: %s", method, destination, time.Since(start))
   231  
   232  	return resp, err
   233  }
   234  
   235  // Encrypt will remotely encrypt msg and aad using a matching AEAD primitive in a remote key handle at keyURL of
   236  // a public key.
   237  // returns:
   238  //
   239  //	cipherText in []byte
   240  //	nonce in []byte
   241  //	error in case of errors during encryption
   242  func (r *RemoteCrypto) Encrypt(msg, aad []byte, keyURL interface{}) ([]byte, []byte, error) {
   243  	startEncrypt := time.Now()
   244  	destination := fmt.Sprintf("%s", keyURL) + encryptURI
   245  
   246  	eReq := encryptReq{
   247  		Message:        msg,
   248  		AssociatedData: aad,
   249  	}
   250  
   251  	httpReqBytes, err := r.marshalFunc(eReq)
   252  	if err != nil {
   253  		return nil, nil, fmt.Errorf("marshal encryption request for Encrypt failed [%s, %w]", destination, err)
   254  	}
   255  
   256  	resp, err := r.postHTTPRequest(destination, httpReqBytes)
   257  	if err != nil {
   258  		return nil, nil, fmt.Errorf("posting Encrypt plaintext failed [%s, %w]", destination, err)
   259  	}
   260  
   261  	// handle response
   262  	defer closeResponseBody(resp.Body, "Encrypt")
   263  
   264  	if resp.StatusCode != http.StatusOK {
   265  		return nil, nil, fmt.Errorf("posting Encrypt returned http error: %s", resp.Status)
   266  	}
   267  
   268  	respBody, err := ioutil.ReadAll(resp.Body)
   269  	if err != nil {
   270  		return nil, nil, fmt.Errorf("read encryption response for Encrypt failed [%s, %w]", destination, err)
   271  	}
   272  
   273  	httpResp := &encryptResp{}
   274  
   275  	err = r.unmarshalFunc(respBody, httpResp)
   276  	if err != nil {
   277  		return nil, nil, fmt.Errorf("unmarshal encryption for Encrypt failed [%s, %w]", destination, err)
   278  	}
   279  
   280  	debugLogger.Printf("overall Encrypt duration: %s", time.Since(startEncrypt))
   281  
   282  	return httpResp.Ciphertext, httpResp.Nonce, nil
   283  }
   284  
   285  // Decrypt will remotely decrypt cipher with aad and given nonce using a matching AEAD primitive in a remote key handle
   286  // at keyURL of a private key.
   287  // returns:
   288  //
   289  //	plainText in []byte
   290  //	error in case of errors
   291  func (r *RemoteCrypto) Decrypt(cipher, aad, nonce []byte, keyURL interface{}) ([]byte, error) {
   292  	startDecrypt := time.Now()
   293  	destination := fmt.Sprintf("%s", keyURL) + decryptURI
   294  
   295  	dReq := decryptReq{
   296  		Ciphertext:     cipher,
   297  		Nonce:          nonce,
   298  		AssociatedData: aad,
   299  	}
   300  
   301  	httpReqBytes, err := r.marshalFunc(dReq)
   302  	if err != nil {
   303  		return nil, fmt.Errorf("marshal decryption request for Decrypt failed [%s, %w]", destination, err)
   304  	}
   305  
   306  	resp, err := r.postHTTPRequest(destination, httpReqBytes)
   307  	if err != nil {
   308  		return nil, fmt.Errorf("posting Decrypt ciphertext failed [%s, %w]", destination, err)
   309  	}
   310  
   311  	// handle response
   312  	defer closeResponseBody(resp.Body, "Decrypt")
   313  
   314  	if resp.StatusCode != http.StatusOK {
   315  		return nil, fmt.Errorf("posting Decrypt returned http error: %s", resp.Status)
   316  	}
   317  
   318  	respBody, err := ioutil.ReadAll(resp.Body)
   319  	if err != nil {
   320  		return nil, fmt.Errorf("read decryption response for Decrypt failed [%s, %w]", destination, err)
   321  	}
   322  
   323  	httpResp := &decryptResp{}
   324  
   325  	err = r.unmarshalFunc(respBody, httpResp)
   326  	if err != nil {
   327  		return nil, fmt.Errorf("unmarshal decryption for Decrypt failed [%s, %w]", destination, err)
   328  	}
   329  
   330  	debugLogger.Printf("overall Decrypt duration: %s", time.Since(startDecrypt))
   331  
   332  	return httpResp.Plaintext, nil
   333  }
   334  
   335  // Sign will remotely sign msg using a matching signature primitive in remote kh key handle at keyURL of a private key.
   336  // returns:
   337  //
   338  //	signature in []byte
   339  //	error in case of errors
   340  func (r *RemoteCrypto) Sign(msg []byte, keyURL interface{}) ([]byte, error) {
   341  	startSign := time.Now()
   342  	destination := fmt.Sprintf("%s", keyURL) + signURI
   343  
   344  	sReq := signReq{
   345  		Message: msg,
   346  	}
   347  
   348  	httpReqBytes, err := r.marshalFunc(sReq)
   349  	if err != nil {
   350  		return nil, fmt.Errorf("marshal signature request for Sign failed [%s, %w]", destination, err)
   351  	}
   352  
   353  	resp, err := r.postHTTPRequest(destination, httpReqBytes)
   354  	if err != nil {
   355  		return nil, fmt.Errorf("posting Sign message failed [%s, %w]", destination, err)
   356  	}
   357  
   358  	// handle response
   359  	defer closeResponseBody(resp.Body, "Sign")
   360  
   361  	if resp.StatusCode != http.StatusOK {
   362  		return nil, fmt.Errorf("posting Sign returned http error: %s", resp.Status)
   363  	}
   364  
   365  	respBody, err := ioutil.ReadAll(resp.Body)
   366  	if err != nil {
   367  		return nil, fmt.Errorf("read signature response for Sign failed [%s, %w]", destination, err)
   368  	}
   369  
   370  	httpResp := &signResp{}
   371  
   372  	err = r.unmarshalFunc(respBody, httpResp)
   373  	if err != nil {
   374  		return nil, fmt.Errorf("unmarshal signature for Sign failed [%s, %w]", destination, err)
   375  	}
   376  
   377  	debugLogger.Printf("overall Sign duration: %s", time.Since(startSign))
   378  
   379  	return httpResp.Signature, nil
   380  }
   381  
   382  // Verify will remotely verify a signature for the given msg using a matching signature primitive in a remote key
   383  // handle at keyURL of a public key.
   384  // returns:
   385  //
   386  //	error in case of errors or nil if signature verification was successful
   387  func (r *RemoteCrypto) Verify(signature, msg []byte, keyURL interface{}) error {
   388  	startVerify := time.Now()
   389  	destination := fmt.Sprintf("%s", keyURL) + verifyURI
   390  
   391  	vReq := verifyReq{
   392  		Message:   msg,
   393  		Signature: signature,
   394  	}
   395  
   396  	httpReqBytes, err := r.marshalFunc(vReq)
   397  	if err != nil {
   398  		return fmt.Errorf("marshal verify request for Verify failed [%s, %w]", destination, err)
   399  	}
   400  
   401  	resp, err := r.postHTTPRequest(destination, httpReqBytes)
   402  	if err != nil {
   403  		return fmt.Errorf("posting Verify signature failed [%s, %w]", destination, err)
   404  	}
   405  
   406  	// handle response
   407  	defer closeResponseBody(resp.Body, "Verify")
   408  
   409  	if resp.StatusCode != http.StatusOK {
   410  		return fmt.Errorf("posting Verify signature returned http error: %s", resp.Status)
   411  	}
   412  
   413  	debugLogger.Printf("overall Verify duration: %s", time.Since(startVerify))
   414  
   415  	return nil
   416  }
   417  
   418  // ComputeMAC remotely computes message authentication code (MAC) for code data with key at keyURL.
   419  // using a matching MAC primitive in kh key handle.
   420  func (r *RemoteCrypto) ComputeMAC(data []byte, keyURL interface{}) ([]byte, error) { // nolint:gocyclo
   421  	keyHash := string(sha256.New().Sum([]byte(fmt.Sprintf("%s_%s", keyURL, data))))
   422  
   423  	if r.opts.ComputeMACCache != nil {
   424  		v, err := r.opts.ComputeMACCache.Get(keyHash)
   425  		if err == nil {
   426  			return v.([]byte), nil
   427  		}
   428  
   429  		if !errors.Is(err, gcache.KeyNotFoundError) {
   430  			return nil, err
   431  		}
   432  	}
   433  
   434  	startComputeMAC := time.Now()
   435  	destination := fmt.Sprintf("%s", keyURL) + computeMACURI
   436  
   437  	mReq := computeMACReq{
   438  		Data: data,
   439  	}
   440  
   441  	httpReqBytes, err := r.marshalFunc(mReq)
   442  	if err != nil {
   443  		return nil, fmt.Errorf("marshal request for ComputeMAC failed [%s, %w]", destination, err)
   444  	}
   445  
   446  	resp, err := r.postHTTPRequest(destination, httpReqBytes)
   447  	if err != nil {
   448  		return nil, fmt.Errorf("posting ComputeMAC request failed [%s, %w]", destination, err)
   449  	}
   450  
   451  	// handle response
   452  	defer closeResponseBody(resp.Body, "ComputeMAC")
   453  
   454  	if resp.StatusCode != http.StatusOK {
   455  		return nil, fmt.Errorf("posting ComputeMAC request returned http error: %s", resp.Status)
   456  	}
   457  
   458  	respBody, err := ioutil.ReadAll(resp.Body)
   459  	if err != nil {
   460  		return nil, fmt.Errorf("read response for ComputeMAC failed [%s, %w]", destination, err)
   461  	}
   462  
   463  	httpResp := &computeMACResp{}
   464  
   465  	err = r.unmarshalFunc(respBody, httpResp)
   466  	if err != nil {
   467  		return nil, fmt.Errorf("unmarshal ComputeMAC response failed [%s, %w]", destination, err)
   468  	}
   469  
   470  	if r.opts.ComputeMACCache != nil {
   471  		if err := r.opts.ComputeMACCache.Set(keyHash, httpResp.MAC); err != nil {
   472  			return nil, fmt.Errorf("failed to store in cache: %w", err)
   473  		}
   474  	}
   475  
   476  	debugLogger.Printf("overall ComputeMAC duration: %s", time.Since(startComputeMAC))
   477  
   478  	return httpResp.MAC, nil
   479  }
   480  
   481  // VerifyMAC remotely determines if mac is a correct authentication code (MAC) for data using a key at KeyURL
   482  // using a matching MAC primitive in kh key handle and returns nil if so, otherwise it returns an error.
   483  func (r *RemoteCrypto) VerifyMAC(mac, data []byte, keyURL interface{}) error {
   484  	startVerifyMAC := time.Now()
   485  	destination := fmt.Sprintf("%s", keyURL) + verifyMACURI
   486  
   487  	vReq := verifyMACReq{
   488  		MAC:  mac,
   489  		Data: data,
   490  	}
   491  
   492  	httpReqBytes, err := r.marshalFunc(vReq)
   493  	if err != nil {
   494  		return fmt.Errorf("marshal request for VerifyMAC failed [%s, %w]", destination, err)
   495  	}
   496  
   497  	resp, err := r.postHTTPRequest(destination, httpReqBytes)
   498  	if err != nil {
   499  		return fmt.Errorf("posting VerifyMAC request failed [%s, %w]", destination, err)
   500  	}
   501  
   502  	// handle response
   503  	defer closeResponseBody(resp.Body, "VerifyMAC")
   504  
   505  	if resp.StatusCode != http.StatusOK {
   506  		return fmt.Errorf("posting VerifyMAC request returned http error: %s", resp.Status)
   507  	}
   508  
   509  	debugLogger.Printf("overall VerifyMAC duration: %s", time.Since(startVerifyMAC))
   510  
   511  	return nil
   512  }
   513  
   514  // WrapKey will remotely execute key wrapping of cek using apu, apv and recipient public key 'recPubKey'.
   515  // 'opts' allows setting the option sender key handle using WithSender() option where the sender key handle consists
   516  // of a remote key located in the option as a keyURL. This option allows ECDH-1PU key wrapping (aka Authcrypt).
   517  // The absence of this option uses ECDH-ES key wrapping (aka Anoncrypt).
   518  //
   519  //	RecipientWrappedKey containing the wrapped cek value
   520  //	error in case of errors
   521  func (r *RemoteCrypto) WrapKey(cek, apu, apv []byte, recPubKey *cryptoapi.PublicKey, // nolint:funlen
   522  	opts ...cryptoapi.WrapKeyOpts) (*cryptoapi.RecipientWrappedKey, error) {
   523  	startWrapKey := time.Now()
   524  	destination := r.keystoreURL + wrapURI
   525  
   526  	pOpts := cryptoapi.NewOpt()
   527  
   528  	for _, opt := range opts {
   529  		opt(pOpts)
   530  	}
   531  
   532  	senderURL := pOpts.SenderKey()
   533  	wReq := &wrapKeyReq{
   534  		CEK:             cek,
   535  		APU:             apu,
   536  		APV:             apv,
   537  		RecipientPubKey: recPubKey,
   538  		Tag:             pOpts.Tag(),
   539  	}
   540  
   541  	if senderURL != nil {
   542  		senderURLStr, ok := senderURL.(string)
   543  
   544  		if !ok {
   545  			return nil, fmt.Errorf("wrapKey invalid senderKey type, should be string with key URL")
   546  		}
   547  
   548  		// if senderURL is set, extract keyID and add it to the request url (for ECDH-1PU wrapping)
   549  		if senderURLStr != "" {
   550  			parts := strings.Split(senderURLStr, "/")
   551  			senderKID := parts[len(parts)-1]
   552  			destination = r.keystoreURL + keysURI + "/" + senderKID + wrapURI
   553  		}
   554  	}
   555  
   556  	httpReqBytes, err := r.marshalFunc(wReq)
   557  	if err != nil {
   558  		return nil, fmt.Errorf("marshal wrapKeyReq for WrapKey failed [%s, %w]", destination, err)
   559  	}
   560  
   561  	resp, err := r.postHTTPRequest(destination, httpReqBytes)
   562  	if err != nil {
   563  		return nil, fmt.Errorf("posting WrapKey failed [%s, %w]", destination, err)
   564  	}
   565  
   566  	// handle response
   567  	defer closeResponseBody(resp.Body, "WrapKey")
   568  
   569  	if resp.StatusCode != http.StatusOK {
   570  		return nil, fmt.Errorf("posting WrapKey returned http error: %s", resp.Status)
   571  	}
   572  
   573  	respBody, err := ioutil.ReadAll(resp.Body)
   574  	if err != nil {
   575  		return nil, fmt.Errorf("read wrap key response for WrapKey failed [%s, %w]", destination, err)
   576  	}
   577  
   578  	rwk, err := r.buildWrappedKeyResponse(respBody, destination)
   579  
   580  	debugLogger.Printf("overall WrapKey duration: %s", time.Since(startWrapKey))
   581  
   582  	return rwk, err
   583  }
   584  
   585  func (r *RemoteCrypto) buildWrappedKeyResponse(respBody []byte, dest string) (*cryptoapi.RecipientWrappedKey, error) {
   586  	httpResp := &wrapKeyResp{}
   587  
   588  	err := r.unmarshalFunc(respBody, httpResp)
   589  	if err != nil {
   590  		return nil, fmt.Errorf("unmarshal wrapKeyResp for WrapKey failed [%s, %w]", dest, err)
   591  	}
   592  
   593  	return &httpResp.RecipientWrappedKey, nil
   594  }
   595  
   596  // UnwrapKey remotely unwraps a key in recWK using recipient private key found at keyURL.
   597  // 'opts' allows setting the option sender key handle using WithSender() optionwhere the sender key handle consists
   598  // of a remote key located in the option as a keyURL. This options allows ECDH-1PU key unwrapping (aka Authcrypt).
   599  // The absence of this option uses ECDH-ES key unwrapping (aka Anoncrypt).
   600  // returns:
   601  //
   602  //	unwrapped key in raw bytes
   603  //	error in case of errors
   604  func (r *RemoteCrypto) UnwrapKey(recWK *cryptoapi.RecipientWrappedKey, keyURL interface{},
   605  	opts ...cryptoapi.WrapKeyOpts) ([]byte, error) {
   606  	startUnwrapKey := time.Now()
   607  	destination := fmt.Sprintf("%s", keyURL) + unwrapURI
   608  
   609  	pOpts := cryptoapi.NewOpt()
   610  
   611  	for _, opt := range opts {
   612  		opt(pOpts)
   613  	}
   614  
   615  	uReq := unwrapKeyReq{
   616  		WrappedKey: *recWK,
   617  		Tag:        pOpts.Tag(),
   618  	}
   619  
   620  	if pOpts.SenderKey() != nil {
   621  		sk, err := ksToCryptoPublicKey(pOpts.SenderKey())
   622  		if err != nil {
   623  			return nil, err
   624  		}
   625  
   626  		uReq.SenderPubKey = sk
   627  	}
   628  
   629  	httpReqBytes, err := r.marshalFunc(uReq)
   630  	if err != nil {
   631  		return nil, fmt.Errorf("marshal unwrapKeyReq for UnwrapKey failed [%s, %w]", destination, err)
   632  	}
   633  
   634  	resp, err := r.postHTTPRequest(destination, httpReqBytes)
   635  	if err != nil {
   636  		return nil, fmt.Errorf("posting UnwrapKey failed [%s, %w]", destination, err)
   637  	}
   638  
   639  	// handle response
   640  	defer closeResponseBody(resp.Body, "UnwrapKey")
   641  
   642  	if resp.StatusCode != http.StatusOK {
   643  		return nil, fmt.Errorf("posting UnwrapKey returned http error: %s", resp.Status)
   644  	}
   645  
   646  	respBody, err := ioutil.ReadAll(resp.Body)
   647  	if err != nil {
   648  		return nil, fmt.Errorf("read unwrapped key response for UnwrapKey failed [%s, %w]", destination, err)
   649  	}
   650  
   651  	httpResp := &unwrapKeyResp{}
   652  
   653  	err = r.unmarshalFunc(respBody, httpResp)
   654  	if err != nil {
   655  		return nil, fmt.Errorf("unmarshal unwrapKeyResp for UnwrapKey failed [%s, %w]", destination, err)
   656  	}
   657  
   658  	debugLogger.Printf("overall UnwrapKey duration: %s", time.Since(startUnwrapKey))
   659  
   660  	return httpResp.Key, err
   661  }
   662  
   663  func ksToCryptoPublicKey(ks interface{}) (*cryptoapi.PublicKey, error) {
   664  	switch kst := ks.(type) {
   665  	case *keyset.Handle:
   666  		sPubKey, err := keyio.ExtractPrimaryPublicKey(kst)
   667  		if err != nil {
   668  			return nil, fmt.Errorf("ksToCryptoPublicKey: failed to extract public key from keyset handle: %w", err)
   669  		}
   670  
   671  		return sPubKey, nil
   672  	case *cryptoapi.PublicKey:
   673  		return kst, nil
   674  	default:
   675  		return nil, fmt.Errorf("ksToCryptoPublicKey: unsupported keyset type %+v", kst)
   676  	}
   677  }
   678  
   679  // SignMulti will create a BBS+ signature of messages using the signer's private key handle found at signerKeyURL.
   680  // returns:
   681  //
   682  //	signature in []byte
   683  //	error in case of errors
   684  func (r *RemoteCrypto) SignMulti(messages [][]byte, signerKeyURL interface{}) ([]byte, error) {
   685  	startSign := time.Now()
   686  	destination := fmt.Sprintf("%s", signerKeyURL) + signMultiURI
   687  
   688  	sReq := signMultiReq{
   689  		Messages: messages,
   690  	}
   691  
   692  	httpReqBytes, err := r.marshalFunc(sReq)
   693  	if err != nil {
   694  		return nil, fmt.Errorf("marshal signature request for BBS+ Sign failed [%s, %w]", destination, err)
   695  	}
   696  
   697  	resp, err := r.postHTTPRequest(destination, httpReqBytes)
   698  	if err != nil {
   699  		return nil, fmt.Errorf("posting BBS+ Sign message failed [%s, %w]", destination, err)
   700  	}
   701  
   702  	// handle response
   703  	defer closeResponseBody(resp.Body, "BBS+ Sign")
   704  
   705  	if resp.StatusCode != http.StatusOK {
   706  		return nil, fmt.Errorf("posting BBS+ sign returned http error: %s", resp.Status)
   707  	}
   708  
   709  	respBody, err := ioutil.ReadAll(resp.Body)
   710  	if err != nil {
   711  		return nil, fmt.Errorf("read signature response for BBS+ Sign failed [%s, %w]", destination, err)
   712  	}
   713  
   714  	httpResp := &signResp{}
   715  
   716  	err = r.unmarshalFunc(respBody, httpResp)
   717  	if err != nil {
   718  		return nil, fmt.Errorf("unmarshal signature for BBS+ Sign failed [%s, %w]", destination, err)
   719  	}
   720  
   721  	debugLogger.Printf("overall BBS+ Sign duration: %s", time.Since(startSign))
   722  
   723  	return httpResp.Signature, nil
   724  }
   725  
   726  // VerifyMulti will BBS+ verify a signature of messages against the signer's public key handle found at signerKeyURL.
   727  // returns:
   728  //
   729  //	error in case of errors or nil if signature verification was successful
   730  func (r *RemoteCrypto) VerifyMulti(messages [][]byte, signature []byte, signerKeyURL interface{}) error {
   731  	startVerify := time.Now()
   732  	destination := fmt.Sprintf("%s", signerKeyURL) + verifyMultiURI
   733  
   734  	vReq := verifyMultiReq{
   735  		Messages:  messages,
   736  		Signature: signature,
   737  	}
   738  
   739  	httpReqBytes, err := r.marshalFunc(vReq)
   740  	if err != nil {
   741  		return fmt.Errorf("marshal verify request for BBS+ Verify failed [%s, %w]", destination, err)
   742  	}
   743  
   744  	resp, err := r.postHTTPRequest(destination, httpReqBytes)
   745  	if err != nil {
   746  		return fmt.Errorf("posting BBS+ Verify signature failed [%s, %w]", destination, err)
   747  	}
   748  
   749  	// handle response
   750  	defer closeResponseBody(resp.Body, "BBS+ Verify")
   751  
   752  	if resp.StatusCode != http.StatusOK {
   753  		return fmt.Errorf("posting BBS+ Verify signature returned http error: %s", resp.Status)
   754  	}
   755  
   756  	debugLogger.Printf("overall BBS+ Verify duration: %s", time.Since(startVerify))
   757  
   758  	return nil
   759  }
   760  
   761  // VerifyProof will verify a BBS+ signature proof (generated e.g. by Verifier's DeriveProof() call) for revealedMessages
   762  // with the signer's public key handle found at signerKeyURL.
   763  // returns:
   764  //
   765  //	error in case of errors or nil if signature proof verification was successful
   766  func (r *RemoteCrypto) VerifyProof(revealedMessages [][]byte, proof, nonce []byte, signerKeyURL interface{}) error {
   767  	startVerifyProof := time.Now()
   768  	destination := fmt.Sprintf("%s", signerKeyURL) + verifyProofURI
   769  
   770  	vReq := verifyProofReq{
   771  		Messages: revealedMessages,
   772  		Proof:    proof,
   773  		Nonce:    nonce,
   774  	}
   775  
   776  	httpReqBytes, err := r.marshalFunc(vReq)
   777  	if err != nil {
   778  		return fmt.Errorf("marshal request for BBS+ Verify proof failed [%s, %w]", destination, err)
   779  	}
   780  
   781  	resp, err := r.postHTTPRequest(destination, httpReqBytes)
   782  	if err != nil {
   783  		return fmt.Errorf("posting BBS+ Verify proof failed [%s, %w]", destination, err)
   784  	}
   785  
   786  	// handle response
   787  	defer closeResponseBody(resp.Body, "BBS+ Verify Proof")
   788  
   789  	if resp.StatusCode != http.StatusOK {
   790  		return fmt.Errorf("posting BBS+ Verify proof returned http error: %s", resp.Status)
   791  	}
   792  
   793  	debugLogger.Printf("overall BBS+ Verify proof duration: %s", time.Since(startVerifyProof))
   794  
   795  	return nil
   796  }
   797  
   798  // DeriveProof will create a BBS+ signature proof for a list of revealed messages using BBS signature (can be built
   799  // using a Signer's SignMulti() call) and the signer's public key handle found at signerKeyURL.
   800  // returns:
   801  //
   802  //	signature proof in []byte
   803  //	error in case of errors
   804  func (r *RemoteCrypto) DeriveProof(messages [][]byte, bbsSignature, nonce []byte, revealedIndexes []int,
   805  	signerKeyURL interface{}) ([]byte, error) {
   806  	startDeriveProof := time.Now()
   807  	destination := fmt.Sprintf("%s", signerKeyURL) + deriveProofURI
   808  
   809  	sReq := deriveProofReq{
   810  		Messages:        messages,
   811  		Signature:       bbsSignature,
   812  		Nonce:           nonce,
   813  		RevealedIndexes: revealedIndexes,
   814  	}
   815  
   816  	httpReqBytes, err := r.marshalFunc(sReq)
   817  	if err != nil {
   818  		return nil, fmt.Errorf("marshal request for BBS+ Derive proof failed [%s, %w]", destination, err)
   819  	}
   820  
   821  	resp, err := r.postHTTPRequest(destination, httpReqBytes)
   822  	if err != nil {
   823  		return nil, fmt.Errorf("posting BBS+ Derive proof message failed [%s, %w]", destination, err)
   824  	}
   825  
   826  	// handle response
   827  	defer closeResponseBody(resp.Body, "BBS+ Derive Proof")
   828  
   829  	if resp.StatusCode != http.StatusOK {
   830  		return nil, fmt.Errorf("posting BBS+ Derive proof returned http error: %s", resp.Status)
   831  	}
   832  
   833  	respBody, err := ioutil.ReadAll(resp.Body)
   834  	if err != nil {
   835  		return nil, fmt.Errorf("read signature response for BBS+ Derive proof failed [%s, %w]", destination, err)
   836  	}
   837  
   838  	httpResp := &deriveProofResp{}
   839  
   840  	err = r.unmarshalFunc(respBody, httpResp)
   841  	if err != nil {
   842  		return nil, fmt.Errorf("unmarshal request for BBS+ Derive proof failed [%s, %w]", destination, err)
   843  	}
   844  
   845  	debugLogger.Printf("overall BBS+ Derive proof duration: %s", time.Since(startDeriveProof))
   846  
   847  	return httpResp.Proof, nil
   848  }
   849  
   850  // closeResponseBody closes the response body.
   851  func closeResponseBody(respBody io.Closer, action string) {
   852  	err := respBody.Close()
   853  	if err != nil {
   854  		errorLogger.Printf("Failed to close response body for '%s' REST call: %s", action, err.Error())
   855  	}
   856  }