2013-03-03 2 views
8

Рассмотрим следующий вызов RSA_generate_key():Как прервать RSA_generate_key?

RSA * rsa = RSA_generate_key(8192, RSA_F4, NULL, NULL); 

генерируя 8192-битный RSA-ключ может занять длительное время (от нескольких секунд до нескольких минут). Предположим, что приложение, содержащее строку кода выше, предлагает пользователю кнопку, которая отменяет генерацию ключа.

Как я могу отменить вычисление и вернуть функцию до того, как ключ был сгенерирован? Я помню, что третий аргумент RSA_generate_key() - это функция обратного вызова, используемая для отображения прогресса. Есть ли способ вернуть обратный вызов значение, означающее «прервать операцию и вернуть»?

Выполнение функции в другом потоке, а затем завершение потока не является вариантом.

ответ

7

С RSA_generate_key обеспечивает обратный вызов прогресса, вы можете отключить функцию longjmp. С небольшим количеством дополнительного кода вы можете создать обертку для RSA_generate_key, которая принимает общую тестовую функцию, которая может использоваться для проверки тайм-аута или флага, установленного системой окон.

#include <openssl/rsa.h> 
#include <stdbool.h> 
#include <setjmp.h> 

struct trampoline_ctx { 
    bool (*testfn)(void *); 
    void *testfn_arg; 
    jmp_buf env; 
}; 

static void trampoline(int ignore1, int ignore2, void *arg) 
{ 
    struct trampoline_ctx *ctx = arg; 
    if (!ctx->testfn(ctx->testfn_arg)) 
    longjmp(ctx->env, 1); 
} 

// like RSA_generate_key, but accepts a test function. If testfn returns 
// false, key generation is terminated and NULL is returned.  
RSA * 
my_generate_key(int num, unsigned long e, 
       bool (*testfn)(void *), void *testfn_arg) 
{ 
    struct trampoline_ctx ctx; 
    ctx.testfn = testfn; 
    ctx.testfn_arg = testfn_arg; 
    if (setjmp(ctx.env)) 
    return NULL; 
    return RSA_generate_key(num, e, trampoline, &ctx); 
} 

Этот подход является удивительно портативным, поскольку longjmp уполномочена как C89 и C99. Недостаток заключается в том, что он может утечка ресурсов, если функция, которую вы не используете, динамически распределяет. Однако на практике утечка может быть достаточно маленькой, чтобы быть незамеченной, если она выполняется нечасто или только по явному запросу пользователя. Чтобы быть уверенным, что это так, запустите код в узком цикле и понаблюдайте за потреблением ресурсов в процессе.

Вот тестовая программа для вышеупомянутой функции:

#include <stdio.h> 
#include <sys/time.h> 

double now() 
{ 
    struct timeval tv; 
    gettimeofday(&tv, NULL); 
    return tv.tv_sec + (double) tv.tv_usec/1e6; 
} 

struct tt_ctx { 
    double start; 
    double limit; 
}; 

bool test_time_limit(void *arg) 
{ 
    struct tt_ctx *ctx = arg; 
    return now() - ctx->start <= ctx->limit; 
} 

int main(int argc, char **argv) 
{ 
    int limit = atoi(argv[1]); 
    struct tt_ctx ctx; 
    ctx.start = now(); 
    ctx.limit = limit/1000.0; 

    RSA *key = my_generate_key(4096, 65537, test_time_limit, &ctx); 
    printf("%p\n", key); 
    return 0; 
} 

Тестовая программа предполагает POSIX gettimeofday, но может быть тривиальным образом преобразованы в другой с высокой разрешающей способностью часов, как это предусмотрено в системе. Процедура испытания следующая:

Добавить оба фрагмента кода в файл и скомпилировать с помощью -lrsa.В тестовой программе будет генерироваться 4096-битный RSA-ключ в течение времени, указанного в миллисекундах в командной строке. Во всех случаях он печатает полученный указатель RSA *, чтобы указать, завершил ли запрос my_generate_key или был прерван. Выход time сопровождает исполнение в качестве проверки вменяемости, чтобы убедиться, что ограничение по времени соблюдается:

# try with a 10ms limit 
$ time ./a.out 10 
(nil)       # too short 
./a.out 10 0.02s user 0.00s system 85% cpu 0.023 total 

# see if 100ms is enough time 
$ time ./a.out 100 
(nil)       # still too short 
./a.out 100 0.10s user 0.00s system 97% cpu 0.106 total 

