mirror of
https://github.com/hanzokms/kmip.git
synced 2026-10-30 20:33:03 +00:00
1390 lines
40 KiB
Go
1390 lines
40 KiB
Go
package kmip
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import (
|
|
"context"
|
|
"crypto"
|
|
"crypto/tls"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"net"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/go-resty/resty/v2"
|
|
"github.com/gosimple/slug"
|
|
kms "github.com/hanzokms/go-sdk"
|
|
kmsErrors "github.com/hanzokms/go-sdk/packages/errors"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// Server implements core KMIP server
|
|
type Server struct {
|
|
// Listen address
|
|
Addr string
|
|
|
|
// Hanzo KMS Base API URL
|
|
KmsBaseAPIURL string
|
|
|
|
// TLS Configuration for the server
|
|
TLSConfig *tls.Config
|
|
|
|
// Log destination (if not set, log is discarded)
|
|
Log *log.Logger
|
|
|
|
// Supported version of KMIP, in the order of the preference
|
|
//
|
|
// If not set, defaults to DefaultSupportedVersions
|
|
SupportedVersions []ProtocolVersion
|
|
|
|
// Network read & write timeouts
|
|
//
|
|
// If set to zero, timeouts are not enforced
|
|
ReadTimeout time.Duration
|
|
WriteTimeout time.Duration
|
|
|
|
// SessionAuthHandler is called after TLS handshake
|
|
//
|
|
// This handler might additionally verify client TLS cert or perform
|
|
// any other kind of auth (say, by soure address)
|
|
SessionAuthHandler func(conn net.Conn) (sessionAuth SessionAuth, err error)
|
|
|
|
CertificatePrivateKey crypto.PrivateKey
|
|
CertificateSerialNumber string
|
|
|
|
KmsAuth kms.AuthInterface
|
|
|
|
l net.Listener
|
|
mu sync.Mutex
|
|
wg sync.WaitGroup
|
|
doneChan chan struct{}
|
|
handlers map[Enum]Handler
|
|
|
|
ServerName string
|
|
CertificateTTL string
|
|
HostnamesOrIps string
|
|
}
|
|
|
|
// Handler processes specific KMIP operation
|
|
type Handler func(req *RequestContext, item *RequestBatchItem) (resp interface{}, err error)
|
|
|
|
type SessionAuth struct {
|
|
ClientCertificateSerialNumber string
|
|
ProjectId string
|
|
ClientId string
|
|
}
|
|
|
|
// SessionContext is initialized for each connection
|
|
type SessionContext struct {
|
|
// Unique session identificator
|
|
SessionID string
|
|
|
|
// Additional opaque data related to connection auth, as returned by Server.SessionAuthHandler
|
|
SessionAuth SessionAuth
|
|
}
|
|
|
|
// RequestContext covers batch of requests
|
|
type RequestContext struct {
|
|
SessionContext
|
|
|
|
IdPlaceholder string
|
|
// RequestAuth captures result of request authentication
|
|
RequestAuth interface{}
|
|
}
|
|
|
|
// ListenAndServe creates TLS listening socket and calls Serve
|
|
//
|
|
// Channel initializedCh will be closed when listener is initialized
|
|
// (or fails to be initialized)
|
|
func (s *Server) ListenAndServe(initializedCh chan struct{}) error {
|
|
addr := s.Addr
|
|
if addr == "" {
|
|
addr = "localhost:5696"
|
|
}
|
|
|
|
l, err := tls.Listen("tcp", addr, s.TLSConfig)
|
|
s.Log.Printf("Listening on %s\n", addr)
|
|
|
|
if err != nil {
|
|
close(initializedCh)
|
|
return err
|
|
}
|
|
|
|
return s.Serve(l, initializedCh)
|
|
}
|
|
|
|
// Serve starts accepting and serving KMIP connection on a given listener
|
|
//
|
|
// Channel initializedCh will be closed when listener is initialized
|
|
// (or fails to be initialized)
|
|
func (s *Server) Serve(l net.Listener, initializedCh chan struct{}) error {
|
|
s.mu.Lock()
|
|
s.l = l
|
|
|
|
if s.Log == nil {
|
|
s.Log = log.New(ioutil.Discard, "", log.LstdFlags)
|
|
}
|
|
|
|
if len(s.SupportedVersions) == 0 {
|
|
s.SupportedVersions = append([]ProtocolVersion(nil), DefaultSupportedVersions...)
|
|
}
|
|
|
|
if s.handlers == nil {
|
|
s.initHandlers()
|
|
}
|
|
s.mu.Unlock()
|
|
|
|
close(initializedCh)
|
|
|
|
defer l.Close()
|
|
|
|
lastSession := uint32(0)
|
|
|
|
var tempDelay time.Duration
|
|
|
|
for {
|
|
conn, err := l.Accept()
|
|
if err != nil {
|
|
select {
|
|
case <-s.getDoneChan():
|
|
return nil
|
|
default:
|
|
}
|
|
|
|
if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
|
|
if tempDelay == 0 {
|
|
tempDelay = 5 * time.Millisecond
|
|
} else {
|
|
tempDelay *= 2
|
|
}
|
|
if max := 1 * time.Second; tempDelay > max {
|
|
tempDelay = max
|
|
}
|
|
s.Log.Printf("[ERROR] Accept error: %s, retrying in %s", err, tempDelay)
|
|
time.Sleep(tempDelay)
|
|
continue
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
lastSession++
|
|
tempDelay = 0
|
|
|
|
s.wg.Add(1)
|
|
go s.serve(conn, fmt.Sprintf("%08x", lastSession))
|
|
}
|
|
}
|
|
|
|
// Shutdown performs graceful shutdown of KMIP server waiting for connections to be closed
|
|
//
|
|
// Context might be used to limit time to wait for draining complete
|
|
func (s *Server) Shutdown(ctx context.Context) error {
|
|
close(s.getDoneChan())
|
|
|
|
s.mu.Lock()
|
|
if s.l != nil {
|
|
s.l.Close()
|
|
s.l = nil
|
|
}
|
|
s.mu.Unlock()
|
|
|
|
waitGroupDone := make(chan struct{})
|
|
|
|
go func() {
|
|
s.wg.Wait()
|
|
close(waitGroupDone)
|
|
}()
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-waitGroupDone:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Handle register handler for operation
|
|
//
|
|
// Server provides default handler for DISCOVER_VERSIONS operation, any other
|
|
func (s *Server) Handle(operation Enum, handler Handler) {
|
|
if s.handlers == nil {
|
|
s.initHandlers()
|
|
}
|
|
|
|
s.handlers[operation] = handler
|
|
}
|
|
|
|
func (s *Server) initHandlers() {
|
|
s.handlers = make(map[Enum]Handler)
|
|
s.handlers[OPERATION_DISCOVER_VERSIONS] = s.handleDiscoverVersions
|
|
s.handlers[OPERATION_CREATE] = s.handleCreate
|
|
s.handlers[OPERATION_GET] = s.handleGet
|
|
s.handlers[OPERATION_DESTROY] = s.handleDestroy
|
|
s.handlers[OPERATION_GET_ATTRIBUTES] = s.handleGetAttributes
|
|
s.handlers[OPERATION_ACTIVATE] = s.handleActivate
|
|
s.handlers[OPERATION_REVOKE] = s.handleRevoke
|
|
s.handlers[OPERATION_QUERY] = s.handleQuery
|
|
s.handlers[OPERATION_LOCATE] = s.handleLocate
|
|
s.handlers[OPERATION_REGISTER] = s.handleRegister
|
|
}
|
|
|
|
func (s *Server) getDoneChan() chan struct{} {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if s.doneChan == nil {
|
|
s.doneChan = make(chan struct{})
|
|
}
|
|
|
|
return s.doneChan
|
|
}
|
|
|
|
func (s *Server) serve(conn net.Conn, session string) {
|
|
defer s.wg.Done()
|
|
defer func() {
|
|
s.Log.Printf("[INFO] [%s] Closed connection from %s", session, conn.RemoteAddr().String())
|
|
conn.Close()
|
|
}()
|
|
|
|
s.Log.Printf("[INFO] [%s] New connection from %s", session, conn.RemoteAddr().String())
|
|
|
|
sessionCtx := &SessionContext{
|
|
SessionID: session,
|
|
}
|
|
|
|
if tlsConn, ok := conn.(*tls.Conn); ok {
|
|
if s.ReadTimeout != 0 {
|
|
_ = conn.SetReadDeadline(time.Now().Add(s.ReadTimeout))
|
|
}
|
|
if s.WriteTimeout != 0 {
|
|
_ = conn.SetWriteDeadline(time.Now().Add(s.WriteTimeout))
|
|
}
|
|
|
|
if err := tlsConn.Handshake(); err != nil {
|
|
s.Log.Printf("[ERROR] [%s] Error in TLS handshake: %s", session, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
s.mu.Lock()
|
|
sessionAuthHandler := s.SessionAuthHandler
|
|
s.mu.Unlock()
|
|
|
|
if sessionAuthHandler != nil {
|
|
var err error
|
|
|
|
sessionCtx.SessionAuth, err = sessionAuthHandler(conn)
|
|
if err != nil {
|
|
s.Log.Printf("[ERROR] [%s] Error in session auth handler: %s", session, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
d := NewDecoder(conn)
|
|
e := NewEncoder(conn)
|
|
|
|
for {
|
|
var req = &Request{}
|
|
|
|
if s.ReadTimeout != 0 {
|
|
_ = conn.SetReadDeadline(time.Now().Add(s.ReadTimeout))
|
|
}
|
|
|
|
err := d.Decode(req)
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
|
|
if err != nil {
|
|
s.Log.Printf("[ERROR] [%s] Error decoding KMIP message: %s", session, err)
|
|
break
|
|
}
|
|
|
|
var resp *Response
|
|
resp, err = s.handleBatch(sessionCtx, req)
|
|
if err != nil {
|
|
s.Log.Printf("[ERROR] [%s] Fatal error handling batch: %s", session, err)
|
|
break
|
|
}
|
|
|
|
if s.WriteTimeout != 0 {
|
|
_ = conn.SetWriteDeadline(time.Now().Add(s.WriteTimeout))
|
|
}
|
|
|
|
err = e.Encode(resp)
|
|
if err != nil {
|
|
s.Log.Printf("[ERROR] [%s] Error encoding KMIP response: %s", session, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleBatch(session *SessionContext, req *Request) (resp *Response, err error) {
|
|
if int(req.Header.BatchCount) != len(req.BatchItems) {
|
|
err = errors.Errorf("request batch count doesn't match number of batch items: %d != %d", req.Header.BatchCount, len(req.BatchItems))
|
|
return
|
|
}
|
|
|
|
if req.Header.AsynchronousIndicator {
|
|
err = errors.New("asynchnronous requests are not supported")
|
|
return
|
|
}
|
|
|
|
resp = &Response{
|
|
Header: ResponseHeader{
|
|
Version: req.Header.Version,
|
|
TimeStamp: time.Now(),
|
|
BatchCount: req.Header.BatchCount,
|
|
},
|
|
BatchItems: make([]ResponseBatchItem, req.Header.BatchCount),
|
|
}
|
|
|
|
if req.Header.Version.Major == 1 && req.Header.Version.Minor == 4 {
|
|
resp.Header.ClientCorrelationValue = req.Header.ClientCorrelationValue
|
|
}
|
|
|
|
requestCtx := &RequestContext{
|
|
SessionContext: *session,
|
|
}
|
|
|
|
for i := range req.BatchItems {
|
|
resp.BatchItems[i].Operation = req.BatchItems[i].Operation
|
|
resp.BatchItems[i].UniqueID = append([]byte(nil), req.BatchItems[i].UniqueID...)
|
|
|
|
var (
|
|
batchResp interface{}
|
|
batchErr error
|
|
)
|
|
|
|
batchResp, batchErr = s.handleWrapped(requestCtx, &req.BatchItems[i])
|
|
if batchErr != nil {
|
|
s.Log.Printf("[WARN] [%s] Request failed, operation %v: %s", requestCtx.SessionID, operationMap[req.BatchItems[i].Operation], batchErr)
|
|
|
|
resp.BatchItems[i].ResultStatus = RESULT_STATUS_OPERATION_FAILED
|
|
resp.BatchItems[i].ResultMessage = batchErr.Error()
|
|
if protoErr, ok := batchErr.(Error); ok {
|
|
resp.BatchItems[i].ResultReason = protoErr.ResultReason()
|
|
} else {
|
|
resp.BatchItems[i].ResultReason = RESULT_REASON_GENERAL_FAILURE
|
|
}
|
|
} else {
|
|
s.Log.Printf("[INFO] [%s] Request processed, operation %v", requestCtx.SessionID, operationMap[req.BatchItems[i].Operation])
|
|
resp.BatchItems[i].ResultStatus = RESULT_STATUS_SUCCESS
|
|
resp.BatchItems[i].ResponsePayload = batchResp
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (s *Server) handleWrapped(request *RequestContext, item *RequestBatchItem) (resp interface{}, err error) {
|
|
defer func() {
|
|
if p := recover(); p != nil {
|
|
err = errors.Errorf("panic: %s", p)
|
|
|
|
buf := make([]byte, 8192)
|
|
|
|
n := runtime.Stack(buf, false)
|
|
s.Log.Printf("[ERROR] [%s] Panic in request handler, operation %s: %s", request.SessionID, operationMap[item.Operation], string(buf[:n]))
|
|
}
|
|
}()
|
|
|
|
handler := s.handlers[item.Operation]
|
|
|
|
if handler == nil {
|
|
s.Log.Printf("[WARN] [%s] Operation not supported: %s", request.SessionID, operationMap[item.Operation])
|
|
err = wrapError(errors.New("operation not supported"), RESULT_REASON_OPERATION_NOT_SUPPORTED)
|
|
return
|
|
}
|
|
|
|
resp, err = handler(request, item)
|
|
return
|
|
}
|
|
|
|
func (s *Server) handleDiscoverVersions(req *RequestContext, item *RequestBatchItem) (resp interface{}, err error) {
|
|
response := DiscoverVersionsResponse{}
|
|
|
|
request, ok := item.RequestPayload.(DiscoverVersionsRequest)
|
|
if !ok {
|
|
err = wrapError(errors.New("wrong request body"), RESULT_REASON_INVALID_MESSAGE)
|
|
return
|
|
}
|
|
|
|
if len(request.ProtocolVersions) == 0 {
|
|
// return all the versions
|
|
response.ProtocolVersions = append([]ProtocolVersion(nil), s.SupportedVersions...)
|
|
} else {
|
|
// find matching versions
|
|
for _, version := range request.ProtocolVersions {
|
|
for _, v := range s.SupportedVersions {
|
|
if version == v {
|
|
response.ProtocolVersions = append(response.ProtocolVersions, v)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
resp = response
|
|
return
|
|
}
|
|
|
|
// TODO: add handling of dates and other attributes
|
|
func (s *Server) handleLocate(req *RequestContext, item *RequestBatchItem) (resp interface{}, err error) {
|
|
response := LocateResponse{}
|
|
|
|
request, ok := item.RequestPayload.(LocateRequest)
|
|
if !ok {
|
|
return nil, wrapError(errors.New("wrong request body"), RESULT_REASON_INVALID_MESSAGE)
|
|
}
|
|
|
|
s.Log.Printf("[DEBUG] Locate request attributes: %+v\n", request.Attributes)
|
|
s.Log.Printf("[DEBUG] Locate request MaximumItems: %d, OffsetItems: %d\n", request.MaximumItems, request.OffsetItems)
|
|
|
|
client := resty.New()
|
|
|
|
apiResp, err := client.R().
|
|
SetHeader("X-Kmip-Server-Certificate-Serial-Number", s.CertificateSerialNumber).
|
|
SetHeader("X-Kmip-Client-Certificate-Serial-Number", req.SessionAuth.ClientCertificateSerialNumber).
|
|
SetHeader("X-Kmip-Project-Id", req.SessionAuth.ProjectId).
|
|
SetHeader("X-Kmip-Client-Id", req.SessionAuth.ClientId).
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.KmsAuth.GetAccessToken())).
|
|
Post(fmt.Sprintf("%s/v1/kmip/spec/locate", s.KmsBaseAPIURL))
|
|
|
|
if err != nil {
|
|
s.Log.Printf("Error: %+v\n", err)
|
|
return nil, kmsErrors.NewRequestError("KmipLocateOperation", err)
|
|
}
|
|
|
|
if apiResp.IsError() {
|
|
return nil, kmsErrors.NewAPIErrorWithResponse("KmipLocateOperation", apiResp)
|
|
}
|
|
|
|
var result KmipLocateAPIResponse
|
|
if err := json.Unmarshal(apiResp.Body(), &result); err != nil {
|
|
return nil, errors.Wrap(err, "failed to decode response")
|
|
}
|
|
|
|
supportedAttributes := []string{
|
|
ATTRIBUTE_NAME_UNIQUE_IDENTIFIER,
|
|
ATTRIBUTE_NAME_CRYPTOGRAPHIC_ALGORITHM,
|
|
ATTRIBUTE_NAME_CRYPTOGRAPHIC_LENGTH,
|
|
ATTRIBUTE_NAME_STATE,
|
|
ATTRIBUTE_NAME_NAME,
|
|
ATTRIBUTE_NAME_OBJECT_TYPE,
|
|
ATTRIBUTE_NAME_CRYPTOGRAPHIC_USAGE_MASK,
|
|
ATTRIBUTE_NAME_CUSTOM_ATTRIBUTE,
|
|
}
|
|
|
|
unsupportedAttributes := []string{}
|
|
|
|
var matchingIds []string
|
|
|
|
for _, object := range result.Objects {
|
|
var algorithm Enum
|
|
var cryptographicLength int32
|
|
|
|
if object.Algorithm == "aes-256-gcm" {
|
|
algorithm = CRYPTO_AES
|
|
cryptographicLength = 256
|
|
} else if object.Algorithm == "aes-128-gcm" {
|
|
algorithm = CRYPTO_AES
|
|
cryptographicLength = 128
|
|
}
|
|
|
|
var state Enum
|
|
if object.IsActive {
|
|
state = STATE_ACTIVE
|
|
} else {
|
|
state = STATE_DEACTIVATED
|
|
}
|
|
|
|
shouldMatch := true
|
|
for _, attribute := range request.Attributes {
|
|
if attribute.Name == ATTRIBUTE_NAME_OBJECT_TYPE {
|
|
if attribute.Value != OBJECT_TYPE_SYMMETRIC_KEY {
|
|
shouldMatch = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if attribute.Name == ATTRIBUTE_NAME_UNIQUE_IDENTIFIER {
|
|
if attribute.Value != object.Id {
|
|
shouldMatch = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if attribute.Name == ATTRIBUTE_NAME_CRYPTOGRAPHIC_ALGORITHM {
|
|
if attribute.Value != algorithm {
|
|
shouldMatch = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if attribute.Name == ATTRIBUTE_NAME_CRYPTOGRAPHIC_LENGTH {
|
|
if attribute.Value != cryptographicLength {
|
|
shouldMatch = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if attribute.Name == ATTRIBUTE_NAME_STATE {
|
|
if attribute.Value != state {
|
|
shouldMatch = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if attribute.Name == ATTRIBUTE_NAME_NAME {
|
|
if nameValue, ok := attribute.Value.(Name); ok {
|
|
slug.CustomSub = map[string]string{"_": "-"}
|
|
slugifiedName := slug.Make(nameValue.Value)
|
|
if slugifiedName != object.Name && nameValue.Value != object.Id {
|
|
shouldMatch = false
|
|
break
|
|
}
|
|
} else {
|
|
return nil, wrapError(errors.New("name attribute is not of type Name"), RESULT_REASON_INVALID_FIELD)
|
|
}
|
|
}
|
|
|
|
if attribute.Name == ATTRIBUTE_NAME_CRYPTOGRAPHIC_USAGE_MASK {
|
|
// intentionally left blank as we do not have cryptographic usage mask yet!
|
|
continue
|
|
}
|
|
|
|
if strings.Contains(attribute.Name, "x-") {
|
|
if attribute.Value != object.KmipMetadata.Attributes[attribute.Name].Value {
|
|
shouldMatch = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if attribute.Name == ATTRIBUTE_NAME_CUSTOM_ATTRIBUTE {
|
|
if customAttribute, ok := attribute.Value.(CustomAttribute); ok {
|
|
if customAttribute.AttributeValue != object.KmipMetadata.Attributes[customAttribute.AttributeName].Value {
|
|
shouldMatch = false
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if !ContainsString(supportedAttributes, attribute.Name) {
|
|
if !ContainsString(unsupportedAttributes, attribute.Name) && !strings.Contains(attribute.Name, "x-") {
|
|
unsupportedAttributes = append(unsupportedAttributes, attribute.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
s.Log.Printf("[DEBUG] Object %s (name: %s) - shouldMatch: %v", object.Id, object.Name, shouldMatch)
|
|
if shouldMatch {
|
|
matchingIds = append(matchingIds, object.Id)
|
|
}
|
|
}
|
|
|
|
if len(unsupportedAttributes) > 0 {
|
|
s.Log.Printf("Unsupported Attributes: %+v\n", unsupportedAttributes)
|
|
}
|
|
|
|
offset := 0
|
|
maximum := len(matchingIds)
|
|
|
|
if request.MaximumItems > 0 {
|
|
maximum = int(request.MaximumItems)
|
|
if maximum > len(matchingIds) {
|
|
maximum = len(matchingIds)
|
|
}
|
|
}
|
|
|
|
if request.OffsetItems > 0 {
|
|
offset = int(request.OffsetItems)
|
|
if offset > len(matchingIds) {
|
|
offset = len(matchingIds)
|
|
}
|
|
}
|
|
|
|
end := offset + maximum
|
|
if end > len(matchingIds) {
|
|
end = len(matchingIds)
|
|
}
|
|
|
|
if len(matchingIds) == 1 {
|
|
req.IdPlaceholder = matchingIds[0]
|
|
}
|
|
|
|
response.LocatedItems = int32(len(matchingIds))
|
|
response.UniqueIdentifiers = append(response.UniqueIdentifiers, matchingIds[offset:end]...)
|
|
|
|
s.Log.Printf("[DEBUG] Final results - Total matching IDs: %d, Returning IDs: %v", len(matchingIds), matchingIds[offset:end])
|
|
|
|
return response, nil
|
|
}
|
|
|
|
func (s *Server) handleRegister(req *RequestContext, item *RequestBatchItem) (resp interface{}, err error) {
|
|
response := RegisterResponse{}
|
|
|
|
request, ok := item.RequestPayload.(RegisterRequest)
|
|
if !ok {
|
|
return nil, wrapError(errors.New("wrong request body"), RESULT_REASON_INVALID_MESSAGE)
|
|
}
|
|
|
|
var payload KmipRegisterAPIRequest
|
|
var keyData []byte
|
|
var algorithm string
|
|
|
|
// Handle different object types
|
|
switch request.ObjectType {
|
|
case OBJECT_TYPE_SYMMETRIC_KEY:
|
|
if request.SymmetricKey.KeyBlock.FormatType != KEY_FORMAT_RAW {
|
|
return nil, wrapError(errors.New("unsupported key format"), RESULT_REASON_INVALID_FIELD)
|
|
}
|
|
|
|
if request.SymmetricKey.KeyBlock.CryptographicAlgorithm != CRYPTO_AES {
|
|
return nil, wrapError(errors.New("unsupported algorithm"), RESULT_REASON_INVALID_FIELD)
|
|
}
|
|
|
|
if request.SymmetricKey.KeyBlock.CryptographicLength != 128 && request.SymmetricKey.KeyBlock.CryptographicLength != 256 {
|
|
return nil, wrapError(errors.New("unsupported cryptographic length"), RESULT_REASON_INVALID_FIELD)
|
|
}
|
|
|
|
if request.SymmetricKey.KeyBlock.WrappingData.WrappingMethod != 0 {
|
|
return nil, wrapError(errors.New("unsupported wrapping method"), RESULT_REASON_INVALID_FIELD)
|
|
}
|
|
|
|
keyData = request.SymmetricKey.KeyBlock.Value.KeyMaterial
|
|
if request.SymmetricKey.KeyBlock.CryptographicLength == 128 {
|
|
algorithm = "aes-128-gcm"
|
|
} else {
|
|
algorithm = "aes-256-gcm"
|
|
}
|
|
|
|
case OBJECT_TYPE_SECRET_DATA:
|
|
keyData = request.SecretData.KeyBlock.Value.KeyMaterial
|
|
|
|
if len(keyData) == 0 {
|
|
return nil, wrapError(errors.New("secret data is empty"), RESULT_REASON_INVALID_FIELD)
|
|
}
|
|
|
|
if request.SecretData.KeyBlock.WrappingData.WrappingMethod != 0 {
|
|
return nil, wrapError(errors.New("unsupported wrapping method for secret data"), RESULT_REASON_INVALID_FIELD)
|
|
}
|
|
|
|
case OBJECT_TYPE_TEMPLATE:
|
|
// Template objects don't contain cryptographic material, they contain attributes
|
|
// We'll handle Template objects separately without calling the register API
|
|
// Generate a unique identifier for the template
|
|
templateId := fmt.Sprintf("template-%d", time.Now().UnixNano())
|
|
|
|
// For Template objects, we don't need to call the external API
|
|
// Just return the template identifier
|
|
response.UniqueIdentifier = templateId
|
|
req.IdPlaceholder = templateId
|
|
|
|
response.TemplateAttribute = request.TemplateAttribute
|
|
|
|
return response, nil
|
|
|
|
default:
|
|
return nil, wrapError(errors.New("unsupported object type"), RESULT_REASON_INVALID_FIELD)
|
|
}
|
|
|
|
// Check if keyData is hex-encoded and decode it if necessary since we only want to send binary data to the API
|
|
keyData, wasHexEncoded, err := decodeHexIfEncoded(keyData)
|
|
if err != nil {
|
|
return nil, wrapError(errors.Wrap(err, "failed to decode hex-encoded key data"), RESULT_REASON_INVALID_FIELD)
|
|
}
|
|
|
|
if request.ObjectType == OBJECT_TYPE_SECRET_DATA {
|
|
dataLength := len(keyData) * 8 // Convert bytes to bits
|
|
if dataLength == 128 {
|
|
algorithm = "aes-128-gcm"
|
|
} else if dataLength == 256 {
|
|
algorithm = "aes-256-gcm"
|
|
} else {
|
|
algorithm = "aes-256-gcm"
|
|
}
|
|
}
|
|
|
|
// Extract KMIP metadata for SecretData
|
|
var kmipMetadata = KmipMetadata{
|
|
Attributes: make(map[string]AttributeMetadata),
|
|
}
|
|
|
|
if request.ObjectType == OBJECT_TYPE_SECRET_DATA {
|
|
kmipMetadata.SecretDataType = int(request.SecretData.SecretDataType)
|
|
kmipMetadata.SecretDataFormatType = int(request.SecretData.KeyBlock.FormatType)
|
|
}
|
|
|
|
kmipMetadata.ObjectType = int(request.ObjectType)
|
|
kmipMetadata.IsHexEncoded = wasHexEncoded
|
|
|
|
payload = KmipRegisterAPIRequest{
|
|
Key: base64.StdEncoding.EncodeToString(keyData),
|
|
Algorithm: algorithm,
|
|
KmipMetadata: kmipMetadata,
|
|
}
|
|
|
|
for _, attribute := range request.TemplateAttribute.Attributes {
|
|
if attribute.Name == ATTRIBUTE_NAME_NAME {
|
|
if nameValue, ok := attribute.Value.(Name); ok {
|
|
payload.Name = nameValue.Value
|
|
} else {
|
|
return nil, wrapError(errors.New("name attribute is not of type Name"), RESULT_REASON_INVALID_FIELD)
|
|
}
|
|
}
|
|
if strings.HasPrefix(attribute.Name, "x-") {
|
|
kmipMetadata.Attributes[attribute.Name] = AttributeMetadata{
|
|
Value: attribute.Value,
|
|
Type: ATTRIBUTE_NAME_CUSTOM_ATTRIBUTE,
|
|
}
|
|
}
|
|
|
|
if attribute.Name == ATTRIBUTE_NAME_CUSTOM_ATTRIBUTE {
|
|
if customAttribute, ok := attribute.Value.(CustomAttribute); ok {
|
|
kmipMetadata.Attributes[customAttribute.AttributeName] = AttributeMetadata{
|
|
Value: customAttribute.AttributeValue,
|
|
Type: ATTRIBUTE_NAME_CUSTOM_ATTRIBUTE,
|
|
}
|
|
} else {
|
|
fmt.Printf("custom attribute is not of type CustomAttribute: %+v\n", attribute.Value)
|
|
}
|
|
}
|
|
}
|
|
|
|
client := resty.New()
|
|
|
|
apiResp, err := client.R().
|
|
SetHeader("X-Kmip-Server-Certificate-Serial-Number", s.CertificateSerialNumber).
|
|
SetHeader("X-Kmip-Client-Certificate-Serial-Number", req.SessionAuth.ClientCertificateSerialNumber).
|
|
SetHeader("X-Kmip-Project-Id", req.SessionAuth.ProjectId).
|
|
SetHeader("X-Kmip-Client-Id", req.SessionAuth.ClientId).
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.KmsAuth.GetAccessToken())).
|
|
SetBody(payload).
|
|
Post(fmt.Sprintf("%s/v1/kmip/spec/register", s.KmsBaseAPIURL))
|
|
|
|
if err != nil {
|
|
s.Log.Printf("Error: %+v\n", err)
|
|
return nil, kmsErrors.NewRequestError("KmipRegisterOperation", err)
|
|
}
|
|
|
|
if apiResp.IsError() {
|
|
return nil, kmsErrors.NewAPIErrorWithResponse("KmipRegisterOperation", apiResp)
|
|
}
|
|
|
|
var result KmipRegisterAPIResponse
|
|
if err := json.Unmarshal(apiResp.Body(), &result); err != nil {
|
|
return nil, errors.Wrap(err, "failed to decode response")
|
|
}
|
|
|
|
req.IdPlaceholder = result.Id
|
|
response.UniqueIdentifier = result.Id
|
|
|
|
return response, nil
|
|
}
|
|
|
|
func (s *Server) handleActivate(req *RequestContext, item *RequestBatchItem) (resp interface{}, err error) {
|
|
response := ActivateResponse{}
|
|
|
|
request, ok := item.RequestPayload.(ActivateRequest)
|
|
if !ok {
|
|
return nil, wrapError(errors.New("wrong request body"), RESULT_REASON_INVALID_MESSAGE)
|
|
}
|
|
|
|
uniqueId := req.IdPlaceholder
|
|
if request.UniqueIdentifier != "" {
|
|
uniqueId = request.UniqueIdentifier
|
|
}
|
|
|
|
payload := KmipActivateAPIRequest{
|
|
Id: uniqueId,
|
|
}
|
|
|
|
client := resty.New()
|
|
|
|
apiResp, err := client.R().
|
|
SetHeader("X-Kmip-Server-Certificate-Serial-Number", s.CertificateSerialNumber).
|
|
SetHeader("X-Kmip-Client-Certificate-Serial-Number", req.SessionAuth.ClientCertificateSerialNumber).
|
|
SetHeader("X-Kmip-Project-Id", req.SessionAuth.ProjectId).
|
|
SetHeader("X-Kmip-Client-Id", req.SessionAuth.ClientId).
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.KmsAuth.GetAccessToken())).
|
|
SetBody(payload).
|
|
Post(fmt.Sprintf("%s/v1/kmip/spec/activate", s.KmsBaseAPIURL))
|
|
|
|
if err != nil {
|
|
s.Log.Printf("Error: %+v\n", err)
|
|
return nil, kmsErrors.NewRequestError("KmipActivateOperation", err)
|
|
}
|
|
|
|
if apiResp.IsError() {
|
|
return nil, kmsErrors.NewAPIErrorWithResponse("KmipActivateOperation", apiResp)
|
|
}
|
|
|
|
var result KmipActivateAPIResponse
|
|
if err := json.Unmarshal(apiResp.Body(), &result); err != nil {
|
|
return nil, errors.Wrap(err, "failed to decode response")
|
|
}
|
|
|
|
if !result.IsActive {
|
|
return nil, wrapError(errors.New("deactivated key cannot be enabled"), RESULT_REASON_ILLEGAL_OPERATION)
|
|
}
|
|
|
|
response.UniqueIdentifier = result.Id
|
|
|
|
return response, nil
|
|
}
|
|
|
|
func (s *Server) handleRevoke(req *RequestContext, item *RequestBatchItem) (resp interface{}, err error) {
|
|
response := RevokeResponse{}
|
|
|
|
request, ok := item.RequestPayload.(RevokeRequest)
|
|
if !ok {
|
|
return nil, wrapError(errors.New("wrong request body"), RESULT_REASON_INVALID_MESSAGE)
|
|
}
|
|
|
|
uniqueId := req.IdPlaceholder
|
|
if request.UniqueIdentifier != "" {
|
|
uniqueId = request.UniqueIdentifier
|
|
}
|
|
|
|
payload := KmipRevokeAPIRequest{
|
|
Id: uniqueId,
|
|
}
|
|
|
|
client := resty.New()
|
|
|
|
apiResp, err := client.R().
|
|
SetHeader("X-Kmip-Server-Certificate-Serial-Number", s.CertificateSerialNumber).
|
|
SetHeader("X-Kmip-Client-Certificate-Serial-Number", req.SessionAuth.ClientCertificateSerialNumber).
|
|
SetHeader("X-Kmip-Project-Id", req.SessionAuth.ProjectId).
|
|
SetHeader("X-Kmip-Client-Id", req.SessionAuth.ClientId).
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.KmsAuth.GetAccessToken())).
|
|
SetBody(payload).
|
|
Post(fmt.Sprintf("%s/v1/kmip/spec/revoke", s.KmsBaseAPIURL))
|
|
|
|
if err != nil {
|
|
s.Log.Printf("Error: %+v\n", err)
|
|
return nil, kmsErrors.NewRequestError("KmipRevokeOperation", err)
|
|
}
|
|
|
|
if apiResp.IsError() {
|
|
return nil, kmsErrors.NewAPIErrorWithResponse("KmipRevokeOperation", apiResp)
|
|
}
|
|
|
|
var result KmipRevokeAPIResponse
|
|
if err := json.Unmarshal(apiResp.Body(), &result); err != nil {
|
|
return nil, errors.Wrap(err, "failed to decode response")
|
|
}
|
|
|
|
response.UniqueIdentifier = result.Id
|
|
|
|
return response, nil
|
|
}
|
|
|
|
func (s *Server) handleGet(req *RequestContext, item *RequestBatchItem) (resp interface{}, err error) {
|
|
response := GetResponse{}
|
|
|
|
request, ok := item.RequestPayload.(GetRequest)
|
|
if !ok {
|
|
return nil, wrapError(errors.New("wrong request body"), RESULT_REASON_INVALID_MESSAGE)
|
|
}
|
|
|
|
uniqueId := req.IdPlaceholder
|
|
if request.UniqueIdentifier != "" {
|
|
uniqueId = request.UniqueIdentifier
|
|
}
|
|
|
|
// Check if this is a Template object (starts with "template-")
|
|
if len(uniqueId) > 9 && uniqueId[:9] == "template-" {
|
|
// Handle Template objects without calling external API
|
|
response.ObjectType = OBJECT_TYPE_TEMPLATE
|
|
response.UniqueIdentifier = uniqueId
|
|
// Template objects don't have cryptographic material, just attributes
|
|
// You might store/retrieve template attributes here
|
|
return response, nil
|
|
}
|
|
|
|
if request.KeyCompressionType != 0 {
|
|
return nil, wrapError(errors.New("key compression is not supported"), RESULT_REASON_INVALID_FIELD)
|
|
}
|
|
|
|
payload := KmipGetAPIRequest{
|
|
Id: uniqueId,
|
|
}
|
|
|
|
client := resty.New()
|
|
|
|
apiResp, err := client.R().
|
|
SetHeader("X-Kmip-Server-Certificate-Serial-Number", s.CertificateSerialNumber).
|
|
SetHeader("X-Kmip-Client-Certificate-Serial-Number", req.SessionAuth.ClientCertificateSerialNumber).
|
|
SetHeader("X-Kmip-Project-Id", req.SessionAuth.ProjectId).
|
|
SetHeader("X-Kmip-Client-Id", req.SessionAuth.ClientId).
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.KmsAuth.GetAccessToken())).
|
|
SetHeader("Content-Type", "application/json").
|
|
SetBody(payload).
|
|
Post(fmt.Sprintf("%s/v1/kmip/spec/get", s.KmsBaseAPIURL))
|
|
|
|
if err != nil {
|
|
s.Log.Printf("Error: %+v\n", err)
|
|
return nil, kmsErrors.NewRequestError("KmipGetOperation", err)
|
|
}
|
|
|
|
if apiResp.IsError() {
|
|
return nil, kmsErrors.NewAPIErrorWithResponse("KmipGetOperation", apiResp)
|
|
}
|
|
|
|
var result KmipGetAPIResponse
|
|
if err := json.Unmarshal(apiResp.Body(), &result); err != nil {
|
|
return nil, errors.Wrap(err, "failed to decode response")
|
|
}
|
|
|
|
response.UniqueIdentifier = result.Id
|
|
|
|
decodedValue, err := base64.StdEncoding.DecodeString(result.Value)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to decode base64 value")
|
|
}
|
|
|
|
// If the key was originally registered as hex-encoded, convert it back to hex for the response
|
|
keyMaterial := decodedValue
|
|
if result.KmipMetadata.IsHexEncoded {
|
|
keyMaterial = encodeToHex(decodedValue)
|
|
}
|
|
|
|
// Use stored metadata from API response
|
|
if result.KmipMetadata.SecretDataType != 0 {
|
|
// This is a SecretData object
|
|
response.ObjectType = OBJECT_TYPE_SECRET_DATA
|
|
response.SecretData.KeyBlock.Value.KeyMaterial = keyMaterial
|
|
response.SecretData.KeyBlock.FormatType = Enum(result.KmipMetadata.SecretDataFormatType)
|
|
response.SecretData.SecretDataType = Enum(result.KmipMetadata.SecretDataType)
|
|
} else {
|
|
// This is a SymmetricKey object (default behavior)
|
|
response.ObjectType = OBJECT_TYPE_SYMMETRIC_KEY
|
|
response.SymmetricKey.KeyBlock.Value.KeyMaterial = keyMaterial
|
|
response.SymmetricKey.KeyBlock.FormatType = KEY_FORMAT_RAW
|
|
|
|
// Set cryptographic parameters for SymmetricKey objects
|
|
if result.Algorithm == "aes-256-gcm" {
|
|
response.SymmetricKey.KeyBlock.CryptographicAlgorithm = CRYPTO_AES
|
|
response.SymmetricKey.KeyBlock.CryptographicLength = 256
|
|
} else if result.Algorithm == "aes-128-gcm" {
|
|
response.SymmetricKey.KeyBlock.CryptographicAlgorithm = CRYPTO_AES
|
|
response.SymmetricKey.KeyBlock.CryptographicLength = 128
|
|
} else {
|
|
return nil, errors.New("unsupported algorithm")
|
|
}
|
|
}
|
|
|
|
// Key wrapping is only supported for SymmetricKey objects
|
|
if request.KeyWrappingSpec.WrappingMethod != 0 && response.ObjectType == OBJECT_TYPE_SYMMETRIC_KEY {
|
|
if request.KeyWrappingSpec.WrappingMethod != WRAPPING_METHOD_ENCRYPT {
|
|
return nil, wrapError(errors.New("selected key wrapping method is not supported"), RESULT_REASON_INVALID_FIELD)
|
|
}
|
|
if request.KeyWrappingSpec.EncryptionKeyInformation.CryptoParams.BlockCipherMode != BLOCK_MODE_NISTKeyWrap {
|
|
return nil, wrapError(errors.New("selected block cipher mode is not supported"), RESULT_REASON_INVALID_FIELD)
|
|
}
|
|
if request.KeyWrappingSpec.EncodingOption != ENCODING_OPTION_NO_ENCODING {
|
|
return nil, wrapError(errors.New("encoding option is not supported"), RESULT_REASON_INVALID_FIELD)
|
|
}
|
|
|
|
payload := KmipGetAPIRequest{
|
|
Id: request.KeyWrappingSpec.EncryptionKeyInformation.UniqueIdentifier,
|
|
}
|
|
|
|
client := resty.New()
|
|
|
|
keyWrapperApiResp, err := client.R().
|
|
SetHeader("X-Kmip-Server-Certificate-Serial-Number", s.CertificateSerialNumber).
|
|
SetHeader("X-Kmip-Client-Certificate-Serial-Number", req.SessionAuth.ClientCertificateSerialNumber).
|
|
SetHeader("X-Kmip-Project-Id", req.SessionAuth.ProjectId).
|
|
SetHeader("X-Kmip-Client-Id", req.SessionAuth.ClientId).
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.KmsAuth.GetAccessToken())).
|
|
SetHeader("Content-Type", "application/json").
|
|
SetBody(payload).
|
|
Post(fmt.Sprintf("%s/v1/kmip/spec/get", s.KmsBaseAPIURL))
|
|
|
|
if err != nil {
|
|
s.Log.Printf("Error: %+v\n", err)
|
|
return nil, kmsErrors.NewRequestError("KmipGetOperation", err)
|
|
}
|
|
|
|
if apiResp.IsError() {
|
|
return nil, kmsErrors.NewAPIErrorWithResponse("KmipGetOperation", apiResp)
|
|
}
|
|
|
|
var keyWrapperResult KmipGetAPIResponse
|
|
if err := json.Unmarshal(keyWrapperApiResp.Body(), &keyWrapperResult); err != nil {
|
|
return nil, errors.Wrap(err, "failed to decode response")
|
|
}
|
|
|
|
decodedKeyWrapper, err := base64.StdEncoding.DecodeString(keyWrapperResult.Value)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to decode base64 value")
|
|
}
|
|
|
|
wrappedKey, err := AesWrap([]byte(decodedKeyWrapper), []byte(decodedValue))
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to wrap key")
|
|
}
|
|
|
|
// If the key was originally hex-encoded, hex-encode the wrapped key too
|
|
wrappedKeyMaterial := wrappedKey
|
|
if result.KmipMetadata.IsHexEncoded {
|
|
wrappedKeyMaterial = encodeToHex(wrappedKey)
|
|
}
|
|
|
|
response.SymmetricKey.KeyBlock.Value.KeyMaterial = wrappedKeyMaterial
|
|
response.SymmetricKey.KeyBlock.WrappingData = KeyWrappingData{
|
|
WrappingMethod: WRAPPING_METHOD_ENCRYPT,
|
|
EncryptionKeyInformation: EncryptionKeyInformation{
|
|
UniqueIdentifier: request.KeyWrappingSpec.EncryptionKeyInformation.UniqueIdentifier,
|
|
CryptoParams: CryptoParams{
|
|
BlockCipherMode: BLOCK_MODE_NISTKeyWrap,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
func (s *Server) handleDestroy(req *RequestContext, item *RequestBatchItem) (resp interface{}, err error) {
|
|
response := DestroyResponse{}
|
|
|
|
request, ok := item.RequestPayload.(DestroyRequest)
|
|
if !ok {
|
|
return nil, wrapError(errors.New("wrong request body"), RESULT_REASON_INVALID_MESSAGE)
|
|
}
|
|
|
|
uniqueId := req.IdPlaceholder
|
|
if request.UniqueIdentifier != "" {
|
|
uniqueId = request.UniqueIdentifier
|
|
}
|
|
|
|
payload := KmipDestroyAPIRequest{
|
|
Id: uniqueId,
|
|
}
|
|
|
|
client := resty.New()
|
|
|
|
apiResp, err := client.R().
|
|
SetHeader("X-Kmip-Server-Certificate-Serial-Number", s.CertificateSerialNumber).
|
|
SetHeader("X-Kmip-Client-Certificate-Serial-Number", req.SessionAuth.ClientCertificateSerialNumber).
|
|
SetHeader("X-Kmip-Project-Id", req.SessionAuth.ProjectId).
|
|
SetHeader("X-Kmip-Client-Id", req.SessionAuth.ClientId).
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.KmsAuth.GetAccessToken())).
|
|
SetHeader("Content-Type", "application/json").
|
|
SetBody(payload).
|
|
Post(fmt.Sprintf("%s/v1/kmip/spec/destroy", s.KmsBaseAPIURL))
|
|
|
|
if err != nil {
|
|
s.Log.Printf("Error: %+v\n", err)
|
|
return nil, kmsErrors.NewRequestError("KmipDestroyOperation", err)
|
|
}
|
|
|
|
if apiResp.IsError() {
|
|
return nil, kmsErrors.NewAPIErrorWithResponse("KmipDestroyOperation", apiResp)
|
|
}
|
|
|
|
var result KmipDestroyAPIResponse
|
|
if err := json.Unmarshal(apiResp.Body(), &result); err != nil {
|
|
return nil, errors.Wrap(err, "failed to decode response")
|
|
}
|
|
|
|
response.UniqueIdentifier = result.Id
|
|
return response, nil
|
|
}
|
|
|
|
/*
|
|
TODO: missing the following required attributes in KMIP 1.4:
|
|
- digest
|
|
- sensitive
|
|
- always sensitive
|
|
- extractable
|
|
- never extractable
|
|
*/
|
|
func (s *Server) handleGetAttributes(req *RequestContext, item *RequestBatchItem) (resp interface{}, err error) {
|
|
response := GetAttributesResponse{}
|
|
|
|
request, ok := item.RequestPayload.(GetAttributesRequest)
|
|
if !ok {
|
|
return nil, wrapError(errors.New("wrong request body"), RESULT_REASON_INVALID_MESSAGE)
|
|
}
|
|
|
|
uniqueId := req.IdPlaceholder
|
|
if request.UniqueIdentifier != "" {
|
|
uniqueId = request.UniqueIdentifier
|
|
}
|
|
|
|
payload := KmipGetAttributeAPIRequest{
|
|
Id: uniqueId,
|
|
}
|
|
|
|
client := resty.New()
|
|
|
|
apiResp, err := client.R().
|
|
SetHeader("X-Kmip-Server-Certificate-Serial-Number", s.CertificateSerialNumber).
|
|
SetHeader("X-Kmip-Client-Certificate-Serial-Number", req.SessionAuth.ClientCertificateSerialNumber).
|
|
SetHeader("X-Kmip-Project-Id", req.SessionAuth.ProjectId).
|
|
SetHeader("X-Kmip-Client-Id", req.SessionAuth.ClientId).
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.KmsAuth.GetAccessToken())).
|
|
SetHeader("Content-Type", "application/json").
|
|
SetBody(payload).
|
|
Post(fmt.Sprintf("%s/v1/kmip/spec/get-attributes", s.KmsBaseAPIURL))
|
|
|
|
if err != nil {
|
|
s.Log.Printf("Error: %+v\n", err)
|
|
return nil, kmsErrors.NewRequestError("KmipGetAttributesOperation", err)
|
|
}
|
|
|
|
if apiResp.IsError() {
|
|
return nil, kmsErrors.NewAPIErrorWithResponse("KmipGetAttributesOperation", apiResp)
|
|
}
|
|
|
|
var result KmipGetAttributeAPIResponse
|
|
if err := json.Unmarshal(apiResp.Body(), &result); err != nil {
|
|
return nil, errors.Wrap(err, "failed to decode response")
|
|
}
|
|
|
|
response.UniqueIdentifier = result.Id
|
|
|
|
var cryptographicLength int32
|
|
if result.Algorithm == "aes-256-gcm" {
|
|
cryptographicLength = 256
|
|
} else if result.Algorithm == "aes-128-gcm" {
|
|
cryptographicLength = 128
|
|
} else {
|
|
return nil, errors.New("unsupported algorithm")
|
|
}
|
|
|
|
var stateValue Enum
|
|
if result.IsActive {
|
|
stateValue = STATE_ACTIVE
|
|
} else {
|
|
stateValue = STATE_DEACTIVATED
|
|
}
|
|
|
|
response.UniqueIdentifier = result.Id
|
|
attributes := []Attribute{
|
|
{
|
|
Name: ATTRIBUTE_NAME_UNIQUE_IDENTIFIER,
|
|
Value: result.Id,
|
|
},
|
|
{
|
|
Name: ATTRIBUTE_NAME_CRYPTOGRAPHIC_ALGORITHM,
|
|
Value: CRYPTO_AES,
|
|
},
|
|
{
|
|
Name: ATTRIBUTE_NAME_OBJECT_TYPE,
|
|
Value: Enum(result.KmipMetadata.ObjectType),
|
|
},
|
|
{
|
|
Name: ATTRIBUTE_NAME_CRYPTOGRAPHIC_LENGTH,
|
|
Value: cryptographicLength,
|
|
},
|
|
{
|
|
Name: ATTRIBUTE_NAME_CRYPTOGRAPHIC_USAGE_MASK,
|
|
Value: int32(CRYPTO_USAGE_MASK_ENCRYPT | CRYPTO_USAGE_MASK_DECRYPT | CRYPTO_USAGE_MASK_WRAP_KEY | CRYPTO_USAGE_MASK_UNWRAP_KEY | CRYPTO_USAGE_MASK_MAC_GENERATE | CRYPTO_USAGE_MASK_MAC_VERIFY),
|
|
},
|
|
{
|
|
Name: ATTRIBUTE_NAME_STATE,
|
|
Value: stateValue,
|
|
},
|
|
{
|
|
Name: ATTRIBUTE_NAME_INITIAL_DATE,
|
|
Value: result.CreatedAt,
|
|
},
|
|
{
|
|
Name: ATTRIBUTE_NAME_ACTIVATION_DATE,
|
|
Value: result.CreatedAt,
|
|
},
|
|
{
|
|
Name: ATTRIBUTE_NAME_LAST_CHANGE_DATE,
|
|
Value: result.UpdatedAt,
|
|
},
|
|
}
|
|
|
|
if result.KmipMetadata.Attributes != nil {
|
|
for name, attributeMetadata := range result.KmipMetadata.Attributes {
|
|
attributes = append(attributes, Attribute{
|
|
Name: name,
|
|
Value: attributeMetadata.Value,
|
|
})
|
|
}
|
|
}
|
|
|
|
if len(request.AttributeNames) == 0 {
|
|
response.Attributes = attributes
|
|
} else {
|
|
for _, requestedAttribute := range request.AttributeNames {
|
|
for _, attribute := range attributes {
|
|
if requestedAttribute == attribute.Name {
|
|
response.Attributes = append(response.Attributes, attribute)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(response.Attributes) == 0 {
|
|
response.Attributes = []Attribute{
|
|
{
|
|
Name: ATTRIBUTE_NAME_UNIQUE_IDENTIFIER,
|
|
Value: result.Id,
|
|
},
|
|
}
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
// Based on the 1.4 spec, this is missing RNG Parameters, Profile Information, and Attestation Types
|
|
func (s *Server) handleQuery(req *RequestContext, item *RequestBatchItem) (resp interface{}, err error) {
|
|
response := QueryResponse{}
|
|
|
|
request, ok := item.RequestPayload.(QueryRequest)
|
|
if !ok {
|
|
return nil, wrapError(errors.New("wrong request body"), RESULT_REASON_INVALID_MESSAGE)
|
|
}
|
|
|
|
if ContainsEnum(request.QueryFunctions, QUERY_OPERATIONS) {
|
|
response.Operations = []Enum{
|
|
OPERATION_CREATE,
|
|
OPERATION_REGISTER,
|
|
OPERATION_LOCATE,
|
|
OPERATION_GET,
|
|
OPERATION_GET_ATTRIBUTES,
|
|
OPERATION_ACTIVATE,
|
|
OPERATION_REVOKE,
|
|
OPERATION_DESTROY,
|
|
OPERATION_QUERY,
|
|
OPERATION_DISCOVER_VERSIONS,
|
|
}
|
|
}
|
|
|
|
if ContainsEnum(request.QueryFunctions, QUERY_OBJECTS) {
|
|
response.ObjectTypes = []Enum{
|
|
OBJECT_TYPE_SYMMETRIC_KEY,
|
|
OBJECT_TYPE_SECRET_DATA,
|
|
OBJECT_TYPE_TEMPLATE,
|
|
}
|
|
}
|
|
|
|
if ContainsEnum(request.QueryFunctions, QUERY_SERVER_INFORMATION) {
|
|
response.VendorIdentification = "Hanzo KMS KMIP Server"
|
|
}
|
|
|
|
if ContainsEnum(request.QueryFunctions, QUERY_PROFILES) {
|
|
response.ProfileInformation = []ProfileInformation{
|
|
{
|
|
ProfileName: PROFILE_NAME_BASELINE_SERVER_BASIC_KMIP_V1_2,
|
|
},
|
|
{
|
|
ProfileName: PROFILE_NAME_BASELINE_SERVER_TLS_V1_2_KMIP_V1_2,
|
|
},
|
|
{
|
|
ProfileName: PROFILE_NAME_STORAGE_ARRAY_SELF_ENCRYPTING_DRIVE_SERVER_KMIP_V1_0,
|
|
},
|
|
{
|
|
ProfileName: PROFILE_NAME_STORAGE_ARRAY_SELF_ENCRYPTING_DRIVE_SERVER_KMIP_V1_1,
|
|
},
|
|
{
|
|
ProfileName: PROFILE_NAME_STORAGE_ARRAY_SELF_ENCRYPTING_DRIVE_SERVER_KMIP_V1_2,
|
|
},
|
|
{
|
|
ProfileName: PROFILE_NAME_BASELINE_SERVER_BASIC_KMIP_V1_3,
|
|
},
|
|
}
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
/* We currently only support creating symmetric keys */
|
|
func (s *Server) handleCreate(req *RequestContext, item *RequestBatchItem) (resp interface{}, err error) {
|
|
response := CreateResponse{}
|
|
|
|
request, ok := item.RequestPayload.(CreateRequest)
|
|
if !ok {
|
|
return nil, wrapError(errors.New("wrong request body"), RESULT_REASON_INVALID_MESSAGE)
|
|
}
|
|
|
|
if request.ObjectType != OBJECT_TYPE_SYMMETRIC_KEY {
|
|
return nil, wrapError(errors.New(fmt.Sprintf("cannot create object type %v with the Create operation", request.ObjectType)),
|
|
RESULT_REASON_INVALID_FIELD)
|
|
}
|
|
|
|
cryptoAlgorithm := request.TemplateAttribute.Attributes.Get(ATTRIBUTE_NAME_CRYPTOGRAPHIC_ALGORITHM)
|
|
if cryptoAlgorithm == nil {
|
|
return nil, wrapError(errors.New("cryptographic algorithm is required"), RESULT_REASON_INVALID_FIELD)
|
|
}
|
|
|
|
if cryptoAlgorithm != CRYPTO_AES {
|
|
return nil, wrapError(errors.New("only AES is supported"), RESULT_REASON_INVALID_FIELD)
|
|
}
|
|
|
|
algorithm := "aes-256-gcm"
|
|
length := request.TemplateAttribute.Attributes.Get(ATTRIBUTE_NAME_CRYPTOGRAPHIC_LENGTH)
|
|
if length != nil {
|
|
if lengthValue, ok := length.(int32); ok {
|
|
algorithm = fmt.Sprintf("aes-%d-gcm", lengthValue)
|
|
} else {
|
|
return nil, wrapError(errors.New("invalid cryptographic length type"), RESULT_REASON_INVALID_FIELD)
|
|
}
|
|
}
|
|
|
|
payload := KmipCreateAPIRequest{
|
|
Algorithm: algorithm,
|
|
}
|
|
|
|
client := resty.New()
|
|
|
|
apiResp, err := client.R().
|
|
SetHeader("X-Kmip-Server-Certificate-Serial-Number", s.CertificateSerialNumber).
|
|
SetHeader("X-Kmip-Client-Certificate-Serial-Number", req.SessionAuth.ClientCertificateSerialNumber).
|
|
SetHeader("X-Kmip-Project-Id", req.SessionAuth.ProjectId).
|
|
SetHeader("X-Kmip-Client-Id", req.SessionAuth.ClientId).
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.KmsAuth.GetAccessToken())).
|
|
SetHeader("Content-Type", "application/json").
|
|
SetBody(payload).
|
|
Post(fmt.Sprintf("%s/v1/kmip/spec/create", s.KmsBaseAPIURL))
|
|
|
|
if err != nil {
|
|
s.Log.Printf("Error: %+v\n", err)
|
|
return nil, kmsErrors.NewRequestError("KmipCreateOperation", err)
|
|
}
|
|
|
|
if apiResp.IsError() {
|
|
return nil, kmsErrors.NewAPIErrorWithResponse("KmipCreateOperation", apiResp)
|
|
}
|
|
|
|
var result KmipCreateAPIResponse
|
|
if err := json.Unmarshal(apiResp.Body(), &result); err != nil {
|
|
return nil, errors.Wrap(err, "failed to decode response")
|
|
}
|
|
|
|
response.ObjectType = OBJECT_TYPE_SYMMETRIC_KEY
|
|
response.UniqueIdentifier = result.Id
|
|
req.IdPlaceholder = result.Id
|
|
|
|
return response, nil
|
|
}
|
|
|
|
// DefaultSupportedVersions is a default list of supported KMIP versions
|
|
var DefaultSupportedVersions = []ProtocolVersion{
|
|
{Major: 1, Minor: 4},
|
|
{Major: 1, Minor: 3},
|
|
{Major: 1, Minor: 2},
|
|
{Major: 1, Minor: 1},
|
|
{Major: 1, Minor: 0},
|
|
}
|