1#!/bin/bash
2#
3# sc_auth - smart card authorization setup script
4#
5# You can log in with a smart card if the authentication_authority field
6# of your user record contains an entry of the form
7#	;pubkeyhash;THEHASH
8# where THEHASH is the hex encoding of the SHA1 of the public key to be used.
9# (In keychains, this is the value in the Label attribute of keys, and of
10# the PublicKeyHash # attribute of certificate records.)
11#
12# This script allows you to get the hash from a smartcard, and to create
13# the appropriate authority entry in a user account. It also lets you list
14# and delete them. It works as is for (local) NetInfo directories. If you
15# use LDAP or more exotic directory sources, you'll have to find your own
16# way to store the authentication_authority information, but the workflow
17# is the same. Feel free to hack.
18#
19# This script assumes the Tiger version of the /usr/bin/security command.
20# It will probably not work (without modification) with future versions.
21#
22# This script has been updated to use the dscl command in place of the
23# deprecated nicl command. To use the standard name in the header file:
24#	/System/Library/Frameworks/DirectoryService.framework/Headers/DirServicesConst.h
25# we have replaced "authentication_authority" with "AuthenticationAuthority"
26
27#set -x
28
29# general functions
30die() { echo "$*" 1>&2; exit 1; }
31note() { [ $verbose = yes ] && echo "$*" 1>&2; }
32
33usage() {
34cat <<EOU
35Usage:	$(basename $0) accept [-v] [-u user] [-d domain] [-k keyname] # by key on inserted card(s)
36	$(basename $0) accept [-v] [-u user] [-d domain] -h hash # by known pubkey hash
37	$(basename $0) remove [-v] [-u user] [-d domain] # remove all public keys for this user
38	$(basename $0) hash [-k keyname] # print hashes for keys on inserted card(s)
39	$(basename $0) list [-v] [-u user] [-d domain] # list pubkey hashes that can authenticate this user
40EOU
41exit 2
42}
43
44# first argument is a command word
45[ -n "$1" ] || usage
46command=$1; shift
47
48# parse options
49user=${USER:-$(logname)}
50keyname=
51hash=
52verbose=no
53domain="."
54while getopts d:h:k:u:v arg; do
55  case $arg in
56  d)	domain="$OPTARG";;
57  h)	hash="$OPTARG";;
58  k)	keyname="$OPTARG";;
59  u)	user="$OPTARG";;
60  v)	verbose=yes;;
61  esac
62done
63shift $(($OPTIND - 1))
64
65
66#
67# Using "security dump-keychain", extract the public key hash for a key
68# on a smartcard and print it to stdout.
69# The optional argument is a regular expression to match against the
70# print name of the key.
71# Prints all matching keys; aborts if none are found.
72#
73hash_for_key() {
74  # hash_for_key [string in name]
75  string=${1:-'.*'}
76  HOME=/no/where /usr/bin/security dump-keychain |
77  awk -v RE="$string" '
78	/^    0x00000001/	{
79		if (matched = ($2 ~ RE)) { name=$0; sub("^.*<blob>=\"", "", name); sub("\"$", "", name); count++; }}
80	/^    0x00000006/	{
81		if (matched) { hash=$2; sub("<blob>=0x", "", hash); print hash, name; }}
82  '
83  HOME=/no/where /usr/bin/security dump-keychain |
84  awk -v RE="$string" '
85	/^    0x01000000/	{
86		if (matched = ($2 ~ RE)) { name=$0; sub("^.*<blob>=\"", "", name); sub("\"$", "", name); count++; }}
87	/^    0x06000000/	{
88		if (matched) { hash=$2; sub("<blob>=0x", "", hash); print hash, name; }}
89  '
90}
91
92
93get_hash() {
94  if [ -n "$hash" ]; then	# passed in
95	echo "$hash"
96  else						# find it
97	hash_for_key "$keyname" |
98	(
99	  read hash rest
100	  [ -n "$hash" ] || die "No matching keys found"
101	  [ $verbose = yes ] && note "Using key \"$rest\""
102	  echo $hash
103	)
104  fi
105}
106
107
108accept_user() {
109  local hash="$1"
110  [ -n "$hash" ] || die "No hash specified"
111  dscl "$domain" -append "/Users/$user" AuthenticationAuthority ";pubkeyhash;$hash"
112}
113
114remove_user() {
115  set -- $(dscl "$domain" -read "/Users/$user" AuthenticationAuthority)
116  shift		# skip authentication_authority: header
117  while [ -n "$1" ]; do
118	case "$1" in
119	\;pubkeyhash\;*)
120	  dscl "$domain" -delete "/Users/$user" AuthenticationAuthority "$1"
121	  [ $verbose = yes ] && note "Removed $1"
122	  ;;
123	esac
124	shift
125  done
126}
127
128list_hashes() {
129  set -- $(dscl "$domain" -read "/Users/$user" AuthenticationAuthority)
130  shift		# skip authentication_authority: header
131  while [ -n "$1" ]; do
132	case "$1" in
133	\;pubkeyhash\;*)
134	  echo $1 | sed -e 's/;pubkeyhash;//'
135	  ;;
136	esac
137	shift
138  done
139}
140
141
142case "$command" in
143  hash)		hash_for_key "$keyname";;
144  accept)	accept_user $(get_hash);;
145  remove)	remove_user;;
146  list)		list_hashes;;
147  *)		usage;;
148esac
149