Check-in [d12f13c977]
Overview
SHA1:d12f13c9776d4b5d96bd544188013f1aeb985a3c
Date: 2016-03-15 18:28:46
User: rkeene
Comment:Added verification system so that passwords are only updated if they currently contain a valid password
Timelines: family | ancestors | descendants | both | trunk
Downloads: Tarball | ZIP archive
Other Links: files | file ages | folders | manifest
Tags And Properties
Context
2016-03-15
18:32
[92d30d85bb] Added a "whoami" command to determine the current users (user: rkeene, tags: trunk)
18:28
[d12f13c977] Added verification system so that passwords are only updated if they currently contain a valid password (user: rkeene, tags: trunk)
2016-03-11
21:45
[47680a1e16] Added README (user: rkeene, tags: trunk)
Changes

Modified build/pre.sh from [7d2c051364] to [7789bf77c1].

    16     16   
    17     17   rm -rf lib
    18     18   
    19     19   if [ "$1" = 'clean' -o "$1" = 'distclean' ]; then
    20     20   	exit 0
    21     21   fi
    22     22   
    23         -"${TCLKIT:-tclkit}" "${ourdir}/teapot-client.kit" get . tcl tcl pki aes
           23  +"${TCLKIT:-tclkit}" "${ourdir}/teapot-client.kit" get . tcl tcl pki aes sha256
    24     24   
    25     25   for platform in linux-ix86 linux-x86_64 macosx-ix86 macosx-x86_64 win32-ix86 win32-x86_64; do
    26     26   	dl_platform="${platform}"
    27     27   	vers='0.9.9'
    28     28   	case "${platform}" in
    29     29   		macosx-*)
    30     30   			vers='0.9.6'

