1/*
2 * "$Id: tls-openssl.c 3757 2012-03-30 06:13:47Z msweet $"
3 *
4 *   TLS support code for the CUPS scheduler using OpenSSL.
5 *
6 *   Copyright 2007-2012 by Apple Inc.
7 *   Copyright 1997-2007 by Easy Software Products, all rights reserved.
8 *
9 *   These coded instructions, statements, and computer programs are the
10 *   property of Apple Inc. and are protected by Federal copyright
11 *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
12 *   which should have been included with this file.  If this file is
13 *   file is missing or damaged, see the license at "http://www.cups.org/".
14 *
15 * Contents:
16 *
17 *   cupsdEndTLS()	- Shutdown a secure session with the client.
18 *   cupsdStartTLS()	- Start a secure session with the client.
19 *   make_certificate() - Make a self-signed SSL/TLS certificate.
20 */
21
22
23/*
24 * Local functions...
25 */
26
27static int		make_certificate(cupsd_client_t *con);
28
29
30/*
31 * 'cupsdEndTLS()' - Shutdown a secure session with the client.
32 */
33
34int					/* O - 1 on success, 0 on error */
35cupsdEndTLS(cupsd_client_t *con)	/* I - Client connection */
36{
37  SSL_CTX	*context;		/* Context for encryption */
38  unsigned long	error;			/* Error code */
39  int		status;			/* Return status */
40
41
42  context = SSL_get_SSL_CTX(con->http.tls);
43
44  switch (SSL_shutdown(con->http.tls))
45  {
46    case 1 :
47	cupsdLogMessage(CUPSD_LOG_DEBUG,
48			"SSL shutdown successful!");
49	status = 1;
50	break;
51
52    case -1 :
53	cupsdLogMessage(CUPSD_LOG_ERROR,
54			"Fatal error during SSL shutdown!");
55
56    default :
57	while ((error = ERR_get_error()) != 0)
58	  cupsdLogMessage(CUPSD_LOG_ERROR, "SSL shutdown failed: %s",
59			  ERR_error_string(error, NULL));
60	status = 0;
61	break;
62  }
63
64  SSL_CTX_free(context);
65  SSL_free(con->http.tls);
66  con->http.tls = NULL;
67
68  return (status);
69}
70
71
72/*
73 * 'cupsdStartTLS()' - Start a secure session with the client.
74 */
75
76int					/* O - 1 on success, 0 on error */
77cupsdStartTLS(cupsd_client_t *con)	/* I - Client connection */
78{
79  SSL_CTX	*context;		/* Context for encryption */
80  BIO		*bio;			/* BIO data */
81  unsigned long	error;			/* Error code */
82
83
84  cupsdLogMessage(CUPSD_LOG_DEBUG, "[Client %d] Encrypting connection.",
85                  con->http.fd);
86
87 /*
88  * Verify that we have a certificate...
89  */
90
91  if (access(ServerKey, 0) || access(ServerCertificate, 0))
92  {
93   /*
94    * Nope, make a self-signed certificate...
95    */
96
97    if (!make_certificate(con))
98      return (0);
99  }
100
101 /*
102  * Create the SSL context and accept the connection...
103  */
104
105  context = SSL_CTX_new(SSLv23_server_method());
106
107  SSL_CTX_set_options(context, SSL_OP_NO_SSLv2); /* Only use SSLv3 or TLS */
108  if (SSLOptions & CUPSD_SSL_NOEMPTY)
109    SSL_CTX_set_options(context, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
110  SSL_CTX_use_PrivateKey_file(context, ServerKey, SSL_FILETYPE_PEM);
111  SSL_CTX_use_certificate_chain_file(context, ServerCertificate);
112
113  bio = BIO_new(_httpBIOMethods());
114  BIO_ctrl(bio, BIO_C_SET_FILE_PTR, 0, (char *)HTTP(con));
115
116  con->http.tls = SSL_new(context);
117  SSL_set_bio(con->http.tls, bio, bio);
118
119  if (SSL_accept(con->http.tls) != 1)
120  {
121    cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to encrypt connection from %s.",
122                    con->http.hostname);
123
124    while ((error = ERR_get_error()) != 0)
125      cupsdLogMessage(CUPSD_LOG_ERROR, "%s", ERR_error_string(error, NULL));
126
127    SSL_CTX_free(context);
128    SSL_free(con->http.tls);
129    con->http.tls = NULL;
130    return (0);
131  }
132
133  cupsdLogMessage(CUPSD_LOG_DEBUG, "Connection from %s now encrypted.",
134                  con->http.hostname);
135
136  return (1);
137}
138
139
140/*
141 * 'make_certificate()' - Make a self-signed SSL/TLS certificate.
142 */
143
144static int				/* O - 1 on success, 0 on failure */
145make_certificate(cupsd_client_t *con)	/* I - Client connection */
146{
147#ifdef HAVE_WAITPID
148  int		pid,			/* Process ID of command */
149		status;			/* Status of command */
150  char		command[1024],		/* Command */
151		*argv[12],		/* Command-line arguments */
152		*envp[MAX_ENV + 1],	/* Environment variables */
153		infofile[1024],		/* Type-in information for cert */
154		seedfile[1024];		/* Random number seed file */
155  int		envc,			/* Number of environment variables */
156		bytes;			/* Bytes written */
157  cups_file_t	*fp;			/* Seed/info file */
158  int		infofd;			/* Info file descriptor */
159
160
161 /*
162  * Run the "openssl" command to seed the random number generator and
163  * generate a self-signed certificate that is good for 10 years:
164  *
165  *     openssl rand -rand seedfile 1
166  *
167  *     openssl req -new -x509 -keyout ServerKey \
168  *             -out ServerCertificate -days 3650 -nodes
169  *
170  * The seeding step is crucial in ensuring that the openssl command
171  * does not block on systems without sufficient entropy...
172  */
173
174  if (!cupsFileFind("openssl", getenv("PATH"), 1, command, sizeof(command)))
175  {
176    cupsdLogMessage(CUPSD_LOG_ERROR,
177                    "No SSL certificate and openssl command not found!");
178    return (0);
179  }
180
181  if (access("/dev/urandom", 0))
182  {
183   /*
184    * If the system doesn't provide /dev/urandom, then any random source
185    * will probably be blocking-style, so generate some random data to
186    * use as a seed for the certificate.  Note that we have already
187    * seeded the random number generator in cupsdInitCerts()...
188    */
189
190    cupsdLogMessage(CUPSD_LOG_INFO,
191                    "Seeding the random number generator...");
192
193   /*
194    * Write the seed file...
195    */
196
197    if ((fp = cupsTempFile2(seedfile, sizeof(seedfile))) == NULL)
198    {
199      cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to create seed file %s - %s",
200                      seedfile, strerror(errno));
201      return (0);
202    }
203
204    for (bytes = 0; bytes < 262144; bytes ++)
205      cupsFilePutChar(fp, CUPS_RAND());
206
207    cupsFileClose(fp);
208
209   /*
210    * Run the openssl command to seed its random number generator...
211    */
212
213    argv[0] = "openssl";
214    argv[1] = "rand";
215    argv[2] = "-rand";
216    argv[3] = seedfile;
217    argv[4] = "1";
218    argv[5] = NULL;
219
220    envc = cupsdLoadEnv(envp, MAX_ENV);
221    envp[envc] = NULL;
222
223    if (!cupsdStartProcess(command, argv, envp, -1, -1, -1, -1, -1, 1, NULL,
224                           NULL, &pid))
225    {
226      unlink(seedfile);
227      return (0);
228    }
229
230    while (waitpid(pid, &status, 0) < 0)
231      if (errno != EINTR)
232      {
233	status = 1;
234	break;
235      }
236
237    cupsdFinishProcess(pid, command, sizeof(command), NULL);
238
239   /*
240    * Remove the seed file, as it is no longer needed...
241    */
242
243    unlink(seedfile);
244
245    if (status)
246    {
247      if (WIFEXITED(status))
248	cupsdLogMessage(CUPSD_LOG_ERROR,
249                	"Unable to seed random number generator - "
250			"the openssl command stopped with status %d!",
251	        	WEXITSTATUS(status));
252      else
253	cupsdLogMessage(CUPSD_LOG_ERROR,
254                	"Unable to seed random number generator - "
255			"the openssl command crashed on signal %d!",
256	        	WTERMSIG(status));
257
258      return (0);
259    }
260  }
261
262 /*
263  * Create a file with the certificate information fields...
264  *
265  * Note: This assumes that the default questions are asked by the openssl
266  * command...
267  */
268
269  if ((fp = cupsTempFile2(infofile, sizeof(infofile))) == NULL)
270  {
271    cupsdLogMessage(CUPSD_LOG_ERROR,
272                    "Unable to create certificate information file %s - %s",
273                    infofile, strerror(errno));
274    return (0);
275  }
276
277  cupsFilePrintf(fp, ".\n.\n.\n%s\n.\n%s\n%s\n",
278                 ServerName, ServerName, ServerAdmin);
279  cupsFileClose(fp);
280
281  cupsdLogMessage(CUPSD_LOG_INFO,
282                  "Generating SSL server key and certificate...");
283
284  argv[0]  = "openssl";
285  argv[1]  = "req";
286  argv[2]  = "-new";
287  argv[3]  = "-x509";
288  argv[4]  = "-keyout";
289  argv[5]  = ServerKey;
290  argv[6]  = "-out";
291  argv[7]  = ServerCertificate;
292  argv[8]  = "-days";
293  argv[9]  = "3650";
294  argv[10] = "-nodes";
295  argv[11] = NULL;
296
297  cupsdLoadEnv(envp, MAX_ENV);
298
299  infofd = open(infofile, O_RDONLY);
300
301  if (!cupsdStartProcess(command, argv, envp, infofd, -1, -1, -1, -1, 1, NULL,
302                         NULL, &pid))
303  {
304    close(infofd);
305    unlink(infofile);
306    return (0);
307  }
308
309  close(infofd);
310  unlink(infofile);
311
312  while (waitpid(pid, &status, 0) < 0)
313    if (errno != EINTR)
314    {
315      status = 1;
316      break;
317    }
318
319  cupsdFinishProcess(pid, command, sizeof(command), NULL);
320
321  if (status)
322  {
323    if (WIFEXITED(status))
324      cupsdLogMessage(CUPSD_LOG_ERROR,
325                      "Unable to create SSL server key and certificate - "
326		      "the openssl command stopped with status %d!",
327	              WEXITSTATUS(status));
328    else
329      cupsdLogMessage(CUPSD_LOG_ERROR,
330                      "Unable to create SSL server key and certificate - "
331		      "the openssl command crashed on signal %d!",
332	              WTERMSIG(status));
333  }
334  else
335  {
336    cupsdLogMessage(CUPSD_LOG_INFO, "Created SSL server key file \"%s\"...",
337		    ServerKey);
338    cupsdLogMessage(CUPSD_LOG_INFO,
339                    "Created SSL server certificate file \"%s\"...",
340		    ServerCertificate);
341  }
342
343  return (!status);
344
345#else
346  return (0);
347#endif /* HAVE_WAITPID */
348}
349
350
351/*
352 * End of "$Id: tls-openssl.c 3757 2012-03-30 06:13:47Z msweet $".
353 */
354