2023-03-15 16:00:52 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2016-09-21 14:29:42 +00:00
|
|
|
package transit
|
|
|
|
|
|
|
|
import (
|
2018-01-08 18:31:38 +00:00
|
|
|
"context"
|
2016-09-21 14:29:42 +00:00
|
|
|
"crypto/hmac"
|
|
|
|
"encoding/base64"
|
2023-01-27 19:39:58 +00:00
|
|
|
"errors"
|
2016-09-21 14:29:42 +00:00
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
2019-04-13 07:44:06 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/framework"
|
2019-04-12 21:54:35 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/helper/keysutil"
|
|
|
|
"github.com/hashicorp/vault/sdk/logical"
|
2019-03-04 20:26:20 +00:00
|
|
|
"github.com/mitchellh/mapstructure"
|
2016-09-21 14:29:42 +00:00
|
|
|
)
|
|
|
|
|
2019-03-04 20:26:20 +00:00
|
|
|
// BatchRequestHMACItem represents a request item for batch processing.
|
|
|
|
// A map type allows us to distinguish between empty and missing values.
|
|
|
|
type batchRequestHMACItem map[string]string
|
|
|
|
|
2020-09-22 13:43:07 +00:00
|
|
|
// batchResponseHMACItem represents a response item for batch processing
|
2019-03-04 20:26:20 +00:00
|
|
|
type batchResponseHMACItem struct {
|
|
|
|
// HMAC for the input present in the corresponding batch request item
|
2019-03-04 22:49:29 +00:00
|
|
|
HMAC string `json:"hmac,omitempty" mapstructure:"hmac"`
|
2019-03-04 20:26:20 +00:00
|
|
|
|
|
|
|
// Valid indicates whether signature matches the signature derived from the input string
|
2019-03-04 22:49:29 +00:00
|
|
|
Valid bool `json:"valid,omitempty" mapstructure:"valid"`
|
2019-03-04 20:26:20 +00:00
|
|
|
|
|
|
|
// Error, if set represents a failure encountered while encrypting a
|
|
|
|
// corresponding batch request item
|
2019-03-04 22:49:29 +00:00
|
|
|
Error string `json:"error,omitempty" mapstructure:"error"`
|
2019-03-04 20:26:20 +00:00
|
|
|
|
|
|
|
// The return paths in some cases are (nil, err) and others
|
|
|
|
// (logical.ErrorResponse(..),nil), and others (logical.ErrorResponse(..),err).
|
|
|
|
// For batch processing to successfully mimic previous handling for simple 'input',
|
|
|
|
// both output values are needed - though 'err' should never be serialized.
|
|
|
|
err error
|
2022-12-13 18:03:40 +00:00
|
|
|
|
|
|
|
// Reference is an arbitrary caller supplied string value that will be placed on the
|
|
|
|
// batch response to ease correlation between inputs and outputs
|
|
|
|
Reference string `json:"reference" mapstructure:"reference"`
|
2019-03-04 20:26:20 +00:00
|
|
|
}
|
|
|
|
|
2016-09-21 14:29:42 +00:00
|
|
|
func (b *backend) pathHMAC() *framework.Path {
|
|
|
|
return &framework.Path{
|
|
|
|
Pattern: "hmac/" + framework.GenericNameRegex("name") + framework.OptionalParamRegex("urlalgorithm"),
|
2023-04-10 18:20:53 +00:00
|
|
|
|
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
|
|
OperationPrefix: operationPrefixTransit,
|
|
|
|
OperationVerb: "generate",
|
|
|
|
OperationSuffix: "hmac|hmac-with-algorithm",
|
|
|
|
},
|
|
|
|
|
2016-09-21 14:29:42 +00:00
|
|
|
Fields: map[string]*framework.FieldSchema{
|
2021-04-08 16:43:39 +00:00
|
|
|
"name": {
|
2016-09-21 14:29:42 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: "The key to use for the HMAC function",
|
|
|
|
},
|
|
|
|
|
2021-04-08 16:43:39 +00:00
|
|
|
"input": {
|
2016-09-21 14:29:42 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: "The base64-encoded input data",
|
|
|
|
},
|
|
|
|
|
2021-04-08 16:43:39 +00:00
|
|
|
"algorithm": {
|
2016-09-21 14:29:42 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Default: "sha2-256",
|
|
|
|
Description: `Algorithm to use (POST body parameter). Valid values are:
|
|
|
|
|
|
|
|
* sha2-224
|
|
|
|
* sha2-256
|
|
|
|
* sha2-384
|
|
|
|
* sha2-512
|
2021-12-08 18:29:33 +00:00
|
|
|
* sha3-224
|
|
|
|
* sha3-256
|
|
|
|
* sha3-384
|
|
|
|
* sha3-512
|
2016-09-21 14:29:42 +00:00
|
|
|
|
|
|
|
Defaults to "sha2-256".`,
|
|
|
|
},
|
|
|
|
|
2021-04-08 16:43:39 +00:00
|
|
|
"urlalgorithm": {
|
2016-09-21 14:29:42 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: `Algorithm to use (POST URL parameter)`,
|
|
|
|
},
|
2017-06-06 20:02:54 +00:00
|
|
|
|
2021-04-08 16:43:39 +00:00
|
|
|
"key_version": {
|
2017-06-06 20:02:54 +00:00
|
|
|
Type: framework.TypeInt,
|
|
|
|
Description: `The version of the key to use for generating the HMAC.
|
|
|
|
Must be 0 (for latest) or a value greater than or equal
|
|
|
|
to the min_encryption_version configured on the key.`,
|
|
|
|
},
|
2023-01-04 14:15:48 +00:00
|
|
|
|
|
|
|
"batch_input": {
|
|
|
|
Type: framework.TypeSlice,
|
|
|
|
Description: `
|
|
|
|
Specifies a list of items to be processed in a single batch. When this parameter
|
|
|
|
is set, if the parameter 'input' is also set, it will be ignored.
|
|
|
|
Any batch output will preserve the order of the batch input.`,
|
|
|
|
},
|
2016-09-21 14:29:42 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
|
|
|
logical.UpdateOperation: b.pathHMACWrite,
|
|
|
|
},
|
|
|
|
|
|
|
|
HelpSynopsis: pathHMACHelpSyn,
|
|
|
|
HelpDescription: pathHMACHelpDesc,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-08 18:31:38 +00:00
|
|
|
func (b *backend) pathHMACWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
2016-09-21 14:29:42 +00:00
|
|
|
name := d.Get("name").(string)
|
2017-06-06 20:02:54 +00:00
|
|
|
ver := d.Get("key_version").(int)
|
2019-03-04 20:26:20 +00:00
|
|
|
|
2016-09-21 14:29:42 +00:00
|
|
|
algorithm := d.Get("urlalgorithm").(string)
|
|
|
|
if algorithm == "" {
|
|
|
|
algorithm = d.Get("algorithm").(string)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the policy
|
2021-09-13 21:44:56 +00:00
|
|
|
p, _, err := b.GetPolicy(ctx, keysutil.PolicyRequest{
|
2018-06-12 16:24:12 +00:00
|
|
|
Storage: req.Storage,
|
|
|
|
Name: name,
|
2019-10-17 17:33:00 +00:00
|
|
|
}, b.GetRandomReader())
|
2016-09-21 14:29:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if p == nil {
|
2017-05-12 18:14:00 +00:00
|
|
|
return logical.ErrorResponse("encryption key not found"), logical.ErrInvalidRequest
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
2018-06-12 16:24:12 +00:00
|
|
|
if !b.System().CachingDisabled() {
|
|
|
|
p.Lock(false)
|
|
|
|
}
|
2016-09-21 14:29:42 +00:00
|
|
|
|
2017-06-06 20:02:54 +00:00
|
|
|
switch {
|
|
|
|
case ver == 0:
|
|
|
|
// Allowed, will use latest; set explicitly here to ensure the string
|
|
|
|
// is generated properly
|
|
|
|
ver = p.LatestVersion
|
|
|
|
case ver == p.LatestVersion:
|
|
|
|
// Allowed
|
|
|
|
case p.MinEncryptionVersion > 0 && ver < p.MinEncryptionVersion:
|
2018-06-12 16:24:12 +00:00
|
|
|
p.Unlock()
|
2017-06-06 20:02:54 +00:00
|
|
|
return logical.ErrorResponse("cannot generate HMAC: version is too old (disallowed by policy)"), logical.ErrInvalidRequest
|
|
|
|
}
|
|
|
|
|
|
|
|
key, err := p.HMACKey(ver)
|
2016-09-21 14:29:42 +00:00
|
|
|
if err != nil {
|
2018-06-12 16:24:12 +00:00
|
|
|
p.Unlock()
|
2016-09-21 14:29:42 +00:00
|
|
|
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
|
|
|
|
}
|
2023-01-27 19:39:58 +00:00
|
|
|
if key == nil && p.Type != keysutil.KeyType_MANAGED_KEY {
|
2018-06-12 16:24:12 +00:00
|
|
|
p.Unlock()
|
2016-09-21 14:29:42 +00:00
|
|
|
return nil, fmt.Errorf("HMAC key value could not be computed")
|
|
|
|
}
|
|
|
|
|
2019-03-04 22:49:29 +00:00
|
|
|
hashAlgorithm, ok := keysutil.HashTypeMap[algorithm]
|
|
|
|
if !ok {
|
2018-06-12 16:24:12 +00:00
|
|
|
p.Unlock()
|
2019-03-04 22:49:29 +00:00
|
|
|
return logical.ErrorResponse("unsupported algorithm %q", hashAlgorithm), nil
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
2019-03-04 22:49:29 +00:00
|
|
|
hashAlg := keysutil.HashFuncMap[hashAlgorithm]
|
|
|
|
|
2019-03-04 20:26:20 +00:00
|
|
|
batchInputRaw := d.Raw["batch_input"]
|
|
|
|
var batchInputItems []batchRequestHMACItem
|
|
|
|
if batchInputRaw != nil {
|
|
|
|
err = mapstructure.Decode(batchInputRaw, &batchInputItems)
|
|
|
|
if err != nil {
|
|
|
|
p.Unlock()
|
2021-04-22 15:20:59 +00:00
|
|
|
return nil, fmt.Errorf("failed to parse batch input: %w", err)
|
2019-03-04 20:26:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(batchInputItems) == 0 {
|
|
|
|
p.Unlock()
|
|
|
|
return logical.ErrorResponse("missing batch input to process"), logical.ErrInvalidRequest
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
valueRaw, ok := d.GetOk("input")
|
|
|
|
if !ok {
|
|
|
|
p.Unlock()
|
|
|
|
return logical.ErrorResponse("missing input for HMAC"), logical.ErrInvalidRequest
|
|
|
|
}
|
|
|
|
|
|
|
|
batchInputItems = make([]batchRequestHMACItem, 1)
|
|
|
|
batchInputItems[0] = batchRequestHMACItem{
|
|
|
|
"input": valueRaw.(string),
|
|
|
|
}
|
|
|
|
}
|
2016-09-21 14:29:42 +00:00
|
|
|
|
2019-03-04 20:26:20 +00:00
|
|
|
response := make([]batchResponseHMACItem, len(batchInputItems))
|
|
|
|
|
|
|
|
for i, item := range batchInputItems {
|
|
|
|
rawInput, ok := item["input"]
|
|
|
|
if !ok {
|
|
|
|
response[i].Error = "missing input for HMAC"
|
|
|
|
response[i].err = logical.ErrInvalidRequest
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
input, err := base64.StdEncoding.DecodeString(rawInput)
|
|
|
|
if err != nil {
|
|
|
|
response[i].Error = fmt.Sprintf("unable to decode input as base64: %s", err)
|
|
|
|
response[i].err = logical.ErrInvalidRequest
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-01-27 19:39:58 +00:00
|
|
|
var retBytes []byte
|
|
|
|
|
|
|
|
if p.Type == keysutil.KeyType_MANAGED_KEY {
|
|
|
|
managedKeySystemView, ok := b.System().(logical.ManagedKeySystemView)
|
|
|
|
if !ok {
|
|
|
|
response[i].err = errors.New("unsupported system view")
|
|
|
|
}
|
|
|
|
|
|
|
|
retBytes, err = p.HMACWithManagedKey(ctx, ver, managedKeySystemView, b.backendUUID, algorithm, input)
|
|
|
|
if err != nil {
|
|
|
|
response[i].err = err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
hf := hmac.New(hashAlg, key)
|
|
|
|
hf.Write(input)
|
|
|
|
retBytes = hf.Sum(nil)
|
|
|
|
}
|
2019-03-04 20:26:20 +00:00
|
|
|
|
|
|
|
retStr := base64.StdEncoding.EncodeToString(retBytes)
|
|
|
|
retStr = fmt.Sprintf("vault:v%s:%s", strconv.Itoa(ver), retStr)
|
|
|
|
response[i].HMAC = retStr
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
2018-06-12 16:24:12 +00:00
|
|
|
|
|
|
|
p.Unlock()
|
2019-03-04 20:26:20 +00:00
|
|
|
|
|
|
|
// Generate the response
|
|
|
|
resp := &logical.Response{}
|
|
|
|
if batchInputRaw != nil {
|
2022-12-13 18:03:40 +00:00
|
|
|
// Copy the references
|
|
|
|
for i := range batchInputItems {
|
|
|
|
response[i].Reference = batchInputItems[i]["reference"]
|
|
|
|
}
|
2019-03-04 20:26:20 +00:00
|
|
|
resp.Data = map[string]interface{}{
|
|
|
|
"batch_results": response,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if response[0].Error != "" || response[0].err != nil {
|
|
|
|
if response[0].Error != "" {
|
|
|
|
return logical.ErrorResponse(response[0].Error), response[0].err
|
|
|
|
} else {
|
|
|
|
return nil, response[0].err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
resp.Data = map[string]interface{}{
|
|
|
|
"hmac": response[0].HMAC,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-21 14:29:42 +00:00
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
2019-03-04 20:26:20 +00:00
|
|
|
func (b *backend) pathHMACVerify(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
2016-09-21 14:29:42 +00:00
|
|
|
name := d.Get("name").(string)
|
|
|
|
algorithm := d.Get("urlalgorithm").(string)
|
|
|
|
if algorithm == "" {
|
|
|
|
algorithm = d.Get("algorithm").(string)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the policy
|
2021-09-13 21:44:56 +00:00
|
|
|
p, _, err := b.GetPolicy(ctx, keysutil.PolicyRequest{
|
2018-06-12 16:24:12 +00:00
|
|
|
Storage: req.Storage,
|
|
|
|
Name: name,
|
2019-10-17 17:33:00 +00:00
|
|
|
}, b.GetRandomReader())
|
2016-09-21 14:29:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if p == nil {
|
2017-05-12 18:14:00 +00:00
|
|
|
return logical.ErrorResponse("encryption key not found"), logical.ErrInvalidRequest
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
2018-06-12 16:24:12 +00:00
|
|
|
if !b.System().CachingDisabled() {
|
|
|
|
p.Lock(false)
|
|
|
|
}
|
2016-09-21 14:29:42 +00:00
|
|
|
|
2019-03-04 22:49:29 +00:00
|
|
|
hashAlgorithm, ok := keysutil.HashTypeMap[algorithm]
|
|
|
|
if !ok {
|
2018-06-12 16:24:12 +00:00
|
|
|
p.Unlock()
|
2019-03-04 22:49:29 +00:00
|
|
|
return logical.ErrorResponse("unsupported algorithm %q", hashAlgorithm), nil
|
2019-03-04 20:26:20 +00:00
|
|
|
}
|
|
|
|
|
2019-03-04 22:49:29 +00:00
|
|
|
hashAlg := keysutil.HashFuncMap[hashAlgorithm]
|
|
|
|
|
2019-03-04 20:26:20 +00:00
|
|
|
batchInputRaw := d.Raw["batch_input"]
|
|
|
|
var batchInputItems []batchRequestHMACItem
|
|
|
|
if batchInputRaw != nil {
|
|
|
|
err := mapstructure.Decode(batchInputRaw, &batchInputItems)
|
|
|
|
if err != nil {
|
|
|
|
p.Unlock()
|
2021-04-22 15:20:59 +00:00
|
|
|
return nil, fmt.Errorf("failed to parse batch input: %w", err)
|
2019-03-04 20:26:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(batchInputItems) == 0 {
|
|
|
|
p.Unlock()
|
|
|
|
return logical.ErrorResponse("missing batch input to process"), logical.ErrInvalidRequest
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// use empty string if input is missing - not an error
|
|
|
|
inputB64 := d.Get("input").(string)
|
|
|
|
hmac := d.Get("hmac").(string)
|
|
|
|
|
|
|
|
batchInputItems = make([]batchRequestHMACItem, 1)
|
|
|
|
batchInputItems[0] = batchRequestHMACItem{
|
|
|
|
"input": inputB64,
|
|
|
|
"hmac": hmac,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
response := make([]batchResponseHMACItem, len(batchInputItems))
|
|
|
|
|
|
|
|
for i, item := range batchInputItems {
|
|
|
|
rawInput, ok := item["input"]
|
|
|
|
if !ok {
|
|
|
|
response[i].Error = "missing input"
|
|
|
|
response[i].err = logical.ErrInvalidRequest
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
input, err := base64.StdEncoding.DecodeString(rawInput)
|
|
|
|
if err != nil {
|
|
|
|
response[i].Error = fmt.Sprintf("unable to decode input as base64: %s", err)
|
|
|
|
response[i].err = logical.ErrInvalidRequest
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
verificationHMAC, ok := item["hmac"]
|
|
|
|
if !ok {
|
|
|
|
response[i].Error = "missing hmac"
|
|
|
|
response[i].err = logical.ErrInvalidRequest
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify the prefix
|
|
|
|
if !strings.HasPrefix(verificationHMAC, "vault:v") {
|
|
|
|
response[i].Error = "invalid HMAC to verify: no prefix"
|
|
|
|
response[i].err = logical.ErrInvalidRequest
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
splitVerificationHMAC := strings.SplitN(strings.TrimPrefix(verificationHMAC, "vault:v"), ":", 2)
|
|
|
|
if len(splitVerificationHMAC) != 2 {
|
|
|
|
response[i].Error = "invalid HMAC: wrong number of fields"
|
|
|
|
response[i].err = logical.ErrInvalidRequest
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
ver, err := strconv.Atoi(splitVerificationHMAC[0])
|
|
|
|
if err != nil {
|
|
|
|
response[i].Error = "invalid HMAC: version number could not be decoded"
|
|
|
|
response[i].err = logical.ErrInvalidRequest
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
verBytes, err := base64.StdEncoding.DecodeString(splitVerificationHMAC[1])
|
|
|
|
if err != nil {
|
|
|
|
response[i].Error = fmt.Sprintf("unable to decode verification HMAC as base64: %s", err)
|
|
|
|
response[i].err = logical.ErrInvalidRequest
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if ver > p.LatestVersion {
|
|
|
|
response[i].Error = "invalid HMAC: version is too new"
|
|
|
|
response[i].err = logical.ErrInvalidRequest
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if p.MinDecryptionVersion > 0 && ver < p.MinDecryptionVersion {
|
|
|
|
response[i].Error = "cannot verify HMAC: version is too old (disallowed by policy)"
|
|
|
|
response[i].err = logical.ErrInvalidRequest
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
key, err := p.HMACKey(ver)
|
|
|
|
if err != nil {
|
|
|
|
response[i].Error = err.Error()
|
|
|
|
response[i].err = logical.ErrInvalidRequest
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if key == nil {
|
|
|
|
response[i].Error = ""
|
|
|
|
response[i].err = fmt.Errorf("HMAC key value could not be computed")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-04-08 16:43:39 +00:00
|
|
|
hf := hmac.New(hashAlg, key)
|
2019-03-04 20:26:20 +00:00
|
|
|
hf.Write(input)
|
|
|
|
retBytes := hf.Sum(nil)
|
|
|
|
response[i].Valid = hmac.Equal(retBytes, verBytes)
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
2018-06-12 16:24:12 +00:00
|
|
|
p.Unlock()
|
2019-03-04 20:26:20 +00:00
|
|
|
|
|
|
|
// Generate the response
|
|
|
|
resp := &logical.Response{}
|
|
|
|
if batchInputRaw != nil {
|
2022-12-13 18:03:40 +00:00
|
|
|
// Copy the references
|
|
|
|
for i := range batchInputItems {
|
|
|
|
response[i].Reference = batchInputItems[i]["reference"]
|
|
|
|
}
|
2019-03-04 20:26:20 +00:00
|
|
|
resp.Data = map[string]interface{}{
|
|
|
|
"batch_results": response,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if response[0].Error != "" || response[0].err != nil {
|
|
|
|
if response[0].Error != "" {
|
|
|
|
return logical.ErrorResponse(response[0].Error), response[0].err
|
|
|
|
} else {
|
|
|
|
return nil, response[0].err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
resp.Data = map[string]interface{}{
|
|
|
|
"valid": response[0].Valid,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp, nil
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const pathHMACHelpSyn = `Generate an HMAC for input data using the named key`
|
|
|
|
|
|
|
|
const pathHMACHelpDesc = `
|
|
|
|
Generates an HMAC sum of the given algorithm and key against the given input data.
|
|
|
|
`
|