Modified hunter2 from [7cb1f824eb] to [e039ea66c1].

   100    100   lappend ::auto_path [file join [file dirname [info script]] lib [platform::identify]]
   101    101   lappend ::auto_path [file join [file dirname [info script]] lib [platform::generic]]
   102    102   lappend ::auto_path [file join [file dirname [info script]] lib]
   103    103   
   104    104   package require pki
   105    105   package require pki::pkcs11
   106    106   package require aes
          107  +package require sha256
   107    108   
   108    109   # Backports for older versions of "pki"
   109    110   proc ::pki::pkcs::parse_public_key {key {password ""}} {
   110    111           array set parsed_key [::pki::_parse_pem $key "-----BEGIN PUBLIC KEY-----" "-----END PUBLIC KEY-----" $password]
   111    112   
   112    113           set key_seq $parsed_key(data)
   113    114   
................................................................................
   203    204   
   204    205   			lappend slotInfo [list handle $handle id $slotID prompt $slotPromptForPIN cert $cert pubkey $pubkey]
   205    206   		}
   206    207   	}
   207    208   
   208    209   	return $slotInfo
   209    210   }
          211  +
          212  +proc _verifyPassword {name password} {
          213  +	set publicKeys [list]
          214  +
          215  +	db eval {SELECT publicKey, verification FROM passwords WHERE name = $name} row {
          216  +		set salt [dict get $row(verification) salt]
          217  +		set hashAlgorithm [dict get $row(verification) hashAlgorithm]
          218  +		set publicKey $row(publicKey)
          219  +
          220  +		set plaintext "${salt}|${publicKey}|${password}"
          221  +
          222  +		switch -- $hashAlgorithm {
          223  +			"sha256" {
          224  +				set verificationHash [sha2::sha256 -hex -- $plaintext]
          225  +			}
          226  +			default {
          227  +				return -code error "Unknown hashing algorithm: $hashAlgorithm"
          228  +			}
          229  +		}
          230  +
          231  +		set row(verificationHash) [dict get $row(verification) hash]
          232  +
          233  +		if {$verificationHash ne $row(verificationHash)} {
          234  +			puts stderr "FAILED: verification failed for $name with public key $publicKey -- it will not get the new password."
          235  +
          236  +			continue
          237  +		}
          238  +
          239  +		lappend publicKeys $publicKey
          240  +	}
          241  +
          242  +	return $publicKeys
          243  +}
   210    244   
   211    245   proc _addPassword {name password publicKeys} {
   212    246   	set fd [open "/dev/urandom" r]
   213    247   	fconfigure $fd -translation binary
   214    248   
   215         -	db eval {DELETE FROM passwords WHERE name = $name;}
   216         -
   217         -	foreach publicKey $publicKeys {
   218         -		set key [read $fd 16]
   219         -		if {[string length $key] != 16} {
   220         -			close $fd
   221         -
   222         -			return -code error "ERROR: Short read from random device"
   223         -		}
   224         -
   225         -		set publicKeyItem [::pki::pkcs::parse_public_key [binary decode base64 $publicKey]]
   226         -
   227         -		set encryptedKey [binary encode base64 [::pki::encrypt -pub -binary -- $key $publicKeyItem]]
   228         -
   229         -		set encryptedPass [binary encode base64 [::aes::aes -dir encrypt -key $key -- $password]]
   230         -
   231         -		db eval {INSERT INTO passwords (name, encryptedPass, encryptedKey, publicKey) VALUES ($name, @encryptedPass, @encryptedKey, @publicKey);}
          249  +	set keySize 16
          250  +
          251  +	# Pad the password with 0 bytes until it is a multiple of the key size
          252  +	set blockPassword $password
          253  +	append blockPassword [string repeat "\x00" [expr {-[string length $password] % $keySize}]]
          254  +
          255  +	db transaction {
          256  +		db eval {DELETE FROM passwords WHERE name = $name;}
          257  +
          258  +		foreach publicKey $publicKeys {
          259  +			set key [read $fd $keySize]
          260  +			if {[string length $key] != $keySize} {
          261  +				close $fd
          262  +
          263  +				return -code error "ERROR: Short read from random device"
          264  +			}
          265  +
          266  +			set salt [read $fd $keySize]
          267  +			set salt [binary encode base64 $salt]
          268  +
          269  +			set publicKeyItem [::pki::pkcs::parse_public_key [binary decode base64 $publicKey]]
          270  +
          271  +			set encryptedKey [binary encode base64 [::pki::encrypt -pub -binary -- $key $publicKeyItem]]
          272  +
          273  +			set encryptedPass [binary encode base64 [::aes::aes -dir encrypt -key $key -- $blockPassword]]
          274  +
          275  +			set verificationHash [sha2::sha256 -hex -- "${salt}|${publicKey}|${password}"]
          276  +			set verification [list salt $salt hashAlgorithm sha256 hash $verificationHash]
          277  +
          278  +			db eval {INSERT INTO passwords (name, encryptedPass, encryptedKey, publicKey, verification) VALUES ($name, @encryptedPass, @encryptedKey, @publicKey, @verification);}
          279  +		}
   232    280   	}
   233    281   
   234    282   	close $fd
   235    283   }
   236    284   
   237    285   proc _prompt {prompt} {
   238    286   	puts -nonewline $prompt
................................................................................
   277    325   			set prompted($slotInfo(id)) 1
   278    326   		}
   279    327   
   280    328   		db eval {SELECT encryptedPass, encryptedKey FROM passwords WHERE name = $name AND publicKey = $pubkey;} row {
   281    329   			set key [::pki::decrypt -binary -priv -- [binary decode base64 $row(encryptedKey)] $slotInfo(cert)]
   282    330   			set password [::aes::aes -dir decrypt -key $key -- [binary decode base64 $row(encryptedPass)]]
   283    331   
   284         -			return $password
          332  +			return [string trimright $password "\x00"]
   285    333   		}
   286    334   	}
   287    335   
   288    336   	return -code error "No valid keys"
   289    337   }
   290    338   
   291    339   proc _modifyPublicKeys {passwordName userNames sql} {
................................................................................
   452    500   }
   453    501   
   454    502   proc updatePassword {passwordName password} {
   455    503   	if {$password eq ""} {
   456    504   		set password [_prompt "Please enter the new password: "]
   457    505   	}
   458    506   
   459         -	db eval {SELECT publicKey FROM passwords WHERE name = $passwordName;} row {
   460         -		lappend publicKeys $row(publicKey)
          507  +	set oldPassword [_getPassword $passwordName]
          508  +
          509  +	set publicKeys [_verifyPassword $passwordName $oldPassword]
          510  +
          511  +	if {[llength $publicKeys] == 0} {
          512  +		puts stderr "Warning: This will delete the password since there are no valid public keys."
   461    513   	}
   462    514   
   463    515   	_addPassword $passwordName $password $publicKeys
   464    516   }
   465    517   
   466    518   proc deletePassword {passwordName} {
   467    519   	db eval {DELETE FROM passwords WHERE name = $passwordName;}
................................................................................
   511    563   
   512    564   ### MAIN
   513    565   
   514    566   sqlite3 db $passwordFile
   515    567   
   516    568   db eval {
   517    569   	CREATE TABLE IF NOT EXISTS users(name, publicKey BLOB);
   518         -	CREATE TABLE IF NOT EXISTS passwords(name, encryptedPass BLOB, encryptedKey BLOB, publicKey BLOB);
          570  +	CREATE TABLE IF NOT EXISTS passwords(name, encryptedPass BLOB, encryptedKey BLOB, publicKey BLOB, verification BLOB);
   519    571   }
   520    572   
   521    573   if {$action in $validCommands} {
   522    574   	if {[catch {
   523    575   		$action {*}$argv
   524    576   	} error]} {
   525    577   		puts stderr "Error: $error"