Архитектура
Примечание.
Не нужно разбираться в материале этой статьи, чтобы использовать QCA — это задокументировано для тех, кому интересно, и для всех, кто планирует расширить или изменить QCA.
Архитектура QCA основана на шаблоне проектирования «Мост». Цель шаблона «Мост» — «отделить абстракцию от её реализации, чтобы они могли изменяться независимо». [Gamma et.al, pg 151].
Чтобы понять, как это разделение работает в случае QCA, проще всего взглянуть на пример — криптографическое хеширование. API довольно прост (хотя из него исключены некоторые части, которые не требуются для этого примера):
class QCA_EXPORT Hash : public Algorithm, public BufferedComputation
{
public:
Hash(const QString &type, const QString &provider);
virtual void clear();
virtual void update(const QCA::SecureArray &a);
virtual QCA::SecureArray final();
}
Реализация класса Hash
почти так же проста:
Hash::Hash(const QString &type, const QString &provider)
:Algorithm(type, provider)
{
}
void Hash::clear()
{
static_cast<HashContext *>(context())->clear();
}
void Hash::update(const QCA::SecureArray &a)
{
static_cast<HashContext *>(context())->update(a);
}
QCA::SecureArray Hash::final()
{
return static_cast<HashContext *>(context())->final();
}
Причина, по которой код выглядит так просто, заключается в том, что различные методы в Hash
просто вызывают эквивалентные подпрограммы в объекте context()
. Контекст исходит из вызова
getContext()
, который выполняется как часть конструктора Algorithm
. Этот вызов
getContext()
заставляет QCA работать со списком криптопровайдеров (обычно плагинов), о которых
он знает, в поисках криптопровайдера, который может создать правильный тип контекста (в данном
случае HashContext
).
Код для HashContext
не нужно связывать с QCA — он может варьироваться в реализации, в том числе
изменяться во время выполнения. Приложению не нужно знать, как реализован HashContext
, потому
что ему просто нужно иметь дело с интерфейсом класса Hash. Фактически, HashContext
не может быть
реализован, поэтому приложение должно выполнить проверку (с помощью
QCA::isSupported()),
прежде чем пытаться использовать функциональность, реализованную с помощью плагинов.
Код для одной реализации (в данном случае вызова OpenSSL) приведён ниже.
class opensslHashContext : public HashContext
{
public:
opensslHashContext(const EVP_MD *algorithm, Provider *p, const QString &type) : HashContext(p, type)
{
m_algorithm = algorithm;
EVP_DigestInit( &m_context, m_algorithm );
};
~opensslHashContext()
{
EVP_MD_CTX_cleanup(&m_context);
}
void clear()
{
EVP_MD_CTX_cleanup(&m_context);
EVP_DigestInit( &m_context, m_algorithm );
}
void update(const QCA::SecureArray &a)
{
EVP_DigestUpdate( &m_context, (unsigned char*)a.data(), a.size() );
}
QCA::SecureArray final()
{
QCA::SecureArray a( EVP_MD_size( m_algorithm ) );
EVP_DigestFinal( &m_context, (unsigned char*)a.data(), 0 );
return a;
}
Provider::Context *clone() const
{
return new opensslHashContext(*this);
}
protected:
const EVP_MD *m_algorithm;
EVP_MD_CTX m_context;
};
Этот подход (с использованием шаблона «Адаптер») очень распространён в бэкэндах QCA, потому что плагины часто основаны на существующих библиотеках.
В дополнение к различным объектам Context
каждый криптопровайдер также имеет параметризованный
класс Factory
с методом createContext()
, как показано ниже:
Context *createContext(const QString &type)
{
//OpenSSL_add_all_digests();
if ( type == "sha1" )
return new opensslHashContext( EVP_sha1(), this, type);
else if ( type == "sha0" )
return new opensslHashContext( EVP_sha(), this, type);
else if ( type == "md5" )
return new opensslHashContext( EVP_md5(), this, type);
else if ( type == "aes128-cfb" )
return new opensslCipherContext( EVP_aes_128_cfb(), 0, this, type);
else if ( type == "aes128-cbc" )
return new opensslCipherContext( EVP_aes_128_cbc(), 0, this, type);
else
return 0;
}
В результате QCA может попросить криптопровайдера предоставить соответствующий объект Context
,
не беспокоясь о том, как он реализован.
Для функциональности, которая реализована с помощью различных алгоритмов (например, HashContext
может поддерживать широкий спектр алгоритмов — MD5, SHA0 и SHA1 в приведённом выше примере; и
CipherContext
и MACContext
также могут это делать), необходимо разрешить приложениям
определить, какие алгоритмы поддерживаются. то обрабатывается через класс InfoContext
. Типичный
пример показан ниже:
class opensslInfoContext : public InfoContext
{
Q_OBJECT
public:
opensslInfoContext(Provider *p) : InfoContext(p)
{
}
Context *clone() const
{
return new opensslInfoContext(*this);
}
QStringList supportedHashTypes() const
{
QStringList list;
list += "sha1";
list += "sha0";
list += "md5";
return list;
}
// Здесь можно указать типы MAC и Cipher
};
Следует обратить внимание, что InfoContext
сам по себе предосталяет функциональность, поэтому
необходимо добавить его в метод createContext()
для криптопровайдера, как показано ниже:
Context *createContext(const QString &type)
{
if ( type == "sha1" )
return new opensslHashContext( EVP_sha1(), this, type);
else if ( type == "sha0" )
return new opensslHashContext( EVP_sha(), this, type);
else if ( type == "md5" )
return new opensslHashContext( EVP_md5(), this, type);
else if ( type == "info" )
return new opensslInfoContext( this );
else
return 0;
}