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 }