OpenSSL enc应用程序中可能的双BIO_free

时间:2012-06-04 15:46:36

标签: objective-c cocoa openssl

(对不起,这很冗长)我正在尝试将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_free(benc)(或我的代码中的BIO_free(encBIO)),因为BIO被推送到writeBIO/out链,该链被释放BIO_free_all?看看BIO_free_all的实现,它只是在BIO链上运行,减少引用计数和释放,因为它不会使指针空出来。这看起来像必须是一个bug,但显然我不愿意假设SSL维护者已经错过了这个而我没有。如果我离开BIO_free(encBIO)电话,我会偶尔崩溃(十分之一),如果我不这样做,我就不会发生泄密。这是Apple事件处理程序,因此调试进一步复杂化。有什么建议吗?

1 个答案:

答案 0 :(得分: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。

问候。