/* * The contents of this file are subject to the AOLserver Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://aolserver.com/. * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is AOLserver Code and related documentation * distributed by AOL. * * The Initial Developer of the Original Code is America Online, * Inc. Portions created by AOL are Copyright (C) 1999 America Online, * Inc. All Rights Reserved. * * Additional code: * * Copyright 2002 Daniel P. Stasinski * Contact: daniel@avenues.org * * Alternatively, the contents of this file may be used under the terms * of the GNU General Public License (the "GPL"), in which case the * provisions of GPL are applicable instead of those above. If you wish * to allow use of your version of this file only under the terms of the * GPL and not to allow others to use your version of this file under the * License, indicate your decision by deleting the provisions above and * replace them with the notice and other provisions required by the GPL. * If you do not delete the provisions above, a recipient may use your * version of this file under either the License or the GPL. */ /* * nsencrypt.c -- * * Public key cipher module * * * Version: 0.4 */ static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/nsencrypt/nsencrypt.c,v 1.6 2005/01/27 20:03:46 dossy Exp $, compiled: " __DATE__ " " __TIME__; #include "ns.h" #include #include #ifdef _WIN32 #include #define bzero(mem, sz) memset((mem), 0, (sz)) #define bcopy(src, dest, count) memmove((dest), (src), (count)) #else #include #endif #include #include #include #include #include #include #include #include #define USE_CIPHER_BLOWFISH #define USE_CIPHER_3DES #define USE_CIPHER_CAST5 #undef USE_CIPHER_IDEA #undef USE_CIPHER_AES #define CIPHER_BLOWFISH 1 #define CIPHER_3DES 2 #define CIPHER_CAST5 3 #define CIPHER_IDEA 4 #define CIPHER_AES 5 #define DEFAULT_KEYSIZE_BLOWFISH 128 #define DEFAULT_KEYSIZE_3DES 168 #define DEFAULT_KEYSIZE_CAST5 128 #define DEFAULT_KEYSIZE_IDEA 128 #define DEFAULT_KEYSIZE_AES 128 #define KEY_PUBLIC 1 #define KEY_PRIVATE 2 #define CMD_ENCRYPT 1 #define CMD_DECRYPT 2 #define MAX_IV_LENGTH 16 #define b64_sizeof(x) (((sizeof(x) + 2) / 3) * 4) #define b64_size(x) (((x + 2) / 3) * 4) #define BADARGS(nl,nh,example) \ if ((argc<(nl)) || (argc>(nh))) { \ Tcl_AppendResult(interp,"wrong # args: should be \"",argv[0], \ (example),"\"",NULL); \ return NS_ERROR; \ } typedef struct { int cipher; int sessionkeylen; long plaintextlen; char sha1[EVP_MAX_MD_SIZE]; char sessioniv[MAX_IV_LENGTH]; char sessionkey[0]; /* THIS MUST BE LAST. DO NOT RESIZE */ } ENC_HEADER; RSA *rsaprivatekey = NULL; RSA *rsapublickey = NULL; int Ns_ModuleVersion = 1; int Ns_ModuleInit(char *hServer, char *hModule); static Ns_Callback ModuleCleanup; static int encryptInterpInit(Tcl_Interp * interp, void *context); static int ns_encrypt(ClientData context, Tcl_Interp * interp, int argc, char **argv); static int ns_decrypt(ClientData context, Tcl_Interp * interp, int argc, char **argv); static char randfile[] = "/dev/urandom"; /* *---------------------------------------------------------------------- * * Ns_ModuleInit -- * * This is the nsencrypt module's entry point. AOLserver runs * this function right after the module is loaded. It is used to * read configuration data, initialize data structures, kick off * the Tcl initialization function (if any), and do other things * at startup. * * Results: * NS_OK or NS_ERROR * *---------------------------------------------------------------------- */ int Ns_ModuleInit(char *hServer, char *hModule) { FILE *fd = NULL; Ns_DString ds; char *publickeyfile = NULL; char *privatekeyfile = NULL; char *path = NULL; ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); OpenSSL_add_all_ciphers(); OpenSSL_add_all_digests(); #ifndef _WIN32 RAND_load_file(randfile, 4096); #else { HCRYPTPROV hProv = 0; BOOL bSuccess = FALSE; BYTE pGoop[4096]; DWORD cbGoop = sizeof(pGoop); if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { if (CryptGenRandom(hProv, cbGoop, pGoop)) bSuccess = TRUE; } if (hProv) CryptReleaseContext(hProv, 0); if (!bSuccess) { Ns_Log(Error, "%s: Could not generate random bytes", hModule); } RAND_seed(pGoop, cbGoop); } #endif /* _WIN32 */ Ns_DStringInit(&ds); path = Ns_ConfigGetPath(hServer, hModule, NULL); if (path) publickeyfile = Ns_ConfigGetValue(path, "PubKeyFile"); if (publickeyfile) { Ns_ModulePath(&ds, hServer, hModule, publickeyfile, NULL); if ((fd = fopen(ds.string, "r"))) { rsapublickey = PEM_read_RSA_PUBKEY(fd, NULL, 0, NULL); if (rsapublickey == NULL) Ns_Log(Debug, "%s: Invalid public key file: %s", hModule, ds.string); else Ns_Log(Notice, "%s: Loaded public key file: %s", hModule, ds.string); fclose(fd); } else Ns_Log(Notice, "%s: Public key file not found: %s", hModule, ds.string); } else Ns_Log(Notice, "%s: No public key loaded.", hModule); Ns_DStringTrunc(&ds, 0); if (path) privatekeyfile = Ns_ConfigGetValue(path, "PrivKeyFile"); if (privatekeyfile) { Ns_ModulePath(&ds, hServer, hModule, privatekeyfile, NULL); if ((fd = fopen(ds.string, "r"))) { rsaprivatekey = PEM_read_RSAPrivateKey(fd, NULL, 0, NULL); if (rsaprivatekey == NULL) Ns_Log(Debug, "%s: Invalid private key file: %s", hModule, ds.string); else Ns_Log(Notice, "%s: Loaded private key file: %s", hModule, ds.string); fclose(fd); } else Ns_Log(Notice, "%s: Private key file not found: %s", hModule, ds.string); } else Ns_Log(Notice, "%s: No private key loaded.", hModule); Ns_DStringFree(&ds); Ns_RegisterShutdown(ModuleCleanup, NULL); return (Ns_TclInitInterps(hServer, encryptInterpInit, NULL)); } /* *---------------------------------------------------------------------- * * ModuleCleanup -- * * Deallocates allocated resources. * * Results: * None * *---------------------------------------------------------------------- */ static void ModuleCleanup(void *ignored) { if (rsapublickey) RSA_free(rsapublickey); if (rsaprivatekey) RSA_free(rsaprivatekey); } /* *---------------------------------------------------------------------- * * encryptInterpInit -- * * Register new commands with the Tcl interpreter. * * Results: * NS_OK or NS_ERROR * *---------------------------------------------------------------------- */ static int encryptInterpInit(Tcl_Interp * interp, void *context) { Tcl_CreateCommand(interp, "ns_encrypt", ns_encrypt, NULL, NULL); Tcl_CreateCommand(interp, "ns_decrypt", ns_decrypt, NULL, NULL); return NS_OK; } /* *---------------------------------------------------------------------- * * get_default_keysize -- * * Get default key size for various encryption methods. * *---------------------------------------------------------------------- */ int get_default_keysize(int cipher) { switch (cipher) { case CIPHER_BLOWFISH: return DEFAULT_KEYSIZE_BLOWFISH; case CIPHER_3DES: return DEFAULT_KEYSIZE_3DES; case CIPHER_CAST5: return DEFAULT_KEYSIZE_CAST5; case CIPHER_IDEA: return DEFAULT_KEYSIZE_IDEA; case CIPHER_AES: return DEFAULT_KEYSIZE_AES; } return (0); } /* *---------------------------------------------------------------------- * * is_valid_keysize -- * * Determines if key size is correct for selected encryption mode. * *---------------------------------------------------------------------- */ int is_valid_keysize(int cipher, int keysize, RSA * rsakey) { if (keysize > ((RSA_size(rsakey) - 11) << 3)) return 0; switch (cipher) { case CIPHER_BLOWFISH: if (!(keysize % 8) && (keysize >= 32) && (keysize <= 448)) return 1; break; case CIPHER_3DES: if (keysize == 168) return 1; break; case CIPHER_CAST5: if (!(keysize % 8) && (keysize >= 40) && (keysize <= 128)) return 1; break; case CIPHER_IDEA: if (keysize == 128) return 1; break; case CIPHER_AES: if ((keysize == 128) || (keysize == 192) || (keysize == 256)) return 1; break; } return (0); } /* *---------------------------------------------------------------------- * * is_valid_cipher -- * * Determines if cipher is available * *---------------------------------------------------------------------- */ int is_valid_cipher(int cipher) { switch (cipher) { #ifdef USE_CIPHER_BLOWFISH case CIPHER_BLOWFISH: return 1; #endif #ifdef USE_CIPHER_3DES case CIPHER_3DES: return 1; #endif #ifdef USE_CIPHER_CAST5 case CIPHER_CAST5: return 1; #endif #ifdef USE_CIPHER_IDEA case CIPHER_IDEA: return 1; #endif #ifdef USE_CIPHER_AES case CIPHER_AES: return 1; #endif } return (0); } /* *---------------------------------------------------------------------- * * crypt_init -- * * Initialize the encryption mode. * *---------------------------------------------------------------------- */ int crypt_init(EVP_CIPHER_CTX * ctx, int cipher, int cmd, int keylen, unsigned char *key, unsigned char *iv) { EVP_CIPHER *evp_cipher = NULL; switch (cipher) { #ifdef USE_CIPHER_BLOWFISH case CIPHER_BLOWFISH: evp_cipher = EVP_bf_cbc(); break; #endif #ifdef USE_CIPHER_3DES case CIPHER_3DES: evp_cipher = EVP_des_ede3_cbc(); break; #endif #ifdef USE_CIPHER_CAST5 case CIPHER_CAST5: evp_cipher = EVP_cast5_cbc(); break; #endif #ifdef USE_CIPHER_IDEA case CIPHER_IDEA: evp_cipher = EVP_idea_cbc(); break; #endif #ifdef USE_CIPHER_AES case CIPHER_AES: switch (keylen) { case 16: evp_cipher = EVP_aes_128_cbc(); break; case 24: evp_cipher = EVP_aes_192_cbc(); break; case 32: evp_cipher = EVP_aes_256_cbc(); break; } break; #endif } if (evp_cipher == NULL) { return 0; } else if (cmd == CMD_ENCRYPT) { if (!(EVP_EncryptInit(ctx, evp_cipher, NULL, NULL))) return 0; EVP_CIPHER_CTX_set_key_length(ctx, keylen); EVP_EncryptInit(ctx, NULL, key, iv); } else if (cmd == CMD_DECRYPT) { if (!(EVP_DecryptInit(ctx, evp_cipher, NULL, NULL))) return 0; EVP_CIPHER_CTX_set_key_length(ctx, keylen); EVP_DecryptInit(ctx, NULL, key, iv); } return 1; } /* *---------------------------------------------------------------------- * * EncryptIt -- * * Main encryption code. * *---------------------------------------------------------------------- */ char * EncryptIt(char *plaintext, int cipher, int keylen, int rsakeytype, RSA * rsakey, int *retval) { EVP_CIPHER_CTX ctx; EVP_MD_CTX sha1ctx; EVP_ENCODE_CTX b64ctx; int tmp, ol, cl; char *ciphertext; ENC_HEADER * header; int headerlen; int resultlen; char *sessionkey; char *result; headerlen = sizeof(ENC_HEADER) + RSA_size(rsakey); header = ns_malloc(headerlen); bzero(header, headerlen); header->cipher = cipher; header->sessionkeylen = keylen; header->plaintextlen = strlen(plaintext); sessionkey = ns_malloc(keylen); RAND_bytes(sessionkey, keylen); RAND_bytes(header->sessioniv, MAX_IV_LENGTH); EVP_DigestInit(&sha1ctx, EVP_sha1()); EVP_DigestUpdate(&sha1ctx, sessionkey, keylen); EVP_DigestUpdate(&sha1ctx, plaintext, header->plaintextlen); EVP_DigestFinal(&sha1ctx, header->sha1, NULL); ciphertext = ns_malloc(((header->plaintextlen / 8) + 1) * 8); crypt_init(&ctx, cipher, CMD_ENCRYPT, keylen, sessionkey, header->sessioniv); cl = 0; EVP_EncryptUpdate(&ctx, &ciphertext[cl], &tmp, plaintext, header->plaintextlen); cl += tmp; EVP_EncryptFinal(&ctx, &ciphertext[cl], &tmp); cl += tmp; if (rsakeytype == KEY_PRIVATE) RSA_private_encrypt(keylen, sessionkey, header->sessionkey, rsakey, RSA_PKCS1_OAEP_PADDING); else RSA_public_encrypt(keylen, sessionkey, header->sessionkey, rsakey, RSA_PKCS1_OAEP_PADDING); resultlen = 1 + b64_size(headerlen) + b64_size((((cl / 8) + 1) * 8)); resultlen += ((resultlen + 65) / 65) + 1; result = (char *) ns_malloc(resultlen); bzero(result, resultlen); EVP_EncodeInit(&b64ctx); ol = 0; EVP_EncodeUpdate(&b64ctx, &result[ol], &tmp, (char *) header, headerlen); ol += tmp; EVP_EncodeUpdate(&b64ctx, &result[ol], &tmp, ciphertext, cl); ol += tmp; EVP_EncodeFinal(&b64ctx, &result[ol], &tmp); ol += tmp; result[ol] = 0; ns_free(ciphertext); ns_free(sessionkey); ns_free(header); return result; } /* *---------------------------------------------------------------------- * * DecryptIt -- * * Main decryption code. * *---------------------------------------------------------------------- */ char *DecryptIt(char *r, int rsakeytype, RSA * rsakey) { EVP_CIPHER_CTX ctx; EVP_ENCODE_CTX b64ctx; ENC_HEADER *header; int tmp, ol, cl, dl; int headerlen; char *data; EVP_MD_CTX sha1ctx; char sha1[EVP_MAX_MD_SIZE]; char *sessionkey; headerlen = sizeof(ENC_HEADER) + RSA_size(rsakey); header = ns_malloc(headerlen); bzero(header, headerlen); cl = strlen(r); data = ns_malloc((cl / 4) * 3); bzero(data, (cl / 4) * 3); EVP_DecodeInit(&b64ctx); dl = 0; EVP_DecodeUpdate(&b64ctx, &data[dl], &tmp, r, cl); dl += tmp; EVP_DecodeFinal(&b64ctx, &data[dl], &tmp); dl += tmp; bcopy(data, header, headerlen); sessionkey = ns_malloc(header->sessionkeylen); if (rsakeytype == KEY_PRIVATE) RSA_private_decrypt(RSA_size(rsakey), header->sessionkey, sessionkey, rsakey, RSA_PKCS1_OAEP_PADDING); else RSA_public_decrypt(RSA_size(rsakey), header->sessionkey, sessionkey, rsakey, RSA_PKCS1_OAEP_PADDING); crypt_init(&ctx, header->cipher, CMD_DECRYPT, header->sessionkeylen, sessionkey, header->sessioniv); ol = 0; EVP_DecryptUpdate(&ctx, &data[ol], &tmp, &data[headerlen], dl - headerlen); ol += tmp; EVP_DecryptFinal(&ctx, &data[ol], &tmp); ol += tmp; data[ol] = 0; bzero(sha1, sizeof(sha1)); EVP_DigestInit(&sha1ctx, EVP_sha1()); EVP_DigestUpdate(&sha1ctx, sessionkey, header->sessionkeylen); EVP_DigestUpdate(&sha1ctx, data, header->plaintextlen); EVP_DigestFinal(&sha1ctx, sha1, NULL); ns_free(header); ns_free(sessionkey); if (memcmp(sha1, header->sha1, EVP_MAX_MD_SIZE)) { ns_free(data); data = NULL; } return data; } /* *---------------------------------------------------------------------- * * ns_encrypt -- * * Encrypts text and returns base64 encoded ciphertext. * * Results: * NS_OK * *---------------------------------------------------------------------- */ static int ns_encrypt(ClientData context, Tcl_Interp * interp, int argc, char **argv) { RSA *rsakey = NULL; FILE *fd = NULL; int keybits = 0; char *result; char *keyfile = NULL; int firstarg = 1; int rsakeytype = KEY_PUBLIC; int cipher; char *plaintext; int retval; rsakey = rsapublickey; cipher = CIPHER_BLOWFISH; BADARGS(2, 6, " -blowfish -3des -cast5 -idea -keyfile -keysize -public -private plaintext"); while (argc--) { if (strcasecmp(argv[firstarg], "-blowfish") == 0) { cipher = CIPHER_BLOWFISH; } else if (strcasecmp(argv[firstarg], "-3des") == 0) { cipher = CIPHER_3DES; } else if (strcasecmp(argv[firstarg], "-cast5") == 0) { cipher = CIPHER_CAST5; } else if (strcasecmp(argv[firstarg], "-idea") == 0) { cipher = CIPHER_IDEA; } else if (strcasecmp(argv[firstarg], "-public") == 0) { rsakeytype = KEY_PUBLIC; rsakey = rsapublickey; } else if (strcasecmp(argv[firstarg], "-private") == 0) { rsakeytype = KEY_PRIVATE; rsakey = rsaprivatekey; } else if (strcasecmp(argv[firstarg], "-keyfile") == 0) { keyfile = argv[++firstarg]; } else if (strcasecmp(argv[firstarg], "-keysize") == 0) { if (Tcl_GetInt(interp, argv[++firstarg], &keybits) != TCL_OK) { Tcl_AppendResult(interp, "Invalid key size.", NULL); return NS_ERROR; } } else if (strcasecmp(argv[firstarg], "-") == 0) { break; } else if (strncasecmp(argv[firstarg], "-", 1) == 0) { Tcl_AppendResult(interp, "Invalid option: ", argv[firstarg], NULL); return NS_ERROR; } else { break; } firstarg++; } if (!is_valid_cipher(cipher)) { Tcl_AppendResult(interp, "Cipher unavailable.", NULL); return NS_ERROR; } plaintext = argv[firstarg++]; if (keyfile != NULL) { if ((fd = fopen(keyfile, "r"))) { if (rsakeytype == KEY_PRIVATE) rsakey = PEM_read_RSAPrivateKey(fd, NULL, 0, NULL); else rsakey = PEM_read_RSA_PUBKEY(fd, NULL, 0, NULL); fclose(fd); if (rsakey == NULL) { Tcl_AppendResult(interp, "Invalid keyfile: ", keyfile, NULL); return NS_ERROR; } } else { Tcl_AppendResult(interp, "Keyfile not found: ", keyfile, NULL); return NS_ERROR; } } if (rsakey == NULL) { Tcl_AppendResult(interp, "No Keyfiles loaded.", NULL); return NS_ERROR; } if (!keybits) keybits = get_default_keysize(cipher); if (!is_valid_keysize(cipher, keybits, rsakey)) { Tcl_AppendResult(interp, "Invalid key size.", NULL); if (keyfile != NULL) { RSA_free(rsakey); } return NS_ERROR; } result = EncryptIt(plaintext, cipher, keybits >> 3, rsakeytype, rsakey, &retval); Tcl_AppendResult(interp, result, NULL); ns_free(result); if (keyfile != NULL) { RSA_free(rsakey); } return NS_OK; } /* *---------------------------------------------------------------------- * * ns_decrypt -- * * Decrypts base64 encoded ciphertext into plaintext. * * Results: * NS_OK * *---------------------------------------------------------------------- */ static int ns_decrypt(ClientData context, Tcl_Interp * interp, int argc, char **argv) { FILE *fd = NULL; RSA *rsakey = NULL; char *result, *r; char *keyfile = NULL; int firstarg = 1; int rsakeytype = KEY_PRIVATE; rsakey = rsaprivatekey; BADARGS(2, 4, " -public -private -keyfile ciphertext"); while (argc--) { if (strcasecmp(argv[firstarg], "-public") == 0) { rsakeytype = KEY_PUBLIC; rsakey = rsapublickey; } else if (strcasecmp(argv[firstarg], "-private") == 0) { rsakeytype = KEY_PRIVATE; rsakey = rsaprivatekey; } else if (strcasecmp(argv[firstarg], "-keyfile") == 0) { keyfile = argv[++firstarg]; } else if (strcasecmp(argv[firstarg], "-") == 0) { rsakey = rsaprivatekey; break; } else if (strncasecmp(argv[firstarg], "-", 1) == 0) { Tcl_AppendResult(interp, "Invalid option: ", argv[firstarg], NULL); return NS_ERROR; } else { break; } firstarg++; } r = argv[firstarg++]; if (keyfile != NULL) { if ((fd = fopen(keyfile, "r"))) { if (rsakeytype == KEY_PRIVATE) rsakey = PEM_read_RSAPrivateKey(fd, NULL, 0, NULL); else rsakey = PEM_read_RSA_PUBKEY(fd, NULL, 0, NULL); fclose(fd); if (rsakey == NULL) { Tcl_AppendResult(interp, "Invalid keyfile: ", keyfile, NULL); return NS_ERROR; } } else { Tcl_AppendResult(interp, "Keyfile not found: ", keyfile, NULL); return NS_ERROR; } } if (rsakey == NULL) { Tcl_AppendResult(interp, "No Keyfiles loaded.", NULL); return NS_ERROR; } result = DecryptIt(r, rsakeytype, rsakey); if (keyfile != NULL) { RSA_free(rsakey); } if (result == NULL) { Tcl_AppendResult(interp, "Encrypted data is corrupt.", NULL); return NS_ERROR; } Tcl_AppendResult(interp, result, NULL); ns_free(result); return NS_OK; }