From 3dac3cb8d75c55f702992faa2c3967b03799099c Mon Sep 17 00:00:00 2001
From: Leigh B Stoller <stoller@flux.utah.edu>
Date: Mon, 30 Jan 2012 13:36:18 -0700
Subject: [PATCH] Changes to make it easier for ProtoGeni users!

* When generating an encrypted SSL certificate, derive an SSH public
  key from the private key and store in the pubkeys table for the
  user. Note that SSH version 2 RSA keys are actually just openssl RSA
  keys, and that ssh-keygen can extract an ssh compatible public key
  from it.

* Change getsslcert.php3 to return the ssh private and public key when
  give the "ssh" boolean argument. This is mostly for the benefit of
  Flack; we probably need a better UI for the user to get this stuff.

* Remove the requirement that users must upload an SSH key to use
  protogeni, since we now create one for them when they create their
  encrypted SSL certificate.

* Some cleanup; instead of looking at the comment field to determine
  what pubkeys are Emulab created (and should not be deleted), use new
  internal and nodelete flags.
---
 account/addpubkey.in  | 46 ++++++++++++++++++++++++++++++++-----------
 account/mkusercert.in | 41 +++++++++++++++++++++++++++++++++++++-
 account/newuser.in    |  7 +------
 account/tbacct.in     |  2 +-
 db/User.pm.in         | 30 +++++++++++++++-------------
 www/dbdefs.php3.in    |  5 ++++-
 www/deletepubkey.php3 | 22 +++++++++++++++++----
 www/getsslcert.php3   | 46 +++++++++++++++++++++++++++++++++----------
 www/joinproject.php3  | 12 +++++------
 www/newproject.php3   | 12 +++++------
 www/showpubkeys.php3  | 25 ++++++++++++++---------
 11 files changed, 176 insertions(+), 72 deletions(-)

diff --git a/account/addpubkey.in b/account/addpubkey.in
index f2a9057790..9cfd09bb23 100644
--- a/account/addpubkey.in
+++ b/account/addpubkey.in
@@ -1,7 +1,7 @@
 #!/usr/bin/perl -wT
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2011 University of Utah and the Flux Group.
+# Copyright (c) 2000-2012 University of Utah and the Flux Group.
 # All rights reserved.
 #
 use English;
@@ -31,7 +31,7 @@ sub usage()
     print " -r      Force a regenerate of initial key for user\n";
     exit(-1);
 }
-my $optlist   = "dkniwfu:rX:s";
+my $optlist   = "dkniwfu:rX:sRNC:S:";
 my $iskey     = 0;
 my $verify    = 0;
 my $initmode  = 0;
@@ -39,6 +39,9 @@ my $force     = 0;
 my $genmode   = 0;
 my $nobody    = 0;
 my $noemail   = 0;
+my $remove    = 0;
+my $nodelete  = 0;
+my $Comment;
 my $xmlfile;
 
 #
