vault/builtin/logical/totp/path_code.go
hashicorp-copywrite[bot] 0b12cdcfd1
[COMPLIANCE] License changes (#22290)
* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License.

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUS-1.1

* Fix test that expected exact offset on hcl file

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
Co-authored-by: Sarah Thompson <sthompson@hashicorp.com>
Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
2023-08-10 18:14:03 -07:00

147 lines
3.7 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package totp
import (
"context"
"fmt"
"time"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
otplib "github.com/pquerna/otp"
totplib "github.com/pquerna/otp/totp"
)
func pathCode(b *backend) *framework.Path {
return &framework.Path{
Pattern: "code/" + framework.GenericNameWithAtRegex("name"),
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixTOTP,
OperationSuffix: "code",
},
Fields: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
Description: "Name of the key.",
},
"code": {
Type: framework.TypeString,
Description: "TOTP code to be validated.",
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{
Callback: b.pathReadCode,
DisplayAttrs: &framework.DisplayAttributes{
OperationVerb: "generate",
},
},
logical.UpdateOperation: &framework.PathOperation{
Callback: b.pathValidateCode,
DisplayAttrs: &framework.DisplayAttributes{
OperationVerb: "validate",
},
},
},
HelpSynopsis: pathCodeHelpSyn,
HelpDescription: pathCodeHelpDesc,
}
}
func (b *backend) pathReadCode(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
name := data.Get("name").(string)
// Get the key
key, err := b.Key(ctx, req.Storage, name)
if err != nil {
return nil, err
}
if key == nil {
return logical.ErrorResponse(fmt.Sprintf("unknown key: %s", name)), nil
}
// Generate password using totp library
totpToken, err := totplib.GenerateCodeCustom(key.Key, time.Now(), totplib.ValidateOpts{
Period: key.Period,
Digits: key.Digits,
Algorithm: key.Algorithm,
})
if err != nil {
return nil, err
}
// Return the secret
return &logical.Response{
Data: map[string]interface{}{
"code": totpToken,
},
}, nil
}
func (b *backend) pathValidateCode(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
name := data.Get("name").(string)
code := data.Get("code").(string)
// Enforce input value requirements
if code == "" {
return logical.ErrorResponse("the code value is required"), nil
}
// Get the key's stored values
key, err := b.Key(ctx, req.Storage, name)
if err != nil {
return nil, err
}
if key == nil {
return logical.ErrorResponse(fmt.Sprintf("unknown key: %s", name)), nil
}
usedName := fmt.Sprintf("%s_%s", name, code)
_, ok := b.usedCodes.Get(usedName)
if ok {
return logical.ErrorResponse("code already used; wait until the next time period"), nil
}
valid, err := totplib.ValidateCustom(code, key.Key, time.Now(), totplib.ValidateOpts{
Period: key.Period,
Skew: key.Skew,
Digits: key.Digits,
Algorithm: key.Algorithm,
})
if err != nil && err != otplib.ErrValidateInputInvalidLength {
return logical.ErrorResponse("an error occurred while validating the code"), err
}
// Take the key skew, add two for behind and in front, and multiple that by
// the period to cover the full possibility of the validity of the key
err = b.usedCodes.Add(usedName, nil, time.Duration(
int64(time.Second)*
int64(key.Period)*
int64((2+key.Skew))))
if err != nil {
return nil, fmt.Errorf("error adding code to used cache: %w", err)
}
return &logical.Response{
Data: map[string]interface{}{
"valid": valid,
},
}, nil
}
const pathCodeHelpSyn = `
Request time-based one-time use password or validate a password for a certain key .
`
const pathCodeHelpDesc = `
This path generates and validates time-based one-time use passwords for a certain key.
`