CERTIFICATE RETRIEVAL


STATUS OF THIS MEMO

   This document is an Internet-Draft and is subject to all provisions of
   Section 10 of RFC2026..

INTRODUCTION

   The CERTIFICATE RETRIEVAL Service is a TCP transaction based query/response
   server, running on port xxxxx, that provides directory service for internet
   users to automate the task of retrieving certificates based on email
   addresses or hostnames.

   This service, together with a corresponding database of X.509 certificates
   can be used to automate security in conjunction with the 802.1x protocol
   for authentication and encryption, webmail services, and email in general
   that employ s/mime to encrypt replies to authors could request a certificate
   via the service rather then requiring the user to supply it up front, so as
   long as you know the email address and they have signed up for a certificate
   the message can be encrypted.

   This method of distributing client certificates isn't intended for extremely
   sensitive material, but merely to protect day to day emails that are currently
   being sent as clear text. The security risk in this method isn't more likely
   to succeed then signing an email and distributing the certificate in that 
   manner.

PROTOCOL

   To access the CERTIFICATE RETRIEVAL service:

      Connect to the service host on TCP service port xxxxx
      (decimal).

      Send a single "command line", ending with  (ASCII CR and
      LF).

      Receive information in response to the command line.  The server
      closes its connection as soon as the output is finished.

   Extention to Service:
   
      This service could be extended to relay services that worked inline
      with Certificate Authorities to cache responses and verify the
      certificates were still valid using OCSP services. Using the relay
      method it would be easier to include additional Certificate
      Authorities, instead of issuing client software updates every time a
      new Certificate Authority commenced operations, or alternatively
      ceased to operate.
      
      Due to political concerns more then technical issues relay services
      should be run by impartial parties that don't have a vested interest
      in competing with other Certificate Authorities so no user would be
      disadvantaged.
      
SECURITY ISSUES

   Spam:
   
      Obviously this would open a potential method for spammers to validate
      their spam lists, and sending encrypted spam which may bypass current
      spam filters.  This system to an extent mimics the PGP Key Exchange
      service, unlike the PGP Key Exchange there will be no effort to allow
      searching apart from exact email or hostnames.  At time of writting
      there are no documented attempts to exploit the PGP Key Exchange for
      the purposes of spam.  Certificate Authorities should offer a way for
      their users to opt out of this method of distributing public
      certificates.
      
   Transfer:
   
      Due to the nature of X.509 it would be highly improbable that the
      certificates could be compromised if they passed via a compromised
      relay, or as the information is passed via any communications medium.
      After the certificate is received issuer, CRL and OCSP checks should
      be performed by client software as per normal with any certificate
      received by other methods.

SIMILAR SERVICES

   LDAP:

      LDAP can already be used in a method similar to described in this
      document, however a simpler approach to security through an extremely
      simple, efficient method of distributing without the need for bulky libs
      and wrappers would help adoption and help increase security in general.
      
   PGP Key Exchange:
   
      Has much more extended capabilities although the concept is basically
      the same, an easy method of distributing public keys.

   OCSP/CRL:
   
      Much simpler services only responding to requests about validity of
      a certificate presented to them, will not respond with a copy of the
      certificate.

