github.com/trustbloc/kms-go@v1.1.2/kms/webkms/remotekms.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/x509"
    12  	"encoding/json"
    13  	"errors"
    14  	"fmt"
    15  	"io"
    16  	"io/ioutil"
    17  	"log"
    18  	"net/http"
    19  	"net/url"
    20  	"os"
    21  	"strings"
    22  	"time"
    23  
    24  	"github.com/trustbloc/kms-go/spi/kms"
    25  )
    26  
    27  const (
    28  	// KeystoreEndpoint represents a remote keystore endpoint with swappable {serverEndpoint} value.
    29  	KeystoreEndpoint = "{serverEndpoint}/v1/keystores"
    30  
    31  	// ContentType is remoteKMS http content-type.
    32  	ContentType = "application/json"
    33  
    34  	logPrefix = " [kms-go/kms/webkms] "
    35  )
    36  
    37  var errorLogger = log.New(os.Stderr, logPrefix, log.Ldate|log.Ltime|log.LUTC)
    38  var debugLogger = log.New(io.Discard, logPrefix, log.Ldate|log.Ltime|log.LUTC)
    39  
    40  // SetDebugOutput used to set output of debug logs.
    41  func SetDebugOutput(out io.Writer) {
    42  	debugLogger.SetOutput(out)
    43  }
    44  
    45  // HTTPClient interface for the http client.
    46  type HTTPClient interface {
    47  	Do(req *http.Request) (*http.Response, error)
    48  }
    49  
    50  type errMessage struct {
    51  	Error string `json:"errMessage"`
    52  }
    53  
    54  type createKeystoreReq struct {
    55  	Controller string      `json:"controller,omitempty"`
    56  	EDV        *edvOptions `json:"edv"`
    57  }
    58  
    59  type edvOptions struct {
    60  	VaultURL   string `json:"vault_url"`
    61  	Capability []byte `json:"capability"`
    62  }
    63  
    64  type createKeyStoreResp struct {
    65  	KeyStoreURL string `json:"key_store_url"`
    66  	Capability  []byte `json:"capability"`
    67  }
    68  
    69  type createKeyReq struct {
    70  	KeyType kms.KeyType `json:"key_type"`
    71  	Attrs   []string    `json:"attrs,omitempty"`
    72  }
    73  
    74  type createKeyResp struct {
    75  	KeyURL    string `json:"key_url"`
    76  	PublicKey []byte `json:"public_key"`
    77  }
    78  
    79  type exportKeyResp struct {
    80  	PublicKey []byte `json:"public_key"`
    81  	KeyType   string `json:"key_type"`
    82  }
    83  
    84  type importKeyReq struct {
    85  	Key     []byte      `json:"key"`
    86  	KeyType kms.KeyType `json:"key_type"`
    87  	KeyID   string      `json:"key_id,omitempty"`
    88  }
    89  
    90  type importKeyResp struct {
    91  	KeyURL string `json:"key_url"`
    92  }
    93  
    94  // MarshalFunc is the interface expected for a marshalling function provided to WithMarshalFn.
    95  type MarshalFunc func(interface{}) ([]byte, error)
    96  
    97  type unmarshalFunc func([]byte, interface{}) error
    98  
    99  // RemoteKMS implementation of kms.KeyManager api.
   100  type RemoteKMS struct {
   101  	httpClient    HTTPClient
   102  	keystoreURL   string
   103  	marshalFunc   MarshalFunc
   104  	unmarshalFunc unmarshalFunc
   105  	opts          *Opts
   106  }
   107  
   108  func checkError(resp *http.Response) error {
   109  	if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
   110  		return nil
   111  	}
   112  
   113  	var errAPI errMessage
   114  
   115  	if err := json.NewDecoder(resp.Body).Decode(&errAPI); err != nil {
   116  		return err
   117  	}
   118  
   119  	return errors.New(errAPI.Error)
   120  }
   121  
   122  // CreateKeyStore calls the key server's create keystore REST function and returns the resulting keystoreURL value.
   123  // Arguments of this function are described below:
   124  //   - httpClient used to POST the request
   125  //   - keyserverURL representing the key server url
   126  //   - marshaller the marshal function used for marshaling content in the client. Usually: `json.Marshal`
   127  //   - headersOpt optional function setting any necessary http headers for key server authorization
   128  //
   129  // Returns:
   130  //   - keystore URL (if successful)
   131  //   - error (if error encountered)
   132  func CreateKeyStore(httpClient HTTPClient, keyserverURL, controller, vaultURL string, // nolint: funlen
   133  	capability []byte, opts ...Opt) (string, []byte, error) {
   134  	createKeyStoreStart := time.Now()
   135  	kmsOpts := NewOpt()
   136  
   137  	for _, opt := range opts {
   138  		opt(kmsOpts)
   139  	}
   140  
   141  	destination := strings.ReplaceAll(KeystoreEndpoint, "{serverEndpoint}", keyserverURL)
   142  	httpReqJSON := &createKeystoreReq{
   143  		Controller: controller,
   144  	}
   145  
   146  	if vaultURL != "" {
   147  		httpReqJSON.EDV = &edvOptions{
   148  			VaultURL:   vaultURL,
   149  			Capability: capability,
   150  		}
   151  	}
   152  
   153  	mReq, err := kmsOpts.marshal(httpReqJSON)
   154  	if err != nil {
   155  		return "", nil, fmt.Errorf("failed to marshal Create keystore request [%s, %w]", destination, err)
   156  	}
   157  
   158  	httpReq, err := http.NewRequest(http.MethodPost, destination, bytes.NewBuffer(mReq))
   159  	if err != nil {
   160  		return "", nil, fmt.Errorf("build request for Create keystore error: %w", err)
   161  	}
   162  
   163  	httpReq.Header.Set("Content-Type", ContentType)
   164  
   165  	if kmsOpts.HeadersFunc != nil {
   166  		httpHeaders, e := kmsOpts.HeadersFunc(httpReq)
   167  		if e != nil {
   168  			return "", nil, fmt.Errorf("add optional request headers error: %w", e)
   169  		}
   170  
   171  		if httpHeaders != nil {
   172  			httpReq.Header = httpHeaders.Clone()
   173  		}
   174  	}
   175  
   176  	start := time.Now()
   177  
   178  	resp, err := httpClient.Do(httpReq)
   179  	if err != nil {
   180  		return "", nil, fmt.Errorf("posting Create keystore failed [%s, %w]", destination, err)
   181  	}
   182  
   183  	// handle response
   184  	defer closeResponseBody(resp.Body, "CreateKeyStore")
   185  
   186  	var httpResp createKeyStoreResp
   187  	err = readResponse(resp, &httpResp, json.Unmarshal)
   188  
   189  	if err != nil {
   190  		return "", nil, fmt.Errorf("create keystore failed [%s, %w]", destination, err)
   191  	}
   192  
   193  	debugLogger.Printf("call of CreateStore http request duration: %s", time.Since(start))
   194  	debugLogger.Printf("overall CreateStore duration: %s", time.Since(createKeyStoreStart))
   195  
   196  	return httpResp.KeyStoreURL, httpResp.Capability, nil
   197  }
   198  
   199  // New creates a new remoteKMS instance using http client connecting to keystoreURL.
   200  func New(keystoreURL string, client HTTPClient, opts ...Opt) *RemoteKMS {
   201  	kmsOpts := NewOpt()
   202  
   203  	for _, opt := range opts {
   204  		opt(kmsOpts)
   205  	}
   206  
   207  	return &RemoteKMS{
   208  		httpClient:    client,
   209  		keystoreURL:   keystoreURL,
   210  		marshalFunc:   json.Marshal,
   211  		unmarshalFunc: json.Unmarshal,
   212  		opts:          kmsOpts,
   213  	}
   214  }
   215  
   216  func (r *RemoteKMS) postHTTPRequest(destination string, mReq []byte) (*http.Response, error) {
   217  	return r.doHTTPRequest(http.MethodPost, destination, mReq)
   218  }
   219  
   220  func (r *RemoteKMS) putHTTPRequest(destination string, mReq []byte) (*http.Response, error) {
   221  	return r.doHTTPRequest(http.MethodPut, destination, mReq)
   222  }
   223  
   224  func (r *RemoteKMS) getHTTPRequest(destination string) (*http.Response, error) {
   225  	return r.doHTTPRequest(http.MethodGet, destination, nil)
   226  }
   227  
   228  func (r *RemoteKMS) doHTTPRequest(method, destination string, mReq []byte) (*http.Response, error) {
   229  	start := time.Now()
   230  
   231  	var (
   232  		httpReq *http.Request
   233  		err     error
   234  	)
   235  
   236  	if mReq != nil {
   237  		httpReq, err = http.NewRequest(method, destination, bytes.NewBuffer(mReq))
   238  		if err != nil {
   239  			return nil, fmt.Errorf("build post request error: %w", err)
   240  		}
   241  	} else {
   242  		httpReq, err = http.NewRequest(method, destination, nil)
   243  		if err != nil {
   244  			return nil, fmt.Errorf("build get request error: %w", err)
   245  		}
   246  	}
   247  
   248  	if method == http.MethodPost {
   249  		httpReq.Header.Set("Content-Type", ContentType)
   250  	}
   251  
   252  	if r.opts.HeadersFunc != nil {
   253  		httpHeaders, e := r.opts.HeadersFunc(httpReq)
   254  		if e != nil {
   255  			return nil, fmt.Errorf("add optional request headers error: %w", e)
   256  		}
   257  
   258  		if httpHeaders != nil {
   259  			httpReq.Header = httpHeaders.Clone()
   260  		}
   261  	}
   262  
   263  	resp, err := r.httpClient.Do(httpReq)
   264  
   265  	debugLogger.Printf("  HTTP %s %s call duration: %s", method, destination, time.Since(start))
   266  
   267  	return resp, err
   268  }
   269  
   270  // Create a new key/keyset/key handle for the type kt remotely
   271  // Returns:
   272  //   - KeyID raw ID of the handle
   273  //   - handle instance representing a remote keystore URL including KeyID
   274  //   - error if failure
   275  func (r *RemoteKMS) Create(kt kms.KeyType, opts ...kms.KeyOpts) (string, interface{}, error) {
   276  	startCreate := time.Now()
   277  
   278  	keyURL, _, err := r.createKey(kt, opts...)
   279  	if err != nil {
   280  		return "", nil, err
   281  	}
   282  
   283  	kid := keyURL[strings.LastIndex(keyURL, "/")+1:]
   284  
   285  	debugLogger.Printf("overall Create key duration: %s", time.Since(startCreate))
   286  
   287  	return kid, keyURL, nil
   288  }
   289  
   290  func (r *RemoteKMS) createKey(kt kms.KeyType, opts ...kms.KeyOpts) (string, []byte, error) {
   291  	destination := r.keystoreURL + "/keys"
   292  
   293  	keyOpts := kms.NewKeyOpt()
   294  
   295  	for _, opt := range opts {
   296  		opt(keyOpts)
   297  	}
   298  
   299  	httpReqJSON := &createKeyReq{
   300  		KeyType: kt,
   301  		Attrs:   keyOpts.Attrs(),
   302  	}
   303  
   304  	marshaledReq, err := r.marshalFunc(httpReqJSON)
   305  	if err != nil {
   306  		return "", nil, fmt.Errorf("failed to marshal Create key request [%s, %w]", destination, err)
   307  	}
   308  
   309  	resp, err := r.postHTTPRequest(destination, marshaledReq)
   310  	if err != nil {
   311  		return "", nil, fmt.Errorf("posting Create key failed [%s, %w]", destination, err)
   312  	}
   313  
   314  	// handle response
   315  	defer closeResponseBody(resp.Body, "Create")
   316  
   317  	var httpResp createKeyResp
   318  
   319  	err = readResponse(resp, &httpResp, r.unmarshalFunc)
   320  	if err != nil {
   321  		return "", nil, fmt.Errorf("create key failed [%s, %w]", destination, err)
   322  	}
   323  
   324  	return httpResp.KeyURL, httpResp.PublicKey, nil
   325  }
   326  
   327  // HealthCheck check kms.
   328  func (r *RemoteKMS) HealthCheck() error {
   329  	parseURL, err := url.Parse(r.keystoreURL)
   330  	if err != nil {
   331  		return err
   332  	}
   333  
   334  	resp, err := r.getHTTPRequest(parseURL.Scheme + "://" + parseURL.Host + "/healthcheck")
   335  	if err != nil {
   336  		return err
   337  	}
   338  
   339  	// handle response
   340  	defer closeResponseBody(resp.Body, "HealthCheck")
   341  
   342  	if resp.StatusCode != http.StatusOK {
   343  		return fmt.Errorf("kms health check return %d status code", resp.StatusCode)
   344  	}
   345  
   346  	return nil
   347  }
   348  
   349  // Get key handle for the given KeyID remotely
   350  // Returns:
   351  //   - handle instance representing a remote keystore URL including KeyID
   352  //   - error if failure
   353  func (r *RemoteKMS) Get(keyID string) (interface{}, error) {
   354  	return r.buildKIDURL(keyID), nil
   355  }
   356  
   357  func (r *RemoteKMS) buildKIDURL(keyID string) string {
   358  	return r.keystoreURL + "/keys/" + keyID
   359  }
   360  
   361  // Rotate remotely a key referenced by KeyID and return a new handle of a keyset including old key and
   362  // new key with type kt. It also returns the updated KeyID as the first return value
   363  // Returns:
   364  //   - new KeyID
   365  //   - handle instance (to private key)
   366  //   - error if failure
   367  func (r *RemoteKMS) Rotate(kt kms.KeyType, keyID string, opts ...kms.KeyOpts) (string, interface{}, error) {
   368  	return "", nil, errors.New("function Rotate is not implemented in remoteKMS")
   369  }
   370  
   371  // ExportPubKeyBytes will remotely fetch a key referenced by id then gets its public key in raw bytes and returns it.
   372  // The key must be an asymmetric key.
   373  // Returns:
   374  //   - marshalled public key []byte
   375  //   - error if it fails to export the public key bytes
   376  func (r *RemoteKMS) ExportPubKeyBytes(keyID string) ([]byte, kms.KeyType, error) {
   377  	startExport := time.Now()
   378  	keyURL := r.buildKIDURL(keyID)
   379  
   380  	destination := keyURL + "/export"
   381  
   382  	resp, err := r.getHTTPRequest(destination)
   383  	if err != nil {
   384  		return nil, "", fmt.Errorf("posting GET ExportPubKeyBytes key failed [%s, %w]", destination, err)
   385  	}
   386  
   387  	// handle response
   388  	defer closeResponseBody(resp.Body, "ExportPubKeyBytes")
   389  
   390  	httpResp := &exportKeyResp{}
   391  
   392  	err = readResponse(resp, &httpResp, r.unmarshalFunc)
   393  	if err != nil {
   394  		return nil, "", fmt.Errorf("export pub key bytes failed [%s, %w]", destination, err)
   395  	}
   396  
   397  	debugLogger.Printf("overall ExportPubKeyBytes duration: %s", time.Since(startExport))
   398  
   399  	return httpResp.PublicKey, kms.KeyType(httpResp.KeyType), nil
   400  }
   401  
   402  // CreateAndExportPubKeyBytes will remotely create a key of type kt and export its public key in raw bytes and returns
   403  // it. The key must be an asymmetric key.
   404  // Returns:
   405  //   - KeyID of the new handle created.
   406  //   - marshalled public key []byte
   407  //   - error if it fails to export the public key bytes
   408  func (r *RemoteKMS) CreateAndExportPubKeyBytes(kt kms.KeyType, opts ...kms.KeyOpts) (string, []byte, error) {
   409  	start := time.Now()
   410  
   411  	keyURL, keyBytes, err := r.createKey(kt, opts...)
   412  	if err != nil {
   413  		return "", nil, err
   414  	}
   415  
   416  	kid := keyURL[strings.LastIndex(keyURL, "/")+1:]
   417  
   418  	debugLogger.Printf("overall CreateAndExportPubKeyBytes duration: %s", time.Since(start))
   419  
   420  	return kid, keyBytes, nil
   421  }
   422  
   423  // PubKeyBytesToHandle is not implemented in remoteKMS.
   424  func (r *RemoteKMS) PubKeyBytesToHandle(pubKey []byte, kt kms.KeyType, opts ...kms.KeyOpts) (interface{}, error) {
   425  	return nil, errors.New("function PubKeyBytesToHandle is not implemented in remoteKMS")
   426  }
   427  
   428  // ImportPrivateKey will import privKey into the KMS storage for the given KeyType then returns the new key id and
   429  // the newly persisted Handle.
   430  // 'privKey' possible types are: *ecdsa.PrivateKey and ed25519.PrivateKey
   431  // 'kt' possible types are signing key types only (ECDSA keys or Ed25519)
   432  // 'opts' allows setting the keysetID of the imported key using WithKeyID() option. If the ID is already used,
   433  // then an error is returned.
   434  // Returns:
   435  //   - KeyID of the handle
   436  //   - handle instance (to private key)
   437  //   - error if import failure (key empty, invalid, doesn't match KeyType, unsupported KeyType or storing key failed)
   438  func (r *RemoteKMS) ImportPrivateKey(privKey interface{}, kt kms.KeyType,
   439  	opts ...kms.PrivateKeyOpts) (string, interface{}, error) {
   440  	pOpts := kms.NewOpt()
   441  
   442  	for _, opt := range opts {
   443  		opt(pOpts)
   444  	}
   445  
   446  	destination := r.keystoreURL + "/keys"
   447  
   448  	keyBytes, err := x509.MarshalPKCS8PrivateKey(privKey)
   449  	if err != nil {
   450  		return "", nil, fmt.Errorf("failed to marshal private key: %w", err)
   451  	}
   452  
   453  	httpReqJSON := &importKeyReq{
   454  		Key:     keyBytes,
   455  		KeyType: kt,
   456  		KeyID:   pOpts.KsID(),
   457  	}
   458  
   459  	marshaledReq, err := r.marshalFunc(httpReqJSON)
   460  	if err != nil {
   461  		return "", nil, fmt.Errorf("failed to marshal ImportKey request [%s, %w]", destination, err)
   462  	}
   463  
   464  	resp, err := r.putHTTPRequest(destination, marshaledReq)
   465  	if err != nil {
   466  		return "", nil, fmt.Errorf("failed to post ImportKey request [%s, %w]", destination, err)
   467  	}
   468  
   469  	// handle response
   470  	defer closeResponseBody(resp.Body, "ImportPrivateKey")
   471  
   472  	var httpResp importKeyResp
   473  	err = readResponse(resp, &httpResp, r.unmarshalFunc)
   474  
   475  	if err != nil {
   476  		return "", nil, fmt.Errorf("import key failed [%s, %w]", destination, err)
   477  	}
   478  
   479  	keyURL := httpResp.KeyURL
   480  
   481  	kid := keyURL[strings.LastIndex(keyURL, "/")+1:]
   482  
   483  	return kid, keyURL, nil
   484  }
   485  
   486  // closeResponseBody closes the response body.
   487  func closeResponseBody(respBody io.Closer, action string) {
   488  	err := respBody.Close()
   489  	if err != nil {
   490  		errorLogger.Printf("Failed to close response body for '%s' REST call: %s", action, err.Error())
   491  	}
   492  }
   493  
   494  func readResponse(resp *http.Response, httpResp interface{}, unmarshal unmarshalFunc) error {
   495  	err := checkError(resp)
   496  	if err != nil {
   497  		return err
   498  	}
   499  
   500  	respBody, err := ioutil.ReadAll(resp.Body)
   501  	if err != nil {
   502  		return fmt.Errorf("read response failed: %w", err)
   503  	}
   504  
   505  	err = unmarshal(respBody, httpResp)
   506  	if err != nil {
   507  		return fmt.Errorf("unmarshal failed: %w", err)
   508  	}
   509  
   510  	return nil
   511  }