1#!/usr/perl5/bin/perl 2# 3# CDDL HEADER START 4# 5# The contents of this file are subject to the terms of the 6# Common Development and Distribution License (the "License"). 7# You may not use this file except in compliance with the License. 8# 9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10# or http://www.opensolaris.org/os/licensing. 11# See the License for the specific language governing permissions 12# and limitations under the License. 13# 14# When distributing Covered Code, include this CDDL HEADER in each 15# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16# If applicable, add the following below this CDDL HEADER, with the 17# fields enclosed by brackets "[]" replaced with your own identifying 18# information: Portions Copyright [yyyy] [name of copyright owner] 19# 20# CDDL HEADER END 21# 22# 23# ident "%Z%%M% %I% %E% SMI" 24# 25# Copyright 2007 Sun Microsystems, Inc. All rights reserved. 26# Use is subject to license terms. 27# 28 29# Server program for code signing server 30# 31# This program implements an ssh-based service to add digital 32# signatures to files. The sshd_config file on the server 33# contains an entry like the following to invoke this program: 34# 35# Subsystem codesign /opt/signing/bin/server 36# 37# The client program sends a ZIP archive of the file to be 38# signed along with the name of a signing credential stored 39# on the server. Each credential is a directory containing 40# a public-key certificate, private key, and a script to 41# perform the appropriate signing operation. 42# 43# This program unpacks the input ZIP archive, invokes the 44# signing script for the specified credential, and sends 45# back an output ZIP archive, which typically contains the 46# (modified) input file but may also contain additional 47# files created by the signing script. 48 49use strict; 50use File::Temp 'tempdir'; 51use File::Path; 52 53my $Base = "/opt/signing"; 54my $Tmpdir = tempdir(CLEANUP => 1); # Temporary directory 55my $Session = $$; 56 57# 58# Main program 59# 60 61# Set up 62open(AUDIT, ">>$Base/audit/log"); 63$| = 1; # Flush output on every write 64 65# Record user and client system 66my $user = `/usr/ucb/whoami`; 67chomp($user); 68my ($client) = split(/\s/, $ENV{SSH_CLIENT}); 69audit("START User=$user Client=$client"); 70 71# Process signing requests 72while (<STDIN>) { 73 if (/^SIGN (\d+) (\S+) (\S+)/) { 74 sign($1, $2, $3); 75 } else { 76 abnormal("WARNING Unknown command"); 77 } 78} 79exit(0); 80 81# 82# get_credential(name) 83# 84# Verify that the user is allowed to use the named credential and 85# return the path to the credential directory. If the user is not 86# authorized to use the credential, return undef. 87# 88sub get_credential { 89 my $name = shift; 90 my $dir; 91 92 $dir = "$Base/cred/$2"; 93 if (!open(F, "<$dir/private")) { 94 abnormal("WARNING Credential $name not available"); 95 $dir = undef; 96 } 97 close(F); 98 return $dir; 99} 100 101# 102# sign(size, cred, path) 103# 104# Sign an individual file. 105# 106sub sign { 107 my ($size, $cred, $path) = @_; 108 my ($cred_dir, $msg); 109 110 # Read input file 111 recvfile("$Tmpdir/in.zip", $size) || return; 112 113 # Check path for use of .. or absolute pathname 114 my @comp = split(m:/:, $path); 115 foreach my $elem (@comp) { 116 if ($elem eq "" || $elem eq "..") { 117 abnormal("WARNING Invalid path $path"); 118 return; 119 } 120 } 121 122 # Get credential directory 123 $cred_dir = get_credential($cred) || return; 124 125 # Create work area 126 rmtree("$Tmpdir/reloc"); 127 mkdir("$Tmpdir/reloc"); 128 chdir("$Tmpdir/reloc"); 129 130 # Read and unpack input ZIP archive 131 system("/usr/bin/unzip -qo ../in.zip $path"); 132 133 # Sign input file using credential-specific script 134 $msg = `cd $cred_dir; ./sign $Tmpdir/reloc/$path`; 135 if ($? != 0) { 136 chomp($msg); 137 abnormal("WARNING $msg"); 138 return; 139 } 140 141 # Pack output file(s) in ZIP archive and return 142 unlink("../out.zip"); 143 system("/usr/bin/zip -qr ../out.zip ."); 144 chdir($Tmpdir); 145 my $hash = `digest -a md5 $Tmpdir/reloc/$path`; 146 sendfile("$Tmpdir/out.zip", $path) || return; 147 148 # Audit successful signing 149 chomp($hash); 150 audit("SIGN $path $cred $hash"); 151} 152 153# 154# sendfile(file, path) 155# 156# Send a ZIP archive to the client. This involves sending 157# an OK SIGN response that includes the file size, followed by 158# the contents of the archive itself. 159# 160sub sendfile { 161 my ($file, $path) = @_; 162 my ($size, $bytes); 163 164 $size = -s $file; 165 if (!open(F, "<$file")) { 166 abnormal("ERROR Internal read error"); 167 return (0); 168 } 169 read(F, $bytes, $size); 170 close(F); 171 print "OK SIGN $size $path\n"; 172 syswrite(STDOUT, $bytes, $size); 173 return (1); 174} 175 176# 177# recvfile(file, size) 178# 179# Receive a ZIP archive from the client. The caller 180# provides the size argument previously obtained from the 181# client request. 182# 183sub recvfile { 184 my ($file, $size) = @_; 185 my $bytes; 186 187 if (!read(STDIN, $bytes, $size)) { 188 abnormal("ERROR No input data"); 189 return (0); 190 } 191 if (!open(F, ">$file")) { 192 abnormal("ERROR Internal write error"); 193 return (0); 194 } 195 syswrite(F, $bytes, $size); 196 close(F); 197 return (1); 198} 199 200# 201# audit(msg) 202# 203# Create an audit record. All records have this format: 204# [date] [time] [session] [keyword] [other parameters] 205# The keywords START and END mark the boundaries of a session. 206# 207sub audit { 208 my ($msg) = @_; 209 my ($sec, $min, $hr, $day, $mon, $yr) = localtime(time); 210 my $timestamp = sprintf("%04d-%02d-%02d %02d:%02d:%02d", 211 $yr+1900, $mon+1, $day, $hr, $min, $sec); 212 213 print AUDIT "$timestamp $Session $msg\n"; 214} 215 216# 217# abnormal(msg) 218# 219# Respond to an abnormal condition, which may be fatal (ERROR) or 220# non-fatal (WARNING). Send the message to the audit error log 221# and to the client program. Exit in case of fatal errors. 222# 223sub abnormal { 224 my $msg = shift; 225 226 audit($msg); 227 print("$msg\n"); 228 exit(1) if ($msg =~ /^ERROR/); 229} 230 231# 232# END() 233# 234# Clean up prior to normal or abnormal exit. 235# 236sub END { 237 audit("END"); 238 close(AUDIT); 239 chdir(""); # so $Tmpdir can be removed 240} 241