Check-in [19e06f8554]
Overview
SHA1:19e06f85548e5707a897e994575429e507349c71
Date: 2016-03-11 20:32:50
User: rkeene
Comment:Added initial version of "hunter2"
Timelines: family | ancestors | descendants | both | trunk
Downloads: Tarball | ZIP archive
Other Links: files | file ages | folders | manifest
Tags And Properties
Context
2016-03-11
20:43
[5b160acdfd] Added license (user: rkeene, tags: trunk)
20:32
[19e06f8554] Added initial version of "hunter2" (user: rkeene, tags: trunk)
2016-03-10
14:13
[01f8fbb7df] initial empty check-in (user: rkeene, tags: trunk)
Changes

Added .fossil-settings/ignore-glob version [376456435d].

            1  +lib

Added build/extra/icon-small-rotated.png version [af607609c9].

cannot compute difference between binary files

Added build/extra/logo.xcf version [826e160ad5].

cannot compute difference between binary files

Added build/pre.sh version [2833e223c2].

            1  +#! /usr/bin/env bash
            2  +
            3  +ourscp="$(which "$0")" || exit 1
            4  +ourdir="$(cd -- "$(dirname "${ourscp}")" && pwd)" || exit 1
            5  +
            6  +if [ ! -f "${ourdir}/teapot-client.kit" ]; then
            7  +	echo "Internal error." >&2
            8  +
            9  +	exit 1
           10  +fi
           11  +
           12  +set -x
           13  +set -e
           14  +
           15  +cd "${ourdir}/.."
           16  +
           17  +rm -rf lib
           18  +"${TCLKIT:-tclkit}" "${ourdir}/teapot-client.kit" get . tcl tcl pki aes
           19  +
           20  +for platform in linux-ix86 linux-x86_64 macosx-ix86 macosx-x86_64 win32-ix86 win32-x86_64; do
           21  +	dl_platform="${platform}"
           22  +	vers='0.9.9'
           23  +	case "${platform}" in
           24  +		macosx-*)
           25  +			vers='0.9.6'
           26  +			;;
           27  +		linux-ix86)
           28  +			dl_platform='linux-glibc2.1-ix86'
           29  +			;;
           30  +		linux-x86_64)
           31  +			dl_platform='linux-glibc2.2-x86_64'
           32  +			;;
           33  +	esac
           34  +
           35  +	dir="lib/$platform/pkiPkcs11-${vers}"
           36  +	file="${dir}/file.zip"
           37  +	url="http://teapot.rkeene.org/package/name/pki::pkcs11/ver/${vers}/arch/${dl_platform}/file.zip"
           38  +
           39  +	mkdir -p "${dir}"
           40  +
           41  +	wget -O "${file}" "${url}" || \
           42  +		curl "${url}" > "${file}" || \
           43  +		rm -f "${file}"
           44  +
           45  +	(
           46  +		cd "${dir}" || exit 1
           47  +		unzip file.zip || exit 1
           48  +	)
           49  +
           50  +	sed -i 's@file join  @file join $dir @' lib/*/pkiPkcs11-*/pkgIndex.tcl
           51  +
           52  +	rm -f "${file}"
           53  +done

Added build/teapot-client.kit version [311afdef38].

cannot compute difference between binary files

