2

(抱歉,这很冗长)我正在尝试将 OpenSSL 支持添加到用 Objective C for OS X 10.6(Snow Leopard)编写的 Cocoa 应用程序中。为了简化问题,我有一个小型包装类,它包含各种 BIO 和密码上下文结构,AETOpenSSLWrapper。如下所示

。H

@interface AETOpenSSLWrapper: public NSObject
{
   BIO *writeBIO,encBIO;
   EVP_CIPHER_CTX *ctx;
   unsigned char *readWriteBuff;
}

@property (readwrite,assign) BIO *writeBIO,*encBIO;
@property (readwrite,assign) EVP_CIPHER_CTX *ctx;
@property (readwrite,assign) unsigned char *readWriteBuff;

-(id)init;
...
-(void)dealloc;
@end

.m

@implementation AETOpenSSLWrapper

@synthesize writeBIO,encBIO,ctx,readWriteBuff;

-(id)init
{
   self=[super init];
   if(self)
      {
      writeBIO=BIO_new(BIO_s_file());
      encBIO=...
      ctx=...
      buff=...
      (error handling omitted)
      }

   return self;
}
@end

然后各种实用方法链接 BIO,写入输出 BIO,刷新等,特别是一个 -(void)pushEncryptingBIO 链接加密过滤器 BIO(已使用密钥、盐和初始向量初始化)

-(void)pushEncryptingBIO
{
   writeBIO=BIO_push(encBIO,writeBIO);
}

最后是我的 dealloc 例程。这是直接从openssl-1.0.1c 发行版提供的enc程序中提取的

-(void)dealloc
{
   if(readWriteBuff!=NULL)
      OPENSSL_free(readWriteBuff);
   if(writeBIO!=NULL)
      BIO_free_all(writeBIO);
   if(encBIO!=NULL) <----------- this looks wrong
      BIO_free(encBIO); <---+

   [super dealloc];
}

等效代码位于加密输入缓冲区的循环之前以及 openssl 源代码树中 apps/enc.c 中的 MAIN() 例程的末尾:

第 657 - 658 行

if (benc != NULL)
   wbio=BIO_push(benc,wbio);

和第 682 - 688 行

end:
   ERR_print_errors(bio_err);
   if (strbuf != NULL) OPENSSL_free(strbuf);
   if (buff != NULL) OPENSSL_free(buff);
   if (in != NULL) BIO_free(in);
   if (out != NULL) BIO_free_all(out);
   if (benc != NULL) BIO_free(benc); <--- are we sure about this?

问题是(最后):由于 BIO 被推送到链上,该调用BIO_free(benc)(或在我的代码中)是否应该存在,该链被释放?查看 的实现,它只是沿着 BIO 链运行,递减 ref 计数并在执行过程中释放,并且不会将指针清空。这看起来一定是一个错误,但显然我不愿意假设 SSL 维护人员错过了这一点,而我没有。如果我留下电话,我偶尔会遇到崩溃(十分之一),如果我不这样做,我不会,但我不想要泄漏。这是在 Apple 事件处理程序中,因此调试更加复杂。有什么建议么?BIO_free(encBIO)writeBIO/outBIO_free_allBIO_free_allBIO_free(encBIO)

4

1 回答 1

1

前段时间我有机会与 BIO 合作,我认为你是对的。从 OpenSSL 手册页:

BIO_free_all() 释放整个 BIO 链,如果发生错误释放链中的单个 BIO,它不会停止。

此外,由于 OpenSSL 清理函数采用指向结构的指针,它们不能更改指针的值(即,它需要指针的地址才能这样做)。即使 OpenSSL 将指针参数设置为 NULL,也只有副本为 NULL,结果证明是无用的行为。因此,您可以将其设置为 NULL。

举一个具体的例子,下面的 C++ 代码用于使用 PKCS#5 标准加密纯文本。它接收密码作为参数,该密码将用于派生 AES 密钥。以下代码不会泄漏(使用 valgrind 检查)。

static int ENCRYPTION_FAILED = 1;
static const EVP_MD *MD = EVP_sha256();
static const EVP_CIPHER *CIPHER = EVP_aes_256_cbc();

static int base64Encrypt(const string& toEncrypt, const string& password, string& base64Encrypted)
{
    static const char magic[]="Salted__";
    int ret = 0;
    EVP_CIPHER_CTX *ctx = NULL;
    BUF_MEM *bptr = NULL;
    unsigned char key[EVP_MAX_KEY_LENGTH], iv[EVP_MAX_IV_LENGTH];
    unsigned char salt[PKCS5_SALT_LEN];
    char *encrypted = NULL;
    /* Allow enough space in output buffer for additional block */
    BIO *bMem = NULL;
    BIO *b64 = NULL;
    BIO *benc = NULL;

    // setting bio context
    if ( (bMem = BIO_new(BIO_s_mem())) == NULL ){
        ret = ENCRYPTION_FAILED; goto err0;
    }
    if ( (b64 = BIO_new(BIO_f_base64())) == NULL ){
        ret = ENCRYPTION_FAILED; goto err0;
    }
    BIO_push(b64,bMem);

    // Generating salt
    if (RAND_pseudo_bytes(salt, sizeof(salt) ) < 0){
        ret = ENCRYPTION_FAILED; goto err0;
    }

    if ((password.size() == 0) && EVP_CIPHER_iv_length(CIPHER) != 0) {
        ret = ENCRYPTION_FAILED; goto err0; 
    }

    // writing salt to bio, base 64 encoded
    if (BIO_write(b64, magic, sizeof magic-1) != sizeof magic-1 || BIO_write(b64, (char *)salt, sizeof salt) != sizeof salt) {
        ret = ENCRYPTION_FAILED; goto err0;
    }   

    // deriving key
    if (!EVP_BytesToKey(CIPHER, MD, salt, (unsigned char *)password.c_str(), password.size(), PKCS5_DEFAULT_ITER, key, iv)){
        ret = ENCRYPTION_FAILED; goto err0;
    }

    if ( (benc=BIO_new(BIO_f_cipher())) == NULL ){
        ret = ENCRYPTION_FAILED; goto err0;
    }
    BIO_get_cipher_ctx(benc, &ctx);

    if (!EVP_CipherInit_ex(ctx, CIPHER, NULL, NULL, NULL, 1)){
        ret = ENCRYPTION_FAILED; goto err0;
    }

    if (!EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, 1)){
        ret = ENCRYPTION_FAILED; goto err0;
    }
    BIO_push(benc, b64);

        // writing to mem bio
    if (BIO_write(benc, (char *)toEncrypt.c_str(), toEncrypt.size()) != (int)toEncrypt.size()){
        ret = ENCRYPTION_FAILED; goto err0;
    }   

    if (!BIO_flush(benc)){

        ret = ENCRYPTION_FAILED; goto err0;
    }

    BIO_get_mem_ptr(benc, &bptr);
    if (bptr->length <= 0){
        ret = ENCRYPTION_FAILED; goto err0;
    }
    encrypted = new char[bptr->length + 1];
    memcpy(encrypted, bptr->data, bptr->length);
    encrypted[bptr->length] = '\0';
    base64Encrypted = encrypted;
    delete[] encrypted;

    if (benc != NULL) BIO_free_all(benc);
    return 0;

    err0:
    if (benc != NULL) BIO_free_all(benc);
    return ret;
}

如您所见,我已经链接了三个 BIO,并且对 BIO_free_all(benc) 的单个调用会清除所有 BIO。

问候。

于 2012-07-24T14:15:22.457 回答