/*************************************** WWWOFFLE - World Wide Web Offline Explorer - Version 2.9j. Certificate handling functions. ******************/ /****************** Written by Andrew M. Bishop This file Copyright 2005-2015 Andrew M. Bishop It may be distributed under the GNU Public License, version 2, or any higher version. See section COPYING of the GNU Public license for conditions under which this file may be redistributed. ***************************************/ #include "autoconfig.h" #include #include #include #include #if HAVE_DIRENT_H # include #else # define dirent direct # if HAVE_SYS_NDIR_H # include # endif # if HAVE_SYS_DIR_H # include # endif # if HAVE_NDIR_H # include # endif #endif #include #include #include #if USE_GNUTLS #include #include #if USE_GCRYPT #include #endif #endif #include "wwwoffle.h" #include "errors.h" #include "config.h" #include "certificates.h" /*+ Need this for Win32 to use binary mode +*/ #ifndef O_BINARY #define O_BINARY 0 #endif #if USE_GNUTLS /*+ The number of bits for Diffie Hellman key exchange. +*/ #define DH_BITS 1024 /*+ The number of bits for RSA private keys. +*/ #define RSA_BITS 2048 /* Local functions */ static gnutls_certificate_credentials_t /*@only@*/ /*@null@*/ GetCredentials(const char *hostname,int server); static int CreateCertificate(const char *filename,/*@null@*/ const char *fake_hostname,/*@null@*/ const char *server_hostname,gnutls_x509_privkey_t key); static int CreatePrivateKey(const char *filename); static int SaveCertificates(gnutls_x509_crt_t *crt_list,int n_crts,const char *filename); static gnutls_x509_privkey_t /*@only@*/ LoadPrivateKey(const char *filename); /*+ A flag to indicate if the gnutls library has been initialised yet. +*/ static int initialised=0; /*+ The trusted root certificate authority certificates. +*/ gnutls_x509_crt_t /*@null@*/ *trusted_x509_crts=NULL; /*+ The number of trusted root certificate authority certificates. +*/ int n_trusted_x509_crts=0; /*+ The WWWOFFLE root certificate authority certificate. +*/ static /*@only@*/ gnutls_x509_crt_t root_crt; /*+ The WWWOFFLE root certificate authority private key. +*/ static /*@only@*/ gnutls_x509_privkey_t root_privkey; /*+ The WWWOFFLE Diffie Hellman parameters for key exchange. +*/ static /*@null@*/ gnutls_dh_params_t dh_params=NULL; /*++++++++++++++++++++++++++++++++++++++ Load in the WWWOFFLE root certificate authority certificate and private key. int LoadRootCredentials Returns zero if OK or something else in case of an error. ++++++++++++++++++++++++++++++++++++++*/ int LoadRootCredentials(void) { int err; struct stat buf; time_t activation,expiration,now; /* Check that the gnutls library hasn't already been initialised. */ if(initialised) PrintMessage(Fatal,"Loading root credentials can only be performed once."); /* Initialise the gnutls library if it hasn't been done yet. */ if(!initialised) gnutls_global_init(); initialised=1; /* Use faster but less secure key generation. */ if(ConfigBoolean(SSLQuickKeyGen)) { #if USE_GCRYPT gcry_control(GCRYCTL_ENABLE_QUICK_RANDOM,0); #else PrintMessage(Warning,"The 'quick-key-gen' options requires libgcrypt which is not compiled in."); #endif } /* Create the certificates directory if needed */ if(stat("certificates",&buf)) { PrintMessage(Inform,"Directory '%s' does not exist [%!s]; creating one.","certificates"); if(mkdir("certificates",(mode_t)ConfigInteger(DirPerm))) PrintMessage(Fatal,"Cannot create directory '%s' [%!s]; check permissions of specified directory.","certificates"); } else if(!S_ISDIR(buf.st_mode)) PrintMessage(Fatal,"The file '%s' is not a directory; delete problem file and start WWWOFFLE again to recreate it.","certificates"); /* Create the certificates/root directory if needed */ if(stat("certificates/root",&buf)) { PrintMessage(Inform,"Directory '%s' does not exist [%!s]; creating one.","certificates/root"); if(mkdir("certificates/root",(mode_t)ConfigInteger(DirPerm))) PrintMessage(Fatal,"Cannot create directory '%s' [%!s]; check permissions of specified directory.","certificates/root"); } else if(!S_ISDIR(buf.st_mode)) PrintMessage(Fatal,"The file '%s' is not a directory; delete problem file and start WWWOFFLE again to recreate it.","certificates/root"); /* Read in the root private key in this directory. */ readagain: if(stat("certificates/root/root-key.pem",&buf)) { PrintMessage(Warning,"The WWWOFFLE root CA private key file 'certificates/root/root-key.pem' does not exist; creating it."); if(CreatePrivateKey("certificates/root/root-key.pem")) PrintMessage(Fatal,"Could not create the WWWOFFLE root CA private key file 'certificates/root/root-key.pem'; check permissions of specified directory."); } if(stat("certificates/root/root-key.pem",&buf)) PrintMessage(Fatal,"The WWWOFFLE root CA private key file 'certificates/root/root-key.pem' still does not exist; check permissions of specified directory."); if(buf.st_mode&(S_IRWXG|S_IRWXO)) PrintMessage(Fatal,"The WWWOFFLE root CA private key file 'certificates/root/root-key.pem' is readable by others; delete problem file and start WWWOFFLE again to recreate it."); if(!S_ISREG(buf.st_mode)) PrintMessage(Fatal,"The WWWOFFLE root CA private key file 'certificates/root/root-key.pem' is not a regular file; delete problem file and start WWWOFFLE again to recreate it."); root_privkey=LoadPrivateKey("certificates/root/root-key.pem"); if(!root_privkey) PrintMessage(Fatal,"The WWWOFFLE root CA private key file 'certificates/root/root-key.pem' cannot be loaded; delete problem file and start WWWOFFLE again to recreate it."); /* Read in the root certificate in this directory. */ if(stat("certificates/root/root-cert.pem",&buf)) { PrintMessage(Warning,"The WWWOFFLE root CA certificate file 'certificates/root/root-cert.pem' does not exist; creating it."); if(CreateCertificate("certificates/root/root-cert.pem",NULL,NULL,root_privkey)) PrintMessage(Fatal,"Could not create the WWWOFFLE root CA certificate file 'certificates/root/root-cert.pem'; check permissions of specified directory."); } if(stat("certificates/root/root-cert.pem",&buf)) PrintMessage(Fatal,"The WWWOFFLE root CA certificate file 'certificates/root/root-cert.pem' still does not exist; check permissions of specified directory."); if(!S_ISREG(buf.st_mode)) PrintMessage(Fatal,"The WWWOFFLE root CA certificate file 'certificates/root/root-cert.pem' is not a regular file; delete problem file and start WWWOFFLE again to recreate it."); root_crt=LoadCertificate("certificates/root/root-cert.pem"); if(!root_crt) PrintMessage(Fatal,"The WWWOFFLE root CA certificate file 'certificates/root/root-cert.pem' cannot be loaded; delete problem file and start WWWOFFLE again to recreate it."); /* Check for expired root certificate and replace it */ now=time(NULL); activation=gnutls_x509_crt_get_activation_time(root_crt); expiration=gnutls_x509_crt_get_expiration_time(root_crt); if(activation==-1 || expiration==-1 || activation>now || expirationd_name[0]=='.' && (ent->d_name[1]==0 || (ent->d_name[1]=='.' && ent->d_name[2]==0))) continue; /* skip . & .. */ filename=(char*)malloc(24+strlen(ent->d_name)); sprintf(filename,"certificates/trusted/%s",ent->d_name); crts=LoadCertificates(filename); if(!crts || !crts[0]) PrintMessage(Warning,"Error reading trusted certificate '%s'; skipping this certificate.",filename); else { int i; i=0; while(crts[i]) i++; trusted_x509_crts=(gnutls_x509_crt_t*)realloc((void*)trusted_x509_crts,(n_trusted_x509_crts+i)*sizeof(gnutls_x509_crt_t)); i=0; while(crts[i]) trusted_x509_crts[n_trusted_x509_crts++]=crts[i++]; } free(filename); } while((ent=readdir(dir))); } closedir(dir); } PrintMessage(Inform,"Read in %d trusted certificates.",n_trusted_x509_crts); /* Check the certificates. */ if(n_trusted_x509_crts) { unsigned char **key=(unsigned char**)malloc(n_trusted_x509_crts*sizeof(unsigned char*)); time_t now; int i,j; /* Remove the duplicates based on the public key id. */ for(i=0;inow || expirationnow || expirationnow || expiration1 && gnutls_x509_crt_check_issuer(cert,cert)>0) cert=certlist[i-2]; for(i=0;inow || expirationnow || expiration((0x7FFFFFFF-now)/(24*3600))) expiration=0x7FFFFFFF; else expiration=now+ConfigInteger(SSLCertExpiry)*24*3600; err=gnutls_x509_crt_set_expiration_time(crt,expiration); if(err<0) {PrintMessage(Warning,"Could not set the certificate expiration time for '%s' [%s].",errmsg_hostname,gnutls_strerror(err));return(12);} /* Associate the request with the private key */ err=gnutls_x509_crt_set_key(crt,key); if(err<0) {PrintMessage(Warning,"Could not set the certificate key for '%s' [%s].",errmsg_hostname,gnutls_strerror(err));return(13);} buffer_size=sizeof(buffer); err=gnutls_x509_privkey_get_key_id(key,0,buffer,&buffer_size); if(err==0) err=gnutls_x509_crt_set_subject_key_id(crt,(void *)buffer,buffer_size); if(err<0) {PrintMessage(Warning,"Could not set the certificate subject key for '%s' [%s].",errmsg_hostname,gnutls_strerror(err));return(14);} /* Sign the certificate */ if(fake_hostname || server_hostname) err=gnutls_x509_crt_sign(crt,root_crt,root_privkey); else err=gnutls_x509_crt_sign(crt,crt,key); if(err<0) {PrintMessage(Warning,"Could not sign the certificate for '%s' [%s].",errmsg_hostname,gnutls_strerror(err));return(15);} /* Export the certificate */ buffer_size=sizeof(buffer); err=gnutls_x509_crt_export(crt,GNUTLS_X509_FMT_PEM,buffer,&buffer_size); if(err<0) {PrintMessage(Warning,"Could not export the certificate for '%s' [%s].",errmsg_hostname,gnutls_strerror(err));return(16);} gnutls_x509_crt_deinit(crt); /* Save it to a file. */ fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC|O_BINARY,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); if(fd<0) {PrintMessage(Warning,"Could not open certificate file '%s' for writing [%!s].",filename);return(-2);} else { write(fd,buffer,buffer_size); close(fd); } return(0); } /*++++++++++++++++++++++++++++++++++++++ Create and save a private key. int CreatePrivateKey Returns zero if OK or something else in case of an error. const char *filename The name of the file to save the private key in. ++++++++++++++++++++++++++++++++++++++*/ static int CreatePrivateKey(const char *filename) { gnutls_x509_privkey_t privkey; unsigned char buffer[2*RSA_BITS]; /* works for 256 bit keys or longer. */ size_t buffer_size=sizeof(buffer); int fd,err; /* Create the file for the certificate. */ fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC|O_EXCL|O_BINARY,S_IRUSR|S_IWUSR); if(fd<0) {PrintMessage(Warning,"Could not open private key file '%s' for writing [%!s].",filename);return(-1);} close(fd); PrintMessage(Inform,"Creating private key, this may take a long time."); /* Create the private key. */ err=gnutls_x509_privkey_init(&privkey); if(err<0) {PrintMessage(Warning,"Could not initialise private key [%s].",gnutls_strerror(err));return(1);} err=gnutls_x509_privkey_generate(privkey,GNUTLS_PK_RSA,RSA_BITS,0); if(err<0) {PrintMessage(Warning,"Could not generate private key [%s].",gnutls_strerror(err));return(2);} /* Export the key */ err=gnutls_x509_privkey_export(privkey,GNUTLS_X509_FMT_PEM,buffer,&buffer_size); if(err<0) {PrintMessage(Warning,"Could not export private key [%s].",gnutls_strerror(err));return(3);} gnutls_x509_privkey_deinit(privkey); /* Save it to a file. */ fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC|O_BINARY,S_IRUSR|S_IWUSR); if(fd<0) {PrintMessage(Warning,"Could not open private key file '%s' for writing [%!s].",filename);return(-2);} else { write(fd,buffer,buffer_size); close(fd); } return(0); } /*++++++++++++++++++++++++++++++++++++++ Load in a single certificate from a file (the first if multiple). gnutls_x509_crt_t LoadCertificate Returns the loaded certificate. const char *filename The name of the file to load the certificate from. ++++++++++++++++++++++++++++++++++++++*/ gnutls_x509_crt_t LoadCertificate(const char *filename) { gnutls_x509_crt_t *crt_list; int i=0; crt_list=LoadCertificates(filename); if(!crt_list || !crt_list[0]) return(NULL); while(crt_list[++i]) gnutls_x509_crt_deinit(crt_list[i]); return(crt_list[0]); } /*++++++++++++++++++++++++++++++++++++++ Load in a set of certificates from a file. gnutls_x509_crt_t *LoadCertificates Returns the loaded certificates (NULL terminated list). const char *filename The name of the file to load the certificates from. ++++++++++++++++++++++++++++++++++++++*/ gnutls_x509_crt_t *LoadCertificates(const char *filename) { static gnutls_x509_crt_t crt_list[257]; unsigned int n_crt=sizeof(crt_list)/sizeof(gnutls_x509_crt_t)-1; int fd,err; crt_list[0]=NULL; /* Load the certificates from the file. */ fd=open(filename,O_RDONLY|O_BINARY); if(fd<0) {PrintMessage(Warning,"Could not open certificate file '%s' for reading [%!s].",filename);return(NULL);} else { struct stat buf; unsigned char *buffer; size_t buffer_size; gnutls_datum_t datum; if(fstat(fd,&buf)) {PrintMessage(Warning,"Could not determine length of certificate file '%s' [%!s].",filename);return(NULL);} buffer=(unsigned char*)malloc(buf.st_size); buffer_size=buf.st_size; if(read(fd,buffer,buffer_size)!=buffer_size) {PrintMessage(Warning,"Could not read certificate file '%s' [%!s].",filename); free(buffer);return(NULL);} datum.data=buffer; datum.size=buffer_size; err=gnutls_x509_crt_list_import(crt_list,&n_crt,&datum,GNUTLS_X509_FMT_PEM,GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED); if(err<0) {PrintMessage(Warning,"Could not import certificates [%s].",gnutls_strerror(err)); free(buffer);return(NULL);} crt_list[err]=NULL; free(buffer); close(fd); } return(crt_list); } /*++++++++++++++++++++++++++++++++++++++ Save a set of certificates to a file. int SaveCertificates Return 0 if OK or another value if an error. gnutls_x509_crt_t *crt_list The certificates to save. int n_crts The number of certificates in the list. const char *filename The name of the file to save the certificates to. ++++++++++++++++++++++++++++++++++++++*/ static int SaveCertificates(gnutls_x509_crt_t *crt_list,int n_crts,const char *filename) { int fd,err; /* Save the certificates to the file. */ fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC|O_BINARY,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); if(fd<0) {PrintMessage(Warning,"Could not open certificate file '%s' for writing [%!s].",filename);return(1);} else { size_t buffer_alloc_size=4096; unsigned char *buffer=malloc(buffer_alloc_size); int i; for(i=0;i