/* * Copyright (C) 2003 Nikos Mavroyanopoulos * Copyright (C) 2004 Free Software Foundation * * This file is part of GNUTLS. * * GNUTLS is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * GNUTLS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include #include #ifdef ENABLE_PKI #include #include #include #include #include #include "certtool-gaa.h" #include #include #include #include static void print_crl_info( gnutls_x509_crl crl, FILE* out, int all); int generate_prime(int bits); void pkcs7_info( void); void pkcs12_info( void); void generate_pkcs12( void); void verify_chain(void); void verify_crl(void); gnutls_x509_privkey load_private_key(int mand); gnutls_x509_crq load_request(void); gnutls_x509_privkey load_ca_private_key(void); gnutls_x509_crt load_ca_cert(void); gnutls_x509_crt load_cert(int mand); void certificate_info( void); void crl_info( void); void privkey_info( void); static void print_certificate_info( gnutls_x509_crt crt, FILE* out, unsigned int); static void gaa_parser(int argc, char **argv); void generate_self_signed( void); void generate_request(void); gnutls_x509_crt* load_cert_list(int mand, int *size); static gaainfo info; FILE* outfile; FILE* infile; static int in_cert_format; static int out_cert_format; #define UNKNOWN "Unknown" /* non interactive operation if set */ int batch; unsigned char buffer[50*1024]; const int buffer_size = sizeof(buffer); static void tls_log_func( int level, const char* str) { fprintf(stderr, "|<%d>| %s", level, str); } int main(int argc, char** argv) { cfg_init(); gaa_parser(argc, argv); return 0; } static gnutls_x509_privkey generate_private_key_int( void) { gnutls_x509_privkey key; int ret, key_type; const char* msg; if (info.dsa) { msg = "DSA"; key_type = GNUTLS_PK_DSA; if (info.bits > 1024) { fprintf(stderr, "The DSA algorithm cannot be used with primes over 1024 bits.\n"); exit(1); } } else { msg = "RSA"; key_type = GNUTLS_PK_RSA; } if (info.privkey) return load_private_key(1); ret = gnutls_x509_privkey_init(&key); if (ret < 0) { fprintf(stderr, "privkey_init: %s\n", gnutls_strerror(ret)); exit(1); } fprintf(stderr, "Generating a %d bit %s private key...\n", info.bits, msg); ret = gnutls_x509_privkey_generate( key, key_type, info.bits, 0); if (ret < 0) { fprintf(stderr, "privkey_generate: %s\n", gnutls_strerror(ret)); exit(1); } return key; } static void print_key_usage( unsigned int x, FILE* out) { if (x&GNUTLS_KEY_DIGITAL_SIGNATURE) fprintf(out,"\t\tDigital signature.\n"); if (x&GNUTLS_KEY_NON_REPUDIATION) fprintf(out,"\t\tNon repudiation.\n"); if (x&GNUTLS_KEY_KEY_ENCIPHERMENT) fprintf(out,"\t\tKey encipherment.\n"); if (x&GNUTLS_KEY_DATA_ENCIPHERMENT) fprintf(out,"\t\tData encipherment.\n"); if (x&GNUTLS_KEY_KEY_AGREEMENT) fprintf(out,"\t\tKey agreement.\n"); if (x&GNUTLS_KEY_KEY_CERT_SIGN) fprintf(out,"\t\tCertificate signing.\n"); if (x&GNUTLS_KEY_CRL_SIGN) fprintf(out,"\t\tCRL signing.\n"); if (x&GNUTLS_KEY_ENCIPHER_ONLY) fprintf(out,"\t\tKey encipher only.\n"); if (x&GNUTLS_KEY_DECIPHER_ONLY) fprintf(out,"\t\tKey decipher only.\n"); } static void print_key_purpose( const char* x, FILE* out) { if (strcasecmp( x, GNUTLS_KP_TLS_WWW_SERVER)==0) fprintf(out,"\t\tTLS WWW Server.\n"); else if (strcasecmp( x, GNUTLS_KP_TLS_WWW_CLIENT)==0) fprintf(out,"\t\tTLS WWW Client.\n"); else if (strcasecmp( x, GNUTLS_KP_CODE_SIGNING)==0) fprintf(out,"\t\tCode signing.\n"); else if (strcasecmp( x, GNUTLS_KP_EMAIL_PROTECTION)==0) fprintf(out,"\t\tEmail protection.\n"); else if (strcasecmp( x, GNUTLS_KP_TIME_STAMPING)==0) fprintf(out,"\t\tTime stamping.\n"); else if (strcasecmp( x, GNUTLS_KP_OCSP_SIGNING)==0) fprintf(out,"\t\tOCSP signing.\n"); else if (strcasecmp( x, GNUTLS_KP_ANY)==0) fprintf(out,"\t\tAny purpose.\n"); else fprintf(out,"\t\t%s\n", x); } static void print_private_key( gnutls_x509_privkey key) { int ret; size_t size; if (!key) return; if (!info.pkcs8) { size = sizeof(buffer); ret = gnutls_x509_privkey_export( key, out_cert_format, buffer, &size); if (ret < 0) { fprintf(stderr, "privkey_export: %s\n", gnutls_strerror(ret)); exit(1); } } else { unsigned int flags; const char* pass; if (info.export) flags = GNUTLS_PKCS_USE_PKCS12_RC2_40; else flags = GNUTLS_PKCS_USE_PKCS12_3DES; if ((pass=get_pass()) == NULL) flags = GNUTLS_PKCS_PLAIN; size = sizeof(buffer); ret = gnutls_x509_privkey_export_pkcs8( key, out_cert_format, pass, flags, buffer, &size); if (ret < 0) { fprintf(stderr, "privkey_export_pkcs8: %s\n", gnutls_strerror(ret)); exit(1); } } fwrite(buffer, 1, size, outfile); } void generate_private_key( void) { gnutls_x509_privkey key; fprintf(stderr, "Generating a private key...\n"); key = generate_private_key_int(); print_private_key( key); gnutls_x509_privkey_deinit(key); } gnutls_x509_crt generate_certificate( gnutls_x509_privkey *ret_key, gnutls_x509_crt ca_crt) { gnutls_x509_crt crt; gnutls_x509_privkey key = NULL; int size, serial, client; int days, result, ca_status; const char* str; int vers = 3; /* the default version in the certificate */ unsigned int usage = 0, server; gnutls_x509_crq crq; /* request */ size = gnutls_x509_crt_init(&crt); if (size < 0) { fprintf(stderr, "crt_init: %s\n", gnutls_strerror(size)); exit(1); } crq = load_request(); if (crq == NULL) { key = load_private_key(1); if (!batch) fprintf(stderr, "Please enter the details of the certificate's distinguished name. " "Just press enter to ignore a field.\n"); /* set the DN. */ get_country_crt_set( crt); get_organization_crt_set(crt); get_unit_crt_set( crt); get_locality_crt_set( crt); get_state_crt_set( crt); get_cn_crt_set( crt); get_uid_crt_set( crt); get_oid_crt_set( crt); if (!batch) fprintf(stderr, "This field should not be used in new certificates.\n"); get_pkcs9_email_crt_set( crt); result = gnutls_x509_crt_set_key( crt, key); if (result < 0) { fprintf(stderr, "set_key: %s\n", gnutls_strerror(result)); exit(1); } } else { result = gnutls_x509_crt_set_crq( crt, crq); if (result < 0) { fprintf(stderr, "set_crq: %s\n", gnutls_strerror(result)); exit(1); } } serial = get_serial(); buffer[3] = serial & 0xff; buffer[2] = (serial >> 8) & 0xff; buffer[1] = (serial >> 16) & 0xff; buffer[0] = 0; result = gnutls_x509_crt_set_serial( crt, buffer, 4); if (result < 0) { fprintf(stderr, "serial: %s\n", gnutls_strerror(result)); exit(1); } if (!batch) fprintf(stderr, "\n\nActivation/Expiration time.\n"); gnutls_x509_crt_set_activation_time( crt, time(NULL)); days = get_days(); result = gnutls_x509_crt_set_expiration_time( crt, time(NULL)+days*24*60*60); if (result < 0) { fprintf(stderr, "serial: %s\n", gnutls_strerror(result)); exit(1); } if (!batch) fprintf(stderr, "\n\nExtensions.\n"); ca_status = get_ca_status(); result = gnutls_x509_crt_set_ca_status( crt, ca_status); if (result < 0) { fprintf(stderr, "ca_status: %s\n", gnutls_strerror(result)); exit(1); } client = get_tls_client_status(); if (client != 0) { result = gnutls_x509_crt_set_key_purpose_oid( crt, GNUTLS_KP_TLS_WWW_CLIENT, 0); if (result < 0) { fprintf(stderr, "key_kp: %s\n", gnutls_strerror(result)); exit(1); } } server = get_tls_server_status(); if (server != 0) { result = 0; str = get_dns_name(); if (str != NULL) { result = gnutls_x509_crt_set_subject_alternative_name( crt, GNUTLS_SAN_DNSNAME, str); } else { str = get_ip_addr(); if (str != NULL) { result = gnutls_x509_crt_set_subject_alternative_name( crt, GNUTLS_SAN_IPADDRESS, str); } } if (result < 0) { fprintf(stderr, "subject_alt_name: %s\n", gnutls_strerror(result)); exit(1); } result = gnutls_x509_crt_set_key_purpose_oid( crt, GNUTLS_KP_TLS_WWW_SERVER, 0); if (result < 0) { fprintf(stderr, "key_kp: %s\n", gnutls_strerror(result)); exit(1); } } else { str = get_email(); if (str != NULL) { result = gnutls_x509_crt_set_subject_alternative_name( crt, GNUTLS_SAN_RFC822NAME, str); if (result < 0) { fprintf(stderr, "subject_alt_name: %s\n", gnutls_strerror(result)); exit(1); } } } if (!ca_status || server) { int pk; pk = gnutls_x509_crt_get_pk_algorithm( crt, NULL); if (pk != GNUTLS_PK_DSA) { /* DSA keys can only sign. */ result = get_sign_status(server); if (result) usage |= GNUTLS_KEY_DIGITAL_SIGNATURE; result = get_encrypt_status( server); if (result) usage |= GNUTLS_KEY_KEY_ENCIPHERMENT; } else usage |= GNUTLS_KEY_DIGITAL_SIGNATURE; } if (ca_status) { result = get_cert_sign_status(); if (result) usage |= GNUTLS_KEY_KEY_CERT_SIGN; result = get_crl_sign_status(); if (result) usage |= GNUTLS_KEY_CRL_SIGN; result = get_code_sign_status(); if (result) { result = gnutls_x509_crt_set_key_purpose_oid( crt, GNUTLS_KP_CODE_SIGNING, 0); if (result < 0) { fprintf(stderr, "key_kp: %s\n", gnutls_strerror(result)); exit(1); } } result = get_ocsp_sign_status(); if (result) { result = gnutls_x509_crt_set_key_purpose_oid( crt, GNUTLS_KP_OCSP_SIGNING, 0); if (result < 0) { fprintf(stderr, "key_kp: %s\n", gnutls_strerror(result)); exit(1); } } result = get_time_stamp_status(); if (result) { result = gnutls_x509_crt_set_key_purpose_oid( crt, GNUTLS_KP_TIME_STAMPING, 0); if (result < 0) { fprintf(stderr, "key_kp: %s\n", gnutls_strerror(result)); exit(1); } } } if (usage != 0) { result = gnutls_x509_crt_set_key_usage( crt, usage); if (result < 0) { fprintf(stderr, "key_usage: %s\n", gnutls_strerror(result)); exit(1); } } /* Version. */ result = gnutls_x509_crt_set_version( crt, vers); if (result < 0) { fprintf(stderr, "set_version: %s\n", gnutls_strerror(result)); exit(1); } /* Subject Key ID. */ size = sizeof(buffer); result = gnutls_x509_crt_get_key_id(crt, 0, buffer, &size); if (result >= 0) { result = gnutls_x509_crt_set_subject_key_id( crt, buffer, size); if (result < 0) { fprintf(stderr, "set_subject_key_id: %s\n", gnutls_strerror(result)); exit(1); } } /* Authority Key ID. */ if (ca_crt != NULL) { size = sizeof(buffer); result = gnutls_x509_crt_get_key_id(ca_crt, 0, buffer, &size); if (result >= 0) { result = gnutls_x509_crt_set_authority_key_id( crt, buffer, size); if (result < 0) { fprintf(stderr, "set_authority_key_id: %s\n", gnutls_strerror(result)); exit(1); } } } *ret_key = key; return crt; } gnutls_x509_crl generate_crl( void) { gnutls_x509_crl crl; gnutls_x509_crt * crts; int size; int days, result, i; int vers = 2; /* the default version in the CRL */ result = gnutls_x509_crl_init(&crl); if (result < 0) { fprintf(stderr, "crl_init: %s\n", gnutls_strerror(result)); exit(1); } crts = load_cert_list(1, &size); for (i=0;i= 0) { print = printable; for (i = 0; i < serial_size; i++) { sprintf(print, "%.2x ", (unsigned char) serial[i]); print += 3; } fprintf(out, "Serial Number (hex): %s\n", printable); } /* Subject */ dn_size = sizeof(dn); ret = gnutls_x509_crt_get_dn(crt, dn, &dn_size); if (ret >= 0) fprintf(out, "Subject: %s\n", dn); else fprintf(stderr, "get_issuer_dn: %s\n", gnutls_strerror(ret)); /* Issuer */ if (all) { dn_size = sizeof(dn); ret = gnutls_x509_crt_get_issuer_dn(crt, dn, &dn_size); if (ret >= 0) fprintf(out, "Issuer: %s\n", dn); else fprintf(stderr, "get_issuer_dn: %s\n", gnutls_strerror(ret)); /* signature algorithm */ fprintf(out, "Signature Algorithm: "); ret = gnutls_x509_crt_get_signature_algorithm(crt); cprint = gnutls_sign_algorithm_get_name( ret); if (cprint == NULL) cprint = UNKNOWN; fprintf(out, "%s\n", cprint); } /* Validity */ fprintf(out, "Validity:\n"); tim = gnutls_x509_crt_get_activation_time(crt); fprintf(out, "\tNot Before: %s", ctime(&tim)); tim = gnutls_x509_crt_get_expiration_time(crt); fprintf(out, "\tNot After: %s", ctime(&tim)); /* Public key algorithm */ fprintf(out, "Subject Public Key Info:\n"); ret = gnutls_x509_crt_get_pk_algorithm(crt, NULL); fprintf(out, "\tPublic Key Algorithm: "); cprint = gnutls_pk_algorithm_get_name( ret); if (cprint == NULL) cprint = UNKNOWN; fprintf(out, "%s\n", cprint); if (version >= 3) fprintf(out, "\nX.509 Extensions:\n"); /* subject alternative name */ for (i = 0; !(ret < 0); i++) { size = sizeof(buffer); ret = gnutls_x509_crt_get_subject_alt_name(crt, i, buffer, &size, &critical); if (i==0 && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { fprintf(out, "\tSubject Alternative name:"); if (critical) fprintf(out, " (critical)"); fprintf(out, "\n"); } if (ret < 0 && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { fprintf(out, "\t\tFound unsupported alternative name.\n"); } else switch (ret) { case GNUTLS_SAN_DNSNAME: fprintf(out, "\t\tDNSname: %s\n", buffer); break; case GNUTLS_SAN_RFC822NAME: fprintf(out, "\t\tRFC822name: %s\n", buffer); break; case GNUTLS_SAN_URI: fprintf(out, "\t\tURI: %s\n", buffer); break; case GNUTLS_SAN_IPADDRESS: fprintf(out, "\t\tIPAddress: %s\n", buffer); break; } } /* CRL dist points. */ ret = 0; for (i = 0; !(ret < 0); i++) { size = sizeof(buffer); ret = gnutls_x509_crt_get_crl_dist_points(crt, i, buffer, &size, NULL, &critical); if (i==0 && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { fprintf(out, "\tCRL Distribution points:"); if (critical) fprintf(out, " (critical)"); fprintf(out, "\n"); } if (ret < 0 && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { fprintf(out, "\t\tError decoding: %s\n", gnutls_strerror(ret)); } else switch (ret) { case GNUTLS_SAN_DNSNAME: fprintf(out, "\t\tDNSname: %s\n", buffer); break; case GNUTLS_SAN_RFC822NAME: fprintf(out, "\t\tRFC822name: %s\n", buffer); break; case GNUTLS_SAN_URI: fprintf(out, "\t\tURI: %s\n", buffer); break; case GNUTLS_SAN_IPADDRESS: fprintf(out, "\t\tIPAddress: %s\n", buffer); break; } } /* check for basicConstraints */ ret = gnutls_x509_crt_get_ca_status( crt, &critical); if (ret >= 0) { fprintf(out, "\tBasic Constraints:"); if (critical) fprintf(out, " (critical)"); fprintf(out, "\n"); if (ret==0) fprintf(out, "\t\tCA:FALSE\n"); else fprintf(out, "\t\tCA:TRUE\n"); } /* Key Usage. */ ret = gnutls_x509_crt_get_key_usage( crt, &key_usage, &critical); if (ret >= 0) { fprintf(out, "\tKey usage: %s\n", critical?"(critical)":""); print_key_usage(key_usage, out); } /* Key Purpose identifiers. */ i = 0; size = sizeof(buffer); if ((ret=gnutls_x509_crt_get_key_purpose_oid( crt, 0, buffer, &size, &critical)) >= 0) { fprintf(out, "\tKey purpose OIDs: %s\n", critical?"(critical)":""); do { size = sizeof(buffer); ret = gnutls_x509_crt_get_key_purpose_oid( crt, i, buffer, &size, &critical); if (ret >= 0) { print_key_purpose(buffer, out); } i++; } while(ret >= 0); } /* Subject Key ID */ size = sizeof(buffer); ret = gnutls_x509_crt_get_subject_key_id(crt, buffer, &size, &critical); if (ret < 0 && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { fprintf(out, "Error getting subject key id: %s\n", gnutls_strerror(ret)); } if (ret >= 0) { print = printable; for (i = 0; i < size; i++) { sprintf(print, "%.2x ", (unsigned char) buffer[i]); print += 3; } fprintf(out, "\tSubject Key ID: %s\n\t\t%s\n", critical?"(critical)":"", printable); } /* Authority Key ID */ size = sizeof(buffer); ret = gnutls_x509_crt_get_authority_key_id(crt, buffer, &size, &critical); if (ret < 0 && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { fprintf(out, "Error getting authority key id: %s\n", gnutls_strerror(ret)); } if (ret >= 0) { print = printable; for (i = 0; i < size; i++) { sprintf(print, "%.2x ", (unsigned char) buffer[i]); print += 3; } fprintf(out, "\tAuthority Key ID: %s\n\t\t%s\n", critical?"(critical)":"", printable); } /* other extensions: */ indx = 0; ret = 0; for (i = 0; !(ret < 0); i++) { size = sizeof(oid); ret = gnutls_x509_crt_get_extension_oid( crt, i, oid, &size); if (ret >= 0) { if (known_oid( oid)) continue; if (strcmp( oid, old_oid) == 0) { indx++; } else { indx = 0; } fprintf( out, "\t%s: ", oid); size = sizeof(buffer); ret = gnutls_x509_crt_get_extension_by_oid( crt, oid, indx, buffer, &size, &critical); if (ret >= 0) { if (critical) fprintf(out, "(critical)\n"); else fprintf(out, "\n"); print = printable; for (j = 0; j < size; j++) { sprintf(print, "%.2x", (unsigned char) buffer[j]); print += 2; } fprintf(out, "\t\tDER Data: %s\n", printable); } ret = 0; strcpy( old_oid, oid); } } /* fingerprint */ fprintf( out, "\nOther information:\n"); if (all) { size = sizeof(buffer); if ((ret=gnutls_x509_crt_get_fingerprint(crt, GNUTLS_DIG_MD5, buffer, &size)) < 0) { fprintf(out, "Error in fingerprint calculation: %s\n", gnutls_strerror(ret)); } else { print = printable; for (i = 0; i < size; i++) { sprintf(print, "%.2x ", (unsigned char) buffer[i]); print += 3; } fprintf(out, "\tFingerprint: %s\n", printable); } } size = sizeof(buffer); if ((ret=gnutls_x509_crt_get_key_id(crt, 0, buffer, &size)) < 0) { fprintf(out, "Error in key id calculation: %s\n", gnutls_strerror(ret)); } else { print = printable; for (i = 0; i < size; i++) { sprintf(print, "%.2x ", (unsigned char) buffer[i]); print += 3; } fprintf(out, "\tPublic Key ID: %s\n", printable); } fprintf(out, "\n"); if (out==stderr && batch == 0) /* interactive */ if (read_yesno( "Is the above information ok? (Y/N): ")==0) { exit(1); } } static void print_crl_info( gnutls_x509_crl crl, FILE* out, int all) { int ret, rc; time_t tim; unsigned int i, j; char serial[128]; size_t serial_size = sizeof(serial), dn_size; char printable[256]; char *print, dn[256]; const char* cprint; fprintf(out, "CRL information:\n"); fprintf(out, "Version: %d\n", gnutls_x509_crl_get_version(crl)); /* Issuer */ if (all) { dn_size = sizeof(dn); ret = gnutls_x509_crl_get_issuer_dn(crl, dn, &dn_size); if (ret >= 0) fprintf(out, "Issuer: %s\n", dn); fprintf(out, "Signature Algorithm: "); ret = gnutls_x509_crl_get_signature_algorithm(crl); cprint = gnutls_sign_algorithm_get_name( ret); if (cprint == NULL) cprint = UNKNOWN; fprintf(out, "%s\n", cprint); } /* Validity */ fprintf(out, "Update dates:\n"); tim = gnutls_x509_crl_get_this_update(crl); fprintf(out, "\tIssued at: %s", ctime(&tim)); tim = gnutls_x509_crl_get_next_update(crl); fprintf(out, "\tNext at: %s", ctime(&tim)); fprintf(out, "\n"); /* Count the certificates. */ rc = gnutls_x509_crl_get_crt_count( crl); fprintf(out, "Revoked certificates: %d\n", rc); for (j=0;j<(unsigned int)rc;j++) { /* serial number */ serial_size = sizeof(serial); ret = gnutls_x509_crl_get_crt_serial(crl, j, serial, &serial_size, &tim); if (ret < 0) { fprintf(stderr, "error: %s\n", gnutls_strerror(ret)); } else { print = printable; for (i = 0; i < serial_size; i++) { sprintf(print, "%.2x ", (unsigned char) serial[i]); print += 3; } fprintf(out, "\tCertificate SN: %s\n", printable); fprintf(out, "\tRevoked at: %s\n", ctime( &tim)); } } } void crl_info() { gnutls_x509_crl crl; int ret; size_t size; gnutls_datum pem; size = fread( buffer, 1, sizeof(buffer)-1, infile); buffer[size] = 0; gnutls_x509_crl_init(&crl); pem.data = buffer; pem.size = size; ret = gnutls_x509_crl_import(crl, &pem, in_cert_format); if (ret < 0) { fprintf(stderr, "Decoding error: %s\n", gnutls_strerror(ret)); exit(1); } print_crl_info( crl, outfile, 1); size = sizeof(buffer); ret = gnutls_x509_crl_export(crl, GNUTLS_X509_FMT_PEM, buffer, &size); if (ret < 0) { fprintf(stderr, "Encoding error: %s\n", gnutls_strerror(ret)); exit(1); } fprintf(outfile, "\n%s\n", buffer); } void privkey_info( void) { gnutls_x509_privkey key; size_t size; int ret; unsigned int i; gnutls_datum pem; char printable[256]; char *print; const char* cprint; const char* pass; size = fread( buffer, 1, sizeof(buffer)-1, infile); buffer[size] = 0; gnutls_x509_privkey_init(&key); pem.data = buffer; pem.size = size; if (!info.pkcs8) { ret = gnutls_x509_privkey_import(key, &pem, in_cert_format); } else { pass = get_pass(); ret = gnutls_x509_privkey_import_pkcs8(key, &pem, in_cert_format, pass, 0); } if (ret < 0) { fprintf(stderr, "Decoding error: %s\n", gnutls_strerror(ret)); exit(1); } /* Public key algorithm */ fprintf(outfile, "Public Key Info:\n"); ret = gnutls_x509_privkey_get_pk_algorithm(key); fprintf(outfile, "\tPublic Key Algorithm: "); cprint = gnutls_pk_algorithm_get_name( ret); if (cprint == NULL) cprint = UNKNOWN; fprintf(outfile, "%s\n", cprint); size = sizeof(buffer); if ((ret=gnutls_x509_privkey_get_key_id(key, 0, buffer, &size)) < 0) { fprintf(stderr, "Error in key id calculation: %s\n", gnutls_strerror(ret)); } else { print = printable; for (i = 0; i < size; i++) { sprintf(print, "%.2x ", (unsigned char) buffer[i]); print += 3; } fprintf(outfile, "Public Key ID: %s\n", printable); } size = sizeof(buffer); ret = gnutls_x509_privkey_export(key, GNUTLS_X509_FMT_PEM, buffer, &size); if (ret < 0) { fprintf(stderr, "Encoding error: %s\n", gnutls_strerror(ret)); exit(1); } fprintf(outfile, "\n%s\n", buffer); } /* mand should be non zero if it is required to read a private key. */ gnutls_x509_privkey load_private_key(int mand) { FILE* fd; gnutls_x509_privkey key; int ret; gnutls_datum dat; size_t size; const char* pass; if (!info.privkey && !mand) return NULL; if (!info.privkey) { fprintf(stderr, "error: a private key was not specified\n"); exit(1); } fd = fopen(info.privkey, "r"); if (fd == NULL) { fprintf(stderr, "error: could not load key file '%s'.\n", info.privkey); exit(1); } size = fread(buffer, 1, sizeof(buffer)-1, fd); buffer[size] = 0; fclose(fd); ret = gnutls_x509_privkey_init(&key); if (ret < 0) { fprintf(stderr, "privkey_init: %s\n", gnutls_strerror(ret)); exit(1); } dat.data = buffer; dat.size = size; if (!info.pkcs8) ret = gnutls_x509_privkey_import( key, &dat, in_cert_format); else { pass = get_pass(); ret = gnutls_x509_privkey_import_pkcs8( key, &dat, in_cert_format, pass, 0); } if (ret < 0) { fprintf(stderr, "privkey_import: %s\n", gnutls_strerror(ret)); exit(1); } return key; } gnutls_x509_crq load_request() { FILE* fd; gnutls_x509_crq crq; int ret; gnutls_datum dat; size_t size; if (!info.request) return NULL; fd = fopen(info.request, "r"); if (fd == NULL) { fprintf(stderr, "File %s does not exist.\n", info.request); exit(1); } size = fread(buffer, 1, sizeof(buffer)-1, fd); buffer[size] = 0; fclose(fd); ret = gnutls_x509_crq_init(&crq); if (ret < 0) { fprintf(stderr, "crq_init: %s\n", gnutls_strerror(ret)); exit(1); } dat.data = buffer; dat.size = size; ret = gnutls_x509_crq_import( crq, &dat, in_cert_format); if (ret < 0) { fprintf(stderr, "crq_import: %s\n", gnutls_strerror(ret)); exit(1); } return crq; } gnutls_x509_privkey load_ca_private_key() { FILE* fd; gnutls_x509_privkey key; int ret; const char* pass; gnutls_datum dat; size_t size; fprintf(stderr, "Loading CA's private key...\n"); if (info.ca_privkey==NULL) { fprintf(stderr, "You must specify a private key of the CA.\n"); exit(1); } fd = fopen(info.ca_privkey, "r"); if (fd == NULL) { fprintf(stderr, "File %s does not exist.\n", info.ca_privkey); exit(1); } size = fread(buffer, 1, sizeof(buffer)-1, fd); buffer[size] = 0; fclose(fd); ret = gnutls_x509_privkey_init(&key); if (ret < 0) { fprintf(stderr, "privkey_init: %s\n", gnutls_strerror(ret)); exit(1); } dat.data = buffer; dat.size = size; if (!info.pkcs8) ret = gnutls_x509_privkey_import( key, &dat, in_cert_format); else { pass = get_pass(); ret = gnutls_x509_privkey_import_pkcs8( key, &dat, in_cert_format, pass, 0); } if (ret < 0) { fprintf(stderr, "privkey_import: %s\n", gnutls_strerror(ret)); exit(1); } return key; } /* Loads the CA's certificate */ gnutls_x509_crt load_ca_cert() { FILE* fd; gnutls_x509_crt crt; int ret; gnutls_datum dat; size_t size; fprintf(stderr, "Loading CA's certificate...\n"); if (info.ca==NULL) { fprintf(stderr, "You must specify a certificate of the CA.\n"); exit(1); } fd = fopen(info.ca, "r"); if (fd == NULL) { fprintf(stderr, "File %s does not exist.\n", info.ca); exit(1); } size = fread(buffer, 1, sizeof(buffer)-1, fd); buffer[size] = 0; fclose(fd); ret = gnutls_x509_crt_init(&crt); if (ret < 0) { fprintf(stderr, "crt_init: %s\n", gnutls_strerror(ret)); exit(1); } dat.data = buffer; dat.size = size; ret = gnutls_x509_crt_import( crt, &dat, in_cert_format); if (ret < 0) { fprintf(stderr, "crt_import: %s\n", gnutls_strerror(ret)); exit(1); } return crt; } /* Loads the certificate * If mand is non zero then a certificate is mandatory. Otherwise * null will be returned if the certificate loading fails. */ gnutls_x509_crt load_cert(int mand) { gnutls_x509_crt *crt; int size; crt = load_cert_list( mand, &size); return crt[0]; } #define MAX_CERTS 256 /* Loads a certificate list */ gnutls_x509_crt* load_cert_list(int mand, int *crt_size) { FILE* fd; static gnutls_x509_crt crt[MAX_CERTS]; char* ptr; int ret, i; gnutls_datum dat; size_t size; int ptr_size; *crt_size = 0; fprintf(stderr, "Loading certificate list...\n"); if (info.cert==NULL) { fprintf(stderr, "You must specify a certificate.\n"); if (mand) exit(1); else return NULL; } fd = fopen(info.cert, "r"); if (fd == NULL) { fprintf(stderr, "File %s does not exist.\n", info.cert); exit(1); } size = fread(buffer, 1, sizeof(buffer)-1, fd); buffer[size] = 0; fclose(fd); ptr = buffer; ptr_size = size; for (i=0;i 0) break; if (ret < 0) { fprintf(stderr, "crt_import: %s\n", gnutls_strerror(ret)); exit(1); } ptr = strstr( ptr, "---END"); if (ptr==NULL) break; ptr++; ptr_size = size; ptr_size -= (unsigned int)((unsigned char*)ptr - (unsigned char*)buffer); if (ptr_size < 0) break; (*crt_size)++; } fprintf(stderr, "Loaded %d certificates.\n", *crt_size); return crt; } /* Generate a PKCS #10 certificate request. */ void generate_request(void) { gnutls_x509_crq crq; gnutls_x509_privkey key; int ret; const char* pass; size_t size; fprintf(stderr, "Generating a PKCS #10 certificate request...\n"); ret = gnutls_x509_crq_init(&crq); if (ret < 0) { fprintf(stderr, "crq_init: %s\n", gnutls_strerror(ret)); exit(1); } /* Load the private key. */ key = generate_private_key_int(); /* Set the DN. */ get_country_crq_set( crq); get_organization_crq_set(crq); get_unit_crq_set( crq); get_locality_crq_set( crq); get_state_crq_set( crq); get_cn_crq_set( crq); get_uid_crq_set( crq); get_oid_crq_set( crq); ret = gnutls_x509_crq_set_version( crq, 1); if (ret < 0) { fprintf(stderr, "set_version: %s\n", gnutls_strerror(ret)); exit(1); } pass = get_challenge_pass(); if (pass != NULL) { ret = gnutls_x509_crq_set_challenge_password( crq, pass); if (ret < 0) { fprintf(stderr, "set_pass: %s\n", gnutls_strerror(ret)); exit(1); } } ret = gnutls_x509_crq_set_key( crq, key); if (ret < 0) { fprintf(stderr, "set_key: %s\n", gnutls_strerror(ret)); exit(1); } ret = gnutls_x509_crq_sign( crq, key); if (ret < 0) { fprintf(stderr, "sign: %s\n", gnutls_strerror(ret)); exit(1); } size = sizeof(buffer); ret = gnutls_x509_crq_export( crq, out_cert_format, buffer, &size); if (ret < 0) { fprintf(stderr, "export: %s\n", gnutls_strerror(ret)); exit(1); } fwrite( buffer, 1, size, outfile); gnutls_x509_crq_deinit(crq); gnutls_x509_privkey_deinit(key); } static void print_verification_res( gnutls_x509_crt crt, gnutls_x509_crt issuer, gnutls_x509_crl *crl_list, int crl_list_size); #define CERT_SEP "-----BEGIN CERT" #define CRL_SEP "-----BEGIN X509 CRL" int _verify_x509_mem( const void* cert, int cert_size) { int siz, i; const char *ptr; int ret; unsigned int output; char name[256]; char issuer_name[256]; size_t name_size; size_t issuer_name_size; gnutls_datum tmp; gnutls_x509_crt *x509_cert_list = NULL; gnutls_x509_crl *x509_crl_list = NULL; int x509_ncerts, x509_ncrls; /* Decode the CA certificate */ /* Decode the CRL list */ siz = cert_size; ptr = cert; i = 1; if (strstr(ptr, CRL_SEP)!=NULL) /* if CRLs exist */ do { x509_crl_list = (gnutls_x509_crl *) realloc( x509_crl_list, i * sizeof(gnutls_x509_crl)); if (x509_crl_list == NULL) { fprintf(stderr, "memory error\n"); exit(1); } tmp.data = (char*)ptr; tmp.size = siz; ret = gnutls_x509_crl_init( &x509_crl_list[i-1]); if (ret < 0) { fprintf(stderr, "Error parsing the CRL[%d]: %s\n", i, gnutls_strerror(ret)); exit(1); } ret = gnutls_x509_crl_import( x509_crl_list[i-1], &tmp, GNUTLS_X509_FMT_PEM); if (ret < 0) { fprintf(stderr, "Error parsing the CRL[%d]: %s\n", i, gnutls_strerror(ret)); exit(1); } /* now we move ptr after the pem header */ ptr = strstr(ptr, CRL_SEP); if (ptr!=NULL) ptr++; i++; } while ((ptr = strstr(ptr, CRL_SEP)) != NULL); x509_ncrls = i - 1; /* Decode the certificate chain. */ siz = cert_size; ptr = cert; i = 1; do { x509_cert_list = (gnutls_x509_crt *) realloc( x509_cert_list, i * sizeof(gnutls_x509_crt)); if (x509_cert_list == NULL) { fprintf(stderr, "memory error\n"); exit(1); } tmp.data = (char*)ptr; tmp.size = siz; ret = gnutls_x509_crt_init( &x509_cert_list[i-1]); if (ret < 0) { fprintf(stderr, "Error parsing the certificate[%d]: %s\n", i, gnutls_strerror(ret)); exit(1); } ret = gnutls_x509_crt_import( x509_cert_list[i-1], &tmp, GNUTLS_X509_FMT_PEM); if (ret < 0) { fprintf(stderr, "Error parsing the certificate[%d]: %s\n", i, gnutls_strerror(ret)); exit(1); } if (i-1 != 0) { /* verify the previous certificate using this one * as CA. */ name_size = sizeof(name); ret = gnutls_x509_crt_get_dn( x509_cert_list[i-2], name, &name_size); if (ret < 0) { fprintf(stderr, "Error in get_dn: %s\n", gnutls_strerror(ret)); exit(1); } fprintf( outfile, "Certificate[%d]: %s\n", i-2, name); /* print issuer */ issuer_name_size = sizeof(issuer_name); ret = gnutls_x509_crt_get_issuer_dn( x509_cert_list[i-2], issuer_name, &issuer_name_size); if (ret < 0) { fprintf(stderr, "Error in get_dn: %s\n", gnutls_strerror(ret)); exit(1); } fprintf( outfile, "\tIssued by: %s\n", name); /* Get the Issuer's name */ name_size = sizeof(name); ret = gnutls_x509_crt_get_dn( x509_cert_list[i-1], name, &name_size); if (ret < 0) { fprintf(stderr, "Error in get_dn: %s\n", gnutls_strerror(ret)); exit(1); } fprintf( outfile, "\tVerifying against certificate[%d].\n", i-1); if (strcmp( issuer_name, name) != 0) { fprintf(stderr, "Error: Issuer's name: %s\n", name); fprintf(stderr, "Error: Issuer's name does not match the next certificate.\n"); exit(1); } fprintf( outfile, "\tVerification output: "); print_verification_res( x509_cert_list[i-2], x509_cert_list[i-1], x509_crl_list, x509_ncrls); fprintf( outfile, ".\n\n"); } /* now we move ptr after the pem header */ ptr = strstr(ptr, CERT_SEP); if (ptr!=NULL) ptr++; i++; } while ((ptr = strstr(ptr, CERT_SEP)) != NULL); x509_ncerts = i - 1; /* The last certificate in the list will be used as * a CA (should be self signed). */ name_size = sizeof(name); ret = gnutls_x509_crt_get_dn( x509_cert_list[x509_ncerts-1], name, &name_size); if (ret < 0) { fprintf(stderr, "Error in get_dn: %s\n", gnutls_strerror(ret)); exit(1); } fprintf( outfile, "Certificate[%d]: %s\n", x509_ncerts-1, name); /* print issuer */ issuer_name_size = sizeof(issuer_name); ret = gnutls_x509_crt_get_issuer_dn( x509_cert_list[x509_ncerts-1], issuer_name, &issuer_name_size); if (ret < 0) { fprintf(stderr, "Error in get_dn: %s\n", gnutls_strerror(ret)); exit(1); } fprintf( outfile, "\tIssued by: %s\n", name); if (strcmp( issuer_name, name) != 0) { fprintf(stderr, "Error: The last certificate is not self signed.\n"); exit(1); } fprintf( outfile, "\tVerification output: "); print_verification_res( x509_cert_list[x509_ncerts-1], x509_cert_list[x509_ncerts-1], x509_crl_list, x509_ncrls); fprintf( outfile, ".\n\n"); for (i=0;i now) { if (comma) fprintf(outfile, ", "); comma = 1; fprintf(outfile, "Not activated"); } if (gnutls_x509_crt_get_expiration_time(crt) < now) { if (comma) fprintf(outfile, ", "); comma = 1; fprintf(outfile, "Expired"); } ret = gnutls_x509_crt_check_revocation( crt, crl_list, crl_list_size); if (ret < 0) { fprintf(stderr, "Error in verification: %s\n", gnutls_strerror(ret)); exit(1); } if (ret == 1) { /* revoked */ if (comma) fprintf(outfile, ", "); comma = 1; fprintf(outfile, "Revoked"); } } void verify_chain( void) { size_t size; size = fread( buffer, 1, sizeof(buffer)-1, infile); buffer[size] = 0; _verify_x509_mem( buffer, size); } void verify_crl( void) { size_t size, dn_size; char dn[128]; unsigned int output; int comma=0; int ret; gnutls_datum pem; gnutls_x509_crl crl; time_t now = time(0); gnutls_x509_crt issuer; issuer = load_ca_cert(); fprintf(outfile, "\nCA certificate:\n"); dn_size = sizeof(dn); ret = gnutls_x509_crt_get_dn(issuer, dn, &dn_size); if (ret >= 0) fprintf(outfile, "\tSubject: %s\n\n", dn); size = fread( buffer, 1, sizeof(buffer)-1, infile); buffer[size] = 0; pem.data = buffer; pem.size = size; gnutls_x509_crl_init( &crl); ret = gnutls_x509_crl_import(crl, &pem, in_cert_format); if (ret < 0) { fprintf(stderr, "CRL decoding error: %s\n", gnutls_strerror(ret)); exit(1); } print_crl_info( crl, outfile, 1); fprintf(outfile, "Verification output: "); ret = gnutls_x509_crl_verify( crl, &issuer, 1, 0, &output); if (ret < 0) { fprintf(stderr, "Error in verification: %s\n", gnutls_strerror(ret)); exit(1); } if (output&GNUTLS_CERT_INVALID) { fprintf(outfile, "Not verified"); comma = 1; } else { fprintf(outfile, "Verified"); comma = 1; } if (output&GNUTLS_CERT_SIGNER_NOT_CA) { if (comma) fprintf(outfile, ", "); fprintf(outfile, "Issuer is not a CA"); comma = 1; } /* Check expiration dates. */ if (gnutls_x509_crl_get_this_update(crl) > now) { if (comma) fprintf(outfile, ", "); comma = 1; fprintf(outfile, "Issued in the future!"); } if (gnutls_x509_crl_get_next_update(crl) < now) { if (comma) fprintf(outfile, ", "); comma = 1; fprintf(outfile, "CRL is not up to date"); } fprintf(outfile, "\n"); } #include #include void generate_pkcs12( void) { gnutls_pkcs12 pkcs12; gnutls_pkcs12_bag bag, kbag; gnutls_x509_crt crt; gnutls_x509_privkey key; int result; size_t size; gnutls_datum data; const char* password; const char* name; unsigned int flags; gnutls_datum key_id; unsigned char _key_id[20]; int index; fprintf(stderr, "Generating a PKCS #12 structure...\n"); key = load_private_key(1); crt = load_cert(0); name = get_pkcs12_key_name(); password = get_pass(); result = gnutls_pkcs12_bag_init( &bag); if (result < 0) { fprintf(stderr, "bag_init: %s\n", gnutls_strerror(result)); exit(1); } size = sizeof(_key_id); result = gnutls_x509_privkey_get_key_id( key, 0, _key_id, &size); if (result < 0) { fprintf(stderr, "key_id: %s\n", gnutls_strerror(result)); exit(1); } key_id.data = _key_id; key_id.size = size; if (crt) { /* add the certificate only if it was specified. */ result = gnutls_pkcs12_bag_set_crt( bag, crt); if (result < 0) { fprintf(stderr, "set_crt: %s\n", gnutls_strerror(result)); exit(1); } index = result; result = gnutls_pkcs12_bag_set_friendly_name( bag, index, name); if (result < 0) { fprintf(stderr, "bag_set_key_id: %s\n", gnutls_strerror(result)); exit(1); } result = gnutls_pkcs12_bag_set_key_id( bag, index, &key_id); if (result < 0) { fprintf(stderr, "bag_set_key_id: %s\n", gnutls_strerror(result)); exit(1); } if (info.export) flags = GNUTLS_PKCS_USE_PKCS12_RC2_40; else flags = GNUTLS_PKCS8_USE_PKCS12_3DES; result = gnutls_pkcs12_bag_encrypt( bag, password, flags); if (result < 0) { fprintf(stderr, "bag_encrypt: %s\n", gnutls_strerror(result)); exit(1); } } /* Key BAG */ result = gnutls_pkcs12_bag_init( &kbag); if (result < 0) { fprintf(stderr, "bag_init: %s\n", gnutls_strerror(result)); exit(1); } if (info.export) flags = GNUTLS_PKCS_USE_PKCS12_RC2_40; else flags = GNUTLS_PKCS_USE_PKCS12_3DES; size = sizeof(buffer); result = gnutls_x509_privkey_export_pkcs8( key, GNUTLS_X509_FMT_DER, password, flags, buffer, &size); if (result < 0) { fprintf(stderr, "key_export: %s\n", gnutls_strerror(result)); exit(1); } data.data = buffer; data.size = size; result = gnutls_pkcs12_bag_set_data( kbag, GNUTLS_BAG_PKCS8_ENCRYPTED_KEY, &data); if (result < 0) { fprintf(stderr, "bag_set_data: %s\n", gnutls_strerror(result)); exit(1); } index = result; result = gnutls_pkcs12_bag_set_friendly_name( kbag, index, name); if (result < 0) { fprintf(stderr, "bag_set_key_id: %s\n", gnutls_strerror(result)); exit(1); } result = gnutls_pkcs12_bag_set_key_id( kbag, result, &key_id); if (result < 0) { fprintf(stderr, "bag_set_key_id: %s\n", gnutls_strerror(result)); exit(1); } /* write the PKCS #12 structure. */ result = gnutls_pkcs12_init(&pkcs12); if (result < 0) { fprintf(stderr, "crt_sign: %s\n", gnutls_strerror(result)); exit(1); } if (crt) { result = gnutls_pkcs12_set_bag( pkcs12, bag); if (result < 0) { fprintf(stderr, "set_bag: %s\n", gnutls_strerror(result)); exit(1); } } result = gnutls_pkcs12_set_bag( pkcs12, kbag); if (result < 0) { fprintf(stderr, "set_bag: %s\n", gnutls_strerror(result)); exit(1); } result = gnutls_pkcs12_generate_mac( pkcs12, password); if (result < 0) { fprintf(stderr, "generate_mac: %s\n", gnutls_strerror(result)); exit(1); } size = sizeof(buffer); result = gnutls_pkcs12_export( pkcs12, out_cert_format, buffer, &size); if (result < 0) { fprintf(stderr, "pkcs12_export: %s\n", gnutls_strerror(result)); exit(1); } fwrite( buffer, 1, size, outfile); } const char* BAGTYPE( gnutls_pkcs12_bag_type x) { switch (x) { case GNUTLS_BAG_PKCS8_ENCRYPTED_KEY: return "PKCS #8 Encrypted key"; case GNUTLS_BAG_EMPTY: return "Empty"; case GNUTLS_BAG_PKCS8_KEY: return "PKCS #8 Key"; case GNUTLS_BAG_CERTIFICATE: return "Certificate"; case GNUTLS_BAG_ENCRYPTED: return "Encrypted"; case GNUTLS_BAG_CRL: return "CRL"; default: return "Unknown"; } } void print_bag_data(gnutls_pkcs12_bag bag) { int result; int count, i, type; gnutls_datum cdata; const char* str; gnutls_datum out; count = gnutls_pkcs12_bag_get_count( bag); if (count < 0) { fprintf(stderr, "get_count: %s\n", gnutls_strerror(count)); exit(1); } fprintf( outfile, "\tElements: %d\n", count); for (i=0;i 0) fprintf(outfile, "Certificates: %u\n", count); for (index = 0;index < count;index++) { size = sizeof(buffer); result = gnutls_pkcs7_get_crt_raw( pkcs7, index, buffer, &size); if (result < 0) { break; } data.data = buffer; data.size = size; result = gnutls_pem_base64_encode_alloc( "CERTIFICATE", &data, &b64); if (result < 0) { fprintf(stderr, "error encoding: %s\n", gnutls_strerror(result)); exit(1); } fputs( b64.data, outfile); fputs( "\n", outfile); gnutls_free( b64.data); } /* Read the CRLs now. */ result = gnutls_pkcs7_get_crl_count( pkcs7); if (result < 0) { fprintf(stderr, "p7_count: %s\n", gnutls_strerror(result)); exit(1); } count = result; if (count > 0) fprintf(outfile, "\nCRLs: %u\n", count); for (index = 0;index < count;index++) { size = sizeof(buffer); result = gnutls_pkcs7_get_crl_raw( pkcs7, index, buffer, &size); if (result < 0) { break; } data.data = buffer; data.size = size; result = gnutls_pem_base64_encode_alloc( "X509 CRL", &data, &b64); if (result < 0) { fprintf(stderr, "error encoding: %s\n", gnutls_strerror(result)); exit(1); } fputs( b64.data, outfile); fputs( "\n", outfile); gnutls_free( b64.data); } } #else /* ENABLE_PKI */ #include int main (int argc, char **argv) { printf ("\nX.509 PKI not supported. This program is a dummy.\n\n"); return 1; }; #endif void certtool_version(void) { fprintf(stderr, "certtool, "); fprintf(stderr, "version %s. Libgnutls %s.\n", LIBGNUTLS_VERSION, gnutls_check_version(NULL)); } void print_license(void) { fputs( "\nCopyright (C) 2004 Free Software Foundation\n" "This program is free software; you can redistribute it and/or modify \n" "it under the terms of the GNU General Public License as published by \n" "the Free Software Foundation; either version 2 of the License, or \n" "(at your option) any later version. \n" "\n" "This program is distributed in the hope that it will be useful, \n" "but WITHOUT ANY WARRANTY; without even the implied warranty of \n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the \n" "GNU General Public License for more details. \n" "\n" "You should have received a copy of the GNU General Public License \n" "along with this program; if not, write to the Free Software \n" "Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n\n", stdout); }