COMMAND LINES AND REPLIES

   A command line is normally a single name specification.  Note that
   the specification formats will evolve with time; the best way to
   obtain the most recent documentation on name specifications is to
   give the server a command line consisting of "?" (that is, a
   question-mark alone as the name specification).  The response from
   the server will list all possible formats that can be used.
   The responses are currently intended to be machine-readable; the
   information is not meant to be passed back directly to a human user.  The
   following three examples illustrate the use of the service as of February
   2004.

   ---------------------------------------------------------------------

      Command line: ?
      Response:

      Please enter an email address or hostname, such as "www.cacert.org".

   ---------------------------------------------------------------------

   Command line: myisp.com
   Response:
      No valid certificate available.

   ---------------------------------------------------------------------

   Command line: www.cacert.org
   Response:
      -----BEGIN CERTIFICATE-----
      MIIFiTCCA3GgAwIBAgICAjYwDQYJKoZIhvcNAQEEBQAweTEQMA4GA1UEChMHUm9v
      dCBDQTEeMBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlD
      QSBDZXJ0IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0
      QGNhY2VydC5vcmcwHhcNMDMwNDAyMDAzNzU2WhcNMDUwNDAxMDAzNzU2WjCBmjEL
      MAkGA1UEBhMCQVUxDDAKBgNVBAgTA05TVzEPMA0GA1UEBxMGU3lkbmV5MRAwDgYD
      VQQKEwdDQSBDZXJ0MR4wHAYDVQQLExVTZXJ2ZXIgQWRtaW5pc3RyYXRpb24xFzAV
      BgNVBAMTDnd3dy5jYWNlcnQub3JnMSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGNh
      Y2VydC5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMWKlVe5/cfDZiPq
      WZTUGvgLA4dbvj/cBJXZgkz6aIvKuBhZ+cXob/xc6tkL20NdXZIW6WIzaGk6SU0a
      vu6grLlHumwuXrFGPNRb8HyRQrvWzgD73Est+CDfLo+Omq55UjDDH0TZoAMV3L0N
      Gb/S7YZeYHNcUzAHcXTE1//LyS8bAgMBAAGjggF7MIIBdzAMBgNVHRMBAf8EAjAA
      MCoGA1UdJQQjMCEGCCsGAQUFBwMBBglghkgBhvhCBAEGCisGAQQBgjcKAwMwCwYD
      VR0PBAQDAgUgMB0GA1UdDgQWBBTN0mx8ONpAgO9SaMf5KcKmDjFgDjCBowYDVR0j
      BIGbMIGYgBQWtTIb1Mfz4OaO873SsDrusjkY0aF9pHsweTEQMA4GA1UEChMHUm9v
      dCBDQTEeMBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlD
      QSBDZXJ0IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0
      QGNhY2VydC5vcmeCAQAwEQYJYIZIAYb4QgEBBAQDAgZAMFYGCWCGSAGG+EIBDQRJ
      FkdUbyBnZXQgeW91ciBvd24gY2VydGlmaWNhdGUgZm9yIEZSRUUgaGVhZCBvdmVy
      IHRvIGh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzANBgkqhkiG9w0BAQQFAAOCAgEAc7vb
      l/zrCweRmHo8dQw7fpvX4KibbpZ5fXT9c1tn+oIUxV1erDI3r5YW6Cha8XIDhMxJ
      tKPXdyAkrzEt3SrQrFy/KndMCjS1ypic2g/IhUjnB7FnH7Jc3RF+w3GX6OO38XtQ
      ydcQk2qD125W7Kl6cgSXjauVz2AyjetHi0jA+SRcmnlIRafKLEDPsnc/1A6CFd4C
      e8J+mrSA4FPOwf1ezAEJAlsBNiM2oIcfbWjrUR/dfaBrGweBQS/mgXxSXJWJnFQI
      wNE47X3ibISAgT74p/zqdVoYBQEXe0LDNsmFU6eqc8OyLEKW3BIgfQxtGxKCLCsA
      G/TjfjxGRhi1FlX9ZPvuWGfV0ks0gMa/VDM7V37LMhVViyTHNTmKuHsOa7UAz/5x
      qJo/ruLuCfGF9Z8jfj0197J0LMQSr09YQ/JSDJE6IFE59/6ju7+Py+2NDRsiqo+/
      qi/4SGbXakOXqJv92NHLqD/jdq8D8RIPggQ2RGG7aNQ5bpmnZk6KEJH9jD50LAs1
      kU+KI/v8o94ZBz+MDhNgJpwT5R/Vvnri2iT9mIysfpCuZCZC6KJKN8rXT031ocmx
      CJyaTBopnpnHwGu238IAMSpq9nPibhCb0OlqMVHdid9qPr8iZDWVhgmgDasigS5Y
      lzsrd9lRVDVN5HITUCFG3Qr5xx7ltSouj/dkykg=
      -----END CERTIFICATE-----

   ---------------------------------------------------------------------

EXAMPLE CODE

   ---------------------------------------------------------------------

/*
   Copyright Jeremey Barrett 2004 for CAcert.org
   You may create derivative works, as long as this copyright notice remains at the top
*/

/*
   Example finger daemon that can return certificates from a database. 
   Currently only tested on debian stable. 
   use the following command to build program
   gcc -o cacert-finger cacert-finger.c -I/usr/include/mysql -L/usr/lib \
   		-lmysqlclient -lz -DUSE_MYSQL
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>

#ifdef USE_MYSQL
#include <mysql.h>
#endif

#ifdef USE_MYSQL
static MYSQL mysql_db;
#endif

#define DEFAULT_PORT    79

#define EMAIL_QUERY   "select CRT from certs where `EMAIL`='%s' and \
			`revoked`='0000-00-00 00:00:00'"
#define DOMAIN_QUERY  "select CRT from domaincerts where \
			`revoked`='0000-00-00 00:00:00' and `CN`='%s'"
#define WILD_DOMAIN_QUERY  "select CRT from domaincerts where \ 
		`revoked`='0000-00-00 00:00:00' and (`CN`='%s' OR  `CN`='%s')"

void err_exit(void)
  {
    perror("");
    exit(1);
  }


#ifdef USE_MYSQL
void do_mysql(int client_fd, char *query)
  {
    MYSQL_RES *res;
    MYSQL_ROW  row;
    char *cert;

    if(!(mysql_real_connect(&mysql_db, "host", "username", "password",
    		"database", 0, "/var/run/mysqld/mysqld.sock", 0) != NULL))
        {
                printf(mysql_error(&mysql_db));
                exit(1);
        }
    
    if(mysql_real_query(&mysql_db, query, strlen(query)))
      goto _err_return;

    res = mysql_store_result(&mysql_db);
    if(mysql_num_rows(res) > 0)
    {
    row = mysql_fetch_row(res);

    if(!row[0])
      goto _free_res_return;

    cert = strstr(row[0], "-----BEGIN CERTIFICATE");
    if(cert) {
      char *response;
      int   response_len = strlen(cert)+2;

      response = (char *)calloc(1, response_len+1);
      if(!response)
        goto _free_res_return;
      memcpy(response, cert, response_len-2);
      response[response_len-2] = '\r';
      response[response_len-1] = '\n';
      
      /* should be checked for errors, more writing */
      write(client_fd, response, response_len); 

      free(response);
    } }

  _free_res_return:
    mysql_free_result(res);

  _err_return:
    mysql_close(&mysql_db);
  }
