| // 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 |
| }, |
| } |
| } |