27bb03bbc0
* adding copyright header * fix fmt and a test
1244 lines
31 KiB
Go
1244 lines
31 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package totp
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net/url"
|
|
"path"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/helper/namespace"
|
|
logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
"github.com/mitchellh/mapstructure"
|
|
otplib "github.com/pquerna/otp"
|
|
totplib "github.com/pquerna/otp/totp"
|
|
)
|
|
|
|
func createKey() (string, error) {
|
|
keyUrl, err := totplib.Generate(totplib.GenerateOpts{
|
|
Issuer: "Vault",
|
|
AccountName: "Test",
|
|
})
|
|
|
|
key := keyUrl.Secret()
|
|
|
|
return strings.ToLower(key), err
|
|
}
|
|
|
|
func generateCode(key string, period uint, digits otplib.Digits, algorithm otplib.Algorithm) (string, error) {
|
|
// Generate password using totp library
|
|
totpToken, err := totplib.GenerateCodeCustom(key, time.Now(), totplib.ValidateOpts{
|
|
Period: period,
|
|
Digits: digits,
|
|
Algorithm: algorithm,
|
|
})
|
|
|
|
return totpToken, err
|
|
}
|
|
|
|
func TestBackend_KeyName(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
tests := []struct {
|
|
Name string
|
|
KeyName string
|
|
Fail bool
|
|
}{
|
|
{
|
|
"without @",
|
|
"sample",
|
|
false,
|
|
},
|
|
{
|
|
"with @ in the beginning",
|
|
"@sample.com",
|
|
true,
|
|
},
|
|
{
|
|
"with @ in the end",
|
|
"sample.com@",
|
|
true,
|
|
},
|
|
{
|
|
"with @ in between",
|
|
"sample@sample.com",
|
|
false,
|
|
},
|
|
{
|
|
"with multiple @",
|
|
"sample@sample@@sample.com",
|
|
false,
|
|
},
|
|
}
|
|
var resp *logical.Response
|
|
for _, tc := range tests {
|
|
resp, err = b.HandleRequest(namespace.RootContext(nil), &logical.Request{
|
|
Path: "keys/" + tc.KeyName,
|
|
Operation: logical.UpdateOperation,
|
|
Storage: config.StorageView,
|
|
Data: map[string]interface{}{
|
|
"generate": true,
|
|
"account_name": "vault",
|
|
"issuer": "hashicorp",
|
|
},
|
|
})
|
|
if tc.Fail {
|
|
if err == nil {
|
|
t.Fatalf("expected an error for test %q", tc.Name)
|
|
}
|
|
continue
|
|
} else if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("bad: test name: %q\nresp: %#v\nerr: %v", tc.Name, resp, err)
|
|
}
|
|
resp, err = b.HandleRequest(namespace.RootContext(nil), &logical.Request{
|
|
Path: "code/" + tc.KeyName,
|
|
Operation: logical.ReadOperation,
|
|
Storage: config.StorageView,
|
|
})
|
|
if err != nil || (resp != nil && resp.IsError()) {
|
|
t.Fatalf("bad: test name: %q\nresp: %#v\nerr: %v", tc.Name, resp, err)
|
|
}
|
|
if resp.Data["code"].(string) == "" {
|
|
t.Fatalf("failed to generate code for test %q", tc.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBackend_readCredentialsDefaultValues(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Generate a new shared key
|
|
key, _ := createKey()
|
|
|
|
keyData := map[string]interface{}{
|
|
"key": key,
|
|
"generate": false,
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"issuer": "",
|
|
"account_name": "",
|
|
"digits": otplib.DigitsSix,
|
|
"period": 30,
|
|
"algorithm": otplib.AlgorithmSHA1,
|
|
"key": key,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, false),
|
|
testAccStepReadKey(t, "test", expected),
|
|
testAccStepReadCreds(t, b, config.StorageView, "test", expected),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_readCredentialsEightDigitsThirtySecondPeriod(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Generate a new shared key
|
|
key, _ := createKey()
|
|
|
|
keyData := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"key": key,
|
|
"digits": 8,
|
|
"generate": false,
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"digits": otplib.DigitsEight,
|
|
"period": 30,
|
|
"algorithm": otplib.AlgorithmSHA1,
|
|
"key": key,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, false),
|
|
testAccStepReadKey(t, "test", expected),
|
|
testAccStepReadCreds(t, b, config.StorageView, "test", expected),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_readCredentialsSixDigitsNinetySecondPeriod(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Generate a new shared key
|
|
key, _ := createKey()
|
|
|
|
keyData := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"key": key,
|
|
"period": 90,
|
|
"generate": false,
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"digits": otplib.DigitsSix,
|
|
"period": 90,
|
|
"algorithm": otplib.AlgorithmSHA1,
|
|
"key": key,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, false),
|
|
testAccStepReadKey(t, "test", expected),
|
|
testAccStepReadCreds(t, b, config.StorageView, "test", expected),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_readCredentialsSHA256(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Generate a new shared key
|
|
key, _ := createKey()
|
|
|
|
keyData := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"key": key,
|
|
"algorithm": "SHA256",
|
|
"generate": false,
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"digits": otplib.DigitsSix,
|
|
"period": 30,
|
|
"algorithm": otplib.AlgorithmSHA256,
|
|
"key": key,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, false),
|
|
testAccStepReadKey(t, "test", expected),
|
|
testAccStepReadCreds(t, b, config.StorageView, "test", expected),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_readCredentialsSHA512(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Generate a new shared key
|
|
key, _ := createKey()
|
|
|
|
keyData := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"key": key,
|
|
"algorithm": "SHA512",
|
|
"generate": false,
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"digits": otplib.DigitsSix,
|
|
"period": 30,
|
|
"algorithm": otplib.AlgorithmSHA512,
|
|
"key": key,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, false),
|
|
testAccStepReadKey(t, "test", expected),
|
|
testAccStepReadCreds(t, b, config.StorageView, "test", expected),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_keyCrudDefaultValues(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
key, _ := createKey()
|
|
|
|
keyData := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"key": key,
|
|
"generate": false,
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"digits": otplib.DigitsSix,
|
|
"period": 30,
|
|
"algorithm": otplib.AlgorithmSHA1,
|
|
"key": key,
|
|
}
|
|
|
|
code, _ := generateCode(key, 30, otplib.DigitsSix, otplib.AlgorithmSHA1)
|
|
invalidCode := "12345678"
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, false),
|
|
testAccStepReadKey(t, "test", expected),
|
|
testAccStepValidateCode(t, "test", code, true, false),
|
|
// Next step should fail because it should be in the used cache
|
|
testAccStepValidateCode(t, "test", code, false, true),
|
|
testAccStepValidateCode(t, "test", invalidCode, false, false),
|
|
testAccStepDeleteKey(t, "test"),
|
|
testAccStepReadKey(t, "test", nil),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_createKeyMissingKeyValue(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
keyData := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"generate": false,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, true),
|
|
testAccStepReadKey(t, "test", nil),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_createKeyInvalidKeyValue(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
keyData := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"key": "1",
|
|
"generate": false,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, true),
|
|
testAccStepReadKey(t, "test", nil),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_createKeyInvalidAlgorithm(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Generate a new shared key
|
|
key, _ := createKey()
|
|
|
|
keyData := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"key": key,
|
|
"algorithm": "BADALGORITHM",
|
|
"generate": false,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, true),
|
|
testAccStepReadKey(t, "test", nil),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_createKeyInvalidPeriod(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Generate a new shared key
|
|
key, _ := createKey()
|
|
|
|
keyData := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"key": key,
|
|
"period": -1,
|
|
"generate": false,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, true),
|
|
testAccStepReadKey(t, "test", nil),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_createKeyInvalidDigits(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Generate a new shared key
|
|
key, _ := createKey()
|
|
|
|
keyData := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"key": key,
|
|
"digits": 20,
|
|
"generate": false,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, true),
|
|
testAccStepReadKey(t, "test", nil),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_generatedKeyDefaultValues(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
keyData := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"generate": true,
|
|
"key_size": 20,
|
|
"exported": true,
|
|
"qr_size": 200,
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"digits": otplib.DigitsSix,
|
|
"period": 30,
|
|
"algorithm": otplib.AlgorithmSHA1,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, false),
|
|
testAccStepReadKey(t, "test", expected),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_generatedKeyDefaultValuesNoQR(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
keyData := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"generate": true,
|
|
"key_size": 20,
|
|
"exported": true,
|
|
"qr_size": 0,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, false),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_generatedKeyNonDefaultKeySize(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
keyData := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"generate": true,
|
|
"key_size": 10,
|
|
"exported": true,
|
|
"qr_size": 200,
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"digits": otplib.DigitsSix,
|
|
"period": 30,
|
|
"algorithm": otplib.AlgorithmSHA1,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, false),
|
|
testAccStepReadKey(t, "test", expected),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_urlPassedNonGeneratedKeyInvalidPeriod(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
urlString := "otpauth://totp/Vault:test@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&algorithm=SHA512&digits=6&period=AZ"
|
|
|
|
keyData := map[string]interface{}{
|
|
"url": urlString,
|
|
"generate": false,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, true),
|
|
testAccStepReadKey(t, "test", nil),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_urlPassedNonGeneratedKeyInvalidDigits(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
urlString := "otpauth://totp/Vault:test@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&algorithm=SHA512&digits=Q&period=60"
|
|
|
|
keyData := map[string]interface{}{
|
|
"url": urlString,
|
|
"generate": false,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, true),
|
|
testAccStepReadKey(t, "test", nil),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_urlPassedNonGeneratedKeyIssuerInFirstPosition(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
urlString := "otpauth://totp/Vault:test@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&algorithm=SHA512&digits=6&period=60"
|
|
|
|
keyData := map[string]interface{}{
|
|
"url": urlString,
|
|
"generate": false,
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "test@email.com",
|
|
"digits": otplib.DigitsSix,
|
|
"period": 60,
|
|
"algorithm": otplib.AlgorithmSHA512,
|
|
"key": "HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ",
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, false),
|
|
testAccStepReadKey(t, "test", expected),
|
|
testAccStepReadCreds(t, b, config.StorageView, "test", expected),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_urlPassedNonGeneratedKeyIssuerInQueryString(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
urlString := "otpauth://totp/test@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&algorithm=SHA512&digits=6&period=60&issuer=Vault"
|
|
|
|
keyData := map[string]interface{}{
|
|
"url": urlString,
|
|
"generate": false,
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "test@email.com",
|
|
"digits": otplib.DigitsSix,
|
|
"period": 60,
|
|
"algorithm": otplib.AlgorithmSHA512,
|
|
"key": "HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ",
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, false),
|
|
testAccStepReadKey(t, "test", expected),
|
|
testAccStepReadCreds(t, b, config.StorageView, "test", expected),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_urlPassedNonGeneratedKeyMissingIssuer(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
urlString := "otpauth://totp/test@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&algorithm=SHA512&digits=6&period=60"
|
|
|
|
keyData := map[string]interface{}{
|
|
"url": urlString,
|
|
"generate": false,
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"issuer": "",
|
|
"account_name": "test@email.com",
|
|
"digits": otplib.DigitsSix,
|
|
"period": 60,
|
|
"algorithm": otplib.AlgorithmSHA512,
|
|
"key": "HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ",
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, false),
|
|
testAccStepReadKey(t, "test", expected),
|
|
testAccStepReadCreds(t, b, config.StorageView, "test", expected),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_urlPassedNonGeneratedKeyMissingAccountName(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
urlString := "otpauth://totp/Vault:?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&algorithm=SHA512&digits=6&period=60"
|
|
|
|
keyData := map[string]interface{}{
|
|
"url": urlString,
|
|
"generate": false,
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "",
|
|
"digits": otplib.DigitsSix,
|
|
"period": 60,
|
|
"algorithm": otplib.AlgorithmSHA512,
|
|
"key": "HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ",
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, false),
|
|
testAccStepReadKey(t, "test", expected),
|
|
testAccStepReadCreds(t, b, config.StorageView, "test", expected),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_urlPassedNonGeneratedKeyMissingAccountNameandIssuer(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
urlString := "otpauth://totp/?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&algorithm=SHA512&digits=6&period=60"
|
|
|
|
keyData := map[string]interface{}{
|
|
"url": urlString,
|
|
"generate": false,
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"issuer": "",
|
|
"account_name": "",
|
|
"digits": otplib.DigitsSix,
|
|
"period": 60,
|
|
"algorithm": otplib.AlgorithmSHA512,
|
|
"key": "HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ",
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, false),
|
|
testAccStepReadKey(t, "test", expected),
|
|
testAccStepReadCreds(t, b, config.StorageView, "test", expected),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_urlPassedNonGeneratedKeyMissingAccountNameandIssuerandPadding(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
urlString := "otpauth://totp/?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZAU&algorithm=SHA512&digits=6&period=60"
|
|
|
|
keyData := map[string]interface{}{
|
|
"url": urlString,
|
|
"generate": false,
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"issuer": "",
|
|
"account_name": "",
|
|
"digits": otplib.DigitsSix,
|
|
"period": 60,
|
|
"algorithm": otplib.AlgorithmSHA512,
|
|
"key": "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZAU===",
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, false),
|
|
testAccStepReadKey(t, "test", expected),
|
|
testAccStepReadCreds(t, b, config.StorageView, "test", expected),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_generatedKeyInvalidSkew(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
keyData := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"skew": "2",
|
|
"generate": true,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, true),
|
|
testAccStepReadKey(t, "test", nil),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_generatedKeyInvalidQRSize(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
keyData := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"qr_size": "-100",
|
|
"generate": true,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, true),
|
|
testAccStepReadKey(t, "test", nil),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_generatedKeyInvalidKeySize(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
keyData := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "Test",
|
|
"key_size": "-100",
|
|
"generate": true,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, true),
|
|
testAccStepReadKey(t, "test", nil),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_generatedKeyMissingAccountName(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
keyData := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"generate": true,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, true),
|
|
testAccStepReadKey(t, "test", nil),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_generatedKeyMissingIssuer(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
keyData := map[string]interface{}{
|
|
"account_name": "test@email.com",
|
|
"generate": true,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, true),
|
|
testAccStepReadKey(t, "test", nil),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_invalidURLValue(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
keyData := map[string]interface{}{
|
|
"url": "notaurl",
|
|
"generate": false,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, true),
|
|
testAccStepReadKey(t, "test", nil),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_urlAndGenerateTrue(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
keyData := map[string]interface{}{
|
|
"url": "otpauth://totp/Vault:test@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&algorithm=SHA512&digits=6&period=60",
|
|
"generate": true,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, true),
|
|
testAccStepReadKey(t, "test", nil),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_keyAndGenerateTrue(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
keyData := map[string]interface{}{
|
|
"key": "HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ",
|
|
"generate": true,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, true),
|
|
testAccStepReadKey(t, "test", nil),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestBackend_generatedKeyExportedFalse(t *testing.T) {
|
|
config := logical.TestBackendConfig()
|
|
config.StorageView = &logical.InmemStorage{}
|
|
b, err := Factory(context.Background(), config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
keyData := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "test@email.com",
|
|
"generate": true,
|
|
"exported": false,
|
|
}
|
|
|
|
expected := map[string]interface{}{
|
|
"issuer": "Vault",
|
|
"account_name": "test@email.com",
|
|
"digits": otplib.DigitsSix,
|
|
"period": 30,
|
|
"algorithm": otplib.AlgorithmSHA1,
|
|
}
|
|
|
|
logicaltest.Test(t, logicaltest.TestCase{
|
|
LogicalBackend: b,
|
|
Steps: []logicaltest.TestStep{
|
|
testAccStepCreateKey(t, "test", keyData, false),
|
|
testAccStepReadKey(t, "test", expected),
|
|
},
|
|
})
|
|
}
|
|
|
|
func testAccStepCreateKey(t *testing.T, name string, keyData map[string]interface{}, expectFail bool) logicaltest.TestStep {
|
|
return logicaltest.TestStep{
|
|
Operation: logical.UpdateOperation,
|
|
Path: path.Join("keys", name),
|
|
Data: keyData,
|
|
ErrorOk: expectFail,
|
|
Check: func(resp *logical.Response) error {
|
|
// Skip this if the key is not generated by vault or if the test is expected to fail
|
|
if !keyData["generate"].(bool) || expectFail {
|
|
return nil
|
|
}
|
|
|
|
// Check to see if barcode and url were returned if exported is false
|
|
if !keyData["exported"].(bool) {
|
|
if resp != nil {
|
|
t.Fatalf("data was returned when exported was set to false")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Check to see if a barcode was returned when qr_size is zero
|
|
if keyData["qr_size"].(int) == 0 {
|
|
if _, exists := resp.Data["barcode"]; exists {
|
|
t.Fatalf("a barcode was returned when qr_size was set to zero")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var d struct {
|
|
Url string `mapstructure:"url"`
|
|
Barcode string `mapstructure:"barcode"`
|
|
}
|
|
|
|
if err := mapstructure.Decode(resp.Data, &d); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check to see if barcode and url are returned
|
|
if d.Barcode == "" {
|
|
t.Fatalf("a barcode was not returned for a generated key")
|
|
}
|
|
|
|
if d.Url == "" {
|
|
t.Fatalf("a url was not returned for a generated key")
|
|
}
|
|
|
|
// Parse url
|
|
urlObject, err := url.Parse(d.Url)
|
|
if err != nil {
|
|
t.Fatal("an error occurred while parsing url string")
|
|
}
|
|
|
|
// Set up query object
|
|
urlQuery := urlObject.Query()
|
|
|
|
// Read secret
|
|
urlSecret := urlQuery.Get("secret")
|
|
|
|
// Check key length
|
|
keySize := keyData["key_size"].(int)
|
|
correctSecretStringSize := (keySize / 5) * 8
|
|
actualSecretStringSize := len(urlSecret)
|
|
|
|
if actualSecretStringSize != correctSecretStringSize {
|
|
t.Fatal("incorrect key string length")
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func testAccStepDeleteKey(t *testing.T, name string) logicaltest.TestStep {
|
|
return logicaltest.TestStep{
|
|
Operation: logical.DeleteOperation,
|
|
Path: path.Join("keys", name),
|
|
}
|
|
}
|
|
|
|
func testAccStepReadCreds(t *testing.T, b logical.Backend, s logical.Storage, name string, validation map[string]interface{}) logicaltest.TestStep {
|
|
return logicaltest.TestStep{
|
|
Operation: logical.ReadOperation,
|
|
Path: path.Join("code", name),
|
|
Check: func(resp *logical.Response) error {
|
|
var d struct {
|
|
Code string `mapstructure:"code"`
|
|
}
|
|
|
|
if err := mapstructure.Decode(resp.Data, &d); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Printf("[TRACE] Generated credentials: %v", d)
|
|
|
|
period := validation["period"].(int)
|
|
key := validation["key"].(string)
|
|
algorithm := validation["algorithm"].(otplib.Algorithm)
|
|
digits := validation["digits"].(otplib.Digits)
|
|
|
|
valid, _ := totplib.ValidateCustom(d.Code, key, time.Now(), totplib.ValidateOpts{
|
|
Period: uint(period),
|
|
Skew: 1,
|
|
Digits: digits,
|
|
Algorithm: algorithm,
|
|
})
|
|
|
|
if !valid {
|
|
t.Fatalf("generated code isn't valid")
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func testAccStepReadKey(t *testing.T, name string, expected map[string]interface{}) logicaltest.TestStep {
|
|
return logicaltest.TestStep{
|
|
Operation: logical.ReadOperation,
|
|
Path: "keys/" + name,
|
|
Check: func(resp *logical.Response) error {
|
|
if resp == nil {
|
|
if expected == nil {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("bad: %#v", resp)
|
|
}
|
|
|
|
var d struct {
|
|
Issuer string `mapstructure:"issuer"`
|
|
AccountName string `mapstructure:"account_name"`
|
|
Period uint `mapstructure:"period"`
|
|
Algorithm string `mapstructure:"algorithm"`
|
|
Digits otplib.Digits `mapstructure:"digits"`
|
|
}
|
|
|
|
if err := mapstructure.Decode(resp.Data, &d); err != nil {
|
|
return err
|
|
}
|
|
|
|
var keyAlgorithm otplib.Algorithm
|
|
switch d.Algorithm {
|
|
case "SHA1":
|
|
keyAlgorithm = otplib.AlgorithmSHA1
|
|
case "SHA256":
|
|
keyAlgorithm = otplib.AlgorithmSHA256
|
|
case "SHA512":
|
|
keyAlgorithm = otplib.AlgorithmSHA512
|
|
}
|
|
|
|
period := expected["period"].(int)
|
|
|
|
switch {
|
|
case d.Issuer != expected["issuer"]:
|
|
return fmt.Errorf("issuer should equal: %s", expected["issuer"])
|
|
case d.AccountName != expected["account_name"]:
|
|
return fmt.Errorf("account_name should equal: %s", expected["account_name"])
|
|
case d.Period != uint(period):
|
|
return fmt.Errorf("period should equal: %d", expected["period"])
|
|
case keyAlgorithm != expected["algorithm"]:
|
|
return fmt.Errorf("algorithm should equal: %s", expected["algorithm"])
|
|
case d.Digits != expected["digits"]:
|
|
return fmt.Errorf("digits should equal: %d", expected["digits"])
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func testAccStepValidateCode(t *testing.T, name string, code string, valid, expectError bool) logicaltest.TestStep {
|
|
return logicaltest.TestStep{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "code/" + name,
|
|
Data: map[string]interface{}{
|
|
"code": code,
|
|
},
|
|
ErrorOk: expectError,
|
|
Check: func(resp *logical.Response) error {
|
|
if resp == nil {
|
|
return fmt.Errorf("bad: %#v", resp)
|
|
}
|
|
|
|
var d struct {
|
|
Valid bool `mapstructure:"valid"`
|
|
}
|
|
|
|
if err := mapstructure.Decode(resp.Data, &d); err != nil {
|
|
return err
|
|
}
|
|
|
|
switch valid {
|
|
case true:
|
|
if d.Valid != true {
|
|
return fmt.Errorf("code was not valid: %s", code)
|
|
}
|
|
|
|
default:
|
|
if d.Valid != false {
|
|
return fmt.Errorf("code was incorrectly validated: %s", code)
|
|
}
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
}
|