@@ -147,9 +150,15 @@ if (defined($options{"n"})) {
 if (defined($options{"i"})) {
     $initmode = 1;
 }
+if (defined($options{"N"})) {
+    $nodelete = 1;
+}
 if (defined($options{"r"})) {
     $force = 1;
 }
+if (defined($options{"R"})) {
+    $remove = 1;
+}
 if (defined($options{"s"})) {
     $noemail = 1;
 }
@@ -159,6 +168,9 @@ if (defined($options{"w"})) {
 if (defined($options{"u"})) {
     $user = $options{"u"};
 }
+if (defined($options{"C"})) {
+    $Comment = $options{"C"};
+}
 if (defined($options{"X"})) {
     $xmlfile = $options{"X"};
 
@@ -383,13 +395,21 @@ sub ParseKey($) {
     # Make up a comment field for the DB. 
     #
     if (!defined($comment)) {
-	$comment = "$type-${user_email}";
+	$comment = (defined($Comment) ? $Comment : "$type-${user_email}");
     }
     $key = "$key $comment";
+    my $safe_key = DBQuoteSpecial($key);
+    my $safe_comment = DBQuoteSpecial($comment);
 
-    DBQueryFatal("replace into user_pubkeys ".
-		 "values ('$user_uid', '$user_dbid', ".
-		 "        0, '$key', now(), '$comment')");
+    if ($remove) {
+	DBQueryFatal("delete from user_pubkeys ".
+		     "where uid_idx='$user_dbid' and comment=$safe_comment");
+    }
+    DBQueryFatal("replace into user_pubkeys set ".
+		 " uid='$user_uid', uid_idx='$user_dbid', ".
+		 " internal='0', nodelete='$nodelete', ".
+		 " idx=NULL, stamp=now(), ".
+		 " pubkey=$safe_key, comment=$safe_comment");
 
     #
     # Mark user record as modified so nodes are updated.
@@ -469,9 +489,10 @@ sub InitUser()
 	my $ident = `cat $sshdir/identity.pub`;
 
 	if ($ident =~ /(\d*\s\d*\s[0-9a-zA-Z]*)\s([-\w\@\.]*)/) {
-	    DBQueryFatal("replace into user_pubkeys ".
-			 "values ('$user_uid', '$user_dbid', ".
-			 "        0, '$1 $2', now(), '$2')");
+	    DBQueryFatal("replace into user_pubkeys set ".
+			 " uid='$user_uid', uid_idx='$user_dbid', ".
+			 " internal='1', nodelete='1', idx=NULL, stamp=now(), ".
+			 " pubkey='$1 $2', comment='$2'");
 	}
 	else {
 	    fatal("Bad protocol 1 public key: $ident\n");
@@ -512,9 +533,10 @@ sub InitUser()
 
 	if ($ident =~
 	    /^(ssh-rsa [-\w\.\@\+\/\=]*) ([-\w\@\.\ ]*)$/) {
-	    DBQueryFatal("replace into user_pubkeys ".
-			 "values ('$user_uid', '$user_dbid', ".
-			 "        0, '$1 $2', now(), '$2')");
+	    DBQueryFatal("replace into user_pubkeys set ".
+			 " uid='$user_uid', uid_idx='$user_dbid', ".
+			 " internal='1', nodelete='1', idx=NULL, stamp=now(), ".
+			 " pubkey='$1 $2', comment='$2'");
 	}
 	else {
 	    fatal("Bad protocol 2 public key: $ident\n");
diff --git a/account/mkusercert.in b/account/mkusercert.in
index 46e61a537a..e9dd39a2c3 100644
--- a/account/mkusercert.in
+++ b/account/mkusercert.in
@@ -1,7 +1,7 @@
 #!/usr/bin/perl -wT
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2011 University of Utah and the Flux Group.
+# Copyright (c) 2000-2012 University of Utah and the Flux Group.
 # All rights reserved.
 #
 use strict;
@@ -53,6 +53,8 @@ my $CACONFIG    = "$SSLDIR/ca.cnf";
 my $EMULAB_CERT = "$TB/etc/emulab.pem";
 my $EMULAB_KEY  = "$TB/etc/emulab.key";
 my $OPENSSL     = "/usr/bin/openssl";
+my $KEYGEN	= "/usr/bin/ssh-keygen";
+my $ADDKEY	= "$TB/sbin/addpubkey";
 my $WORKDIR     = "$TB/ssl";
 my $SAVEUID	= $UID;
 
@@ -471,6 +473,43 @@ if ($encrypted) {
 
     chown($user_number, $default_groupgid, "$target")
 	or fatal("Could not chown $target: $!");
+
+    chmod(0600, $target)
+	or fatal("Could not chmod $target: $!");
+
+    #
+    # Create an SSH key from the private key. Mostly for geni users,
+    # who tend not to know how to do such things.
+    #
+    my $pemfile = "$ssldir/encrypted.pem";
+    my $sshdir  = "$USERDIR/$user_uid/.ssh";
+    my $pphrase = User::escapeshellarg($password);
+    # This comment is special. It functions as a cross table reference
+    # between pubkeys and sslcerts. I might do this differently later.
+    my $comment = User::escapeshellarg("sslcert:${serial}");
+
+    # ssh-keygen whines and refuses to extract unless the mode is 600.
+    chmod(0600, $pemfile)
+	or fatal("Could not chmod $pemfile: $!");
+
+    system("$KEYGEN -P $pphrase -y -f $pemfile > $sshdir/encrypted.pub") == 0
+	or fatal("Could not extract ssh pubkey from $pemfile");
+
+    #
+    # The key format is identical to openssh, so just copy it over.
+    #
+    system("/bin/cp usercert_key.pem $sshdir/encrypted.key") == 0
+	or fatal("Could not copy private key to $sshdir/encrypted.key: $!");
+    chmod(0600, "$sshdir/encrypted.key")
+	or fatal("Could not chmod $sshdir/encrypted.key: $!");
+
+    #
+    # And add the pubkey to the DB. Mark it as nodelete and that it should
+    # remove existing key with same comment. 
+    #
+    $EUID = $UID;
+    system("$ADDKEY -s -N -R -C $comment -u $user_uid -f $sshdir/encrypted.pub")
+	== 0 or fatal("Could not add pubkey $sshdir/encrypted.pub");
 }
 
 TBScriptUnlock();
diff --git a/account/newuser.in b/account/newuser.in
index 485d6c3d5f..3e6d1f4391 100644
--- a/account/newuser.in
+++ b/account/newuser.in
@@ -1,7 +1,7 @@
 #!/usr/bin/perl -w
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2011 University of Utah and the Flux Group.
+# Copyright (c) 2000-2012 University of Utah and the Flux Group.
 # All rights reserved.
 #
 use English;
@@ -342,11 +342,6 @@ if (exists($xmlparse->{'attribute'}->{'passphrase'}) &&
 	fatal("Checkpass failed with $?");
     }
     $newuser_args{'initial_passphrase'} = $passphrase;
-
-    if (! (exists($xmlparse->{'attribute'}->{'pubkey'}) ||
-	   exists($xmlparse->{'pubkeys'}))) {
-	UserError("Must provide SSH pubkey when requesting SSL Certificate")
-    }
 }
 
 #
diff --git a/account/tbacct.in b/account/tbacct.in
index 50c3274d71..b211b54b87 100644
--- a/account/tbacct.in
+++ b/account/tbacct.in
@@ -459,7 +459,7 @@ sub AddUser()
     if (defined($target_user->initial_passphrase())) {
 	my $pphrase = User::escapeshellarg($target_user->initial_passphrase());
 	
-	system("$MKUSERCERT -p '$pphrase' $user");
+	system("$MKUSERCERT -p $pphrase $user");
 	if ($?) {
 	    fatal("Could not create initial encrypted SSL certificate");
 	}
diff --git a/db/User.pm.in b/db/User.pm.in
index 7e1302f992..8bf70ead3a 100644
--- a/db/User.pm.in
+++ b/db/User.pm.in
@@ -1135,8 +1135,8 @@ sub SSLCert($$$;$)
 }
 
 #
-# Get user ssh keys. This is bogus; I am making sure not to return the
-# emulab key which is not encrypted. The keys table needs work.
+# Get user ssh keys, but do not include the "internal" keys, which
+# are the Emulab generated unencrypted keys.
 #
 sub GetSSHKeys($$)
 {
@@ -1151,8 +1151,7 @@ sub GetSSHKeys($$)
 
     my $query_result =
 	DBQueryWarn("select pubkey from user_pubkeys ".
-		    "where uid_idx='$uid_idx' and ".
-		    "      comment not like '%${OURDOMAIN}'");
+		    "where uid_idx='$uid_idx' and internal=0");
 
     return -1
 	if (!defined($query_result));
@@ -1179,8 +1178,7 @@ sub DeleteSSHKeys($)
 
     my $query_result =
 	DBQueryWarn("delete from user_pubkeys ".
-		    "where uid_idx='$uid_idx' and ".
-		    "      comment not like '%${OURDOMAIN}'");
+		    "where uid_idx='$uid_idx' and internal=0");
 
     return -1
 	if (!defined($query_result));
@@ -1189,8 +1187,7 @@ sub DeleteSSHKeys($)
 }
 
 #
-# Get (hopefully) unencrypted, locally-generated user ssh keys.  This is 
-# bogus; I am making sure to only return locally-generated keys.
+# Get (hopefully) unencrypted, locally-generated user ssh keys.
 #
 sub GetDefaultSSHKeys($$;$)
 {
@@ -1210,8 +1207,7 @@ sub GetDefaultSSHKeys($$;$)
 
     my $query_result =
 	DBQueryWarn("select pubkey from user_pubkeys ".
-		    "where uid_idx='$uid_idx' and ".
-		    "      comment like '%\@${OURDOMAIN}' $extra");
+		    "where uid_idx='$uid_idx' and internal=1 $extra");
 
     return -1
 	if (!defined($query_result));
@@ -1761,11 +1757,17 @@ sub HomeDir($)
 
 sub escapeshellarg($)
 {
-    my ($str) = @_;
+    my ($str)  = @_;
+    my @chars  = split('', $str);
+    my $result = "";
 
-    $str =~ s/[^[:alnum:]]/\\$&/g;
-    $str =~ /^(.+)$/; # untaint the result
-    return $1;
+    foreach my $ch (@chars) {
+        if ($ch eq '\'') {
+            $result = $result . "\'\\\'";
+	}
+	$result = $result . "$ch";
+    }
+    return "'$result'";
 }
 
 #
diff --git a/www/dbdefs.php3.in b/www/dbdefs.php3.in
index 72c9e2f1db..dd27208549 100644
--- a/www/dbdefs.php3.in
+++ b/www/dbdefs.php3.in
@@ -1,7 +1,7 @@
 <?php
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2011 University of Utah and the Flux Group.
+# Copyright (c) 2000-2012 University of Utah and the Flux Group.
 # All rights reserved.
 #
 # Database Constants
@@ -33,6 +33,9 @@ $TBDB_MMLENGTH  = 64;
 $TBDB_ARCHIVE_TAGLEN = 64;
 $TBDB_ARCHIVE_MSGLEN = 2048;
 
+# Minimum length.
+$TBDB_MINPASSPHRASE = 10;
+
 #
 # Current policy is to prefix the EID with the PID. Make sure it is not
 # too long for the database. PID is 12, and the max is 32, so the user
diff --git a/www/deletepubkey.php3 b/www/deletepubkey.php3
index fa13974471..a635e400f3 100644
--- a/www/deletepubkey.php3
+++ b/www/deletepubkey.php3
@@ -1,7 +1,7 @@
 <?php
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2003, 2006, 2007 University of Utah and the Flux Group.
+# Copyright (c) 2000-2012 University of Utah and the Flux Group.
 # All rights reserved.
 #
 include("defs.php3");
@@ -47,9 +47,18 @@ if (! mysql_num_rows($query_result)) {
     USERERROR("Public Key for user '$target_uid' does not exist!", 1);
 }
 
-$row    = mysql_fetch_array($query_result);
-$pubkey = $row['pubkey'];
-$chunky = chunk_split($pubkey, 70, "<br>\n");
+$row      = mysql_fetch_array($query_result);
+$pubkey   = $row['pubkey'];
+$chunky   = chunk_split($pubkey, 70, "<br>\n");
+$internal = $row['internal'];
+$nodelete = $row['nodelete'];
+
+#
+# Internal keys cannot be deleted without admin.
+#
+if (($internal || $nodelete) && !$isadmin) {
+    USERERROR("You are not allowed to delete your system keys!", 1);
+}
 
 #
 # We run this twice. The first time we are checking for a confirmation
@@ -94,6 +103,11 @@ if (!isset($confirmed)) {
               <td>$chunky</td>
            </tr>
           </table>\n";
+
+    if ($internal || $nodelete) {
+	echo "<center><font color=red size=+1>";
+	echo "This is an internal key!</font><center>";
+    }
     
     PAGEFOOTER();
     return;
diff --git a/www/getsslcert.php3 b/www/getsslcert.php3
index ea8643e01d..fe88250658 100644
--- a/www/getsslcert.php3
+++ b/www/getsslcert.php3
@@ -1,7 +1,7 @@
 <?php
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2009 University of Utah and the Flux Group.
+# Copyright (c) 2000-2012 University of Utah and the Flux Group.
 # All rights reserved.
 #
 include("defs.php3");
@@ -17,10 +17,14 @@ $isadmin   = ISADMIN();
 # Verify page arguments
 #
 $optargs = OptionalPageArguments("target_user", PAGEARG_USER,
-				 "p12", PAGEARG_BOOLEAN);
+				 "p12",  PAGEARG_BOOLEAN,
+				 "ssh",  PAGEARG_BOOLEAN);
 if (!isset($p12)) {
     $p12 = 0;
 }
+if (!isset($ssh)) {
+    $ssh = 0;
+}
 
 # Default to current user if not provided.
 if (!isset($target_user)) {
@@ -58,7 +62,7 @@ if ($p12) {
 }
 
 $query_result =& $target_user->TableLookUp("user_sslcerts",
-					   "cert,privkey",
+					   "cert,privkey,idx",
 					   "encrypted=1 and revoked is null");
 
 if (!mysql_num_rows($query_result)) {
@@ -69,12 +73,34 @@ $row  = mysql_fetch_array($query_result);
 $cert = $row["cert"];
 $key  = $row["privkey"];
 
-header("Content-Type: text/plain");
-echo "-----BEGIN RSA PRIVATE KEY-----\n";
-echo $key;
-echo "-----END RSA PRIVATE KEY-----\n";
-echo "-----BEGIN CERTIFICATE-----\n";
-echo $cert;
-echo "-----END CERTIFICATE-----\n";
+if ($ssh) {
+    $serial  = $row['idx'];
+    $comment = "sslcert:${serial}";
+    $pubkey_result =& $target_user->TableLookUp("user_pubkeys",
+						"pubkey",
+						"comment='$comment'");
+    if (!mysql_num_rows($query_result)) {
+	PAGEHEADER("Download SSL Certificate for $target_uid");
+	USERERROR("There is no SSH pubkey for certificate!", 1);
+    }
+    $row  = mysql_fetch_array($pubkey_result);
+    $pubkey = $row['pubkey'];
+    
+    header("Content-Type: text/plain");
+    echo "-----BEGIN RSA PRIVATE KEY-----\n";
+    echo $key;
+    echo "-----END RSA PRIVATE KEY-----\n";
+    echo $pubkey;
+    echo "\n";
+}
+else {
+    header("Content-Type: text/plain");
+    echo "-----BEGIN RSA PRIVATE KEY-----\n";
+    echo $key;
+    echo "-----END RSA PRIVATE KEY-----\n";
+    echo "-----BEGIN CERTIFICATE-----\n";
+    echo $cert;
+    echo "-----END CERTIFICATE-----\n";
+}
 
 ?>
diff --git a/www/joinproject.php3 b/www/joinproject.php3
index 3eca95ce2b..0b5ab192fb 100644
--- a/www/joinproject.php3
+++ b/www/joinproject.php3
@@ -1,7 +1,7 @@
 <?php
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2011 University of Utah and the Flux Group.
+# Copyright (c) 2000-2012 University of Utah and the Flux Group.
 # All rights reserved.
 #
 include("defs.php3");
@@ -738,6 +738,10 @@ if (! $returning) {
 	elseif ($formfields["passphrase1"] != $formfields["passphrase2"]) {
 	    $errors["Confirm Pass Phrase"] = "Does not match Pass Phrase";
 	}
+	elseif (strlen($formfields["passphrase1"]) < $TBDB_MINPASSPHRASE) {
+	    $errors["Pass Phrase"] =
+		"Too short; $TBDB_MINPASSPHRASE char minimum";
+	}
 	elseif (! CHECKPASSWORD(($USERSELECTUIDS ?
 				 $formfields["joining_uid"] : "ignored"),
 				$formfields["passphrase1"],
@@ -745,12 +749,6 @@ if (! $returning) {
 				$formfields["usr_email"], $checkerror)) {
 	    $errors["Pass Phrase"] = "$checkerror";
 	}
-	if (! (isset($_FILES['usr_keyfile']) &&
-	       $_FILES['usr_keyfile']['name'] != "" &&
-	       $_FILES['usr_keyfile']['name'] != "none")) {
-	    $errors["SSH Pub Key"] =
-		"You must provide an SSH pubkey to use Geni";
-	}
     }
 }
 if (!$forwikionly) {
diff --git a/www/newproject.php3 b/www/newproject.php3
index 8ea4360af3..aff553e406 100755
--- a/www/newproject.php3
+++ b/www/newproject.php3
@@ -1,7 +1,7 @@
 <?php
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2011 University of Utah and the Flux Group.
+# Copyright (c) 2000-2012 University of Utah and the Flux Group.
 # All rights reserved.
 #
 include("defs.php3");
@@ -869,6 +869,10 @@ if (! $returning) {
 	elseif ($formfields["passphrase1"] != $formfields["passphrase2"]) {
 	    $errors["Confirm Pass Phrase"] = "Does not match Pass Phrase";
 	}
+	elseif (strlen($formfields["passphrase1"]) < $TBDB_MINPASSPHRASE) {
+	    $errors["Pass Phrase"] =
+		"Too short; $TBDB_MINPASSPHRASE char minimum";
+	}
 	elseif (! CHECKPASSWORD(($USERSELECTUIDS ?
 				 $formfields["proj_head_uid"] : "ignored"),
 				$formfields["passphrase1"],
@@ -876,12 +880,6 @@ if (! $returning) {
 				$formfields["usr_email"], $checkerror)) {
 	    $errors["Pass Phrase"] = "$checkerror";
 	}
-	if (! (isset($_FILES['usr_keyfile']) &&
-	       $_FILES['usr_keyfile']['name'] != "" &&
-	       $_FILES['usr_keyfile']['name'] != "none")) {
-	    $errors["SSH Pub Key"] =
-		"You must provide an SSH pubkey to use Geni";
-	}
     }
 }
 
diff --git a/www/showpubkeys.php3 b/www/showpubkeys.php3
index c1e1e22880..fe624bcc3a 100644
--- a/www/showpubkeys.php3
+++ b/www/showpubkeys.php3
@@ -1,7 +1,7 @@
 <?php
 #
 # EMULAB-COPYRIGHT
-# Copyright (c) 2000-2007 University of Utah and the Flux Group.
+# Copyright (c) 2000-2012 University of Utah and the Flux Group.
 # All rights reserved.
 #
 include("defs.php3");
@@ -75,21 +75,28 @@ function SPITFORM($formfields, $errors)
 	    $pubkey  = $row['pubkey'];
 	    $date    = $row['stamp'];
 	    $idx     = $row['idx'];
+	    $internal= $row['internal'];
+	    $nodelete= $row['nodelete'];
 	    $fnote   = "";
 
-	    if (strstr($comment, $BOSSNODE)) {
+	    if ($internal || $nodelete) {
 		$fnote = "[<b>1</b>]";
 	    }
 	    $chunky  = chunk_split("$pubkey $fnote", 75, "<br>\n");
 
 	    $delurl = CreateURL("deletepubkey", $target_user, "key", $idx);
 
-	    echo "<tr>
-                     <td align=center>
-                       <A href='$delurl'>
-                          <img alt='Delete Key' src=redball.gif></A>
-                     </td>
-                     <td>$chunky</td>
+	    echo "<tr>\n";
+	    if (($internal || $nodelete) && !$isadmin) {
+		echo "<td>&nbsp</td>";
+	    }
+	    else {
+		echo "<td align=center>
+                          <A href='$delurl'>
+                             <img alt='Delete Key' src=redball.gif></A>
+                      </td>";
+	    }
+	    echo "    <td>$chunky</td>
                   </tr>\n";
 	}
 	echo "</table>\n";
@@ -101,7 +108,7 @@ function SPITFORM($formfields, $errors)
     }
     echo "<blockquote><blockquote><blockquote>
           <ol>
-            <li> Please do not delete your Emulab generated public key.
+            <li> Your Emulab generated public keys may not be deleted.
           </ol>
           </blockquote></blockquote></blockquote>\n";
 
-- 
GitLab