# try with 1 whole second: 
$ time ./a.out 1000 
0x2369010      # success! 
./a.out 1000 0.64s user 0.00s system 99% cpu 0.649 total 
+2

Из всех ответов здесь это, наверное, самый умный. Поэтому я собираюсь присудить награду за ваш ответ. –

+0

Блестящий ответ и прекрасно продуманный. :) –

+0

Codelite всегда говорит: устарело ... – delive

2

Обратный вызов прогресса не может использоваться для отмены функции, это чисто для отображения прогресса пользователю. (Read more here)

Создайте цепочку для вызова RSA_generate_key(), чтобы она выполнялась в фоновом режиме. Когда пользователь нажимает кнопку отмены, убейте поток.

+0

Это, вероятно, не очень хорошая идея - принудительно прекращение нити может вызвать много проблем, если библиотека не является исключением и асинхронным -терминационный сейф. –

+0

Если вы не хотите убивать нить, вы можете просто перейти к ней и продолжить ее/закончить при включении в фоновом режиме, когда пользователь нажмет кнопку «Отменить». – jszobody

+0

@jsz: Но это сгорит циклы процессора. –

1

Хотя я никогда не сталкивался с такой ситуацией и никогда ничего не делал раньше. Вы можете попробовать следующее, если вы можете изменить исходный код OpenSSL:

Скажем, переменная V делится между двумя функциями. Таким образом, либо это глобальная переменная, либо статическая переменная в файле и используется RSA_generate_key и говорят другую функцию RSA_set_V.

RSA_generate_key будет читать только переменную. RSA_set_V устанавливает переменную.

В RSA_generate_key цикл времени занимает дополнительное условие проверки этой переменной. Он может завершить этот цикл или выйти из функции на определенное значение V. , например.

while (V!=CERTAIN_VALUE && Rest_of_Condition) { 
     //Loop body. 
    } 

Теперь, в RSA_set_V, установите V в CERTAIN_VALUE. Когда пользователь нажимает кнопку «Стоп», вызовите RSA_set_V. Он должен остановить RSA_generate_key.

Однако у него мало проблем. Это может замедлить RSA_generate_key.

0

Согласно source code, без изменения кода openssl мы не можем прерывать метод RSA_generate_key, чтобы он возвращался.

Я думал два способа сделать это:

  1. позволяют метод работает в фоновом потоке, и когда пользователь нажимает кнопку отмены, пусть метод запуска до конца в фоновом режиме. и пусть нить умирает впоследствии. и проигнорировать результаты. и начать новый поток, если пользователь запросит другое поколение.

  2. Другой - это запуск подпроцесса для генерации ключа. Структура RSA содержит множество элементов BIGNUM, которая также является структурой некоторых ints/ulongs, после того, как подпроцесс сгенерировал ключи, скопируйте эти соответствующие элементы обратно в родительский процесс. , если пользователь нажимает кнопку отмены, а затем убивает подпроцесс в безопасности. Но не уверен, достаточно ли для вашего сенарио копировать эти ints/longs.

0

Если вы не хотите прекратить нить, вы можете отказаться от нового процесса и прекратить его, если пользователь прерывается?

2

Я считаю, что проблема заключается в том, что обратный вызов обычно не дает вам возможность передавать информацию в только получить информацию назад. Как уже упоминалось в некоторых других плакатах, вы можете подумать об изменении исходного кода реальной процедуры генерации ключей и компиляции своей собственной версии. В этом случае вы можете передать какое-то значение по ссылке обратно в свою вызывающую процедуру, которую вы могли бы установить снаружи, и тем самым вызвать условие сбоя.

Примечание: RSA_generate_key фактически устарел, и теперь просто обертка для RSA_generate_key_ex.

По версии 1.19.4.2 файла rsa_gen.c, если вы не в FIPS_mode, ключ будет генерироваться статическим методом rsa_builtin_keygen. В FIPS_mode он будет сгенерирован любым rsa->meth->rsa_keygen при вводе RSA_generate_key.

Сказано, что существует множество мест, где обратный вызов (параметр cb) передается другим методам, предположительно, чтобы эти методы могли, в свою очередь, вызвать его для обновления статуса.

Трюк будет обновлять либо BN_GENCB, либо содержать какой-либо флаг «отменить», либо изменить способ обратного вызова в самом методе, так что, когда срабатывает метод обратного вызова, вы можете установить флаг, который будет выполнять функция вызова, вырвав подпрограмму генерации.

Как вы это понимаете, это все, что вам нужно, чтобы выяснить и проработать - но, надеюсь, это даст вам немного начала.