From 79fc24b24794eb59749731af736050f08ceb815e Mon Sep 17 00:00:00 2001 From: Anand Date: Tue, 23 Sep 2025 21:15:26 +0530 Subject: [PATCH] code refactoring and unit tests --- .gitignore | 31 ++ Makefile | 4 +- SECURITY_AUDIT_REPORT.md | 270 +++++++++++++++ TAGS | 416 ++++++++++++++++++++++ actions.go | 226 ++++++------ crypto.go | 49 +-- db.go | 91 ++--- defns.go | 15 + export.go | 42 +-- {test => sample}/testpgp.go | 0 main.go => scripts/main.go | 77 ++--- tests/utils_test.go | 669 ++++++++++++++++++++++++++++++++++++ utils.go | 150 ++++---- 13 files changed, 1727 insertions(+), 313 deletions(-) create mode 100644 .gitignore create mode 100644 SECURITY_AUDIT_REPORT.md create mode 100644 TAGS create mode 100644 defns.go rename {test => sample}/testpgp.go (100%) rename main.go => scripts/main.go (74%) create mode 100644 tests/utils_test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8fd3804 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binaries +*.test + +# Output of go build +*.out + +# Go workspace file (Go 1.18+) +go.work +go.work.sum + +# Dependency directories (optional, if you use vendoring) +vendor/ + +# IDE/editor files +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS-generated files +.DS_Store +Thumbs.db +allpasswds* \ No newline at end of file diff --git a/Makefile b/Makefile index e4f7b33..23e2b22 100644 --- a/Makefile +++ b/Makefile @@ -3,10 +3,10 @@ PROGRAM=varuh all: program -program: $(wildcard *.go) +program: scripts/main.go $(wildcard *.go) @echo "Building ${PROGRAM}" @go mod tidy - @go build -o ${PROGRAM} *.go + @go build -o ${PROGRAM} scripts/main.go install: @echo -n "Installing ${PROGRAM}" diff --git a/SECURITY_AUDIT_REPORT.md b/SECURITY_AUDIT_REPORT.md new file mode 100644 index 0000000..e62979e --- /dev/null +++ b/SECURITY_AUDIT_REPORT.md @@ -0,0 +1,270 @@ +# Varuh Security Audit Report + +## Executive Summary + +This document outlines security vulnerabilities and design flaws identified in the Varuh password manager during a comprehensive security audit. The analysis covers cryptographic implementation, database security, input validation, file handling, and overall system architecture. + +## Critical Security Issues + +### 1. SQL Injection Vulnerabilities + +**Severity: HIGH** + +**Location:** `db.go:526-530` and `db.go:702` + +**Issue:** Direct string interpolation in SQL queries without proper parameterization. + +```go +// Vulnerable code in searchDatabaseEntry function +searchTerm = fmt.Sprintf("%%%s%%", term) +query := db.Where(fmt.Sprintf("title like \"%s\"", searchTerm)) + +for _, field := range []string{"user", "url", "notes", "tags"} { + query = query.Or(fmt.Sprintf("%s like \"%s\"", field, searchTerm)) +} + +// Vulnerable code in iterateEntries function +rows, err = db.Model(&Entry{}).Order(fmt.Sprintf("%s %s", orderKey, order)).Rows() +``` + +**Impact:** Attackers could potentially execute arbitrary SQL commands by crafting malicious search terms or order parameters. + +**Recommendation:** Use GORM's parameterized queries: +```go +query := db.Where("title LIKE ?", "%"+term+"%") +query = query.Or("user LIKE ?", "%"+term+"%") +// etc. +``` + +### 2. Weak Random Number Generation + +**Severity: MEDIUM-HIGH** + +**Location:** `crypto.go:478` + +**Issue:** Use of `math/rand` with time-based seeding for password generation. + +```go +rand.Seed(time.Now().UnixNano()) +length = rand.Intn(4) + 12 +``` + +**Impact:** Predictable random numbers could lead to weaker password generation. + +**Recommendation:** Use `crypto/rand` consistently throughout the application. + +### 3. Insecure File Permissions + +**Severity: MEDIUM** + +**Location:** Multiple files + +**Issue:** Inconsistent file permission handling. + +```go +// In utils.go - config files created with 0644 +fh, err := os.OpenFile(configFile, os.O_RDWR, 0644) + +// In crypto.go - encrypted files use 0600 (correct) +err = os.WriteFile(encDbPath, encText, 0600) +``` + +**Impact:** Configuration files may be readable by other users on the system. + +**Recommendation:** Use 0600 permissions for all sensitive files. + +## Design Flaws + +### 4. Password Storage in Memory + +**Severity: MEDIUM** + +**Location:** Throughout the application + +**Issue:** Passwords are stored in plain text in memory during operations and passed between functions as strings. + +**Impact:** Passwords may persist in memory longer than necessary and could be exposed through memory dumps. + +**Recommendation:** +- Use secure memory clearing functions +- Minimize password lifetime in memory +- Consider using byte slices that can be zeroed + +### 5. Insufficient Input Validation + +**Severity: MEDIUM** + +**Location:** Multiple locations + +**Issues:** +- No length limits on user inputs (titles, URLs, notes) +- No validation of URL format beyond basic HTTP/HTTPS prefix +- No sanitization of custom field names + +**Impact:** Potential for denial of service or data corruption. + +**Recommendation:** Implement comprehensive input validation and sanitization. + +### 6. Signal Handling Race Conditions + +**Severity: LOW-MEDIUM** + +**Location:** `actions.go:40-50` and `actions.go:82-92` + +**Issue:** Signal handlers may not properly clean up resources or may cause race conditions. + +```go +go func() { + sig := <-sigChan + fmt.Println("Received signal", sig) + // Reencrypt + encryptDatabase(defaultDB, &encPasswd) + os.Exit(1) +}() +``` + +**Impact:** Potential data loss or corruption during unexpected termination. + +**Recommendation:** Implement proper cleanup mechanisms and avoid race conditions. + +### 7. Clipboard Security + +**Severity: LOW-MEDIUM** + +**Location:** `utils.go:599` + +**Issue:** Passwords are copied to system clipboard without automatic clearing. + +```go +func copyPasswordToClipboard(passwd string) { + clipboard.WriteAll(passwd) +} +``` + +**Impact:** Passwords may remain in clipboard history accessible to other applications. + +**Recommendation:** Implement clipboard clearing after a timeout or provide user notification. + +## Cryptographic Concerns + +### 8. Argon2 Parameters + +**Severity: LOW** + +**Location:** `crypto.go:68` + +**Issue:** Argon2 parameters may be insufficient for current security standards. + +```go +key = argon2.Key([]byte(passPhrase), salt, 3, 32*1024, 4, KEY_SIZE) +``` + +**Impact:** Weaker key derivation than recommended. + +**Recommendation:** Use Argon2id with higher memory (64MB+) and iterations (4+). + +### 9. HMAC Implementation + +**Severity: LOW** + +**Location:** `crypto.go:183-186` + +**Issue:** HMAC-SHA512 is used for authentication, which is acceptable but SHA-256 would be sufficient. + +**Impact:** Minor performance impact, no security impact. + +**Recommendation:** Consider using HMAC-SHA256 for better performance. + +## File System Security + +### 10. Temporary File Handling + +**Severity: MEDIUM** + +**Location:** `export.go:171` and `export.go:188` + +**Issue:** Temporary files may not be properly cleaned up in all error scenarios. + +```go +tmpFile = randomFileName(os.TempDir(), ".tmp") +// ... operations ... +os.Remove(tmpFile) // May not execute in error cases +``` + +**Impact:** Sensitive data may remain in temporary files. + +**Recommendation:** Use defer statements for cleanup and ensure proper error handling. + +### 11. File Overwrite Operations + +**Severity: MEDIUM** + +**Location:** `crypto.go:194-203` + +**Issue:** Atomic file operations are attempted but may fail partially. + +```go +err = os.WriteFile(encDbPath, encText, 0600) +if err == nil { + err = os.WriteFile(dbPath, encText, 0600) + if err == nil { + os.Remove(encDbPath) + } +} +``` + +**Impact:** Potential data loss if operations fail partially. + +**Recommendation:** Implement proper atomic file operations using rename operations. + +## Database Security + +### 12. Database Schema Exposure + +**Severity: LOW** + +**Location:** `db.go:18-60` + +**Issue:** Database schema is well-documented and predictable. + +**Impact:** Makes it easier for attackers to understand the data structure. + +**Recommendation:** Consider obfuscating field names or using generic field names. + +### 13. No Database Encryption at Rest + +**Severity: MEDIUM** + +**Location:** Database operations + +**Issue:** SQLite databases are stored unencrypted when not using the application's encryption. + +**Impact:** Database files may be readable if accessed directly. + +**Recommendation:** Consider using SQLCipher or similar encrypted database solutions. + +## Recommendations Summary + +### Immediate Actions Required: +1. **Fix SQL injection vulnerabilities** - Use parameterized queries +2. **Implement proper input validation** - Add length limits and sanitization +3. **Fix file permissions** - Use 0600 for all sensitive files +4. **Improve random number generation** - Use crypto/rand consistently + +### Medium Priority: +1. **Implement secure memory handling** - Clear sensitive data from memory +2. **Improve file operations** - Use atomic operations +3. **Add clipboard security** - Implement timeout or clearing +4. **Enhance signal handling** - Prevent race conditions + +### Long-term Improvements: +1. **Upgrade cryptographic parameters** - Use stronger Argon2 settings +2. **Consider database encryption** - Use SQLCipher +3. **Implement audit logging** - Track security-relevant events +4. **Add comprehensive testing** - Security-focused test suite + +## Conclusion + +While Varuh implements good cryptographic practices with AES-256 and XChaCha20-Poly1305, several critical security vulnerabilities need immediate attention. The SQL injection vulnerabilities are the most serious concern and should be addressed immediately. The overall architecture is sound, but implementation details need refinement to meet security best practices. + +The application would benefit from a security-focused refactoring to address these issues systematically, with particular attention to input validation, memory management, and file handling security. \ No newline at end of file diff --git a/TAGS b/TAGS new file mode 100644 index 0000000..e90d98d --- /dev/null +++ b/TAGS @@ -0,0 +1,416 @@ +!_TAG_FILE_FORMAT 2 +!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted/ +!_TAG_PROGRAM_AUTHOR Joel Stemmer /stemmertech@gmail.com/ +!_TAG_PROGRAM_NAME gotags +!_TAG_PROGRAM_URL https://github.com/jstemmer/gotags +!_TAG_PROGRAM_VERSION 1.4.1 /go1.24.5/ +APP ./defns.go 10;" c access:public line:10 +AUTHOR_INFO ./defns.go 12;" c access:public line:12 +ActiveDB ./utils.go 35;" w access:public ctype:Settings line:35 type:string +Address ./db.go 81;" t access:public line:81 type:struct +Area ./db.go 87;" w access:public ctype:Address line:87 type:string +AssertContains ./tests/test_config.go 232;" f access:public line:232 signature:(t *testing.T, str, substr string, message string) +AssertEmpty ./tests/test_config.go 225;" f access:public line:225 signature:(t *testing.T, str string, message string) +AssertEqual ./tests/test_config.go 190;" f access:public line:190 signature:(t *testing.T, actual, expected interface{}, message string) +AssertError ./tests/test_config.go 183;" f access:public line:183 signature:(t *testing.T, err error, message string) +AssertFalse ./tests/test_config.go 211;" f access:public line:211 signature:(t *testing.T, condition bool, message string) +AssertFileContent ./tests/test_config.go 137;" f access:public line:137 signature:(t *testing.T, filePath string, expectedContent string) +AssertFileExists ./tests/test_config.go 109;" f access:public line:109 signature:(t *testing.T, filePath string) +AssertFileNotExists ./tests/test_config.go 116;" f access:public line:116 signature:(t *testing.T, filePath string) +AssertFilePermissions ./tests/test_config.go 123;" f access:public line:123 signature:(t *testing.T, filePath string, expectedMode os.FileMode) +AssertFileSize ./tests/test_config.go 150;" f access:public line:150 signature:(t *testing.T, filePath string, expectedSize int64) +AssertFileSizeGreaterThan ./tests/test_config.go 163;" f access:public line:163 signature:(t *testing.T, filePath string, minSize int64) +AssertLength ./tests/test_config.go 246;" f access:public line:246 signature:(t *testing.T, slice interface{}, expectedLength int, message string) +AssertNoError ./tests/test_config.go 176;" f access:public line:176 signature:(t *testing.T, err error, message string) +AssertNotContains ./tests/test_config.go 239;" f access:public line:239 signature:(t *testing.T, str, substr string, message string) +AssertNotEmpty ./tests/test_config.go 218;" f access:public line:218 signature:(t *testing.T, str string, message string) +AssertNotEqual ./tests/test_config.go 197;" f access:public line:197 signature:(t *testing.T, actual, expected interface{}, message string) +AssertTrue ./tests/test_config.go 204;" f access:public line:204 signature:(t *testing.T, condition bool, message string) +AssumeYes ./utils.go 29;" w access:public ctype:SettingsOverride line:29 type:bool +AutoEncrypt ./utils.go 37;" w access:public ctype:Settings line:37 type:bool +BgColor ./utils.go 50;" w access:public ctype:Settings line:50 type:string +Building ./db.go 84;" w access:public ctype:Address line:84 type:string +Cipher ./utils.go 36;" w access:public ctype:Settings line:36 type:string +City ./db.go 88;" w access:public ctype:Address line:88 type:string +Class ./db.go 27;" w access:public ctype:Entry line:27 type:string +CleanupTestConfig ./tests/test_config.go 31;" m access:public ctype:TestConfig line:31 signature:() +CmdOption ./scripts/main.go 15;" t access:public line:15 type:struct +Color ./utils.go 49;" w access:public ctype:Settings line:49 type:string +ConfigPath ./tests/test_config.go 14;" w access:public ctype:TestConfig line:14 type:string +ConfigPath ./utils.go 40;" w access:public ctype:Settings line:40 type:string +Copy ./db.go 105;" m access:public ctype:Entry line:105 signature:(e2 *Entry) +Copy ./db.go 146;" m access:public ctype:ExtendedEntry line:146 signature:(e2 *ExtendedEntry) +CopyPassword ./utils.go 28;" w access:public ctype:SettingsOverride line:28 type:bool +Country ./db.go 90;" w access:public ctype:Address line:90 type:string +CreateTestCardDatabase ./tests/test_config.go 62;" m access:public ctype:TestConfig line:62 signature:(t *testing.T) +CreateTestDatabase ./tests/test_config.go 36;" m access:public ctype:TestConfig line:36 signature:(t *testing.T) +CreateTestDatabaseWithCustomFields ./tests/test_config.go 86;" m access:public ctype:TestConfig line:86 signature:(t *testing.T) +CustomEntry ./actions.go 17;" t access:public line:17 type:struct +DELIMSIZE ./utils.go 23;" c access:public line:23 type:int +Default ./scripts/main.go 20;" w access:public ctype:CmdOption line:20 type:string +Delim ./utils.go 48;" w access:public ctype:Settings line:48 type:string +Entry ./db.go 18;" t access:public line:18 type:struct +Entry ./db.go 73;" w access:public ctype:ExtendedEntry line:73 type:Entry +Entry ./db.go 96;" w access:public ctype:Address line:96 type:Entry +EntryID ./db.go 74;" w access:public ctype:ExtendedEntry line:74 type:int +EntryID ./db.go 97;" w access:public ctype:Address line:97 type:int +ExpiryDate ./db.go 25;" w access:public ctype:Entry line:25 type:string +ExtendedEntry ./db.go 67;" t access:public line:67 type:struct +FieldName ./db.go 69;" w access:public ctype:ExtendedEntry line:69 type:string +FieldValue ./db.go 70;" w access:public ctype:ExtendedEntry line:70 type:string +HMAC_SHA512_SIZE ./crypto.go 27;" c access:public line:27 +Help ./scripts/main.go 18;" w access:public ctype:CmdOption line:18 type:string +ID ./db.go 19;" w access:public ctype:Entry line:19 type:int +ID ./db.go 68;" w access:public ctype:ExtendedEntry line:68 type:int +ID ./db.go 82;" w access:public ctype:Address line:82 type:int +Issuer ./db.go 26;" w access:public ctype:Entry line:26 type:string +KEY_N_ITER ./crypto.go 26;" c access:public line:26 +KEY_SIZE ./crypto.go 24;" c access:public line:24 +KeepEncrypted ./utils.go 38;" w access:public ctype:Settings line:38 type:bool +Landmark ./db.go 92;" w access:public ctype:Address line:92 type:string +ListOrder ./utils.go 47;" w access:public ctype:Settings line:47 type:string +Locality ./db.go 86;" w access:public ctype:Address line:86 type:string +Long ./scripts/main.go 17;" w access:public ctype:CmdOption line:17 type:string +MAGIC_HEADER ./crypto.go 28;" c access:public line:28 +MapString ./utils.go 57;" f access:public line:57 signature:(vs []string, f func(string) string) type:[]string +Notes ./db.go 29;" w access:public ctype:Entry line:29 type:string +Number ./db.go 83;" w access:public ctype:Address line:83 type:string +Password ./db.go 23;" w access:public ctype:Entry line:23 type:string +Path ./scripts/main.go 19;" w access:public ctype:CmdOption line:19 type:string +Pin ./db.go 24;" w access:public ctype:Entry line:24 type:string +SALT_SIZE ./crypto.go 25;" c access:public line:25 +Settings ./utils.go 34;" t access:public line:34 type:struct +SettingsOverride ./utils.go 26;" t access:public line:26 type:struct +SetupTestConfig ./tests/test_config.go 18;" f access:public ctype:TestConfig line:18 signature:(t *testing.T) type:*TestConfig +Short ./scripts/main.go 16;" w access:public ctype:CmdOption line:16 type:string +ShowPasswords ./utils.go 27;" w access:public ctype:SettingsOverride line:27 type:bool +ShowPasswords ./utils.go 39;" w access:public ctype:Settings line:39 type:bool +State ./db.go 89;" w access:public ctype:Address line:89 type:string +Street ./db.go 85;" w access:public ctype:Address line:85 type:string +TableName ./db.go 100;" m access:public ctype:Address line:100 signature:() type:string +TableName ./db.go 62;" m access:public ctype:Entry line:62 signature:() type:string +TableName ./db.go 77;" m access:public ctype:ExtendedEntry line:77 signature:() type:string +Tags ./db.go 30;" w access:public ctype:Entry line:30 type:string +TempDir ./tests/test_config.go 12;" w access:public ctype:TestConfig line:12 type:string +TestAddCustomFields ./tests/test_actions.go 125;" f access:public line:125 signature:(t *testing.T) +TestAddNewCardEntry ./tests/test_actions.go 41;" f access:public line:41 signature:(t *testing.T) +TestAddNewDatabaseCardEntry ./tests/test_db.go 173;" f access:public line:173 signature:(t *testing.T) +TestAddNewDatabaseEntry ./tests/test_db.go 111;" f access:public line:111 signature:(t *testing.T) +TestAddNewEntry ./tests/test_actions.go 63;" f access:public line:63 signature:(t *testing.T) +TestAddOrUpdateCustomFields ./tests/test_actions.go 85;" f access:public line:85 signature:(t *testing.T) +TestAuthorInfo ./tests/test_main.go 30;" f access:public line:30 signature:(t *testing.T) +TestCheckActiveDatabase ./tests/test_utils.go 403;" f access:public line:403 signature:(t *testing.T) +TestCheckValidExpiry ./tests/test_utils.go 603;" f access:public line:603 signature:(t *testing.T) +TestCloneEntry ./tests/test_db.go 449;" f access:public line:449 signature:(t *testing.T) +TestCmdOption ./tests/test_main.go 98;" f access:public line:98 signature:(t *testing.T) +TestConfig ./tests/test_config.go 11;" t access:public line:11 type:struct +TestConstants ./tests/test_main.go 11;" f access:public line:11 signature:(t *testing.T) +TestCopyCurrentEntry ./tests/test_actions.go 449;" f access:public line:449 signature:(t *testing.T) +TestCopyPasswordToClipboard ./tests/test_utils.go 472;" f access:public line:472 signature:(t *testing.T) +TestCreateNewEntry ./tests/test_db.go 42;" f access:public line:42 signature:(t *testing.T) +TestCreateNewExEntry ./tests/test_db.go 62;" f access:public line:62 signature:(t *testing.T) +TestDBPath ./tests/test_config.go 13;" w access:public ctype:TestConfig line:13 type:string +TestDecryptDatabase ./tests/test_actions.go 557;" f access:public line:557 signature:(t *testing.T) +TestDetectCardType ./tests/test_utils.go 523;" f access:public line:523 signature:(t *testing.T) +TestEditCurrentCardEntry ./tests/test_actions.go 134;" f access:public line:134 signature:(t *testing.T) +TestEditCurrentEntry ./tests/test_actions.go 170;" f access:public line:170 signature:(t *testing.T) +TestEncryptActiveDatabase ./tests/test_actions.go 495;" f access:public line:495 signature:(t *testing.T) +TestEncryptDatabase ./tests/test_actions.go 517;" f access:public line:517 signature:(t *testing.T) +TestEncryptDecryptFileAES ./tests/test_crypto.go 122;" f access:public line:122 signature:(t *testing.T) +TestEncryptDecryptFileXChachaPoly ./tests/test_crypto.go 182;" f access:public line:182 signature:(t *testing.T) +TestEncryptionConsistency ./tests/test_crypto.go 323;" f access:public line:323 signature:(t *testing.T) +TestEntriesToStringArray ./tests/test_db.go 540;" f access:public line:540 signature:(t *testing.T) +TestEntryCopy ./tests/test_db.go 631;" f access:public line:631 signature:(t *testing.T) +TestExportFilePermissions ./tests/test_export.go 524;" f access:public line:524 signature:(t *testing.T) +TestExportToCSV ./tests/test_export.go 275;" f access:public line:275 signature:(t *testing.T) +TestExportToFile ./tests/test_export.go 10;" f access:public line:10 signature:(t *testing.T) +TestExportToHTML ./tests/test_export.go 205;" f access:public line:205 signature:(t *testing.T) +TestExportToMarkdown ./tests/test_export.go 71;" f access:public line:71 signature:(t *testing.T) +TestExportToMarkdownLimited ./tests/test_export.go 138;" f access:public line:138 signature:(t *testing.T) +TestExportToPDF ./tests/test_export.go 345;" f access:public line:345 signature:(t *testing.T) +TestExportWithCardEntries ./tests/test_export.go 478;" f access:public line:478 signature:(t *testing.T) +TestExportWithCustomFields ./tests/test_export.go 428;" f access:public line:428 signature:(t *testing.T) +TestExportWithEmptyDatabase ./tests/test_export.go 385;" f access:public line:385 signature:(t *testing.T) +TestExtendedEntryCopy ./tests/test_db.go 714;" f access:public line:714 signature:(t *testing.T) +TestFindCurrentEntry ./tests/test_actions.go 294;" f access:public line:294 signature:(t *testing.T) +TestFunctionTypes ./tests/test_main.go 40;" f access:public line:40 signature:(t *testing.T) +TestGenerateKey ./tests/test_crypto.go 66;" f access:public line:66 signature:(t *testing.T) +TestGenerateKeyArgon2 ./tests/test_crypto.go 34;" f access:public line:34 signature:(t *testing.T) +TestGeneratePassword ./tests/test_crypto.go 242;" f access:public line:242 signature:(t *testing.T) +TestGenerateRandomBytes ./tests/test_crypto.go 9;" f access:public line:9 signature:(t *testing.T) +TestGenerateStrongPassword ./tests/test_crypto.go 267;" f access:public line:267 signature:(t *testing.T) +TestGetActiveDatabase ./tests/test_utils.go 166;" f access:public line:166 signature:(t *testing.T) +TestGetColor ./tests/test_utils.go 245;" f access:public line:245 signature:(t *testing.T) +TestGetEntryById ./tests/test_db.go 229;" f access:public line:229 signature:(t *testing.T) +TestGetExtendedEntries ./tests/test_db.go 593;" f access:public line:593 signature:(t *testing.T) +TestGetOrCreateLocalConfig ./tests/test_utils.go 136;" f access:public line:136 signature:(t *testing.T) +TestHasActiveDatabase ./tests/test_utils.go 159;" f access:public line:159 signature:(t *testing.T) +TestHideSecret ./tests/test_utils.go 52;" f access:public line:52 signature:(t *testing.T) +TestInitNewDatabase ./tests/test_db.go 82;" f access:public line:82 signature:(t *testing.T) +TestIsActiveDatabaseEncrypted ./tests/test_utils.go 410;" f access:public line:410 signature:(t *testing.T) +TestIsActiveDatabaseEncryptedAndMaxKryptOn ./tests/test_utils.go 424;" f access:public line:424 signature:(t *testing.T) +TestIsEncryptOn ./tests/test_utils.go 417;" f access:public line:417 signature:(t *testing.T) +TestIsFileEncrypted ./tests/test_crypto.go 97;" f access:public line:97 signature:(t *testing.T) +TestIterateEntries ./tests/test_db.go 391;" f access:public line:391 signature:(t *testing.T) +TestListAllEntries ./tests/test_actions.go 257;" f access:public line:257 signature:(t *testing.T) +TestListCurrentEntry ./tests/test_actions.go 215;" f access:public line:215 signature:(t *testing.T) +TestMapString ./tests/test_utils.go 10;" f access:public line:10 signature:(t *testing.T) +TestMigrateDatabase ./tests/test_actions.go 602;" f access:public line:602 signature:(t *testing.T) +TestOpenDatabase ./tests/test_db.go 10;" f access:public line:10 signature:(t *testing.T) +TestPrettifyCardNumber ./tests/test_utils.go 297;" f access:public line:297 signature:(t *testing.T) +TestPrintCardEntry ./tests/test_utils.go 321;" f access:public line:321 signature:(t *testing.T) +TestPrintDelim ./tests/test_utils.go 275;" f access:public line:275 signature:(t *testing.T) +TestPrintEntry ./tests/test_utils.go 353;" f access:public line:353 signature:(t *testing.T) +TestPrintEntryMinimal ./tests/test_utils.go 374;" f access:public line:374 signature:(t *testing.T) +TestRandomFileName ./tests/test_utils.go 493;" f access:public line:493 signature:(t *testing.T) +TestReadInput ./tests/test_utils.go 395;" f access:public line:395 signature:(t *testing.T) +TestReadPassword ./tests/test_utils.go 182;" f access:public line:182 signature:(t *testing.T) +TestRemoveCurrentEntry ./tests/test_actions.go 404;" f access:public line:404 signature:(t *testing.T) +TestRemoveDatabaseEntry ./tests/test_db.go 500;" f access:public line:500 signature:(t *testing.T) +TestRemoveMultipleEntries ./tests/test_actions.go 349;" f access:public line:349 signature:(t *testing.T) +TestRewriteBaseFile ./tests/test_utils.go 189;" f access:public line:189 signature:(t *testing.T) +TestRewriteFile ./tests/test_utils.go 217;" f access:public line:217 signature:(t *testing.T) +TestSearchDatabaseEntries ./tests/test_db.go 333;" f access:public line:333 signature:(t *testing.T) +TestSearchDatabaseEntry ./tests/test_db.go 274;" f access:public line:274 signature:(t *testing.T) +TestSetActiveDatabasePath ./tests/test_actions.go 17;" f access:public line:17 signature:(t *testing.T) +TestSetAssumeYes ./tests/test_utils.go 446;" f access:public line:446 signature:(t *testing.T) +TestSetCopyPasswordToClipboard ./tests/test_utils.go 439;" f access:public line:439 signature:(t *testing.T) +TestSetShowPasswords ./tests/test_utils.go 432;" f access:public line:432 signature:(t *testing.T) +TestSetType ./tests/test_utils.go 453;" f access:public line:453 signature:(t *testing.T) +TestShowActiveDatabasePath ./tests/test_actions.go 10;" f access:public line:10 signature:(t *testing.T) +TestUpdateActiveDbPath ./tests/test_utils.go 173;" f access:public line:173 signature:(t *testing.T) +TestUpdateSettings ./tests/test_utils.go 103;" f access:public line:103 signature:(t *testing.T) +TestValidateCardPin ./tests/test_utils.go 578;" f access:public line:578 signature:(t *testing.T) +TestValidateCvv ./tests/test_utils.go 551;" f access:public line:551 signature:(t *testing.T) +TestVoidWrapperFunctions ./tests/test_main.go 80;" f access:public line:80 signature:(t *testing.T) +TestWrapperFunctions ./tests/test_main.go 62;" f access:public line:62 signature:(t *testing.T) +TestWriteSettings ./tests/test_utils.go 75;" f access:public line:75 signature:(t *testing.T) +TestWrongPasswordDecryption ./tests/test_crypto.go 290;" f access:public line:290 signature:(t *testing.T) +Timestamp ./db.go 32;" w access:public ctype:Entry line:32 type:time.Time +Timestamp ./db.go 71;" w access:public ctype:ExtendedEntry line:71 type:time.Time +Title ./db.go 20;" w access:public ctype:Entry line:20 type:string +Type ./db.go 31;" w access:public ctype:Entry line:31 type:string +Type ./db.go 94;" w access:public ctype:Address line:94 type:string +Type ./utils.go 30;" w access:public ctype:SettingsOverride line:30 type:string +Url ./db.go 22;" w access:public ctype:Entry line:22 type:string +User ./db.go 21;" w access:public ctype:Entry line:21 type:string +VERSION ./defns.go 9;" c access:public line:9 +WrapperMaxKryptStringFunc ./actions.go 23;" f access:public line:23 signature:(fn actionFunc) type:actionFunc +WrapperMaxKryptVoidFunc ./actions.go 65;" f access:public line:65 signature:(fn voidFunc) type:voidFunc +ZipCode ./db.go 93;" w access:public ctype:Address line:93 type:string +actionFunc ./defns.go 3;" t access:private line:3 type:func(string) error +actionFunc2 ./defns.go 4;" t access:private line:4 type:func(string) error, string +addCustomEntries ./db.go 259;" f access:private line:259 signature:(db *gorm.DB, entry *Entry, customEntries []CustomEntry) type:error +addCustomFields ./actions.go 418;" f access:private line:418 signature:(reader *bufio.Reader) type:[]CustomEntry +addNewCardEntry ./actions.go 223;" f access:private line:223 signature:() type:error +addNewDatabaseCardEntry ./db.go 402;" f access:private line:402 signature:(cardName, cardNumber, cardHolder, cardIssuer, cardClass, cardCvv, cardPin, cardExpiry, notes, tags string, customEntries []CustomEntry) type:error +addNewDatabaseEntry ./db.go 318;" f access:private line:318 signature:(title, userName, url, passwd, tags string, notes string, customEntries []CustomEntry) type:error +addNewEntry ./actions.go 301;" f access:private line:301 signature:() type:error +addOrUpdateCustomFields ./actions.go 370;" f access:private line:370 signature:(reader *bufio.Reader, entry *Entry) type:[]CustomEntry, bool +bufio ./actions.go 5;" i line:5 +bufio ./export.go 4;" i line:4 +bufio ./utils.go 5;" i line:5 +bytes ./test/testpgp.go 6;" i line:6 +checkActiveDatabase ./utils.go 529;" f access:private line:529 signature:() type:error +checkValidExpiry ./utils.go 661;" f access:private line:661 signature:(expiryDate string) type:bool +cloneEntry ./db.go 643;" f access:private line:643 signature:(entry *Entry) type:error, *Entry +cloneExtendedEntries ./db.go 666;" f access:private line:666 signature:(entry *Entry, exEntries []ExtendedEntry) type:error +contains ./tests/test_utils.go 631;" f access:private line:631 signature:(s, substr string) type:bool +copyCurrentEntry ./actions.go 786;" f access:private line:786 signature:(idString string) type:error +copyPasswordToClipboard ./utils.go 598;" f access:private line:598 signature:(passwd string) +createNewEntry ./db.go 165;" f access:private line:165 signature:(db *gorm.DB) type:error +createNewExEntry ./db.go 170;" f access:private line:170 signature:(db *gorm.DB) type:error +crypto/aes ./crypto.go 5;" i line:5 +crypto/cipher ./crypto.go 6;" i line:6 +crypto/hmac ./crypto.go 7;" i line:7 +crypto/rand ./crypto.go 21;" i line:21 +crypto/sha512 ./crypto.go 8;" i line:8 +database/sql ./db.go 5;" i line:5 +decryptDatabase ./actions.go 901;" f access:private line:901 signature:(dbPath string) type:error, string +decryptFileAES ./crypto.go 210;" f access:private line:210 signature:(encDbPath string, password string) type:error +decryptFileXChachaPoly ./crypto.go 363;" f access:private line:363 signature:(encDbPath string, password string) type:error +detectCardType ./utils.go 610;" f access:private line:610 signature:(cardNum string) type:string, error +editCurrentCardEntry ./actions.go 449;" f access:private line:449 signature:(entry *Entry) type:error +editCurrentEntry ./actions.go 512;" f access:private line:512 signature:(idString string) type:error +encoding/csv ./export.go 5;" i line:5 +encoding/hex ./utils.go 6;" i line:6 +encoding/json ./utils.go 7;" i line:7 +encryptActiveDatabase ./actions.go 829;" f access:private line:829 signature:() type:error +encryptDatabase ./actions.go 848;" f access:private line:848 signature:(dbPath string, givenPasswd *string) type:error +encryptFileAES ./crypto.go 129;" f access:private line:129 signature:(dbPath string, password string) type:error +encryptFileXChachaPoly ./crypto.go 292;" f access:private line:292 signature:(dbPath string, password string) type:error +entriesToStringArray ./db.go 717;" f access:private line:717 signature:(skipLongFields bool) type:error, [][]string +errors ./actions.go 6;" i line:6 +errors ./crypto.go 9;" i line:9 +errors ./export.go 6;" i line:6 +errors ./utils.go 8;" i line:8 +exportToCsv ./export.go 341;" f access:private line:341 signature:(fileName string) type:error +exportToFile ./export.go 15;" f access:private line:15 signature:(fileName string) type:error +exportToHTML ./export.go 286;" f access:private line:286 signature:(fileName string) type:error +exportToMarkdown ./export.go 72;" f access:private line:72 signature:(fileName string) type:error +exportToMarkdownLimited ./export.go 211;" f access:private line:211 signature:(fileName string) type:error +exportToPDF ./export.go 147;" f access:private line:147 signature:(fileName string) type:error +fieldName ./actions.go 18;" w access:private ctype:CustomEntry line:18 type:string +fieldValue ./actions.go 19;" w access:private ctype:CustomEntry line:19 type:string +findCurrentEntry ./actions.go 668;" f access:private line:668 signature:(term string) type:error +fmt ./actions.go 7;" i line:7 +fmt ./crypto.go 10;" i line:10 +fmt ./db.go 6;" i line:6 +fmt ./export.go 7;" i line:7 +fmt ./scripts/main.go 6;" i line:6 +fmt ./test/testpgp.go 7;" i line:7 +fmt ./utils.go 9;" i line:9 +genPass ./scripts/main.go 40;" f access:private line:40 signature:() type:error, string +generateKey ./crypto.go 74;" f access:private line:74 signature:(passPhrase string, oldSalt *[]byte) type:error, []byte, []byte +generateKeyArgon2 ./crypto.go 48;" f access:private line:48 signature:(passPhrase string, oldSalt *[]byte) type:error, []byte, []byte +generatePassword ./crypto.go 440;" f access:private line:440 signature:(length int) type:error, string +generateRandomBytes ./crypto.go 31;" f access:private line:31 signature:(size int) type:error, []byte +generateStrongPassword ./crypto.go 466;" f access:private line:466 signature:() type:error, string +getActiveDatabase ./utils.go 184;" f access:private line:184 signature:() type:error, string +getColor ./utils.go 258;" f access:private line:258 signature:(code string) type:string +getEntryById ./db.go 495;" f access:private line:495 signature:(id int) type:error, *Entry +getExtendedEntries ./db.go 755;" f access:private line:755 signature:(entry *Entry) type:[]ExtendedEntry +getOrCreateLocalConfig ./utils.go 118;" f access:private line:118 signature:(app string) type:error, *Settings +github.com/atotto/clipboard ./utils.go 10;" i line:10 +github.com/kirsle/configdir ./utils.go 11;" i line:11 +github.com/polyglothacker/creditcard ./utils.go 12;" i line:12 +github.com/pythonhacker/argparse ./scripts/main.go 7;" i line:7 +golang.org/x/crypto/argon2 ./crypto.go 11;" i line:11 +golang.org/x/crypto/chacha20poly1305 ./crypto.go 12;" i line:12 +golang.org/x/crypto/openpgp ./test/testpgp.go 8;" i line:8 +golang.org/x/crypto/pbkdf2 ./crypto.go 13;" i line:13 +golang.org/x/crypto/ssh/terminal ./utils.go 13;" i line:13 +gorm.io/driver/sqlite ./db.go 7;" i line:7 +gorm.io/gorm ./actions.go 8;" i line:8 +gorm.io/gorm ./db.go 8;" i line:8 +gorm.io/gorm/logger ./db.go 9;" i line:9 +hasActiveDatabase ./utils.go 164;" f access:private line:164 signature:() type:bool +hideSecret ./utils.go 66;" f access:private line:66 signature:(secret string) type:string +initNewDatabase ./db.go 175;" f access:private line:175 signature:(dbPath string) type:error +initializeCmdLine ./scripts/main.go 181;" f access:private line:181 signature:(parser *argparse.Parser) type:map[string]interface{} +intersection ./db.go 564;" f access:private line:564 signature:(entry1 []Entry, entry2 []Entry) type:[]Entry +io ./crypto.go 14;" i line:14 +io/fs ./utils.go 14;" i line:14 +io/ioutil ./test/testpgp.go 9;" i line:9 +isActiveDatabaseEncrypted ./utils.go 540;" f access:private line:540 signature:() type:bool +isActiveDatabaseEncryptedAndMaxKryptOn ./utils.go 562;" f access:private line:562 signature:() type:bool, string +isEncryptOn ./utils.go 555;" f access:private line:555 signature:() type:bool +isFileEncrypted ./crypto.go 98;" f access:private line:98 signature:(encDbPath string) type:error, bool +iterateEntries ./db.go 691;" f access:private line:691 signature:(orderKey string, order string) type:error, []Entry +listAllEntries ./actions.go 613;" f access:private line:613 signature:() type:error +listCurrentEntry ./actions.go 583;" f access:private line:583 signature:(idString string) type:error +main ./scripts/main.go 232;" f access:private line:232 signature:() +main ./scripts/main.go 3;" p line:3 +main ./test/testpgp.go 15;" f access:private line:15 signature:() +main ./test/testpgp.go 3;" p line:3 +math/big ./crypto.go 15;" i line:15 +math/rand ./crypto.go 16;" i line:16 +migrateDatabase ./actions.go 940;" f access:private line:940 signature:(dbPath string) type:error +openActiveDatabase ./db.go 238;" f access:private line:238 signature:() type:error, *gorm.DB +openDatabase ./db.go 156;" f access:private line:156 signature:(filePath string) type:error, *gorm.DB +os ./actions.go 9;" i line:9 +os ./crypto.go 17;" i line:17 +os ./db.go 10;" i line:10 +os ./export.go 8;" i line:8 +os ./scripts/main.go 8;" i line:8 +os ./test/testpgp.go 10;" i line:10 +os ./tests/test_actions.go 4;" i line:4 +os ./tests/test_config.go 4;" i line:4 +os ./tests/test_crypto.go 4;" i line:4 +os ./tests/test_db.go 4;" i line:4 +os ./tests/test_export.go 4;" i line:4 +os ./tests/test_utils.go 4;" i line:4 +os ./utils.go 15;" i line:15 +os/exec ./export.go 9;" i line:9 +os/signal ./actions.go 10;" i line:10 +os/user ./test/testpgp.go 11;" i line:11 +path/filepath ./actions.go 11;" i line:11 +path/filepath ./db.go 11;" i line:11 +path/filepath ./export.go 10;" i line:10 +path/filepath ./test/testpgp.go 12;" i line:12 +path/filepath ./tests/test_actions.go 5;" i line:5 +path/filepath ./tests/test_config.go 5;" i line:5 +path/filepath ./tests/test_db.go 5;" i line:5 +path/filepath ./tests/test_export.go 5;" i line:5 +path/filepath ./tests/test_utils.go 5;" i line:5 +path/filepath ./utils.go 16;" i line:16 +performAction ./scripts/main.go 62;" f access:private line:62 signature:(optMap map[string]interface{}) +prettifyCardNumber ./utils.go 324;" f access:private line:324 signature:(cardNumber string) type:string +printCardEntry ./utils.go 347;" f access:private line:347 signature:(entry *Entry, settings *Settings, delim bool) type:error +printDelim ./utils.go 303;" f access:private line:303 signature:(delimChar string, color string) +printEntry ./utils.go 418;" f access:private line:418 signature:(entry *Entry, delim bool) type:error +printEntryMinimal ./utils.go 483;" f access:private line:483 signature:(entry *Entry, delim bool) type:error +printUsage ./scripts/main.go 24;" f access:private line:24 signature:() type:error +printVersionInfo ./scripts/main.go 32;" f access:private line:32 signature:() type:error +randomFileName ./utils.go 603;" f access:private line:603 signature:(folder string, suffix string) type:string +readInput ./utils.go 519;" f access:private line:519 signature:(reader *bufio.Reader, prompt string) type:string +readPassword ./utils.go 214;" f access:private line:214 signature:() type:error, string +regexp ./utils.go 17;" i line:17 +removeCurrentEntry ./actions.go 741;" f access:private line:741 signature:(idString string) type:error +removeDatabaseEntry ./db.go 613;" f access:private line:613 signature:(entry *Entry) type:error +removeMultipleEntries ./actions.go 712;" f access:private line:712 signature:(idRangeEntry string) type:error +replaceCustomEntries ./db.go 287;" f access:private line:287 signature:(db *gorm.DB, entry *Entry, updatedEntries []CustomEntry) type:error +rewriteBaseFile ./utils.go 224;" f access:private line:224 signature:(path string, contents []byte, mode fs.FileMode) type:error, string +rewriteFile ./utils.go 242;" f access:private line:242 signature:(path string, contents []byte, mode fs.FileMode) type:error, string +searchDatabaseEntries ./db.go 584;" f access:private line:584 signature:(terms []string, operator string) type:error, []Entry +searchDatabaseEntry ./db.go 515;" f access:private line:515 signature:(term string) type:error, []Entry +setActiveDatabasePath ./actions.go 130;" f access:private line:130 signature:(dbPath string) type:error +setAssumeYes ./utils.go 589;" f access:private line:589 signature:() type:error +setCopyPasswordToClipboard ./utils.go 584;" f access:private line:584 signature:() type:error +setShowPasswords ./utils.go 577;" f access:private line:577 signature:() type:error +setType ./utils.go 594;" f access:private line:594 signature:(_type string) +settingFunc ./defns.go 7;" t access:private line:7 type:func(string) +settingsRider ./utils.go 54;" v access:private line:54 type:SettingsOverride +showActiveDatabasePath ./actions.go 107;" f access:private line:107 signature:() type:error +strconv ./actions.go 12;" i line:12 +strconv ./db.go 12;" i line:12 +strconv ./utils.go 18;" i line:18 +stringPtr ./tests/test_actions.go 638;" f access:private line:638 signature:(s string) type:*string +strings ./actions.go 13;" i line:13 +strings ./db.go 13;" i line:13 +strings ./export.go 11;" i line:11 +strings ./scripts/main.go 9;" i line:9 +strings ./utils.go 19;" i line:19 +syscall ./actions.go 14;" i line:14 +testing ./tests/test_actions.go 6;" i line:6 +testing ./tests/test_config.go 6;" i line:6 +testing ./tests/test_crypto.go 5;" i line:5 +testing ./tests/test_db.go 6;" i line:6 +testing ./tests/test_export.go 6;" i line:6 +testing ./tests/test_main.go 4;" i line:4 +testing ./tests/test_utils.go 6;" i line:6 +tests ./tests/test_actions.go 1;" p line:1 +tests ./tests/test_config.go 1;" p line:1 +tests ./tests/test_crypto.go 1;" p line:1 +tests ./tests/test_db.go 1;" p line:1 +tests ./tests/test_export.go 1;" p line:1 +tests ./tests/test_main.go 1;" p line:1 +tests ./tests/test_utils.go 1;" p line:1 +time ./crypto.go 18;" i line:18 +time ./db.go 14;" i line:14 +time ./utils.go 20;" i line:20 +union ./db.go 546;" f access:private line:546 signature:(entry1 []Entry, entry2 []Entry) type:[]Entry +unsafe ./crypto.go 19;" i line:19 +updateActiveDbPath ./utils.go 201;" f access:private line:201 signature:(dbPath string) type:error +updateDatabaseCardEntry ./db.go 347;" f access:private line:347 signature:(entry *Entry, cardName, cardNumber, cardHolder, cardClass, cardCvv, cardPin, cardExpiry, notes, tags string, customEntries []CustomEntry, flag bool) type:error +updateDatabaseEntry ./db.go 447;" f access:private line:447 signature:(entry *Entry, title, userName, url, passwd, tags string, notes string, customEntries []CustomEntry, flag bool) type:error +updateSettings ./utils.go 95;" f access:private line:95 signature:(settings *Settings, configFile string) type:error +validateCardPin ./utils.go 650;" f access:private line:650 signature:(cardPin string) type:bool +validateCvv ./utils.go 632;" f access:private line:632 signature:(cardCvv string, cardClass string) type:bool +varuh ./actions.go 2;" p line:2 +varuh ./crypto.go 2;" p line:2 +varuh ./db.go 2;" p line:2 +varuh ./defns.go 1;" p line:1 +varuh ./export.go 1;" p line:1 +varuh ./scripts/main.go 10;" i line:10 +varuh ./tests/test_actions.go 7;" i line:7 +varuh ./tests/test_config.go 7;" i line:7 +varuh ./tests/test_crypto.go 6;" i line:6 +varuh ./tests/test_db.go 7;" i line:7 +varuh ./tests/test_export.go 7;" i line:7 +varuh ./tests/test_main.go 5;" i line:5 +varuh ./tests/test_utils.go 7;" i line:7 +varuh ./utils.go 2;" p line:2 +voidFunc ./defns.go 5;" t access:private line:5 type:func() error +voidFunc2 ./defns.go 6;" t access:private line:6 type:func() error, string +writeSettings ./utils.go 77;" f access:private line:77 signature:(settings *Settings, configFile string) type:error diff --git a/actions.go b/actions.go index 7b024b4..9d38920 100644 --- a/actions.go +++ b/actions.go @@ -1,5 +1,5 @@ // Actions on the database -package main +package varuh import ( "bufio" @@ -15,12 +15,12 @@ import ( ) type CustomEntry struct { - fieldName string - fieldValue string + FieldName string + FieldValue string } // Wrappers (closures) for functions accepting strings as input for in/out encryption -func WrapperMaxKryptStringFunc(fn actionFunc) actionFunc { +func WrapperMaxKryptStringFunc(fn ActionFunc) ActionFunc { return func(inputStr string) error { var maxKrypt bool @@ -32,7 +32,7 @@ func WrapperMaxKryptStringFunc(fn actionFunc) actionFunc { // If max krypt on - then autodecrypt on call and auto encrypt after call if maxKrypt { - err, encPasswd = decryptDatabase(defaultDB) + err, encPasswd = DecryptDatabase(defaultDB) if err != nil { return err } @@ -44,7 +44,7 @@ func WrapperMaxKryptStringFunc(fn actionFunc) actionFunc { sig := <-sigChan fmt.Println("Received signal", sig) // Reencrypt - encryptDatabase(defaultDB, &encPasswd) + EncryptDatabase(defaultDB, &encPasswd) os.Exit(1) }() } @@ -53,7 +53,7 @@ func WrapperMaxKryptStringFunc(fn actionFunc) actionFunc { // If max krypt on - then autodecrypt on call and auto encrypt after call if maxKrypt { - encryptDatabase(defaultDB, &encPasswd) + EncryptDatabase(defaultDB, &encPasswd) } return err @@ -62,7 +62,7 @@ func WrapperMaxKryptStringFunc(fn actionFunc) actionFunc { } // Wrappers (closures) for functions accepting no input for in/out encryption -func WrapperMaxKryptVoidFunc(fn voidFunc) voidFunc { +func WrapperMaxKryptVoidFunc(fn VoidFunc) VoidFunc { return func() error { var maxKrypt bool @@ -74,7 +74,7 @@ func WrapperMaxKryptVoidFunc(fn voidFunc) voidFunc { // If max krypt on - then autodecrypt on call and auto encrypt after call if maxKrypt { - err, encPasswd = decryptDatabase(defaultDB) + err, encPasswd = DecryptDatabase(defaultDB) if err != nil { return err } @@ -86,7 +86,7 @@ func WrapperMaxKryptVoidFunc(fn voidFunc) voidFunc { sig := <-sigChan fmt.Println("Received signal", sig) // Reencrypt - encryptDatabase(defaultDB, &encPasswd) + EncryptDatabase(defaultDB, &encPasswd) os.Exit(1) }() } @@ -95,7 +95,7 @@ func WrapperMaxKryptVoidFunc(fn voidFunc) voidFunc { // If max krypt on - then autodecrypt on call and auto encrypt after call if maxKrypt { - encryptDatabase(defaultDB, &encPasswd) + EncryptDatabase(defaultDB, &encPasswd) } return err @@ -104,9 +104,9 @@ func WrapperMaxKryptVoidFunc(fn voidFunc) voidFunc { } // Print the current active database path -func showActiveDatabasePath() error { +func ShowActiveDatabasePath() error { - err, settings := getOrCreateLocalConfig(APP) + err, settings := GetOrCreateLocalConfig(APP) if err != nil { fmt.Printf("Error parsing config - \"%s\"\n", err.Error()) @@ -127,13 +127,13 @@ func showActiveDatabasePath() error { } // Set the current active database path -func setActiveDatabasePath(dbPath string) error { +func SetActiveDatabasePath(dbPath string) error { var fullPath string var activeEncrypted bool var newEncrypted bool - err, settings := getOrCreateLocalConfig(APP) + err, settings := GetOrCreateLocalConfig(APP) if err != nil { fmt.Printf("Error parsing config - \"%s\"\n", err.Error()) @@ -155,11 +155,11 @@ func setActiveDatabasePath(dbPath string) error { return nil } - if _, flag = isFileEncrypted(settings.ActiveDB); flag { + if _, flag = IsFileEncrypted(settings.ActiveDB); flag { activeEncrypted = true } - if _, flag = isFileEncrypted(fullPath); flag { + if _, flag = IsFileEncrypted(fullPath); flag { newEncrypted = true } @@ -167,7 +167,7 @@ func setActiveDatabasePath(dbPath string) error { if settings.AutoEncrypt { if !activeEncrypted { fmt.Printf("Encrypting current active database - %s\n", settings.ActiveDB) - err = encryptActiveDatabase() + err = EncryptActiveDatabase() if err == nil { activeEncrypted = true } @@ -177,7 +177,7 @@ func setActiveDatabasePath(dbPath string) error { if !settings.AutoEncrypt { // Decrypt new database if it is encrypted fmt.Printf("Database %s is encrypted, decrypting it\n", fullPath) - err, _ = decryptDatabase(fullPath) + err, _ = DecryptDatabase(fullPath) if err != nil { fmt.Printf("Decryption Error - \"%s\", not switching databases\n", err.Error()) return err @@ -204,7 +204,7 @@ func setActiveDatabasePath(dbPath string) error { } settings.ActiveDB = fullPath - err = updateSettings(settings, settings.ConfigPath) + err = UpdateSettings(settings, settings.ConfigPath) if err == nil { fmt.Println("Switched active database successfully.") } else { @@ -220,7 +220,7 @@ func setActiveDatabasePath(dbPath string) error { } // Text menu driven function to add a new entry for a card type -func addNewCardEntry() error { +func AddNewCardEntry() error { var cardHolder string var cardName string @@ -242,7 +242,7 @@ func addNewCardEntry() error { reader := bufio.NewReader(os.Stdin) cardNumber = readInput(reader, "Card Number") - cardClass, err = detectCardType(cardNumber) + cardClass, err = DetectCardType(cardNumber) if err != nil { fmt.Printf("Error - %s\n", err.Error()) @@ -255,22 +255,22 @@ func addNewCardEntry() error { cardExpiry = readInput(reader, "Expiry Date as mm/dd") // expiry has to be in the form of / - if !checkValidExpiry(cardExpiry) { + if !CheckValidExpiry(cardExpiry) { return errors.New("Invalid Expiry Date") } fmt.Printf("CVV: ") - err, cardCvv = readPassword() + err, cardCvv = ReadPassword() - if !validateCvv(cardCvv, cardClass) { + if !ValidateCvv(cardCvv, cardClass) { fmt.Printf("\nError - Invalid CVV for %s\n", cardClass) return errors.New(fmt.Sprintf("Error - Invalid CVV for %s\n", cardClass)) } fmt.Printf("\nCard PIN: ") - err, cardPin = readPassword() + err, cardPin = ReadPassword() - if !validateCardPin(cardPin) { + if !ValidateCardPin(cardPin) { fmt.Printf("\n") } @@ -285,9 +285,9 @@ func addNewCardEntry() error { tags = readInput(reader, "\nTags (separated by space): ") notes = readInput(reader, "Notes") - customEntries = addCustomFields(reader) + customEntries = AddCustomFields(reader) - err = addNewDatabaseCardEntry(cardName, cardNumber, cardHolder, cardIssuer, + err = AddNewDatabaseCardEntry(cardName, cardNumber, cardHolder, cardIssuer, cardClass, cardCvv, cardPin, cardExpiry, notes, tags, customEntries) if err != nil { @@ -298,7 +298,7 @@ func addNewCardEntry() error { } // Text menu driven function to add a new entry -func addNewEntry() error { +func AddNewEntry() error { var userName string var title string @@ -313,8 +313,8 @@ func addNewEntry() error { return err } - if settingsRider.Type == "card" { - return addNewCardEntry() + if SettingsRider.Type == "card" { + return AddNewCardEntry() } reader := bufio.NewReader(os.Stdin) @@ -327,11 +327,11 @@ func addNewEntry() error { userName = readInput(reader, "Username") fmt.Printf("Password (enter to generate new): ") - err, passwd = readPassword() + err, passwd = ReadPassword() if len(passwd) == 0 { fmt.Printf("\nGenerating password ...") - err, passwd = generateStrongPassword() + err, passwd = GenerateStrongPassword() fmt.Printf("done") } // fmt.Printf("Password => %s\n", passwd) @@ -353,10 +353,10 @@ func addNewEntry() error { return errors.New("invalid input") } - customEntries = addCustomFields(reader) + customEntries = AddCustomFields(reader) // Trim spaces - err = addNewDatabaseEntry(title, userName, url, passwd, tags, notes, customEntries) + err = AddNewDatabaseEntry(title, userName, url, passwd, tags, notes, customEntries) if err != nil { fmt.Printf("Error adding entry - \"%s\"\n", err.Error()) @@ -367,14 +367,14 @@ func addNewEntry() error { // Function to update existing custom entries and add new ones // The bool part of the return value indicates whether to take action -func addOrUpdateCustomFields(reader *bufio.Reader, entry *Entry) ([]CustomEntry, bool) { +func AddOrUpdateCustomFields(reader *bufio.Reader, entry *Entry) ([]CustomEntry, bool) { var customEntries []ExtendedEntry var editedCustomEntries []CustomEntry var newCustomEntries []CustomEntry var flag bool - customEntries = getExtendedEntries(entry) + customEntries = GetExtendedEntries(entry) if len(customEntries) > 0 { @@ -403,7 +403,7 @@ func addOrUpdateCustomFields(reader *bufio.Reader, entry *Entry) ([]CustomEntry, } } - newCustomEntries = addCustomFields(reader) + newCustomEntries = AddCustomFields(reader) editedCustomEntries = append(editedCustomEntries, newCustomEntries...) @@ -415,7 +415,7 @@ func addOrUpdateCustomFields(reader *bufio.Reader, entry *Entry) ([]CustomEntry, } // Function to add custom fields to an entry -func addCustomFields(reader *bufio.Reader) []CustomEntry { +func AddCustomFields(reader *bufio.Reader) []CustomEntry { // Custom fields var custom string @@ -446,7 +446,7 @@ func addCustomFields(reader *bufio.Reader) []CustomEntry { } // Edit a card entry by id -func editCurrentCardEntry(entry *Entry) error { +func EditCurrentCardEntry(entry *Entry) error { var klass string var err error var flag bool @@ -461,7 +461,7 @@ func editCurrentCardEntry(entry *Entry) error { fmt.Printf("Card Number: %s\n", entry.Url) number := readInput(reader, "New Card Number") if number != "" { - klass, err = detectCardType(number) + klass, err = DetectCardType(number) if err != nil { fmt.Printf("Error - %s\n", err.Error()) @@ -473,32 +473,32 @@ func editCurrentCardEntry(entry *Entry) error { fmt.Printf("Card CVV: %s\n", entry.Password) fmt.Printf("New Card CVV: ") - err, cvv := readPassword() + err, cvv := ReadPassword() - if cvv != "" && !validateCvv(cvv, klass) { + if cvv != "" && !ValidateCvv(cvv, klass) { fmt.Printf("\nError - Invalid CVV for %s\n", klass) return errors.New(fmt.Sprintf("Error - Invalid CVV for %s\n", klass)) } fmt.Printf("\nCard PIN: %s\n", entry.Pin) fmt.Printf("New Card PIN: ") - err, pin := readPassword() + err, pin := ReadPassword() - if pin != "" && !validateCardPin(pin) { + if pin != "" && !ValidateCardPin(pin) { fmt.Printf("\n") } fmt.Printf("\nCard Expiry Date: %s\n", entry.ExpiryDate) expiryDate := readInput(reader, "New Card Expiry Date (as mm/dd): ") // expiry has to be in the form of / - if expiryDate != "" && !checkValidExpiry(expiryDate) { + if expiryDate != "" && !CheckValidExpiry(expiryDate) { return errors.New("Invalid Expiry Date") } tags := readInput(reader, "\nTags (separated by space): ") notes := readInput(reader, "Notes") - customEntries, flag = addOrUpdateCustomFields(reader, entry) + customEntries, flag = AddOrUpdateCustomFields(reader, entry) // Update - err = updateDatabaseCardEntry(entry, title, number, name, + err = UpdateDatabaseCardEntry(entry, title, number, name, klass, cvv, pin, expiryDate, notes, tags, customEntries, flag) if err != nil { @@ -509,7 +509,7 @@ func editCurrentCardEntry(entry *Entry) error { } // Edit a current entry by id -func editCurrentEntry(idString string) error { +func EditCurrentEntry(idString string) error { var userName string var title string @@ -527,14 +527,14 @@ func editCurrentEntry(idString string) error { id, _ = strconv.Atoi(idString) - err, entry = getEntryById(id) + err, entry = GetEntryById(id) if err != nil || entry == nil { fmt.Printf("No entry found for id %d\n", id) return err } if entry.Type == "card" { - return editCurrentCardEntry(entry) + return EditCurrentCardEntry(entry) } reader := bufio.NewReader(os.Stdin) @@ -554,11 +554,11 @@ func editCurrentEntry(idString string) error { fmt.Printf("Current Password: %s\n", entry.Password) fmt.Printf("New Password ([y/Y] to generate new, enter will keep old one): ") - err, passwd = readPassword() + err, passwd = ReadPassword() if strings.ToLower(passwd) == "y" { fmt.Printf("\nGenerating new password ...") - err, passwd = generateStrongPassword() + err, passwd = GenerateStrongPassword() } // fmt.Printf("Password => %s\n", passwd) @@ -568,10 +568,10 @@ func editCurrentEntry(idString string) error { fmt.Printf("\nCurrent Notes: %s\n", entry.Notes) notes = readInput(reader, "New Notes") - customEntries, flag := addOrUpdateCustomFields(reader, entry) + customEntries, flag := AddOrUpdateCustomFields(reader, entry) // Update - err = updateDatabaseEntry(entry, title, userName, url, passwd, tags, notes, customEntries, flag) + err = UpdateDatabaseEntry(entry, title, userName, url, passwd, tags, notes, customEntries, flag) if err != nil { fmt.Printf("Error updating entry - \"%s\"\n", err.Error()) } @@ -580,7 +580,7 @@ func editCurrentEntry(idString string) error { } // List current entry by id -func listCurrentEntry(idString string) error { +func ListCurrentEntry(idString string) error { var id int var err error @@ -593,24 +593,24 @@ func listCurrentEntry(idString string) error { id, _ = strconv.Atoi(idString) // fmt.Printf("Listing current entry - %d\n", id) - err, entry = getEntryById(id) + err, entry = GetEntryById(id) if err != nil || entry == nil { fmt.Printf("No entry found for id %d\n", id) return err } - err = printEntry(entry, true) + err = PrintEntry(entry, true) - if err == nil && settingsRider.CopyPassword { + if err == nil && SettingsRider.CopyPassword { // fmt.Printf("Copying password " + entry.Password + " to clipboard\n") - copyPasswordToClipboard(entry.Password) + CopyPasswordToClipboard(entry.Password) } return err } // List all entries -func listAllEntries() error { +func ListAllEntries() error { var err error var maxKrypt bool @@ -621,7 +621,7 @@ func listAllEntries() error { // If max krypt on - then autodecrypt on call and auto encrypt after call if maxKrypt { - err, passwd = decryptDatabase(defaultDB) + err, passwd = DecryptDatabase(defaultDB) if err != nil { return err } @@ -631,7 +631,7 @@ func listAllEntries() error { return err } - err, settings := getOrCreateLocalConfig(APP) + err, settings := GetOrCreateLocalConfig(APP) if err != nil { fmt.Printf("Error parsing config - \"%s\"\n", err.Error()) @@ -639,14 +639,14 @@ func listAllEntries() error { } orderKeys := strings.Split(settings.ListOrder, ",") - err, entries := iterateEntries(orderKeys[0], orderKeys[1]) + err, entries := IterateEntries(orderKeys[0], orderKeys[1]) if err == nil { if len(entries) > 0 { - fmt.Printf("%s", getColor(strings.ToLower(settings.Color))) - printDelim(settings.Delim, settings.Color) + fmt.Printf("%s", GetColor(strings.ToLower(settings.Color))) + PrintDelim(settings.Delim, settings.Color) for _, entry := range entries { - printEntry(&entry, false) + PrintEntry(&entry, false) } } else { fmt.Println("No entries.") @@ -658,14 +658,14 @@ func listAllEntries() error { // If max krypt on - then autodecrypt on call and auto encrypt after call if maxKrypt { - err = encryptDatabase(defaultDB, &passwd) + err = EncryptDatabase(defaultDB, &passwd) } return err } // Find current entry by term - prints all matches -func findCurrentEntry(term string) error { +func FindCurrentEntry(term string) error { var err error var entries []Entry @@ -677,7 +677,7 @@ func findCurrentEntry(term string) error { terms = strings.Split(term, " ") - err, entries = searchDatabaseEntries(terms, "AND") + err, entries = SearchDatabaseEntries(terms, "AND") if err != nil || len(entries) == 0 { fmt.Printf("Entry for query \"%s\" not found\n", term) return err @@ -690,18 +690,18 @@ func findCurrentEntry(term string) error { pcopy = true // Single entry means copy password can be enabled } else { - _, settings := getOrCreateLocalConfig(APP) - fmt.Printf("%s", getColor(strings.ToLower(settings.Color))) - printDelim(settings.Delim, settings.Color) + _, settings := GetOrCreateLocalConfig(APP) + fmt.Printf("%s", GetColor(strings.ToLower(settings.Color))) + PrintDelim(settings.Delim, settings.Color) } for _, entry := range entries { - printEntry(&entry, delim) + PrintEntry(&entry, delim) } - if pcopy && settingsRider.CopyPassword { + if pcopy && SettingsRider.CopyPassword { // Single entry - copyPasswordToClipboard(entries[0].Password) + CopyPasswordToClipboard(entries[0].Password) } } @@ -709,7 +709,7 @@ func findCurrentEntry(term string) error { } // Remove a range of entries - say 10-14 -func removeMultipleEntries(idRangeEntry string) error { +func RemoveMultipleEntries(idRangeEntry string) error { var err error var idRange []string @@ -731,14 +731,14 @@ func removeMultipleEntries(idRangeEntry string) error { } for idNum := id1; idNum <= id2; idNum++ { - err = removeCurrentEntry(fmt.Sprintf("%d", idNum)) + err = RemoveCurrentEntry(fmt.Sprintf("%d", idNum)) } return err } // Remove current entry by id -func removeCurrentEntry(idString string) error { +func RemoveCurrentEntry(idString string) error { var err error var entry *Entry @@ -750,20 +750,20 @@ func removeCurrentEntry(idString string) error { } if strings.Contains(idString, "-") { - return removeMultipleEntries(idString) + return RemoveMultipleEntries(idString) } id, _ = strconv.Atoi(idString) - err, entry = getEntryById(id) + err, entry = GetEntryById(id) if err != nil || entry == nil { fmt.Printf("No entry with id %d was found\n", id) return err } - printEntryMinimal(entry, true) + PrintEntryMinimal(entry, true) - if !settingsRider.AssumeYes { + if !SettingsRider.AssumeYes { response = readInput(bufio.NewReader(os.Stdin), "Please confirm removal [Y/n]: ") } else { response = "y" @@ -771,7 +771,7 @@ func removeCurrentEntry(idString string) error { if strings.ToLower(response) != "n" { // Drop from the database - err = removeDatabaseEntry(entry) + err = RemoveDatabaseEntry(entry) if err == nil { fmt.Printf("Entry with id %d was removed from the database\n", id) } @@ -783,7 +783,7 @@ func removeCurrentEntry(idString string) error { } // Copy current entry by id into new entry -func copyCurrentEntry(idString string) error { +func CopyCurrentEntry(idString string) error { var err error var entry *Entry @@ -798,24 +798,24 @@ func copyCurrentEntry(idString string) error { id, _ = strconv.Atoi(idString) - err, entry = getEntryById(id) + err, entry = GetEntryById(id) if err != nil || entry == nil { fmt.Printf("No entry with id %d was found\n", id) return err } - err, entryNew = cloneEntry(entry) + err, entryNew = CloneEntry(entry) if err != nil { fmt.Printf("Error cloning entry: \"%s\"\n", err.Error()) return err } - exEntries = getExtendedEntries(entry) + exEntries = GetExtendedEntries(entry) if len(exEntries) > 0 { fmt.Printf("%d extended entries found\n", len(exEntries)) - err = cloneExtendedEntries(entryNew, exEntries) + err = CloneExtendedEntries(entryNew, exEntries) if err != nil { fmt.Printf("Error cloning extended entries: \"%s\"\n", err.Error()) return err @@ -826,7 +826,7 @@ func copyCurrentEntry(idString string) error { } // Encrypt the active database -func encryptActiveDatabase() error { +func EncryptActiveDatabase() error { var err error var dbPath string @@ -835,17 +835,17 @@ func encryptActiveDatabase() error { return err } - err, dbPath = getActiveDatabase() + err, dbPath = GetActiveDatabase() if err != nil { fmt.Printf("Error getting active database path - \"%s\"\n", err.Error()) return err } - return encryptDatabase(dbPath, nil) + return EncryptDatabase(dbPath, nil) } // Encrypt the database using AES -func encryptDatabase(dbPath string, givenPasswd *string) error { +func EncryptDatabase(dbPath string, givenPasswd *string) error { var err error var passwd string @@ -858,11 +858,11 @@ func encryptDatabase(dbPath string, givenPasswd *string) error { if len(passwd) == 0 { fmt.Printf("Encryption Password: ") - err, passwd = readPassword() + err, passwd = ReadPassword() if err == nil { fmt.Printf("\nEncryption Password again: ") - err, passwd2 = readPassword() + err, passwd2 = ReadPassword() if err == nil { if passwd != passwd2 { fmt.Println("\nPassword mismatch.") @@ -877,17 +877,17 @@ func encryptDatabase(dbPath string, givenPasswd *string) error { } } - // err = encryptFileAES(dbPath, passwd) - _, settings := getOrCreateLocalConfig(APP) + // err = EncryptFileAES(dbPath, passwd) + _, settings := GetOrCreateLocalConfig(APP) switch settings.Cipher { case "aes": - err = encryptFileAES(dbPath, passwd) + err = EncryptFileAES(dbPath, passwd) case "xchacha", "chacha", "xchachapoly": - err = encryptFileXChachaPoly(dbPath, passwd) + err = EncryptFileXChachaPoly(dbPath, passwd) default: fmt.Println("No cipher set, defaulting to AES") - err = encryptFileAES(dbPath, passwd) + err = EncryptFileAES(dbPath, passwd) } if err == nil { @@ -898,35 +898,35 @@ func encryptDatabase(dbPath string, givenPasswd *string) error { } // Decrypt an encrypted database -func decryptDatabase(dbPath string) (error, string) { +func DecryptDatabase(dbPath string) (error, string) { var err error var passwd string var flag bool - if err, flag = isFileEncrypted(dbPath); !flag { + if err, flag = IsFileEncrypted(dbPath); !flag { fmt.Println(err.Error()) return err, "" } fmt.Printf("Decryption Password: ") - err, passwd = readPassword() + err, passwd = ReadPassword() if err != nil { fmt.Printf("\nError reading password - \"%s\"\n", err.Error()) return err, "" } - _, settings := getOrCreateLocalConfig(APP) + _, settings := GetOrCreateLocalConfig(APP) switch settings.Cipher { case "aes": - err = decryptFileAES(dbPath, passwd) + err = DecryptFileAES(dbPath, passwd) case "xchacha", "chacha", "xchachapoly": - err = decryptFileXChachaPoly(dbPath, passwd) + err = DecryptFileXChachaPoly(dbPath, passwd) default: fmt.Println("No cipher set, defaulting to AES") - err = decryptFileAES(dbPath, passwd) + err = DecryptFileAES(dbPath, passwd) } if err == nil { @@ -937,7 +937,7 @@ func decryptDatabase(dbPath string) (error, string) { } // Migrate an existing database to the new schema -func migrateDatabase(dbPath string) error { +func MigrateDatabase(dbPath string) error { var err error var flag bool @@ -949,15 +949,15 @@ func migrateDatabase(dbPath string) error { return err } - if err, flag = isFileEncrypted(dbPath); flag { - err, passwd = decryptDatabase(dbPath) + if err, flag = IsFileEncrypted(dbPath); flag { + err, passwd = DecryptDatabase(dbPath) if err != nil { fmt.Printf("Error decrypting - %s: %s\n", dbPath, err.Error()) return err } } - err, db = openDatabase(dbPath) + err, db = OpenDatabase(dbPath) if err != nil { fmt.Printf("Error opening database path - %s: %s\n", dbPath, err.Error()) @@ -981,7 +981,7 @@ func migrateDatabase(dbPath string) error { if flag { // File was encrypted - encrypt it again - encryptDatabase(dbPath, &passwd) + EncryptDatabase(dbPath, &passwd) } fmt.Println("Migration successful.") diff --git a/crypto.go b/crypto.go index c1744b7..9a98ffb 100644 --- a/crypto.go +++ b/crypto.go @@ -1,5 +1,5 @@ // Cryptographic functions -package main +package varuh import ( "crypto/aes" @@ -28,7 +28,7 @@ const HMAC_SHA512_SIZE = 64 const MAGIC_HEADER = 0xcafebabe // Generate random bytes of the given length -func generateRandomBytes(size int) (error, []byte) { +func GenerateRandomBytes(size int) (error, []byte) { var data []byte data = make([]byte, size) @@ -45,15 +45,18 @@ func generateRandomBytes(size int) (error, []byte) { // Generate a key from the given passphrase and (optional) salt // If 2nd argument is nil, salt will be generated. Uses argon2 -func generateKeyArgon2(passPhrase string, oldSalt *[]byte) (error, []byte, []byte) { +func GenerateKeyArgon2(passPhrase string, oldSalt *[]byte) (error, []byte, []byte) { var salt []byte var key []byte var err error if oldSalt == nil { - err, salt = generateRandomBytes(SALT_SIZE) + err, salt = GenerateRandomBytes(SALT_SIZE) } else { + if len(*oldSalt) != SALT_SIZE { + return errors.New("invalid salt length"), key, salt + } salt = *oldSalt } @@ -71,15 +74,18 @@ func generateKeyArgon2(passPhrase string, oldSalt *[]byte) (error, []byte, []byt // Generate a key from the given passphrase and (optional) salt // If 2nd argument is nil, salt will be generated. Uses pbkdf2 -func generateKey(passPhrase string, oldSalt *[]byte) (error, []byte, []byte) { +func GenerateKey(passPhrase string, oldSalt *[]byte) (error, []byte, []byte) { var salt []byte var key []byte var err error if oldSalt == nil { - err, salt = generateRandomBytes(SALT_SIZE) + err, salt = GenerateRandomBytes(SALT_SIZE) } else { + if len(*oldSalt) != SALT_SIZE { + return errors.New("invalid salt length"), key, salt + } salt = *oldSalt } @@ -95,7 +101,7 @@ func generateKey(passPhrase string, oldSalt *[]byte) (error, []byte, []byte) { } // Return if file is encrypted by looking at the magic header -func isFileEncrypted(encDbPath string) (error, bool) { +func IsFileEncrypted(encDbPath string) (error, bool) { var magicBytes string var header []byte @@ -115,6 +121,9 @@ func isFileEncrypted(encDbPath string) (error, bool) { _, err = io.ReadFull(fh, header[:]) if err != nil { + if err == io.EOF { + return fmt.Errorf("Not an encrypted database - file is empty"), false + } return fmt.Errorf("Error - Can't read file header -\"%s\"\n", err.Error()), false } @@ -126,7 +135,7 @@ func isFileEncrypted(encDbPath string) (error, bool) { } // Encrypt the database path using AES -func encryptFileAES(dbPath string, password string) error { +func EncryptFileAES(dbPath string, password string) error { var err error var key []byte @@ -145,7 +154,7 @@ func encryptFileAES(dbPath string, password string) error { return err } - err, key, salt = generateKeyArgon2(password, nil) + err, key, salt = GenerateKeyArgon2(password, nil) if err != nil { fmt.Printf("Error - Key derivation failed -\"%s\"\n", err) @@ -168,7 +177,7 @@ func encryptFileAES(dbPath string, password string) error { nonceSize := aesGCM.NonceSize() // fmt.Printf("%d\n", nonceSize) - err, nonce = generateRandomBytes(nonceSize) + err, nonce = GenerateRandomBytes(nonceSize) if err != nil { fmt.Printf("Error - Nonce generation failed -\"%s\"\n", err) @@ -207,7 +216,7 @@ func encryptFileAES(dbPath string, password string) error { } // Decrypt an already encrypted database file using given password using AES -func decryptFileAES(encDbPath string, password string) error { +func DecryptFileAES(encDbPath string, password string) error { var encText []byte var cipherText []byte @@ -233,7 +242,7 @@ func decryptFileAES(encDbPath string, password string) error { // Read the hmac hash checksum hmacHash, encText = encText[:HMAC_SHA512_SIZE], encText[HMAC_SHA512_SIZE:] - err, key, _ = generateKeyArgon2(password, &salt) + err, key, _ = GenerateKeyArgon2(password, &salt) if err != nil { fmt.Printf("Error - Key derivation failed -\"%s\"\n", err) @@ -278,7 +287,7 @@ func decryptFileAES(encDbPath string, password string) error { return err } - err, origFile = rewriteFile(encDbPath, plainText, 0600) + err, origFile = RewriteFile(encDbPath, plainText, 0600) if err != nil { fmt.Printf("Error writing decrypted data to %s - \"%s\"\n", origFile, err.Error()) @@ -289,7 +298,7 @@ func decryptFileAES(encDbPath string, password string) error { } // Encrypt a file using XChaCha20-Poly1305 cipher -func encryptFileXChachaPoly(dbPath string, password string) error { +func EncryptFileXChachaPoly(dbPath string, password string) error { var err error var key []byte @@ -308,7 +317,7 @@ func encryptFileXChachaPoly(dbPath string, password string) error { return err } - err, key, salt = generateKeyArgon2(password, nil) + err, key, salt = GenerateKeyArgon2(password, nil) if err != nil { fmt.Printf("Error - Key derivation failed -\"%s\"\n", err) @@ -360,7 +369,7 @@ func encryptFileXChachaPoly(dbPath string, password string) error { } // Decrypt an already encrypted database file using given password using XChaCha20-Poly1305 -func decryptFileXChachaPoly(encDbPath string, password string) error { +func DecryptFileXChachaPoly(encDbPath string, password string) error { var encText []byte var cipherText []byte @@ -386,7 +395,7 @@ func decryptFileXChachaPoly(encDbPath string, password string) error { // Read the hmac hash checksum hmacHash, encText = encText[:HMAC_SHA512_SIZE], encText[HMAC_SHA512_SIZE:] - err, key, _ = generateKeyArgon2(password, &salt) + err, key, _ = GenerateKeyArgon2(password, &salt) if err != nil { fmt.Printf("Error - Key derivation failed -\"%s\"\n", err) @@ -426,7 +435,7 @@ func decryptFileXChachaPoly(encDbPath string, password string) error { } // err = os.WriteFile("test.sqlite3", oplainText, 0600) - err, origFile = rewriteFile(encDbPath, plainText, 0600) + err, origFile = RewriteFile(encDbPath, plainText, 0600) if err != nil { fmt.Printf("Error writing decrypted data to %s - \"%s\"\n", origFile, err.Error()) @@ -437,7 +446,7 @@ func decryptFileXChachaPoly(encDbPath string, password string) error { } // Generate a random password - for adding listings -func generatePassword(length int) (error, string) { +func GeneratePassword(length int) (error, string) { var data []byte const source = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=+_()$#@!~:/%" @@ -463,7 +472,7 @@ func generatePassword(length int) (error, string) { // at least one upper case alphabet [A-Z] // at least one punctuation character // at least length 12 -func generateStrongPassword() (error, string) { +func GenerateStrongPassword() (error, string) { var data []byte var length int diff --git a/db.go b/db.go index 37b554b..d1f3e89 100644 --- a/db.go +++ b/db.go @@ -1,8 +1,9 @@ // Database operations for varuh -package main +package varuh import ( "database/sql" + "errors" "fmt" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -153,7 +154,15 @@ func (e1 *ExtendedEntry) Copy(e2 *ExtendedEntry) { } // Create a new database -func openDatabase(filePath string) (error, *gorm.DB) { +func OpenDatabase(filePath string) (error, *gorm.DB) { + // Check if file exists first + if filePath == "" { + return errors.New("database path cannot be empty"), nil + } + + if _, err := os.Stat(filePath); os.IsNotExist(err) { + return fmt.Errorf("database file does not exist: %s", filePath), nil + } db, err := gorm.Open(sqlite.Open(filePath), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), @@ -162,25 +171,29 @@ func openDatabase(filePath string) (error, *gorm.DB) { } // Create a new table for Entries in the database -func createNewEntry(db *gorm.DB) error { +func CreateNewEntry(db *gorm.DB) error { return db.AutoMigrate(&Entry{}) } // Create a new table for Extended Entries in the database -func createNewExEntry(db *gorm.DB) error { +func CreateNewExEntry(db *gorm.DB) error { return db.AutoMigrate(&ExtendedEntry{}) } // Init new database including tables -func initNewDatabase(dbPath string) error { +func InitNewDatabase(dbPath string) error { var err error var db *gorm.DB var absPath string - if hasActiveDatabase() { + if dbPath == "" { + return errors.New("database path cannot be empty") + } + + if HasActiveDatabase() { // Has an active database - encrypt it before creating new one - _, activeDbPath := getActiveDatabase() + _, activeDbPath := GetActiveDatabase() absPath, _ = filepath.Abs(dbPath) if absPath == activeDbPath { @@ -189,7 +202,7 @@ func initNewDatabase(dbPath string) error { } else { // TBD fmt.Printf("Encrytping current database - %s\n", activeDbPath) - encryptDatabase(activeDbPath, nil) + EncryptDatabase(activeDbPath, nil) } } @@ -198,19 +211,19 @@ func initNewDatabase(dbPath string) error { os.Remove(dbPath) } - err, db = openDatabase(dbPath) + err, db = OpenDatabase(dbPath) if err != nil { fmt.Printf("Error creating new database - \"%s\"\n", err.Error()) return err } - err = createNewEntry(db) + err = CreateNewEntry(db) if err != nil { fmt.Printf("Error creating schema - \"%s\"\n", err.Error()) return err } - err = createNewExEntry(db) + err = CreateNewExEntry(db) if err != nil { fmt.Printf("Error creating schema - \"%s\"\n", err.Error()) return err @@ -225,7 +238,7 @@ func initNewDatabase(dbPath string) error { if err == nil { fmt.Printf("Updating active db path - %s\n", absPath) - updateActiveDbPath(absPath) + UpdateActiveDbPath(absPath) } else { fmt.Printf("Error - %s\n", err.Error()) return err @@ -240,13 +253,13 @@ func openActiveDatabase() (error, *gorm.DB) { var dbPath string var err error - err, dbPath = getActiveDatabase() + err, dbPath = GetActiveDatabase() if err != nil { fmt.Printf("Error getting active database path - %s\n", err.Error()) return err, nil } - err, db := openDatabase(dbPath) + err, db := OpenDatabase(dbPath) if err != nil { fmt.Printf("Error opening active database path - %s: %s\n", dbPath, err.Error()) return err, nil @@ -256,12 +269,12 @@ func openActiveDatabase() (error, *gorm.DB) { } // Add custom entries to a database entry -func addCustomEntries(db *gorm.DB, entry *Entry, customEntries []CustomEntry) error { +func AddCustomEntries(db *gorm.DB, entry *Entry, customEntries []CustomEntry) error { var count int var err error - err = createNewExEntry(db) + err = CreateNewExEntry(db) if err != nil { fmt.Printf("Error creating schema - \"%s\"\n", err.Error()) return err @@ -270,7 +283,7 @@ func addCustomEntries(db *gorm.DB, entry *Entry, customEntries []CustomEntry) er for _, customEntry := range customEntries { var exEntry ExtendedEntry - exEntry = ExtendedEntry{FieldName: customEntry.fieldName, FieldValue: customEntry.fieldValue, + exEntry = ExtendedEntry{FieldName: customEntry.FieldName, FieldValue: customEntry.FieldValue, EntryID: entry.ID} resultEx := db.Create(&exEntry) @@ -284,13 +297,13 @@ func addCustomEntries(db *gorm.DB, entry *Entry, customEntries []CustomEntry) er } // Replace custom entries to a database entry (Drop existing and add fresh) -func replaceCustomEntries(db *gorm.DB, entry *Entry, updatedEntries []CustomEntry) error { +func ReplaceCustomEntries(db *gorm.DB, entry *Entry, updatedEntries []CustomEntry) error { var count int var err error var customEntries []ExtendedEntry - err = createNewExEntry(db) + err = CreateNewExEntry(db) if err != nil { fmt.Printf("Error creating schema - \"%s\"\n", err.Error()) return err @@ -301,7 +314,7 @@ func replaceCustomEntries(db *gorm.DB, entry *Entry, updatedEntries []CustomEntr for _, customEntry := range updatedEntries { var exEntry ExtendedEntry - exEntry = ExtendedEntry{FieldName: customEntry.fieldName, FieldValue: customEntry.fieldValue, + exEntry = ExtendedEntry{FieldName: customEntry.FieldName, FieldValue: customEntry.FieldValue, EntryID: entry.ID} resultEx := db.Create(&exEntry) @@ -315,7 +328,7 @@ func replaceCustomEntries(db *gorm.DB, entry *Entry, updatedEntries []CustomEntr } // Add a new entry to current database -func addNewDatabaseEntry(title, userName, url, passwd, tags string, +func AddNewDatabaseEntry(title, userName, url, passwd, tags string, notes string, customEntries []CustomEntry) error { var entry Entry @@ -333,7 +346,7 @@ func addNewDatabaseEntry(title, userName, url, passwd, tags string, // Add custom fields if given fmt.Printf("Created new entry with id: %d.\n", entry.ID) if len(customEntries) > 0 { - return addCustomEntries(db, &entry, customEntries) + return AddCustomEntries(db, &entry, customEntries) } return nil } else if result.Error != nil { @@ -344,7 +357,7 @@ func addNewDatabaseEntry(title, userName, url, passwd, tags string, return err } -func updateDatabaseCardEntry(entry *Entry, cardName, cardNumber, cardHolder, cardClass, +func UpdateDatabaseCardEntry(entry *Entry, cardName, cardNumber, cardHolder, cardClass, cardCvv, cardPin, cardExpiry, notes, tags string, customEntries []CustomEntry, flag bool) error { @@ -389,7 +402,7 @@ func updateDatabaseCardEntry(entry *Entry, cardName, cardNumber, cardHolder, car } if flag { - replaceCustomEntries(db, entry, customEntries) + ReplaceCustomEntries(db, entry, customEntries) } fmt.Println("Updated entry.") return nil @@ -399,7 +412,7 @@ func updateDatabaseCardEntry(entry *Entry, cardName, cardNumber, cardHolder, car } // Add a new card entry to current database -func addNewDatabaseCardEntry(cardName, cardNumber, cardHolder, cardIssuer, cardClass, +func AddNewDatabaseCardEntry(cardName, cardNumber, cardHolder, cardIssuer, cardClass, cardCvv, cardPin, cardExpiry, notes, tags string, customEntries []CustomEntry) error { var entry Entry @@ -432,7 +445,7 @@ func addNewDatabaseCardEntry(cardName, cardNumber, cardHolder, cardIssuer, cardC // Add custom fields if given fmt.Printf("Created new entry with id: %d.\n", entry.ID) if len(customEntries) > 0 { - return addCustomEntries(db, &entry, customEntries) + return AddCustomEntries(db, &entry, customEntries) } return nil } else if result.Error != nil { @@ -444,7 +457,7 @@ func addNewDatabaseCardEntry(cardName, cardNumber, cardHolder, cardIssuer, cardC } // Update current database entry with new values -func updateDatabaseEntry(entry *Entry, title, userName, url, passwd, tags string, +func UpdateDatabaseEntry(entry *Entry, title, userName, url, passwd, tags string, notes string, customEntries []CustomEntry, flag bool) error { var updateMap map[string]interface{} @@ -482,7 +495,7 @@ func updateDatabaseEntry(entry *Entry, title, userName, url, passwd, tags string } if flag { - replaceCustomEntries(db, entry, customEntries) + ReplaceCustomEntries(db, entry, customEntries) } fmt.Println("Updated entry.") return nil @@ -492,7 +505,7 @@ func updateDatabaseEntry(entry *Entry, title, userName, url, passwd, tags string } // Find entry given the id -func getEntryById(id int) (error, *Entry) { +func GetEntryById(id int) (error, *Entry) { var entry Entry var err error @@ -512,7 +525,7 @@ func getEntryById(id int) (error, *Entry) { } // Search database for the given string and return all matches -func searchDatabaseEntry(term string) (error, []Entry) { +func SearchDatabaseEntry(term string) (error, []Entry) { var entries []Entry var err error @@ -581,7 +594,7 @@ func intersection(entry1 []Entry, entry2 []Entry) []Entry { } // Search database for the given terms and returns matches according to operator -func searchDatabaseEntries(terms []string, operator string) (error, []Entry) { +func SearchDatabaseEntries(terms []string, operator string) (error, []Entry) { var err error var finalEntries []Entry @@ -589,7 +602,7 @@ func searchDatabaseEntries(terms []string, operator string) (error, []Entry) { for idx, term := range terms { var entries []Entry - err, entries = searchDatabaseEntry(term) + err, entries = SearchDatabaseEntry(term) if err != nil { fmt.Printf("Error searching for term: %s - \"%s\"\n", term, err.Error()) return err, entries @@ -610,7 +623,7 @@ func searchDatabaseEntries(terms []string, operator string) (error, []Entry) { } // Remove a given database entry -func removeDatabaseEntry(entry *Entry) error { +func RemoveDatabaseEntry(entry *Entry) error { var err error var db *gorm.DB @@ -625,7 +638,7 @@ func removeDatabaseEntry(entry *Entry) error { } // Delete extended entries if any - exEntries = getExtendedEntries(entry) + exEntries = GetExtendedEntries(entry) if len(exEntries) > 0 { res = db.Delete(exEntries) if res.Error != nil { @@ -640,7 +653,7 @@ func removeDatabaseEntry(entry *Entry) error { } // Clone an entry and return cloned entry -func cloneEntry(entry *Entry) (error, *Entry) { +func CloneEntry(entry *Entry) (error, *Entry) { var entryNew Entry var err error @@ -663,7 +676,7 @@ func cloneEntry(entry *Entry) (error, *Entry) { } // Clone extended entries for an entry and return error code -func cloneExtendedEntries(entry *Entry, exEntries []ExtendedEntry) error { +func CloneExtendedEntries(entry *Entry, exEntries []ExtendedEntry) error { var err error var db *gorm.DB @@ -688,7 +701,7 @@ func cloneExtendedEntries(entry *Entry, exEntries []ExtendedEntry) error { } // Return an iterator over all entries using the given order query keys -func iterateEntries(orderKey string, order string) (error, []Entry) { +func IterateEntries(orderKey string, order string) (error, []Entry) { var err error var db *gorm.DB @@ -714,7 +727,7 @@ func iterateEntries(orderKey string, order string) (error, []Entry) { } // Export all entries to string array -func entriesToStringArray(skipLongFields bool) (error, [][]string) { +func EntriesToStringArray(skipLongFields bool) (error, [][]string) { var err error var db *gorm.DB @@ -752,7 +765,7 @@ func entriesToStringArray(skipLongFields bool) (error, [][]string) { } // Get extended entries associated to an entry -func getExtendedEntries(entry *Entry) []ExtendedEntry { +func GetExtendedEntries(entry *Entry) []ExtendedEntry { var err error var db *gorm.DB diff --git a/defns.go b/defns.go new file mode 100644 index 0000000..a83ec83 --- /dev/null +++ b/defns.go @@ -0,0 +1,15 @@ +package varuh + +type ActionFunc func(string) error +type ActionFunc2 func(string) (error, string) +type VoidFunc func() error +type VoidFunc2 func() (error, string) +type SettingFunc func(string) + +const VERSION = 0.41 +const APP = "varuh" + +const AUTHOR_INFO = ` +AUTHORS + Copyright (C) 2025 @skeptichacker Anand B Pillai +` diff --git a/export.go b/export.go index 30442b6..314e4bd 100644 --- a/export.go +++ b/export.go @@ -1,4 +1,4 @@ -package main +package varuh import ( "bufio" @@ -12,7 +12,7 @@ import ( ) // Export data to a varity of file types -func exportToFile(fileName string) error { +func ExportToFile(fileName string) error { var err error var maxKrypt bool @@ -26,7 +26,7 @@ func exportToFile(fileName string) error { if ext == ".csv" || ext == ".md" || ext == ".html" || ext == ".pdf" { // If max krypt on - then autodecrypt on call and auto encrypt after call if maxKrypt { - err, passwd = decryptDatabase(defaultDB) + err, passwd = DecryptDatabase(defaultDB) if err != nil { return err } @@ -35,13 +35,13 @@ func exportToFile(fileName string) error { switch ext { case ".csv": - err = exportToCsv(fileName) + err = ExportToCSV(fileName) case ".md": - err = exportToMarkdown(fileName) + err = ExportToMarkdown(fileName) case ".html": - err = exportToHTML(fileName) + err = ExportToHTML(fileName) case ".pdf": - err = exportToPDF(fileName) + err = ExportToPDF(fileName) default: fmt.Printf("Error - extn %s not supported\n", ext) return fmt.Errorf("format %s not supported", ext) @@ -58,7 +58,7 @@ func exportToFile(fileName string) error { // If max krypt on - then autodecrypt on call and auto encrypt after call if maxKrypt { - err = encryptDatabase(defaultDB, &passwd) + err = EncryptDatabase(defaultDB, &passwd) } return err @@ -69,7 +69,7 @@ func exportToFile(fileName string) error { } // Export current database to markdown -func exportToMarkdown(fileName string) error { +func ExportToMarkdown(fileName string) error { var err error var dataArray [][]string @@ -77,7 +77,7 @@ func exportToMarkdown(fileName string) error { var maxLengths [7]int var headers []string = []string{" ID ", " Title ", " User ", " URL ", " Password ", " Notes ", " Modified "} - err, dataArray = entriesToStringArray(false) + err, dataArray = EntriesToStringArray(false) if err != nil { fmt.Printf("Error exporting entries to string array - \"%s\"\n", err.Error()) @@ -144,7 +144,7 @@ func exportToMarkdown(fileName string) error { } // This needs pandoc and pdflatex support -func exportToPDF(fileName string) error { +func ExportToPDF(fileName string) error { var err error var tmpFile string @@ -165,12 +165,12 @@ func exportToPDF(fileName string) error { if pdfTkFound { fmt.Printf("PDF Encryption Password: ") - err, passwd = readPassword() + err, passwd = ReadPassword() } - tmpFile = randomFileName(os.TempDir(), ".tmp") + tmpFile = RandomFileName(os.TempDir(), ".tmp") // fmt.Printf("Temp file => %s\n", tmpFile) - err = exportToMarkdownLimited(tmpFile) + err = ExportToMarkdownLimited(tmpFile) if err == nil { var args []string = []string{"-o", fileName, "-f", "markdown", "-V", "geometry:landscape", "--columns=600", "--pdf-engine", "xelatex", "--dpi=150", tmpFile} @@ -185,7 +185,7 @@ func exportToPDF(fileName string) error { fmt.Printf("\nFile %s created without password.\n", fileName) if pdfTkFound && len(passwd) > 0 { - tmpFile = randomFileName(".", ".pdf") + tmpFile = RandomFileName(".", ".pdf") // fmt.Printf("pdf file => %s\n", tmpFile) args = []string{fileName, "output", tmpFile, "user_pw", passwd} cmd = exec.Command("pdftk", args...) @@ -208,7 +208,7 @@ func exportToPDF(fileName string) error { } // Export current database to markdown minus the long fields -func exportToMarkdownLimited(fileName string) error { +func ExportToMarkdownLimited(fileName string) error { var err error var dataArray [][]string @@ -216,7 +216,7 @@ func exportToMarkdownLimited(fileName string) error { var maxLengths [5]int var headers []string = []string{" ID ", " Title ", " User ", " Password ", " Modified "} - err, dataArray = entriesToStringArray(true) + err, dataArray = EntriesToStringArray(true) if err != nil { fmt.Printf("Error exporting entries to string array - \"%s\"\n", err.Error()) @@ -283,14 +283,14 @@ func exportToMarkdownLimited(fileName string) error { } // Export current database to html -func exportToHTML(fileName string) error { +func ExportToHTML(fileName string) error { var err error var dataArray [][]string var fh *os.File var headers []string = []string{" ID ", " Title ", " User ", " URL ", " Password ", " Notes ", " Modified "} - err, dataArray = entriesToStringArray(false) + err, dataArray = EntriesToStringArray(false) if err != nil { fmt.Printf("Error exporting entries to string array - \"%s\"\n", err.Error()) @@ -338,13 +338,13 @@ func exportToHTML(fileName string) error { } // Export current database to CSV -func exportToCsv(fileName string) error { +func ExportToCSV(fileName string) error { var err error var dataArray [][]string var fh *os.File - err, dataArray = entriesToStringArray(false) + err, dataArray = EntriesToStringArray(false) if err != nil { fmt.Printf("Error exporting entries to string array - \"%s\"\n", err.Error()) diff --git a/test/testpgp.go b/sample/testpgp.go similarity index 100% rename from test/testpgp.go rename to sample/testpgp.go diff --git a/main.go b/scripts/main.go similarity index 74% rename from main.go rename to scripts/main.go index 0078724..6af7ed3 100644 --- a/main.go +++ b/scripts/main.go @@ -7,22 +7,9 @@ import ( "github.com/pythonhacker/argparse" "os" "strings" + "varuh" ) -const VERSION = 0.4 -const APP = "varuh" - -const AUTHOR_INFO = ` -AUTHORS - Copyright (C) 2022 Anand B Pillai -` - -type actionFunc func(string) error -type actionFunc2 func(string) (error, string) -type voidFunc func() error -type voidFunc2 func() (error, string) -type settingFunc func(string) - // Structure to keep the options data type CmdOption struct { Short string @@ -42,7 +29,7 @@ func printUsage() error { // Print the program's version info and exit func printVersionInfo() error { - fmt.Printf("%s version %.2f\n", APP, VERSION) + fmt.Printf("%s version %.2f\n", varuh.APP, varuh.VERSION) os.Exit(0) return nil @@ -53,7 +40,7 @@ func genPass() (error, string) { var err error var passwd string - err, passwd = generateStrongPassword() + err, passwd = varuh.GenerateStrongPassword() if err != nil { fmt.Printf("Error generating password - \"%s\"\n", err.Error()) @@ -62,8 +49,8 @@ func genPass() (error, string) { fmt.Println(passwd) - if settingsRider.CopyPassword { - copyPasswordToClipboard(passwd) + if varuh.SettingsRider.CopyPassword { + varuh.CopyPasswordToClipboard(passwd) fmt.Println("Password copied to clipboard") } @@ -75,46 +62,46 @@ func performAction(optMap map[string]interface{}) { var flag bool - boolActionsMap := map[string]voidFunc{ - "add": WrapperMaxKryptVoidFunc(addNewEntry), + boolActionsMap := map[string]varuh.VoidFunc{ + "add": varuh.WrapperMaxKryptVoidFunc(varuh.AddNewEntry), "version": printVersionInfo, "help": printUsage, - "path": showActiveDatabasePath, - "list-all": WrapperMaxKryptVoidFunc(listAllEntries), - "encrypt": encryptActiveDatabase, + "path": varuh.ShowActiveDatabasePath, + "list-all": varuh.WrapperMaxKryptVoidFunc(varuh.ListAllEntries), + "encrypt": varuh.EncryptActiveDatabase, } - stringActionsMap := map[string]actionFunc{ - "edit": WrapperMaxKryptStringFunc(editCurrentEntry), - "init": initNewDatabase, - "list-entry": WrapperMaxKryptStringFunc(listCurrentEntry), - "remove": WrapperMaxKryptStringFunc(removeCurrentEntry), - "clone": WrapperMaxKryptStringFunc(copyCurrentEntry), - "use-db": setActiveDatabasePath, - "export": exportToFile, - "migrate": migrateDatabase, + stringActionsMap := map[string]varuh.ActionFunc{ + "edit": varuh.WrapperMaxKryptStringFunc(varuh.EditCurrentEntry), + "init": varuh.InitNewDatabase, + "list-entry": varuh.WrapperMaxKryptStringFunc(varuh.ListCurrentEntry), + "remove": varuh.WrapperMaxKryptStringFunc(varuh.RemoveCurrentEntry), + "clone": varuh.WrapperMaxKryptStringFunc(varuh.CopyCurrentEntry), + "use-db": varuh.SetActiveDatabasePath, + "export": varuh.ExportToFile, + "migrate": varuh.MigrateDatabase, } - stringListActionsMap := map[string]actionFunc{ - "find": WrapperMaxKryptStringFunc(findCurrentEntry), + stringListActionsMap := map[string]varuh.ActionFunc{ + "find": varuh.WrapperMaxKryptStringFunc(varuh.FindCurrentEntry), } - stringActions2Map := map[string]actionFunc2{ - "decrypt": decryptDatabase, + stringActions2Map := map[string]varuh.ActionFunc2{ + "decrypt": varuh.DecryptDatabase, } - flagsActions2Map := map[string]voidFunc2{ + flagsActions2Map := map[string]varuh.VoidFunc2{ "genpass": genPass, } - flagsActionsMap := map[string]voidFunc{ - "show": setShowPasswords, - "copy": setCopyPasswordToClipboard, - "assume-yes": setAssumeYes, + flagsActionsMap := map[string]varuh.VoidFunc{ + "show": varuh.SetShowPasswords, + "copy": varuh.SetCopyPasswordToClipboard, + "assume-yes": varuh.SetAssumeYes, } - flagsSettingsMap := map[string]settingFunc{ - "type": setType, + flagsSettingsMap := map[string]varuh.SettingFunc{ + "type": varuh.SetType, } // Flag actions - always done @@ -248,7 +235,7 @@ func main() { parser := argparse.NewParser("varuh", "Password manager for the command line for Unix like operating systems", - AUTHOR_INFO, + varuh.AUTHOR_INFO, ) optMap := initializeCmdLine(parser) @@ -259,7 +246,7 @@ func main() { fmt.Println(parser.Usage(err)) } - getOrCreateLocalConfig(APP) + varuh.GetOrCreateLocalConfig(varuh.APP) performAction(optMap) } diff --git a/tests/utils_test.go b/tests/utils_test.go new file mode 100644 index 0000000..02cc917 --- /dev/null +++ b/tests/utils_test.go @@ -0,0 +1,669 @@ +package tests + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + "time" + "varuh" +) + +// TestMapString tests the MapString function +func TestMapString(t *testing.T) { + // Test case 1: Empty slice + result := varuh.MapString([]string{}, func(s string) string { + return strings.ToUpper(s) + }) + if len(result) != 0 { + t.Errorf("Expected empty slice, got %v", result) + } + + // Test case 2: Single element + result = varuh.MapString([]string{"hello"}, func(s string) string { + return strings.ToUpper(s) + }) + if len(result) != 1 || result[0] != "HELLO" { + t.Errorf("Expected [HELLO], got %v", result) + } + + // Test case 3: Multiple elements + result = varuh.MapString([]string{"hello", "world", "test"}, func(s string) string { + return strings.ToUpper(s) + }) + expected := []string{"HELLO", "WORLD", "TEST"} + if len(result) != 3 { + t.Errorf("Expected length 3, got %d", len(result)) + } + for i, v := range expected { + if result[i] != v { + t.Errorf("Expected %s at index %d, got %s", v, i, result[i]) + } + } + + // Test case 4: Identity function + result = varuh.MapString([]string{"a", "b", "c"}, func(s string) string { + return s + }) + expected = []string{"a", "b", "c"} + for i, v := range expected { + if result[i] != v { + t.Errorf("Expected %s at index %d, got %s", v, i, result[i]) + } + } +} + +// TestHideSecret tests the HideSecret function +func TestHideSecret(t *testing.T) { + // Test case 1: Empty string + result := varuh.HideSecret("") + if result != "" { + t.Errorf("Expected empty string, got %s", result) + } + + // Test case 2: Single character + result = varuh.HideSecret("a") + if result != "*" { + t.Errorf("Expected '*', got %s", result) + } + + // Test case 3: Multiple characters + result = varuh.HideSecret("password") + if len(result) != 8 { + t.Errorf("Expected length 8, got %d", len(result)) + } + for _, char := range result { + if char != '*' { + t.Errorf("Expected all '*' characters, got %c", char) + } + } + + // Test case 4: Long string + longString := "this_is_a_very_long_password_string" + result = varuh.HideSecret(longString) + if len(result) != len(longString) { + t.Errorf("Expected length %d, got %d", len(longString), len(result)) + } + for _, char := range result { + if char != '*' { + t.Errorf("Expected all '*' characters, got %c", char) + } + } +} + +// TestWriteSettings tests the WriteSettings function +func TestWriteSettings(t *testing.T) { + // Create a temporary directory for testing + tempDir := t.TempDir() + configFile := filepath.Join(tempDir, "test_config.json") + + // Test case 1: Valid settings + settings := &varuh.Settings{ + ActiveDB: "/tmp/test.db", + Cipher: "aes", + AutoEncrypt: true, + KeepEncrypted: true, + ShowPasswords: false, + ConfigPath: configFile, + ListOrder: "id,asc", + Delim: ">", + Color: "default", + BgColor: "bgblack", + } + + err := varuh.WriteSettings(settings, configFile) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + + // Verify file was created + if _, err := os.Stat(configFile); os.IsNotExist(err) { + t.Errorf("Config file was not created") + } + + // Test case 2: Invalid path (directory doesn't exist) + invalidPath := "/nonexistent/path/config.json" + err = varuh.WriteSettings(settings, invalidPath) + if err == nil { + t.Errorf("Expected error for invalid path, got nil") + } +} + +// TestUpdateSettings tests the UpdateSettings function +func TestUpdateSettings(t *testing.T) { + // Create a temporary directory for testing + tempDir := t.TempDir() + configFile := filepath.Join(tempDir, "test_config.json") + + // First create a config file + settings := &varuh.Settings{ + ActiveDB: "/tmp/test.db", + Cipher: "aes", + AutoEncrypt: true, + KeepEncrypted: true, + ShowPasswords: false, + ConfigPath: configFile, + ListOrder: "id,asc", + Delim: ">", + Color: "default", + BgColor: "bgblack", + } + + err := varuh.WriteSettings(settings, configFile) + if err != nil { + t.Fatalf("Failed to create initial config file: %v", err) + } + + // Test case 1: Update existing file + settings.ShowPasswords = true + settings.Color = "red" + err = varuh.UpdateSettings(settings, configFile) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + + // Test case 2: Update non-existent file + nonExistentFile := filepath.Join(tempDir, "nonexistent.json") + err = varuh.UpdateSettings(settings, nonExistentFile) + if err == nil { + t.Errorf("Expected error for non-existent file, got nil") + } +} + +// TestGetColor tests the GetColor function +func TestGetColor(t *testing.T) { + // Test case 1: Valid colors + testCases := map[string]string{ + "black": "\x1b[30m", + "blue": "\x1B[34m", + "red": "\x1B[31m", + "green": "\x1B[32m", + "yellow": "\x1B[33m", + "magenta": "\x1B[35m", + "cyan": "\x1B[36m", + "white": "\x1B[37m", + "bright": "\x1b[1m", + "dim": "\x1b[2m", + "reset": "\x1B[0m", + "default": "\x1B[0m", + } + + for color, expected := range testCases { + result := varuh.GetColor(color) + if result != expected { + t.Errorf("Expected %q for color %s, got %q", expected, color, result) + } + } + + // Test case 2: Invalid color (should return default) + result := varuh.GetColor("invalid") + expected := "\x1B[0m" + if result != expected { + t.Errorf("Expected default color for invalid input, got %q", result) + } + + // Test case 3: Empty string + result = varuh.GetColor("") + if result != expected { + t.Errorf("Expected default color for empty input, got %q", result) + } +} + +// TestPrettifyCardNumber tests the PrettifyCardNumber function +func TestPrettifyCardNumber(t *testing.T) { + // Test case 1: 15-digit card (Amex format) + result := varuh.PrettifyCardNumber("123456789012345") + expected := "1234 567890 12345" + if result != expected { + t.Errorf("Expected %s, got %s", expected, result) + } + + // Test case 2: 16-digit card (Standard format) + result = varuh.PrettifyCardNumber("1234567890123456") + expected = "1234 5678 9012 3456" + if result != expected { + t.Errorf("Expected %s, got %s", expected, result) + } + + // Test case 3: Card with spaces (should remove spaces) + result = varuh.PrettifyCardNumber("1234 5678 9012 3456") + expected = "1234 5678 9012 3456" + if result != expected { + t.Errorf("Expected %s, got %s", expected, result) + } + + // Test case 4: Invalid length (should return empty) + result = varuh.PrettifyCardNumber("123") + expected = "" + if result != expected { + t.Errorf("Expected empty string for invalid length, got %s", result) + } + + // Test case 5: Empty string + result = varuh.PrettifyCardNumber("") + expected = "" + if result != expected { + t.Errorf("Expected empty string, got %s", result) + } +} + +// TestValidateCvv tests the ValidateCvv function +func TestValidateCvv(t *testing.T) { + // Test case 1: Valid 3-digit CVV for non-Amex + result := varuh.ValidateCvv("123", "Visa") + if !result { + t.Errorf("Expected true for valid 3-digit CVV, got false") + } + + // Test case 2: Valid 4-digit CVV for Amex + result = varuh.ValidateCvv("1234", "American Express") + if !result { + t.Errorf("Expected true for valid 4-digit CVV for Amex, got false") + } + + // Test case 3: Invalid 3-digit CVV for Amex + result = varuh.ValidateCvv("123", "American Express") + if result { + t.Errorf("Expected false for 3-digit CVV for Amex, got true") + } + + // Test case 4: Invalid 4-digit CVV for non-Amex + result = varuh.ValidateCvv("1234", "Visa") + if result { + t.Errorf("Expected false for 4-digit CVV for non-Amex, got true") + } + + // Test case 5: Invalid CVV with letters + result = varuh.ValidateCvv("abc", "Visa") + if result { + t.Errorf("Expected false for CVV with letters, got true") + } + + // Test case 6: Empty CVV + result = varuh.ValidateCvv("", "Visa") + if result { + t.Errorf("Expected false for empty CVV, got true") + } +} + +// TestValidateCardPin tests the ValidateCardPin function +func TestValidateCardPin(t *testing.T) { + // Test case 1: Valid 4-digit PIN + result := varuh.ValidateCardPin("1234") + if !result { + t.Errorf("Expected true for valid 4-digit PIN, got false") + } + + // Test case 2: Valid 6-digit PIN + result = varuh.ValidateCardPin("123456") + if !result { + t.Errorf("Expected true for valid 6-digit PIN, got false") + } + + // Test case 3: Invalid 3-digit PIN + result = varuh.ValidateCardPin("123") + if result { + t.Errorf("Expected false for 3-digit PIN, got true") + } + + // Test case 4: Invalid PIN with letters + result = varuh.ValidateCardPin("abcd") + if result { + t.Errorf("Expected false for PIN with letters, got true") + } + + // Test case 5: Empty PIN + result = varuh.ValidateCardPin("") + if result { + t.Errorf("Expected false for empty PIN, got true") + } +} + +// TestCheckValidExpiry tests the CheckValidExpiry function +func TestCheckValidExpiry(t *testing.T) { + // Test case 1: Valid expiry date (current year) + currentYear := time.Now().Year() % 100 + validExpiry := fmt.Sprintf("12/%d", currentYear) + result := varuh.CheckValidExpiry(validExpiry) + if !result { + t.Errorf("Expected true for valid expiry %s, got false", validExpiry) + } + + // Test case 2: Valid expiry date (future year) + futureYear := currentYear + 1 + validExpiry = fmt.Sprintf("06/%d", futureYear) + result = varuh.CheckValidExpiry(validExpiry) + if !result { + t.Errorf("Expected true for valid expiry %s, got false", validExpiry) + } + + // Test case 3: Invalid month (0) + result = varuh.CheckValidExpiry("0/25") + if result { + t.Errorf("Expected false for invalid month 0, got true") + } + + // Test case 4: Invalid month (13) + result = varuh.CheckValidExpiry("13/25") + if result { + t.Errorf("Expected false for invalid month 13, got true") + } + + // Test case 5: Past year + pastYear := currentYear - 1 + result = varuh.CheckValidExpiry(fmt.Sprintf("12/%d", pastYear)) + if result { + t.Errorf("Expected false for past year, got true") + } + + // Test case 6: Invalid format (single number) + result = varuh.CheckValidExpiry("12") + if result { + t.Errorf("Expected false for invalid format, got true") + } + + // Test case 7: Invalid format (no slash) + result = varuh.CheckValidExpiry("1225") + if result { + t.Errorf("Expected false for invalid format, got true") + } + + // Test case 8: Non-numeric month + result = varuh.CheckValidExpiry("ab/25") + if result { + t.Errorf("Expected false for non-numeric month, got true") + } + + // Test case 9: Non-numeric year + result = varuh.CheckValidExpiry("12/ab") + if result { + t.Errorf("Expected false for non-numeric year, got true") + } +} + +// TestDetectCardType tests the DetectCardType function +func TestDetectCardType(t *testing.T) { + // Test case 1: Valid Visa card + cardType, err := varuh.DetectCardType("4111111111111111") + if err != nil { + t.Errorf("Expected no error for valid Visa card, got %v", err) + } + if cardType == "" { + t.Errorf("Expected card type for Visa card, got empty string") + } + + // Test case 2: Valid Mastercard + cardType, err = varuh.DetectCardType("5555555555554444") + if err != nil { + t.Errorf("Expected no error for valid Mastercard, got %v", err) + } + if cardType == "" { + t.Errorf("Expected card type for Mastercard, got empty string") + } + + // Test case 3: Valid American Express + cardType, err = varuh.DetectCardType("378282246310005") + if err != nil { + t.Errorf("Expected no error for valid Amex card, got %v", err) + } + if cardType == "" { + t.Errorf("Expected card type for Amex card, got empty string") + } + + // Test case 4: Empty card number + cardType, err = varuh.DetectCardType("") + if err == nil { + t.Errorf("Expected error for empty card number, got nil") + } + + // Test case 5: Invalid card number + cardType, err = varuh.DetectCardType("1234567890123456") + if err == nil { + t.Errorf("Expected error for invalid card number, got nil") + } +} + +// TestRandomFileName tests the RandomFileName function +func TestRandomFileName(t *testing.T) { + // Test case 1: Basic functionality + folder := "/tmp" + suffix := ".txt" + result := varuh.RandomFileName(folder, suffix) + + // Check that result contains the folder path + if !strings.HasPrefix(result, folder) { + t.Errorf("Expected result to start with %s, got %s", folder, result) + } + + // Check that result ends with suffix + if !strings.HasSuffix(result, suffix) { + t.Errorf("Expected result to end with %s, got %s", suffix, result) + } + + // Check that the filename part is 32 characters (16 bytes * 2 for hex) + baseName := filepath.Base(result) + expectedLength := len(suffix) + 32 // 32 hex chars + suffix + if len(baseName) != expectedLength { + t.Errorf("Expected filename length %d, got %d", expectedLength, len(baseName)) + } + + // Test case 2: Empty folder + result = varuh.RandomFileName("", suffix) + if result == "" { + t.Errorf("Expected non-empty result for empty folder") + } + + // Test case 3: Empty suffix + result = varuh.RandomFileName(folder, "") + if !strings.HasPrefix(result, folder) { + t.Errorf("Expected result to start with %s, got %s", folder, result) + } +} + +// TestSetShowPasswords tests the SetShowPasswords function +func TestSetShowPasswords(t *testing.T) { + err := varuh.SetShowPasswords() + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + // Note: We can't easily test the global state change without more complex setup +} + +// TestSetCopyPasswordToClipboard tests the SetCopyPasswordToClipboard function +func TestSetCopyPasswordToClipboard(t *testing.T) { + err := varuh.SetCopyPasswordToClipboard() + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + // Note: We can't easily test the global state change without more complex setup +} + +// TestSetAssumeYes tests the SetAssumeYes function +func TestSetAssumeYes(t *testing.T) { + err := varuh.SetAssumeYes() + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + // Note: We can't easily test the global state change without more complex setup +} + +// TestSetType tests the SetType function +func TestSetType(t *testing.T) { + testType := "card" + varuh.SetType(testType) + // Note: We can't easily test the global state change without more complex setup +} + +// TestCopyPasswordToClipboard tests the CopyPasswordToClipboard function +func TestCopyPasswordToClipboard(t *testing.T) { + testPassword := "test_password_123" + varuh.CopyPasswordToClipboard(testPassword) + // Note: We can't easily test clipboard functionality without more complex setup + // This test mainly ensures the function doesn't panic +} + +// TestRewriteBaseFile tests the RewriteBaseFile function +func TestRewriteBaseFile(t *testing.T) { + // Create a temporary directory for testing + tempDir := t.TempDir() + testFile := filepath.Join(tempDir, "test.txt.enc") + contents := []byte("test content") + mode := os.FileMode(0644) + + // Test case 1: Valid file creation + err, resultPath := varuh.RewriteBaseFile(testFile, contents, mode) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + + expectedPath := filepath.Join(tempDir, "test.txt") + if resultPath != expectedPath { + t.Errorf("Expected path %s, got %s", expectedPath, resultPath) + } + + // Verify file was created + if _, err := os.Stat(expectedPath); os.IsNotExist(err) { + t.Errorf("File was not created") + } + + // Verify file contents + readContents, err := os.ReadFile(expectedPath) + if err != nil { + t.Errorf("Failed to read created file: %v", err) + } + if string(readContents) != string(contents) { + t.Errorf("Expected contents %s, got %s", string(contents), string(readContents)) + } +} + +// TestRewriteFile tests the RewriteFile function +func TestRewriteFile(t *testing.T) { + // Create a temporary directory for testing + tempDir := t.TempDir() + testFile := filepath.Join(tempDir, "test.txt") + contents := []byte("test content") + mode := os.FileMode(0644) + + // Test case 1: Valid file creation + err, resultPath := varuh.RewriteFile(testFile, contents, mode) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + + if resultPath != testFile { + t.Errorf("Expected path %s, got %s", testFile, resultPath) + } + + // Verify file was created + if _, err := os.Stat(testFile); os.IsNotExist(err) { + t.Errorf("File was not created") + } + + // Verify file contents + readContents, err := os.ReadFile(testFile) + if err != nil { + t.Errorf("Failed to read created file: %v", err) + } + if string(readContents) != string(contents) { + t.Errorf("Expected contents %s, got %s", string(contents), string(readContents)) + } +} + +// TestPrintDelim tests the PrintDelim function +func TestPrintDelim(t *testing.T) { + // This function prints to stdout, so we mainly test that it doesn't panic + // We can't easily capture stdout in a unit test without complex setup + + // Test case 1: Normal delimiter + varuh.PrintDelim(">", "default") + + // Test case 2: Underscore color (should change delimiter to space) + varuh.PrintDelim(">", "underscore") + + // Test case 3: Multi-character delimiter (should take first character) + varuh.PrintDelim(">>>", "default") + + // Test case 4: Empty delimiter + varuh.PrintDelim("", "default") + + // If we get here without panicking, the test passes +} + +// TestGetOrCreateLocalConfig tests the GetOrCreateLocalConfig function +func TestGetOrCreateLocalConfig(t *testing.T) { + // Test case 1: Create config for test app + appName := "test_app_" + fmt.Sprintf("%d", time.Now().Unix()) + err, settings := varuh.GetOrCreateLocalConfig(appName) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if settings == nil { + t.Errorf("Expected settings, got nil") + } + + // Verify default settings + if settings.Cipher != "aes" { + t.Errorf("Expected cipher 'aes', got %s", settings.Cipher) + } + if !settings.AutoEncrypt { + t.Errorf("Expected AutoEncrypt to be true, got false") + } + if !settings.KeepEncrypted { + t.Errorf("Expected KeepEncrypted to be true, got false") + } + if settings.ShowPasswords { + t.Errorf("Expected ShowPasswords to be false, got true") + } + if settings.ListOrder != "id,asc" { + t.Errorf("Expected ListOrder 'id,asc', got %s", settings.ListOrder) + } + if settings.Delim != ">" { + t.Errorf("Expected Delim '>', got %s", settings.Delim) + } + if settings.Color != "default" { + t.Errorf("Expected Color 'default', got %s", settings.Color) + } + if settings.BgColor != "bgblack" { + t.Errorf("Expected BgColor 'bgblack', got %s", settings.BgColor) + } + + // Test case 2: Get existing config + err, settings2 := varuh.GetOrCreateLocalConfig(appName) + if err != nil { + t.Errorf("Expected no error for existing config, got %v", err) + } + if settings2 == nil { + t.Errorf("Expected settings for existing config, got nil") + } +} + +// TestHasActiveDatabase tests the HasActiveDatabase function +func TestHasActiveDatabase(t *testing.T) { + // This function depends on the global config and file system state + // We can't easily test it without complex setup, but we can ensure it doesn't panic + result := varuh.HasActiveDatabase() + // result should be a boolean + _ = result +} + +// TestGetActiveDatabase tests the GetActiveDatabase function +func TestGetActiveDatabase(t *testing.T) { + // This function depends on the global config and file system state + // We can't easily test it without complex setup, but we can ensure it doesn't panic + err, dbPath := varuh.GetActiveDatabase() + // The function can return nil error and empty path when no active database is configured + // This is a valid state, so we just ensure it doesn't panic + _ = err + _ = dbPath +} + +// TestUpdateActiveDbPath tests the UpdateActiveDbPath function +func TestUpdateActiveDbPath(t *testing.T) { + // This function depends on the global config and file system state + // We can't easily test it without complex setup, but we can ensure it doesn't panic + testPath := "/tmp/test.db" + err := varuh.UpdateActiveDbPath(testPath) + // We expect either an error or success + _ = err +} diff --git a/utils.go b/utils.go index 4289065..a8d525b 100644 --- a/utils.go +++ b/utils.go @@ -1,5 +1,5 @@ // Utility functions -package main +package varuh import ( "bufio" @@ -51,7 +51,7 @@ type Settings struct { } // Global settings override -var settingsRider SettingsOverride +var SettingsRider SettingsOverride // Map a function to an array of strings func MapString(vs []string, f func(string) string) []string { @@ -63,7 +63,7 @@ func MapString(vs []string, f func(string) string) []string { } // Print a secret -func hideSecret(secret string) string { +func HideSecret(secret string) string { var stars []string for i := 0; i < len(secret); i++ { @@ -74,7 +74,7 @@ func hideSecret(secret string) string { } // Write settings to disk -func writeSettings(settings *Settings, configFile string) error { +func WriteSettings(settings *Settings, configFile string) error { fh, err := os.Create(configFile) if err != nil { @@ -92,7 +92,7 @@ func writeSettings(settings *Settings, configFile string) error { } // Write updated settings to disk -func updateSettings(settings *Settings, configFile string) error { +func UpdateSettings(settings *Settings, configFile string) error { fh, err := os.OpenFile(configFile, os.O_RDWR, 0644) if err != nil { @@ -115,7 +115,7 @@ func updateSettings(settings *Settings, configFile string) error { } // Make the per-user configuration folder and return local settings -func getOrCreateLocalConfig(app string) (error, *Settings) { +func GetOrCreateLocalConfig(app string) (error, *Settings) { var settings Settings var configPath string @@ -150,7 +150,7 @@ func getOrCreateLocalConfig(app string) (error, *Settings) { // fmt.Printf("Creating default configuration ...") settings = Settings{"", "aes", true, true, false, configFile, "id,asc", ">", "default", "bgblack"} - if err = writeSettings(&settings, configFile); err == nil { + if err = WriteSettings(&settings, configFile); err == nil { // fmt.Println(" ...done") } else { return err, nil @@ -161,12 +161,12 @@ func getOrCreateLocalConfig(app string) (error, *Settings) { } // Return if there is an active, decrypted database -func hasActiveDatabase() bool { +func HasActiveDatabase() bool { - err, settings := getOrCreateLocalConfig(APP) + err, settings := GetOrCreateLocalConfig(APP) if err == nil && settings.ActiveDB != "" { if _, err := os.Stat(settings.ActiveDB); err == nil { - if _, flag := isFileEncrypted(settings.ActiveDB); !flag { + if _, flag := IsFileEncrypted(settings.ActiveDB); !flag { return true } return false @@ -181,9 +181,9 @@ func hasActiveDatabase() bool { } // Get the current active database -func getActiveDatabase() (error, string) { +func GetActiveDatabase() (error, string) { - err, settings := getOrCreateLocalConfig(APP) + err, settings := GetOrCreateLocalConfig(APP) if err == nil && settings.ActiveDB != "" { if _, err := os.Stat(settings.ActiveDB); err == nil { return nil, settings.ActiveDB @@ -198,20 +198,20 @@ func getActiveDatabase() (error, string) { } // Update the active db path -func updateActiveDbPath(dbPath string) error { +func UpdateActiveDbPath(dbPath string) error { - _, settings := getOrCreateLocalConfig(APP) + _, settings := GetOrCreateLocalConfig(APP) if settings != nil { settings.ActiveDB = dbPath } - return updateSettings(settings, settings.ConfigPath) + return UpdateSettings(settings, settings.ConfigPath) } // Read the password from console without echoing -func readPassword() (error, string) { +func ReadPassword() (error, string) { var passwd []byte var err error @@ -221,7 +221,7 @@ func readPassword() (error, string) { } // Rewrite the contents of the base file (path minus extension) with the new contents -func rewriteBaseFile(path string, contents []byte, mode fs.FileMode) (error, string) { +func RewriteBaseFile(path string, contents []byte, mode fs.FileMode) (error, string) { var err error var origFile string @@ -239,7 +239,7 @@ func rewriteBaseFile(path string, contents []byte, mode fs.FileMode) (error, str } // Rewrite the contents of the file with the new contents -func rewriteFile(path string, contents []byte, mode fs.FileMode) (error, string) { +func RewriteFile(path string, contents []byte, mode fs.FileMode) (error, string) { var err error @@ -255,7 +255,7 @@ func rewriteFile(path string, contents []byte, mode fs.FileMode) (error, string) } // Get color codes for console colors -func getColor(code string) string { +func GetColor(code string) string { colors := map[string]string{ "black": "\x1b[30m", @@ -300,7 +300,7 @@ func getColor(code string) string { } // Print the delimiter line for listings -func printDelim(delimChar string, color string) { +func PrintDelim(delimChar string, color string) { var delims []string @@ -321,7 +321,7 @@ func printDelim(delimChar string, color string) { } // Prettify credit/debit card numbers -func prettifyCardNumber(cardNumber string) string { +func PrettifyCardNumber(cardNumber string) string { // Amex cards are 15 digits - group as 4, 6, 5 // Any 16 digits - group as 4/4/4/4 @@ -344,24 +344,24 @@ func prettifyCardNumber(cardNumber string) string { } // Print a card entry to the console -func printCardEntry(entry *Entry, settings *Settings, delim bool) error { +func PrintCardEntry(entry *Entry, settings *Settings, delim bool) error { var customEntries []ExtendedEntry - fmt.Printf("%s", getColor(strings.ToLower(settings.Color))) + fmt.Printf("%s", GetColor(strings.ToLower(settings.Color))) if strings.HasPrefix(settings.BgColor, "bg") { - fmt.Printf("%s", getColor(strings.ToLower(settings.BgColor))) + fmt.Printf("%s", GetColor(strings.ToLower(settings.BgColor))) } if delim { - printDelim(settings.Delim, settings.Color) + PrintDelim(settings.Delim, settings.Color) } fmt.Printf("[Type: card]\n") fmt.Printf("ID: %d\n", entry.ID) fmt.Printf("Card Name: %s\n", entry.Title) fmt.Printf("Card Holder: %s\n", entry.User) - fmt.Printf("Card Number: %s\n", prettifyCardNumber(entry.Url)) + fmt.Printf("Card Number: %s\n", PrettifyCardNumber(entry.Url)) fmt.Printf("Card Type: %s\n", entry.Class) if entry.Issuer != "" { @@ -373,7 +373,7 @@ func printCardEntry(entry *Entry, settings *Settings, delim bool) error { passwd := strings.TrimSpace(entry.Password) pin := strings.TrimSpace(entry.Pin) - if settings.ShowPasswords || settingsRider.ShowPasswords { + if settings.ShowPasswords || SettingsRider.ShowPasswords { if len(passwd) > 0 { fmt.Printf("Card CVV: %s\n", passwd) @@ -384,10 +384,10 @@ func printCardEntry(entry *Entry, settings *Settings, delim bool) error { } else { if len(passwd) > 0 { - fmt.Printf("Card CVV: %s\n", hideSecret(passwd)) + fmt.Printf("Card CVV: %s\n", HideSecret(passwd)) } if len(pin) > 0 { - fmt.Printf("Card PIN: %s\n", hideSecret(passwd)) + fmt.Printf("Card PIN: %s\n", HideSecret(passwd)) } } @@ -398,7 +398,7 @@ func printCardEntry(entry *Entry, settings *Settings, delim bool) error { fmt.Printf("Notes: %s\n", entry.Notes) } // Query extended entries - customEntries = getExtendedEntries(entry) + customEntries = GetExtendedEntries(entry) if len(customEntries) > 0 { for _, customEntry := range customEntries { fmt.Printf("%s: %s\n", customEntry.FieldName, customEntry.FieldValue) @@ -406,22 +406,22 @@ func printCardEntry(entry *Entry, settings *Settings, delim bool) error { } fmt.Printf("Modified: %s\n", entry.Timestamp.Format("2006-01-02 15:04:05")) - printDelim(settings.Delim, settings.Color) + PrintDelim(settings.Delim, settings.Color) // Reset - fmt.Printf("%s", getColor("default")) + fmt.Printf("%s", GetColor("default")) return nil } // Print an entry to the console -func printEntry(entry *Entry, delim bool) error { +func PrintEntry(entry *Entry, delim bool) error { var err error var settings *Settings var customEntries []ExtendedEntry - err, settings = getOrCreateLocalConfig(APP) + err, settings = GetOrCreateLocalConfig(APP) if err != nil { fmt.Printf("Error parsing config - \"%s\"\n", err.Error()) @@ -429,16 +429,16 @@ func printEntry(entry *Entry, delim bool) error { } if entry.Type == "card" { - return printCardEntry(entry, settings, delim) + return PrintCardEntry(entry, settings, delim) } - fmt.Printf("%s", getColor(strings.ToLower(settings.Color))) + fmt.Printf("%s", GetColor(strings.ToLower(settings.Color))) if strings.HasPrefix(settings.BgColor, "bg") { - fmt.Printf("%s", getColor(strings.ToLower(settings.BgColor))) + fmt.Printf("%s", GetColor(strings.ToLower(settings.BgColor))) } if delim { - printDelim(settings.Delim, settings.Color) + PrintDelim(settings.Delim, settings.Color) } fmt.Printf("[Type: password]\n") @@ -447,10 +447,10 @@ func printEntry(entry *Entry, delim bool) error { fmt.Printf("User: %s\n", entry.User) fmt.Printf("URL: %s\n", entry.Url) - if settings.ShowPasswords || settingsRider.ShowPasswords { + if settings.ShowPasswords || SettingsRider.ShowPasswords { fmt.Printf("Password: %s\n", entry.Password) } else { - fmt.Printf("Password: %s\n", hideSecret(entry.Password)) + fmt.Printf("Password: %s\n", HideSecret(entry.Password)) } if len(entry.Tags) > 0 { @@ -460,7 +460,7 @@ func printEntry(entry *Entry, delim bool) error { fmt.Printf("Notes: %s\n", entry.Notes) } // Query extended entries - customEntries = getExtendedEntries(entry) + customEntries = GetExtendedEntries(entry) if len(customEntries) > 0 { for _, customEntry := range customEntries { @@ -470,35 +470,35 @@ func printEntry(entry *Entry, delim bool) error { fmt.Printf("Modified: %s\n", entry.Timestamp.Format("2006-01-02 15:04:05")) - printDelim(settings.Delim, settings.Color) + PrintDelim(settings.Delim, settings.Color) // Reset - fmt.Printf("%s", getColor("default")) + fmt.Printf("%s", GetColor("default")) return nil } // Print an entry to the console with minimal data -func printEntryMinimal(entry *Entry, delim bool) error { +func PrintEntryMinimal(entry *Entry, delim bool) error { var err error var settings *Settings - err, settings = getOrCreateLocalConfig(APP) + err, settings = GetOrCreateLocalConfig(APP) if err != nil { fmt.Printf("Error parsing config - \"%s\"\n", err.Error()) return err } - fmt.Printf("%s", getColor(strings.ToLower(settings.Color))) + fmt.Printf("%s", GetColor(strings.ToLower(settings.Color))) if strings.HasPrefix(settings.BgColor, "bg") { - fmt.Printf("%s", getColor(strings.ToLower(settings.BgColor))) + fmt.Printf("%s", GetColor(strings.ToLower(settings.BgColor))) } if delim { - printDelim(settings.Delim, settings.Color) + PrintDelim(settings.Delim, settings.Color) } fmt.Printf("Title: %s\n", entry.Title) @@ -506,10 +506,10 @@ func printEntryMinimal(entry *Entry, delim bool) error { fmt.Printf("URL: %s\n", entry.Url) fmt.Printf("Modified: %s\n", entry.Timestamp.Format("2006-01-02 15:04:05")) - printDelim(settings.Delim, settings.Color) + PrintDelim(settings.Delim, settings.Color) // Reset - fmt.Printf("%s", getColor("default")) + fmt.Printf("%s", GetColor("default")) return nil @@ -528,7 +528,7 @@ func readInput(reader *bufio.Reader, prompt string) string { // Check for an active, decrypted database func checkActiveDatabase() error { - if !hasActiveDatabase() { + if !HasActiveDatabase() { fmt.Printf("No decrypted active database found.\n") return errors.New("no active database") } @@ -539,10 +539,10 @@ func checkActiveDatabase() error { // Return true if active database is encrypted func isActiveDatabaseEncrypted() bool { - err, settings := getOrCreateLocalConfig(APP) + err, settings := GetOrCreateLocalConfig(APP) if err == nil && settings.ActiveDB != "" { if _, err := os.Stat(settings.ActiveDB); err == nil { - if _, flag := isFileEncrypted(settings.ActiveDB); flag { + if _, flag := IsFileEncrypted(settings.ActiveDB); flag { return true } } @@ -554,17 +554,17 @@ func isActiveDatabaseEncrypted() bool { // Return true if always encrypt is on func isEncryptOn() bool { - _, settings := getOrCreateLocalConfig(APP) + _, settings := GetOrCreateLocalConfig(APP) return settings.KeepEncrypted } // Combination of above 2 logic plus auto encryption on (a play on CryptOn) func isActiveDatabaseEncryptedAndMaxKryptOn() (bool, string) { - err, settings := getOrCreateLocalConfig(APP) + err, settings := GetOrCreateLocalConfig(APP) if err == nil && settings.ActiveDB != "" { if _, err := os.Stat(settings.ActiveDB); err == nil { - if _, flag := isFileEncrypted(settings.ActiveDB); flag && settings.KeepEncrypted && settings.AutoEncrypt { + if _, flag := IsFileEncrypted(settings.ActiveDB); flag && settings.KeepEncrypted && settings.AutoEncrypt { return true, settings.ActiveDB } } @@ -574,40 +574,44 @@ func isActiveDatabaseEncryptedAndMaxKryptOn() (bool, string) { } // (Temporarily) enable showing of passwords -func setShowPasswords() error { +func SetShowPasswords() error { // fmt.Printf("Setting show passwords to true\n") - settingsRider.ShowPasswords = true + SettingsRider.ShowPasswords = true return nil } // Copy the password to clipboard - only for single listings or single search results -func setCopyPasswordToClipboard() error { - settingsRider.CopyPassword = true +func SetCopyPasswordToClipboard() error { + SettingsRider.CopyPassword = true return nil } -func setAssumeYes() error { - settingsRider.AssumeYes = true +func SetAssumeYes() error { + SettingsRider.AssumeYes = true return nil } -func setType(_type string) { - settingsRider.Type = _type +func SetType(_type string) { + SettingsRider.Type = _type } -func copyPasswordToClipboard(passwd string) { +func CopyPasswordToClipboard(passwd string) { clipboard.WriteAll(passwd) } // Generate a random file name -func randomFileName(folder string, suffix string) string { +func RandomFileName(folder string, suffix string) string { - _, name := generateRandomBytes(16) + _, name := GenerateRandomBytes(16) return filepath.Join(folder, hex.EncodeToString(name)+suffix) } // Detect card type from card number -func detectCardType(cardNum string) (string, error) { +func DetectCardType(cardNum string) (string, error) { + // Handle empty or invalid input + if cardNum == "" { + return "", errors.New("card number cannot be empty") + } var cardTypeIndex creditcard.CardType var err error @@ -629,7 +633,7 @@ func detectCardType(cardNum string) (string, error) { } // Validate CVV -func validateCvv(cardCvv string, cardClass string) bool { +func ValidateCvv(cardCvv string, cardClass string) bool { var matched bool @@ -647,7 +651,7 @@ func validateCvv(cardCvv string, cardClass string) bool { return false } -func validateCardPin(cardPin string) bool { +func ValidateCardPin(cardPin string) bool { // A PIN is 4 digits or more if matched, _ := regexp.Match(`^\d{4,}$`, []byte(cardPin)); matched { @@ -658,7 +662,7 @@ func validateCardPin(cardPin string) bool { } // Verify if the expiry date is in the form mm/dd -func checkValidExpiry(expiryDate string) bool { +func CheckValidExpiry(expiryDate string) bool { pieces := strings.Split(expiryDate, "/") @@ -670,12 +674,12 @@ func checkValidExpiry(expiryDate string) bool { month, err = strconv.Atoi(pieces[0]) if err != nil { - fmt.Printf("Error parsing month: %s: \"%s\"\n", month, err.Error()) + fmt.Printf("Error parsing month: %d: \"%s\"\n", month, err.Error()) return false } year, err = strconv.Atoi(pieces[1]) if err != nil { - fmt.Printf("Error parsing year: %s: \"%s\"\n", year, err.Error()) + fmt.Printf("Error parsing year: %d: \"%s\"\n", year, err.Error()) return false }