xref: /illumos-gate/usr/src/tools/codesign/codesign_server.pl (revision 23259b79afff8cc5e183c5be57e05120f378fa72)
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