client/ekmclient/confidentialekmclient.go (146 lines of code) (raw):
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package ekmclient defines an HTTP client for contacting Confidential EKM services.
package ekmclient
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"fmt"
"io/ioutil"
"net/http"
"strings"
cwpb "github.com/GoogleCloudPlatform/stet/proto/confidential_wrap_go_proto"
sspb "github.com/GoogleCloudPlatform/stet/proto/secure_session_go_proto"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)
const (
beginSessionEndpoint = "/session/beginsession"
handshakeEndpoint = "/session/handshake"
negotiateAttestationEndpoint = "/session/negotiateattestation"
finalizeEndpoint = "/session/finalize"
endSessionEndpoint = "/session/endsession"
confidentialWrapEndpoint = ":confidentialwrap"
confidentialUnwrapEndpoint = ":confidentialunwrap"
tlsAlertRecord = 21
)
// ConfidentialEKMClient is an HTTP client that has methods for making
// requests to a server implementing the EKM UDE protocol.
type ConfidentialEKMClient struct {
URI string
AuthToken string
CertPool *x509.CertPool
}
// NewConfidentialEKMClient constructs a new ConfidentialEKMClient against
// the given URI.
func NewConfidentialEKMClient(uri string) ConfidentialEKMClient {
return ConfidentialEKMClient{URI: uri}
}
// GetJWTToken gets the JWT associated with the client.
// Test-only method.
func (c ConfidentialEKMClient) GetJWTToken() string {
return c.AuthToken
}
// SetJWTToken sets the JWT associated with the client.
// Test-only method.
func (c *ConfidentialEKMClient) SetJWTToken(token string) {
c.AuthToken = token
}
// Removes the last two path component from the key URI.
func removeEndpointPathComponent(url string) string {
for i := 0; i < 2; i++ {
substrIndex := strings.LastIndex(url, "/")
if substrIndex == -1 {
break
}
url = url[0:substrIndex]
}
return url
}
func (c ConfidentialEKMClient) post(ctx context.Context, url string, protoReq, protoResp proto.Message) error {
marshaled, err := protojson.Marshal(protoReq)
if err != nil {
return fmt.Errorf("error marshaling request: %w", err)
}
httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(marshaled))
if err != nil {
return fmt.Errorf("error creating HTTP request: %w", err)
}
httpReq.Header.Set("Content-Type", "application/json")
if c.AuthToken != "" {
httpReq.Header.Set("Authorization", "Bearer "+c.AuthToken)
}
client := http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: c.CertPool,
},
},
}
httpResp, err := client.Do(httpReq)
if err != nil {
return fmt.Errorf("HTTP call returned with error: %w", err)
}
defer httpResp.Body.Close()
respBody, err := ioutil.ReadAll(httpResp.Body)
if err != nil {
return fmt.Errorf("error reading HTTP response body: %w", err)
}
if httpResp.StatusCode != http.StatusOK {
return fmt.Errorf("non-OK status returned: %s - %s", httpResp.Status, string(respBody))
}
if err = protojson.Unmarshal(respBody, protoResp); err != nil {
return fmt.Errorf("error unmarshaling response: %w", err)
}
return nil
}
func (c ConfidentialEKMClient) BeginSession(ctx context.Context, req *sspb.BeginSessionRequest) (*sspb.BeginSessionResponse, error) {
resp := &sspb.BeginSessionResponse{}
url := removeEndpointPathComponent(c.URI) + beginSessionEndpoint
if err := c.post(ctx, url, req, resp); err != nil {
return nil, err
}
if resp.GetTlsRecords()[0] == tlsAlertRecord {
return resp, fmt.Errorf("TLS alert in response: %s", hex.EncodeToString(resp.GetTlsRecords()))
}
return resp, nil
}
func (c ConfidentialEKMClient) Handshake(ctx context.Context, req *sspb.HandshakeRequest) (*sspb.HandshakeResponse, error) {
resp := &sspb.HandshakeResponse{}
url := removeEndpointPathComponent(c.URI) + handshakeEndpoint
if err := c.post(ctx, url, req, resp); err != nil {
return nil, err
}
return resp, nil
}
func (c ConfidentialEKMClient) NegotiateAttestation(ctx context.Context, req *sspb.NegotiateAttestationRequest) (*sspb.NegotiateAttestationResponse, error) {
resp := &sspb.NegotiateAttestationResponse{}
url := removeEndpointPathComponent(c.URI) + negotiateAttestationEndpoint
if err := c.post(ctx, url, req, resp); err != nil {
return nil, err
}
return resp, nil
}
func (c ConfidentialEKMClient) Finalize(ctx context.Context, req *sspb.FinalizeRequest) (*sspb.FinalizeResponse, error) {
resp := &sspb.FinalizeResponse{}
url := removeEndpointPathComponent(c.URI) + finalizeEndpoint
if err := c.post(ctx, url, req, resp); err != nil {
return nil, err
}
return resp, nil
}
func (c ConfidentialEKMClient) EndSession(ctx context.Context, req *sspb.EndSessionRequest) (*sspb.EndSessionResponse, error) {
resp := &sspb.EndSessionResponse{}
url := removeEndpointPathComponent(c.URI) + endSessionEndpoint
if err := c.post(ctx, url, req, resp); err != nil {
return nil, err
}
return resp, nil
}
func (c ConfidentialEKMClient) ConfidentialWrap(ctx context.Context, req *cwpb.ConfidentialWrapRequest) (*cwpb.ConfidentialWrapResponse, error) {
resp := &cwpb.ConfidentialWrapResponse{}
url := c.URI + confidentialWrapEndpoint
if err := c.post(ctx, url, req, resp); err != nil {
return nil, err
}
return resp, nil
}
func (c ConfidentialEKMClient) ConfidentialUnwrap(ctx context.Context, req *cwpb.ConfidentialUnwrapRequest) (*cwpb.ConfidentialUnwrapResponse, error) {
resp := &cwpb.ConfidentialUnwrapResponse{}
url := c.URI + confidentialUnwrapEndpoint
if err := c.post(ctx, url, req, resp); err != nil {
return nil, err
}
return resp, nil
}