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