package transit import ( "context" "crypto/sha256" "crypto/sha512" "encoding/base64" "fmt" "hash" "github.com/hashicorp/vault/helper/errutil" "github.com/hashicorp/vault/helper/keysutil" "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical/framework" ) func (b *backend) pathSign() *framework.Path { return &framework.Path{ Pattern: "sign/" + framework.GenericNameRegex("name") + framework.OptionalParamRegex("urlalgorithm"), Fields: map[string]*framework.FieldSchema{ "name": &framework.FieldSchema{ Type: framework.TypeString, Description: "The key to use", }, "input": &framework.FieldSchema{ Type: framework.TypeString, Description: "The base64-encoded input data", }, "context": &framework.FieldSchema{ Type: framework.TypeString, Description: `Base64 encoded context for key derivation. Required if key derivation is enabled; currently only available with ed25519 keys.`, }, "hash_algorithm": &framework.FieldSchema{ Type: framework.TypeString, Default: "sha2-256", Description: `Hash algorithm to use (POST body parameter). Valid values are: * sha2-224 * sha2-256 * sha2-384 * sha2-512 Defaults to "sha2-256". Not valid for all key types, including ed25519.`, }, "algorithm": &framework.FieldSchema{ Type: framework.TypeString, Default: "sha2-256", Description: `Deprecated: use "hash_algorithm" instead.`, }, "urlalgorithm": &framework.FieldSchema{ Type: framework.TypeString, Description: `Hash algorithm to use (POST URL parameter)`, }, "key_version": &framework.FieldSchema{ Type: framework.TypeInt, Description: `The version of the key to use for signing. Must be 0 (for latest) or a value greater than or equal to the min_encryption_version configured on the key.`, }, "prehashed": &framework.FieldSchema{ Type: framework.TypeBool, Description: `Set to 'true' when the input is already hashed. If the key type is 'rsa-2048' or 'rsa-4096', then the algorithm used to hash the input should be indicated by the 'algorithm' parameter.`, }, "signature_algorithm": &framework.FieldSchema{ Type: framework.TypeString, Description: `The signature algorithm to use for signing. Currently only applies to RSA key types. Options are 'pss' or 'pkcs1v15'. Defaults to 'pss'`, }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: b.pathSignWrite, }, HelpSynopsis: pathSignHelpSyn, HelpDescription: pathSignHelpDesc, } } func (b *backend) pathVerify() *framework.Path { return &framework.Path{ Pattern: "verify/" + framework.GenericNameRegex("name") + framework.OptionalParamRegex("urlalgorithm"), Fields: map[string]*framework.FieldSchema{ "name": &framework.FieldSchema{ Type: framework.TypeString, Description: "The key to use", }, "context": &framework.FieldSchema{ Type: framework.TypeString, Description: `Base64 encoded context for key derivation. Required if key derivation is enabled; currently only available with ed25519 keys.`, }, "signature": &framework.FieldSchema{ Type: framework.TypeString, Description: "The signature, including vault header/key version", }, "hmac": &framework.FieldSchema{ Type: framework.TypeString, Description: "The HMAC, including vault header/key version", }, "input": &framework.FieldSchema{ Type: framework.TypeString, Description: "The base64-encoded input data to verify", }, "urlalgorithm": &framework.FieldSchema{ Type: framework.TypeString, Description: `Hash algorithm to use (POST URL parameter)`, }, "hash_algorithm": &framework.FieldSchema{ Type: framework.TypeString, Default: "sha2-256", Description: `Hash algorithm to use (POST body parameter). Valid values are: * sha2-224 * sha2-256 * sha2-384 * sha2-512 Defaults to "sha2-256". Not valid for all key types.`, }, "algorithm": &framework.FieldSchema{ Type: framework.TypeString, Default: "sha2-256", Description: `Deprecated: use "hash_algorithm" instead.`, }, "prehashed": &framework.FieldSchema{ Type: framework.TypeBool, Description: `Set to 'true' when the input is already hashed. If the key type is 'rsa-2048' or 'rsa-4096', then the algorithm used to hash the input should be indicated by the 'algorithm' parameter.`, }, "signature_algorithm": &framework.FieldSchema{ Type: framework.TypeString, Description: `The signature algorithm to use for signature verification. Currently only applies to RSA key types. Options are 'pss' or 'pkcs1v15'. Defaults to 'pss'`, }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: b.pathVerifyWrite, }, HelpSynopsis: pathVerifyHelpSyn, HelpDescription: pathVerifyHelpDesc, } } func (b *backend) pathSignWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { name := d.Get("name").(string) ver := d.Get("key_version").(int) inputB64 := d.Get("input").(string) hashAlgorithm := d.Get("urlalgorithm").(string) if hashAlgorithm == "" { hashAlgorithm = d.Get("hash_algorithm").(string) if hashAlgorithm == "" { hashAlgorithm = d.Get("algorithm").(string) } } prehashed := d.Get("prehashed").(bool) sigAlgorithm := d.Get("signature_algorithm").(string) input, err := base64.StdEncoding.DecodeString(inputB64) if err != nil { return logical.ErrorResponse(fmt.Sprintf("unable to decode input as base64: %s", err)), logical.ErrInvalidRequest } // Get the policy p, _, err := b.lm.GetPolicy(ctx, keysutil.PolicyRequest{ Storage: req.Storage, Name: name, }) if err != nil { return nil, err } if p == nil { return logical.ErrorResponse("encryption key not found"), logical.ErrInvalidRequest } if !b.System().CachingDisabled() { p.Lock(false) } if !p.Type.SigningSupported() { p.Unlock() return logical.ErrorResponse(fmt.Sprintf("key type %v does not support signing", p.Type)), logical.ErrInvalidRequest } contextRaw := d.Get("context").(string) var context []byte if len(contextRaw) != 0 { context, err = base64.StdEncoding.DecodeString(contextRaw) if err != nil { p.Unlock() return logical.ErrorResponse("failed to base64-decode context"), logical.ErrInvalidRequest } } if p.Type.HashSignatureInput() && !prehashed { var hf hash.Hash switch hashAlgorithm { case "sha2-224": hf = sha256.New224() case "sha2-256": hf = sha256.New() case "sha2-384": hf = sha512.New384() case "sha2-512": hf = sha512.New() default: p.Unlock() return logical.ErrorResponse(fmt.Sprintf("unsupported hash algorithm %s", hashAlgorithm)), nil } hf.Write(input) input = hf.Sum(nil) } sig, err := p.Sign(ver, context, input, hashAlgorithm, sigAlgorithm) if err != nil { p.Unlock() return nil, err } if sig == nil { p.Unlock() return nil, fmt.Errorf("signature could not be computed") } // Generate the response resp := &logical.Response{ Data: map[string]interface{}{ "signature": sig.Signature, }, } if len(sig.PublicKey) > 0 { resp.Data["public_key"] = sig.PublicKey } p.Unlock() return resp, nil } func (b *backend) pathVerifyWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { sig := d.Get("signature").(string) hmac := d.Get("hmac").(string) switch { case sig != "" && hmac != "": return logical.ErrorResponse("provide one of 'signature' or 'hmac'"), logical.ErrInvalidRequest case sig == "" && hmac == "": return logical.ErrorResponse("neither a 'signature' nor an 'hmac' were given to verify"), logical.ErrInvalidRequest case hmac != "": return b.pathHMACVerify(ctx, req, d, hmac) } name := d.Get("name").(string) inputB64 := d.Get("input").(string) hashAlgorithm := d.Get("urlalgorithm").(string) if hashAlgorithm == "" { hashAlgorithm = d.Get("hash_algorithm").(string) if hashAlgorithm == "" { hashAlgorithm = d.Get("algorithm").(string) } } prehashed := d.Get("prehashed").(bool) sigAlgorithm := d.Get("signature_algorithm").(string) input, err := base64.StdEncoding.DecodeString(inputB64) if err != nil { return logical.ErrorResponse(fmt.Sprintf("unable to decode input as base64: %s", err)), logical.ErrInvalidRequest } // Get the policy p, _, err := b.lm.GetPolicy(ctx, keysutil.PolicyRequest{ Storage: req.Storage, Name: name, }) if err != nil { return nil, err } if p == nil { return logical.ErrorResponse("encryption key not found"), logical.ErrInvalidRequest } if !b.System().CachingDisabled() { p.Lock(false) } if !p.Type.SigningSupported() { p.Unlock() return logical.ErrorResponse(fmt.Sprintf("key type %v does not support verification", p.Type)), logical.ErrInvalidRequest } contextRaw := d.Get("context").(string) var context []byte if len(contextRaw) != 0 { context, err = base64.StdEncoding.DecodeString(contextRaw) if err != nil { p.Unlock() return logical.ErrorResponse("failed to base64-decode context"), logical.ErrInvalidRequest } } if p.Type.HashSignatureInput() && !prehashed { var hf hash.Hash switch hashAlgorithm { case "sha2-224": hf = sha256.New224() case "sha2-256": hf = sha256.New() case "sha2-384": hf = sha512.New384() case "sha2-512": hf = sha512.New() default: p.Unlock() return logical.ErrorResponse(fmt.Sprintf("unsupported hash algorithm %s", hashAlgorithm)), nil } hf.Write(input) input = hf.Sum(nil) } valid, err := p.VerifySignature(context, input, sig, hashAlgorithm, sigAlgorithm) if err != nil { switch err.(type) { case errutil.UserError: p.Unlock() return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest case errutil.InternalError: p.Unlock() return nil, err default: p.Unlock() return nil, err } } // Generate the response resp := &logical.Response{ Data: map[string]interface{}{ "valid": valid, }, } p.Unlock() return resp, nil } const pathSignHelpSyn = `Generate a signature for input data using the named key` const pathSignHelpDesc = ` Generates a signature of the input data using the named key and the given hash algorithm. ` const pathVerifyHelpSyn = `Verify a signature or HMAC for input data created using the named key` const pathVerifyHelpDesc = ` Verifies a signature or HMAC of the input data using the named key and the given hash algorithm. `