#endif


int read_line(int fd, char *buf, int size)
  {
    int n = 0;
    int t = 0;
    char *p;
    char *q;

    for(; t < size; )  {
      n = read(fd, buf+t, size-t);
      if(n < 0) {
        switch(errno) {
          case EINTR:
            continue;

          default:
            return -1;
        }
      }

      if(n == 0) {
        return t;
      }

      if(t)
        p = buf+t-1;
      else
        p = buf;

      for(; *p && *p != '\n' && *p != '\r'; p++);

      if(*p) {
        *p = 0;
        t = p-buf;
        return t;
      }
        
      t += n;
    }

    return -1;
  }


int handle_request(int fd)
  {
    char *buf;
    int   buf_size;
    int   len;
    int   i;
    int   email_query = 0;
    char  query[4096]; /* ugly */
    char *email = NULL;
    char *domain = NULL;
    char *wildcard = NULL;

    buf_size = 2048;

    buf = (char *)calloc(1, buf_size);
    if(!buf)
      return -1;

    if((len = read_line(fd, buf, buf_size-1)) <= 0)
      goto _free_return;

    memset(buf+len, 0, buf_size-len);

    if(strchr(buf, '@')) {
      email_query = 1;
      email = buf;
    }
    else {
      char *p;
      char *q;

      p = strchr(buf, '.');
      if(p) {
        q = strchr(p+1, '.');
        if(q) {
          wildcard = (char *)calloc(1, strlen(p)+2);
          if(wildcard) {
            *wildcard = '*';
            memcpy(wildcard+1, p, strlen(p));
          }
        }
      }

      domain = buf;
    }

    memset(query, 0, sizeof(query));

    if(email_query) {
      snprintf(query, sizeof(query)-2, EMAIL_QUERY, email);
    }
    else {
      if(wildcard && strcmp(wildcard, domain))
        snprintf(query, sizeof(query)-2, WILD_DOMAIN_QUERY, domain, wildcard);
      else
        snprintf(query, sizeof(query)-2, DOMAIN_QUERY, domain);

      if(wildcard)
        free(wildcard);
    }


    /* go and do MySQL stuff here */

#ifdef USE_MYSQL
    do_mysql(fd, query);
#else
    strcat(query, "\n");
    write(fd, query, strlen(query));
#endif


  _free_return:
    free(buf);
    return 0;
  }


int main(int argc, char **argv)
  {
    struct sockaddr_in server_sock;
    struct sockaddr_in client_sock;
    int client_addr_size;
    int server_fd = -1;
    int client_fd = -1;
    int pid;

    /* take command line args, e.g. -p <port>, later */

    if ((pid=fork()) < 0)
    {
        perror ("Fork failed");
        exit(errno);


    }

    if (pid)
    {
        exit(0);
    }

    server_fd = socket(PF_INET, SOCK_STREAM, 0);
    if(server_fd < 0)
      err_exit();
    
    memset(&server_sock, 0, sizeof(server_sock));

    server_sock.sin_family = AF_INET;
    server_sock.sin_addr.s_addr = INADDR_ANY;
    server_sock.sin_port = htons(DEFAULT_PORT);
    
    if(bind(server_fd, (struct sockaddr *)&server_sock,
    				sizeof(struct sockaddr_in)) < 0)
      err_exit();

    if(listen(server_fd, 64) < 0)
      err_exit();

    setgid(65534);
    setuid(65534);

    for(;;) {
      memset(&client_sock, 0, sizeof(client_sock));
      client_addr_size = sizeof(client_sock);

      client_fd = accept(server_fd,
      		(struct sockaddr *)&client_sock, &client_addr_size);
      if(client_fd < 0)
        err_exit();

      if(handle_request(client_fd) < 0)
        break;

      close(client_fd);
      client_fd = -1;
    }

    if(client_fd >= 0)
      close(client_fd);

    close(server_fd);

    exit(0);
  }