2

(冗長で申し訳ありません) OS X 10.6 (Snow Leopard) 用の Objective C で記述された Cocoa アプリケーションに OpenSSL サポートを追加する実験を行っています。簡単にするために、さまざまな 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 カウントを減らして解放し、ポインターを NULL アウトしません。これはバグに違いないように見えますが、明らかに、SSL メンテナーがこれを見逃していると思いたくありません。通話をそのままにしておくと、時折クラッシュが発生します (10 分の 1)。そうしないとクラッシュしませんが、リークは望ましくありません。これは Apple Event Handler 内にあるため、デバッグはさらに複雑になります。助言がありますか?BIO_free(encBIO)writeBIO/outBIO_free_allBIO_free_allBIO_free(encBIO)

4

1 に答える 1

1

しばらく前に BIO と仕事をする機会がありましたが、あなたは正しいと思います。OpenSSL の man ページから:

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;
}

ご覧のとおり、3 つの BIO を連鎖させ、BIO_free_all(benc) を 1 回呼び出すだけで、すべての BIO がクリーンアップされます。

よろしく。

于 2012-07-24T14:15:22.457 に答える