Added hunter2 version [195a50ba5e].

            1  +#! /usr/bin/env tclsh
            2  +
            3  +set passwordFile [lindex $argv 0]
            4  +set action [lindex $argv 1]
            5  +
            6  +set validCommands [list "listLocalKeys" "listPasswords" "listAvailablePasswords" "listUsers" "addUser" "addPassword" "authorizeUser" "authorizeUsers" "deauthorizeUser" "deauthorizeUsers" "getPassword" "updatePassword" "deletePassword" "help"]
            7  +
            8  +proc _argDescription {command argName} {
            9  +	switch -- $argName {
           10  +		"passwordName" {
           11  +			return "$argName - Name of the password entry"
           12  +		}
           13  +		"key" {
           14  +			return "$argName - Public key of the user"
           15  +		}
           16  +		"password" {
           17  +			return "$argName - A plain-text password"
           18  +		}
           19  +		"userName" {
           20  +			return "$argName - A user name"
           21  +		}
           22  +		"action" {
           23  +			return "$argName - An action name for help with"
           24  +		}
           25  +		"args" {
           26  +			return "userList - A list of usernames"
           27  +		}
           28  +	}
           29  +
           30  +	return "<UNKNOWN>"
           31  +}
           32  +
           33  +proc _printHelp {channel command} {
           34  +	if {$command == ""} {
           35  +		puts $channel "Usage: hunter2 <passwordFile> <action> \[<actionArgs...>\]"
           36  +		puts $channel ""
           37  +		puts $channel "Actions:"
           38  +		puts $channel "    [join $::validCommands {, }]"
           39  +		puts $channel ""
           40  +		puts $channel "    hunter2 <file> help <action>    for help with an action"
           41  +	} else {
           42  +		set args [info args $command]
           43  +		set printArgs [list]
           44  +		foreach arg $args {
           45  +			if {$arg == "args"} {
           46  +				set arg "userList"
           47  +			}
           48  +			lappend printArgs "<$arg>"
           49  +		}
           50  +
           51  +		puts $channel "Usage: hunter2 <passwordFile> $command [join $printArgs]"
           52  +
           53  +		if {[llength $args] > 0} {
           54  +			puts $channel ""
           55  +			puts $channel "Arguments:"
           56  +			foreach arg $args {
           57  +				puts $channel "    [_argDescription $command $arg]"
           58  +			}
           59  +		}
           60  +	}
           61  +}
           62  +
           63  +if {[llength $argv] < 2} {
           64  +	_printHelp stderr ""
           65  +
           66  +	exit 1
           67  +}
           68  +
           69  +set argv [lrange $argv 2 end]
           70  +
           71  +package require sqlite3
           72  +package require platform
           73  +
           74  +lappend ::auto_path [file join [file dirname [info script]] lib [platform::identify]]
           75  +lappend ::auto_path [file join [file dirname [info script]] lib [platform::generic]]
           76  +lappend ::auto_path [file join [file dirname [info script]] lib]
           77  +
           78  +package require pki
           79  +package require pki::pkcs11
           80  +package require aes
           81  +
           82  +# Backports for older versions of "pki"
           83  +proc ::pki::pkcs::parse_public_key {key {password ""}} {
           84  +        array set parsed_key [::pki::_parse_pem $key "-----BEGIN PUBLIC KEY-----" "-----END PUBLIC KEY-----" $password]
           85  +
           86  +        set key_seq $parsed_key(data)
           87  +
           88  +        ::asn::asnGetSequence key_seq pubkeyinfo
           89  +                ::asn::asnGetSequence pubkeyinfo pubkey_algoid
           90  +                        ::asn::asnGetObjectIdentifier pubkey_algoid oid
           91  +                ::asn::asnGetBitString pubkeyinfo pubkey
           92  +        set ret(pubkey_algo) [::pki::_oid_number_to_name $oid]
           93  +
           94  +        switch -- $ret(pubkey_algo) {
           95  +                "rsaEncryption" {
           96  +                        set pubkey [binary format B* $pubkey]
           97  +
           98  +                        ::asn::asnGetSequence pubkey pubkey_parts
           99  +                                ::asn::asnGetBigInteger pubkey_parts ret(n)
          100  +                                ::asn::asnGetBigInteger pubkey_parts ret(e)
          101  +
          102  +                        set ret(n) [::math::bignum::tostr $ret(n)]
          103  +                        set ret(e) [::math::bignum::tostr $ret(e)]
          104  +                        set ret(l) [expr {int([::pki::_bits $ret(n)] / 8.0000 + 0.5) * 8}]
          105  +                        set ret(type) rsa
          106  +                }
          107  +                default {
          108  +                        error "Unknown algorithm"
          109  +                }
          110  +        }
          111  +
          112  +        return [array get ret]
          113  +}
          114  +
          115  +proc ::pki::rsa::serialize_public_key {keylist} {
          116  +        array set key $keylist
          117  +
          118  +        foreach entry [list n e] {
          119  +                if {![info exists key($entry)]} {
          120  +                        return -code error "Key does not contain an element $entry"
          121  +                }
          122  +        }
          123  +
          124  +        set pubkey [::asn::asnSequence \
          125  +                [::asn::asnBigInteger [::math::bignum::fromstr $key(n)]] \
          126  +                [::asn::asnBigInteger [::math::bignum::fromstr $key(e)]] \
          127  +                ]  
          128  +        set pubkey_algo_params [::asn::asnNull]
          129  +
          130  +        binary scan $pubkey B* pubkey_bitstring
          131  +
          132  +        set ret [::asn::asnSequence \
          133  +                [::asn::asnSequence \
          134  +                                [::asn::asnObjectIdentifier [::pki::_oid_name_to_number rsaEncryption]] \
          135  +                                $pubkey_algo_params \
          136  +                        ] \
          137  +                        [::asn::asnBitString $pubkey_bitstring] \
          138  +                        ]
          139  +
          140  +        return [list data $ret begin "-----BEGIN PUBLIC KEY-----" end "-----END PUBLIC KEY-----"]
          141  +}
          142  +# End backports
          143  +
          144  +# Start internal functions
          145  +proc _listCertificates {} {
          146  +	if {![info exists ::env(PKCS11MODULE)]} {
          147  +		return [list]
          148  +	}
          149  +
          150  +	set ::env(CACKEY_NO_EXTRA_CERTS) 1
          151  +
          152  +	set handle [::pki::pkcs11::loadmodule $::env(PKCS11MODULE)]
          153  +
          154  +	set slotInfo [list]
          155  +	foreach slot [::pki::pkcs11::listslots $handle] {
          156  +		set slotID [lindex $slot 0]
          157  +		set slotLabel [lindex $slot 1]
          158  +		set slotFlags [lindex $slot 2]
          159  +
          160  +		if {"TOKEN_PRESENT" ni $slotFlags} {
          161  +			continue
          162  +		}
          163  +
          164  +		if {"TOKEN_INITIALIZED" ni $slotFlags} {
          165  +			continue
          166  +		}
          167  +
          168  +		set slotPromptForPIN false
          169  +		if {"PROTECTED_AUTHENTICATION_PATH" ni $slotFlags} {
          170  +			if {"LOGIN_REQUIRED" in $slotFlags} {
          171  +				set slotPromptForPIN true
          172  +			}
          173  +		}
          174  +
          175  +		foreach cert [::pki::pkcs11::listcerts $handle $slotID] {
          176  +			set pubkey [binary encode base64 [dict get [::pki::rsa::serialize_public_key $cert] data]]
          177  +
          178  +			lappend slotInfo [list handle $handle id $slotID prompt $slotPromptForPIN cert $cert pubkey $pubkey]
          179  +		}
          180  +	}
          181  +
          182  +	return $slotInfo
          183  +}
          184  +
          185  +proc _addPassword {name password publicKeys} {
          186  +	set fd [open "/dev/urandom" r]
          187  +	fconfigure $fd -translation binary
          188  +
          189  +	db eval {DELETE FROM passwords WHERE name = $name;}
          190  +
          191  +	foreach publicKey $publicKeys {
          192  +		set key [read $fd 16]
          193  +		if {[string length $key] != 16} {
          194  +			close $fd
          195  +
          196  +			return -code error "ERROR: Short read from random device"
          197  +		}
          198  +
          199  +		set publicKeyItem [::pki::pkcs::parse_public_key [binary decode base64 $publicKey]]
          200  +
          201  +		set encryptedKey [binary encode base64 [::pki::encrypt -pub -binary -- $key $publicKeyItem]]
          202  +
          203  +		set encryptedPass [binary encode base64 [::aes::aes -dir encrypt -key $key -- $password]]
          204  +
          205  +		db eval {INSERT INTO passwords (name, encryptedPass, encryptedKey, publicKey) VALUES ($name, @encryptedPass, @encryptedKey, @publicKey);}
          206  +	}
          207  +
          208  +	close $fd
          209  +}
          210  +
          211  +proc _prompt {prompt} {
          212  +	puts -nonewline $prompt
          213  +	flush stdout
          214  +
          215  +	puts -nonewline [exec stty -echo]
          216  +	flush stdout
          217  +
          218  +	set password [gets stdin]
          219  +
          220  +	puts -nonewline [exec stty echo]
          221  +	puts ""
          222  +	flush stdout
          223  +
          224  +	return $password
          225  +}
          226  +
          227  +proc _getPassword {name} {
          228  +	foreach slotInfoDict [_listCertificates] {
          229  +		unset -nocomplain slotInfo
          230  +		array set slotInfo $slotInfoDict
          231  +
          232  +		set pubkey $slotInfo(pubkey)
          233  +		set prompt $slotInfo(prompt)
          234  +
          235  +		if {[info exists prompted($slotInfo(id))]} {
          236  +			set prompt false
          237  +		}
          238  +
          239  +		if {$prompt} {
          240  +			set PIN [_prompt "Please enter the PIN for [dict get $slotInfo(cert) subject]: "]
          241  +
          242  +			if {![::pki::pkcs11::login $slotInfo(handle) $slotInfo(id) $PIN]} {
          243  +				return -code error "Unable to authenticate"
          244  +			}
          245  +
          246  +			set prompted($slotInfo(id)) 1
          247  +		}
          248  +
          249  +		db eval {SELECT encryptedPass, encryptedKey FROM passwords WHERE name = $name AND publicKey = $pubkey;} row {
          250  +			set key [::pki::decrypt -binary -priv -- [binary decode base64 $row(encryptedKey)] $slotInfo(cert)]
          251  +			set password [::aes::aes -dir decrypt -key $key -- [binary decode base64 $row(encryptedPass)]]
          252  +
          253  +			return $password
          254  +		}
          255  +	}
          256  +
          257  +	return -code error "No valid keys"
          258  +}
          259  +
          260  +proc _modifyPublicKeys {passwordName userNames sql} {
          261  +	set publicKeys [list]
          262  +
          263  +	db eval {SELECT publicKey FROM passwords WHERE name = $passwordName;} row {
          264  +		lappend publicKeys $row(publicKey)
          265  +	}
          266  +
          267  +	set changeRequired 0
          268  +	foreach user $userNames {
          269  +		unset -nocomplain row
          270  +		db eval {SELECT publicKey FROM users WHERE name = $user;} row $sql
          271  +	}
          272  +
          273  +	if {!$changeRequired} {
          274  +		return
          275  +	}
          276  +
          277  +	set password [_getPassword $passwordName]
          278  +
          279  +	_addPassword $passwordName $password $publicKeys
          280  +}
          281  +
          282  +proc _getUsersForPassword {passwordNames} {
          283  +	set userNames [list]
          284  +
          285  +	foreach passwordName $passwordNames {
          286  +		db eval {SELECT publicKey FROM passwords WHERE name = $passwordName;} passwordRow {
          287  +			db eval {SELECT name FROM users WHERE publicKey = $passwordRow(publicKey)} userRow {
          288  +				if {$userRow(name) in $userNames} {
          289  +					continue
          290  +				}
          291  +
          292  +				lappend userNames $userRow(name)
          293  +			}
          294  +		}
          295  +	}
          296  +
          297  +	return $userNames
          298  +}
          299  +
          300  +proc _getPasswordsForUser {userNames} {
          301  +	set passwordNames [list]
          302  +
          303  +	foreach userName $userNames {
          304  +		db eval {SELECT publicKey FROM users WHERE name = $userName;} userRow {
          305  +			db eval {SELECT name FROM passwords WHERE publicKey = $userRow(publicKey)} passwordRow {
          306  +				if {$passwordRow(name) in $passwordNames} {
          307  +					continue
          308  +				}
          309  +
          310  +				lappend passwordNames $passwordRow(name)
          311  +			}
          312  +		}
          313  +	}
          314  +
          315  +	return $passwordNames
          316  +}
          317  +# End internal functions
          318  +
          319  +# Start user CLI functions
          320  +proc listLocalKeys {} {
          321  +	foreach slotInfoDict [_listCertificates] {
          322  +		unset -nocomplain slotInfo
          323  +		array set slotInfo $slotInfoDict
          324  +
          325  +		set subject [dict get $slotInfo(cert) subject]
          326  +		set pubkey  $slotInfo(pubkey)
          327  +
          328  +		lappend publicKeys($subject) $pubkey
          329  +	}
          330  +
          331  +	foreach {subject pubkeys} [array get publicKeys] {
          332  +		puts "$subject"
          333  +
          334  +		foreach pubkey $pubkeys {
          335  +			puts "  |-> $pubkey"
          336  +		}
          337  +	}
          338  +}
          339  +
          340  +proc listAvailablePasswords {} {
          341  +	set passwordNames [list]
          342  +	foreach slotInfoDict [_listCertificates] {
          343  +		unset -nocomplain slotInfo
          344  +		array set slotInfo $slotInfoDict
          345  +
          346  +		set pubkey $slotInfo(pubkey)
          347  +
          348  +		unset -nocomplain row
          349  +		db eval {SELECT name FROM passwords WHERE publicKey = $pubkey;} row {
          350  +			if {$row(name) in $passwordNames} {
          351  +				continue
          352  +			}
          353  +
          354  +			lappend passwordNames $row(name)
          355  +		}
          356  +	}
          357  +
          358  +
          359  +	foreach passwordName $passwordNames {
          360  +		puts "$passwordName - [join [_getUsersForPassword [list $passwordName]] {, }]"
          361  +	}
          362  +}
          363  +
          364  +proc listPasswords {} {
          365  +	db eval {SELECT DISTINCT name FROM passwords;} row {
          366  +		puts "$row(name) - [join [_getUsersForPassword [list $row(name)]] {, }]"
          367  +	}
          368  +}
          369  +
          370  +proc listUsers {} {
          371  +	db eval {SELECT DISTINCT name FROM users;} row {
          372  +		puts "$row(name) - [join [_getPasswordsForUser [list $row(name)]] {, }]"
          373  +	}
          374  +}
          375  +
          376  +proc addUser {userName key} {
          377  +	set keyRaw [binary decode base64 $key]
          378  +	set keyVerify [::pki::pkcs::parse_public_key $keyRaw]
          379  +
          380  +	db eval {INSERT INTO users (name, publicKey) VALUES ($userName, @key);}
          381  +
          382  +	# XXX:TODO:Go through and re-authorize if possible
          383  +}
          384  +
          385  +proc deleteUser {userName} {
          386  +	# XXX:TODO: Go through and de-authorize
          387  +}
          388  +
          389  +proc addPassword {passwordName password args} {
          390  +	set initialUsers $args
          391  +
          392  +	if {$password eq ""} {
          393  +		set password [_prompt "Please enter the new password: "]
          394  +	}
          395  +
          396  +	# Verify that this password does not already exist
          397  +	set exists [db eval {SELECT 1 FROM passwords WHERE name = $passwordName LIMIT 1;}]
          398  +	if {$exists == "1"} {
          399  +		return -code error "Password \"$passwordName\" already exists, cannot add."
          400  +	}
          401  +
          402  +	# Get keys for initial users
          403  +	set publicKeys [list]
          404  +	foreach user $initialUsers {
          405  +		unset -nocomplain row
          406  +		db eval {SELECT publicKey FROM users WHERE name = $user;} row {
          407  +			lappend publicKeys $row(publicKey)
          408  +		}
          409  +	}
          410  +
          411  +	_addPassword $passwordName $password $publicKeys
          412  +}
          413  +
          414  +proc getPassword {passwordName} {
          415  +	puts [_getPassword $passwordName]
          416  +}
          417  +
          418  +proc updatePassword {passwordName password} {
          419  +	db eval {SELECT publicKey FROM passwords WHERE name = $passwordName;} row {
          420  +		lappend publicKeys $row(publicKey)
          421  +	}
          422  +
          423  +	_addPassword $passwordName $password $publicKeys
          424  +}
          425  +
          426  +proc deletePassword {passwordName} {
          427  +	db eval {DELETE FROM passwords WHERE name = $passwordName;}
          428  +}
          429  +
          430  +proc authorizeUsers {passwordName args} {
          431  +	set users $args
          432  +
          433  +	_modifyPublicKeys $passwordName $users {
          434  +		if {$row(publicKey) in $publicKeys} {
          435  +			continue
          436  +		}
          437  +
          438  +		lappend publicKeys $row(publicKey)
          439  +
          440  +		set changeRequired 1
          441  +	}
          442  +}
          443  +
          444  +proc authorizeUser {passwordName userName} {
          445  +	return [authorizeUsers $passwordName $userName]
          446  +}
          447  +
          448  +proc deauthorizeUsers {passwordName args} {
          449  +	set users $args
          450  +
          451  +	_modifyPublicKeys $passwordName $users {
          452  +		set idx [lsearch -exact $publicKeys $row(publicKey)]
          453  +		if {$idx == -1} {
          454  +			continue
          455  +		}
          456  +
          457  +		set publicKeys [lreplace $publicKeys $idx $idx]
          458  +
          459  +		set changeRequired 1
          460  +	}
          461  +}
          462  +
          463  +proc deauthorizeUser {passwordName userName} {
          464  +	return [deauthorizeUsers $passwordName $userName]
          465  +}
          466  +
          467  +proc help {{action ""}} {
          468  +	_printHelp stdout $action
          469  +}
          470  +# End user CLI functions
          471  +
          472  +### MAIN
          473  +
          474  +sqlite3 db $passwordFile
          475  +
          476  +db eval {
          477  +	CREATE TABLE IF NOT EXISTS users(name, publicKey BLOB);
          478  +	CREATE TABLE IF NOT EXISTS passwords(name, encryptedPass BLOB, encryptedKey BLOB, publicKey BLOB);
          479  +}
          480  +
          481  +if {$action in $validCommands} {
          482  +	$action {*}$argv
          483  +} else {
          484  +	puts stderr "Invalid action"
          485  +
          486  +	exit 1
          487  +}
          488  +
          489  +exit 0
          490  +