@@ -102,10 +102,11 @@ lappend ::auto_path [file join [file dirname [info script]] lib] package require pki package require pki::pkcs11 package require aes +package require sha256 # Backports for older versions of "pki" proc ::pki::pkcs::parse_public_key {key {password ""}} { array set parsed_key [::pki::_parse_pem $key "-----BEGIN PUBLIC KEY-----" "-----END PUBLIC KEY-----" $password] @@ -205,32 +206,79 @@ } } return $slotInfo } + +proc _verifyPassword {name password} { + set publicKeys [list] + + db eval {SELECT publicKey, verification FROM passwords WHERE name = $name} row { + set salt [dict get $row(verification) salt] + set hashAlgorithm [dict get $row(verification) hashAlgorithm] + set publicKey $row(publicKey) + + set plaintext "${salt}|${publicKey}|${password}" + + switch -- $hashAlgorithm { + "sha256" { + set verificationHash [sha2::sha256 -hex -- $plaintext] + } + default { + return -code error "Unknown hashing algorithm: $hashAlgorithm" + } + } + + set row(verificationHash) [dict get $row(verification) hash] + + if {$verificationHash ne $row(verificationHash)} { + puts stderr "FAILED: verification failed for $name with public key $publicKey -- it will not get the new password." + + continue + } + + lappend publicKeys $publicKey + } + + return $publicKeys +} proc _addPassword {name password publicKeys} { set fd [open "/dev/urandom" r] fconfigure $fd -translation binary - db eval {DELETE FROM passwords WHERE name = $name;} - - foreach publicKey $publicKeys { - set key [read $fd 16] - if {[string length $key] != 16} { - close $fd - - return -code error "ERROR: Short read from random device" - } - - set publicKeyItem [::pki::pkcs::parse_public_key [binary decode base64 $publicKey]] - - set encryptedKey [binary encode base64 [::pki::encrypt -pub -binary -- $key $publicKeyItem]] - - set encryptedPass [binary encode base64 [::aes::aes -dir encrypt -key $key -- $password]] - - db eval {INSERT INTO passwords (name, encryptedPass, encryptedKey, publicKey) VALUES ($name, @encryptedPass, @encryptedKey, @publicKey);} + set keySize 16 + + # Pad the password with 0 bytes until it is a multiple of the key size + set blockPassword $password + append blockPassword [string repeat "\x00" [expr {-[string length $password] % $keySize}]] + + db transaction { + db eval {DELETE FROM passwords WHERE name = $name;} + + foreach publicKey $publicKeys { + set key [read $fd $keySize] + if {[string length $key] != $keySize} { + close $fd + + return -code error "ERROR: Short read from random device" + } + + set salt [read $fd $keySize] + set salt [binary encode base64 $salt] + + set publicKeyItem [::pki::pkcs::parse_public_key [binary decode base64 $publicKey]] + + set encryptedKey [binary encode base64 [::pki::encrypt -pub -binary -- $key $publicKeyItem]] + + set encryptedPass [binary encode base64 [::aes::aes -dir encrypt -key $key -- $blockPassword]] + + set verificationHash [sha2::sha256 -hex -- "${salt}|${publicKey}|${password}"] + set verification [list salt $salt hashAlgorithm sha256 hash $verificationHash] + + db eval {INSERT INTO passwords (name, encryptedPass, encryptedKey, publicKey, verification) VALUES ($name, @encryptedPass, @encryptedKey, @publicKey, @verification);} + } } close $fd } @@ -279,11 +327,11 @@ db eval {SELECT encryptedPass, encryptedKey FROM passwords WHERE name = $name AND publicKey = $pubkey;} row { set key [::pki::decrypt -binary -priv -- [binary decode base64 $row(encryptedKey)] $slotInfo(cert)] set password [::aes::aes -dir decrypt -key $key -- [binary decode base64 $row(encryptedPass)]] - return $password + return [string trimright $password "\x00"] } } return -code error "No valid keys" } @@ -454,12 +502,16 @@ proc updatePassword {passwordName password} { if {$password eq ""} { set password [_prompt "Please enter the new password: "] } - db eval {SELECT publicKey FROM passwords WHERE name = $passwordName;} row { - lappend publicKeys $row(publicKey) + set oldPassword [_getPassword $passwordName] + + set publicKeys [_verifyPassword $passwordName $oldPassword] + + if {[llength $publicKeys] == 0} { + puts stderr "Warning: This will delete the password since there are no valid public keys." } _addPassword $passwordName $password $publicKeys } @@ -513,11 +565,11 @@ sqlite3 db $passwordFile db eval { CREATE TABLE IF NOT EXISTS users(name, publicKey BLOB); - CREATE TABLE IF NOT EXISTS passwords(name, encryptedPass BLOB, encryptedKey BLOB, publicKey BLOB); + CREATE TABLE IF NOT EXISTS passwords(name, encryptedPass BLOB, encryptedKey BLOB, publicKey BLOB, verification BLOB); } if {$action in $validCommands} { if {[catch { $action {*}$argv