Web-сервисы Java: Подпись и шифрование с помощью Axis2 WS-Security

Как использовать Axis2 и Rampart для подписи и шифрования сообщений

Введение в принципы криптографии с открытым ключом и способы их применения в WS-Security для подписи и шифрования сообщений SOAP с помощью пар открытых и закрытых ключей в сочетании с секретными ключами. Денис Сосновский продолжает свой цикл статей о Web-сервисах Java обсуждением особенностей подписи и шифрования WS-Security и WS-SecurityPolicy с примерами кода, в которых используются Axis2 и Rampart.

Денис Сосноски, консультант, Sosnoski Software Solutions, Inc.

Денис Сосноски (Dennis Sosnoski) - основатель и ведущий специалист консалтинговой компании по технологиям Java - Sosnoski Software Solutions, Inc., специализирующейся в обучении и консультировании по проблемам XML и Web-сервисов. Он имеет более чем 30-летний опыт работы в профессиональном проектировании ПО, специализируясь на серверных XML и Java-технологиях. Денис является основным разработчиком интегрированной системы с открытым программным кодом JiBX XML Data Binding, построенной на базе технологии классов Java и связанной системы Web-сервисов JibxSoap, также как и системы Web-сервисов Apache Axis2. Он также был одним их экспертов при разработке спецификаций JAX-WS 2.0.



16.06.2009

Когда Web-сервисы обмениваются деловыми данными, безопасность имеет решающее значение. Перехват данных посторонними или признание подложных данных действительными может привести к негативным финансовым или юридическим последствиям. В приложении всегда можно разработать и реализовать специальные средства безопасности для работы с Web-сервисами – как и с любой формой обмена данными, – но это рискованный подход, потому что даже незначительный и незаметный промах может привести к серьезной уязвимости. Одно из главных преимуществ SOAP перед более простыми формами обмена данными состоит в том, что этот протокол допускает модульные расширения. Практически с момента первого выпуска SOAP безопасность стала одним из основных направлений разработки расширений, что привело к стандартизации WS-Security и сопутствующих технологий, которые позволяют настроить средства безопасности для каждого сервиса.

Требования к безопасности при обмене информацией, как правило, имеют три аспекта:

  • конфиденциальность: доступ к содержимому сообщения должен иметь только тот, кому оно предназначено;
  • целостность: сообщение должно доставляться без изменений;
  • подлинность: источник сообщения можно проверить.

WS-Security позволяет решить все три задачи. В этой статье мы покажем, как это сделать с помощью Axis2 и расширения WS-Security Rampart. Но сначала я кратко напомню о принципах шифрования с открытым ключом – основе большинства функций шифрования и подписи WS-Security.

Об этом цикле статей

Web-сервисы ― важная функция технологии Java™ в корпоративных вычислениях. В этом цикле статей консультант по XML и Web-сервисам Денис Сосновский рассказывает об основных структурах и технологиях, важных для Java-разработчиков, использующих Web-сервисы. Следите за статьями цикла, чтобы быть в курсе последних разработок в данной области и знать, как их использовать в своих собственных проектах.

Шифрование с открытым ключом

На протяжении большей части истории человечества безопасный обмен сообщениями был основан на той или иной форме общего секрета. Секрет мог принимать форму кода, когда участники обмена имеют согласованный набор подстановок фразы или действий. Или это мог быть шифр, когда текст с использованием некоторого алгоритма преобразуется в другой текст. Секрет может принимать и другие формы, такие как язык, неизвестный тем, кто может иметь доступ к сообщениям. Общий секрет делал обмен сообщениями безопасным. Если же кто-то посторонний раскрывал этот секрет, обмен тайными сообщениями оказывался под угрозой, что могло привести к катастрофическим последствиям для его участников. (Примером может служить код Enigma и немецкая военная связь во время Второй мировой войны.)

При шифровании с открытым ключом используется принципиально иной подход к обеспечению безопасности, который не требует общего секрета. Он основан на идее математических функций-"ловушек", которые легко вычислить в одном направлении, но очень трудно в обратном. Например, легко найти произведение двух простых чисел (даже очень больших, если работать с компьютером), но гораздо труднее найти по такому произведению первоначальные сомножители. Если при построении алгоритма шифрования использовать функцию в легко вычисляемом направлении, то тому, кто захочет раскрыть шифр, придется работать в обратном. И при хорошо подобранном алгоритме задачу взлома шифра можно сделать настолько трудной, что будет невозможно решить ее за отрезок времени, в течение которого это было бы опасно для обмена сообщениями (по крайней мере, до изобретения действующего квантового компьютера или рождения человека с небывалыми психическими способностями).

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

Подпись сообщений

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

На практике чаще используется подпись сообщения, чем просто его шифрование своим закрытым ключом. С одной стороны, нужен некоторый способ создания ожидаемого значения для расшифрованного сообщения. Обычно это делается с помощью другой разновидности математической функции-ловушки: хеш-функции, которую легко вычислить, но трудно воспроизвести (то есть трудно внести в сообщение какие-то изменения, не изменив значения хеша этого сообщения, или получить другое сообщение с тем же значением хеша, что и у указанного сообщения). С помощью такой хеш-функции можно создать хеш-значение (его обычно называют дайджест) для сообщения, которое нужно подписать, а затем зашифровать этот дайджест, используя свой закрытый ключ, и отправить зашифрованный дайджест вместе с сообщением. Получатель может применить тот же хеш-алгоритм к этому сообщению, а затем расшифровать зашифрованный дайджест с помощью вашего открытого ключа и сравнить полученные значения. Если они совпадают, получатель может быть уверен (в пределах современной технологии и в предположении, что вы храните свой закрытый ключ в секрете), что это сообщение от вас.

Когда имеешь дело с XML, в процессе подписи сообщений добавляется еще один шаг. XML-сообщения выражаются в текстовой форме, но XML игнорирует некоторые аспекты представления текста (такие как порядок следования атрибутов в элементе или пробелы между открывающим и закрывающим тегами). В связи с этой особенностью представления текста организация W3C (ответственная за спецификацию XML) решила перед вычислением значения дайджеста преобразовать XML-сообщения в канонический текст (см. Ресурсы). Имеется несколько алгоритмов канонизации, которые можно использовать для XML-сообщений. Отдельно взятый распространенный алгоритм не имеет особого значения, пока обе стороны, участвующие в обмене сообщениями, не договорятся о его использовании.

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

Сертификаты

Итак, пары открытого и закрытого ключей можно использовать для шифрования и подписи сообщений между двумя сторонами при условии, что каждая сторона имеет доступ к открытому ключу другой. Остается решить вопрос, как получить открытый ключ, сохранив безопасность. Наиболее широко используемый способ — одна или несколько доверенных третьих сторон, поручившихся за открытый ключ. Цифровые сертификаты — это механизм передачи открытых ключей при такой форме поручительства.

Цифровой сертификат представляет собой оболочку для открытого ключа, в которой содержится информация, идентифицирующая владельца этого ключа. Эта оболочка подписывается доверенной третьей стороной, и эта подпись включается в сертификат. Доверенная третья сторона подтверждает открытый ключ и идентифицирующую информацию, выдавая сертификат за своей подписью. Конечно, остается ахиллесова пята установления подлинности доверенной третьей стороны. Обычно это делается путем встраивания в программное обеспечение (такое как JVM) сертификатов определенных доверенных третьих сторон, называемых выпускающими органами.

Кроме описанных здесь, существует множество других цифровых сертификатов, в том числе способы отзыва сертификатов, выданных по ошибке (что, к сожалению, случается) или со взломанными закрытыми ключами, сроки действия сертификатов и расширения для указания предполагаемой области применения сертификата. В разделе Ресурсы содержатся ссылки на дополнительные сведения по цифровым сертификатам и шифрованию с открытым ключом. Можно также познакомиться с документацией инструмента безопасности keytool, включенной в пакет установки JDK. В документации по keytool дано хорошее введение в структуру сертификатов и управление ими наряду с хранилищами ключей (см. далее) и другими аспектами безопасности. В этой статье тоже приводится пример работы с keytool.

Хранилища ключей

Большинство Java-программ безопасности работает с закрытыми ключами и цифровыми сертификатами с помощью хранилищ ключей. Хранилище ключей — это просто файл, содержащий ключ и сертификаты в зашифрованном виде. Для доступа к хранилищу ключей необходим пароль. Каждый закрытый ключ в хранилище ключей снова шифруется с требованием дополнительного пароля в целях безопасного управления ключами. Любое программное обеспечение, которое использует хранилище ключей и закрытый ключ, должно иметь хранилище ключей и доступные во время выполнения пароли закрытых ключей. Это ограничивает безопасность, обеспечиваемую этими паролями (потому что любой, у кого есть доступ к исходному коду, может определить, как загружаются пароли). Поэтому необходимо обеспечить физическую безопасность системы, содержащей программное обеспечение, и все резервные копии этой системы. А хранилище ключей и пароль нужно держать только в этой системе и в этих резервных копиях, чтобы сохранить свои закрытые ключи в безопасности.

Секретные ключи и симметричное шифрование

Хотя шифрование с открытым ключом, использующее асимметричное шифрование, лежит в основе многих полезных функций WS-Security, старомодное шифрование с секретным ключом по-прежнему играет важную роль. Для обеспечения эквивалентных уровней защиты асимметричные алгоритмы шифрования, как правило, требуют гораздо более интенсивных вычислений, чем симметричные алгоритмы, основанные на секретных ключах (когда один и тот же ключ используется для шифрования и расшифровки, а, значит, этот ключ всегда должен храниться в тайне). По этой причине часто используется комбинация из двух технологий: высокозатратное асимметричное шифрование используется для защиты обмена секретным ключом, который затем можно использовать для малозатратного симметричного шифрования. Пример такого подхода приведен в этой статье при рассмотрении шифрования сообщений в WS-Security.


Настройка

В этой статье используется тот же пример приложения, что и в статье Основы стандарта Axis2 WS-Security, где рассказано, как в WS-Security с помощью Axis2 и Rampart реализовать UsernameToken. Однако для поддержки работы с криптографическими функциями WS-Security на основе открытого ключа необходимо внести несколько изменений, поэтому к этой статье прилагается отдельный пакет кода (см. Загрузки).

Корневой каталог кода примера — jws05code. Внутри этого каталога находятся:

  • файл Ant build.xml;
  • файл build.properties, который настраивает функционирование примера приложения;
  • файл library.wsdl, содержащий определение служб для примера приложения;
  • файл log4j.properties, используемый для настройки регистрации со стороны клиента;
  • Несколько XML-файлов определения свойств (все они — с именами XXX-policy-client.xml или XXX-policy-server.xml).

Прежде чем использовать этот пример, необходимо:

  1. Изменить файл build.properties, указав путь к своей установке Axis2.
  2. Убедиться, что установка Axis2 дополнена кодом Rampart, как описано в разделе Установка RampartОснов стандарта Axis2 WS-Security. (Хороший способ проверки — поискать в каталоге repository/modules файл модуля rampart-x.y.mar).
  3. Добавить источник услуг безопасности Bouncy Castle org.bouncycastle.jce.provider.BouncyCastleProvider (он требуется для криптографических функций с открытым ключом, используемых в этом примере кода) в конфигурацию безопасности JVM (файл lib/security/java.security) (см. Ресурсы ).
  4. Добавить файл JAR Bouncy Castle (с именем bcprov-jdkxx-vvv.jar, где xx — ваша версия Java, а vvv — версия кода Bouncy Castle) как в каталог lib своей установки Axis2, так и в каталог WEB-INF/lib серверного приложения Axis2.

Теперь все готово для создания примера приложения и экспериментов с примерами средств безопасности, приведенными в следующих разделах.


Подпись сообщений

Для подписи требуется намного большая спецификация, чем пример UsernameToken из Основ стандарта Axis2 WS-Security. Нам необходимо:

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

Часть этой информации обрабатывается как данные конфигурации, встроенные в документ WS-SecurityPolicy для данной службы. Другие части включены в обмен сообщениями во время выполнения.

В листинге 1 показан документ WS-Policy, используемый для настройки клиента Axis2 для подписи сообщений. (Листинг 1 отредактирован по ширине страницы. Полный текст см. в примере кода sign-policy-client.xml).

Листинг 1. WS-Policy/WS-SecurityPolicy для подписи (клиент)
<!-- Client policy for signing all messages, with certificates included in each
 message -->
<wsp:Policy wsu:Id="SignOnly"
    xmlns:wsu="http://.../oasis-200401-wss-wssecurity-utility-1.0.xsd"
    xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
  <wsp:ExactlyOne>
    <wsp:All>
      <sp:AsymmetricBinding
          xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
        <wsp:Policy>
          <sp:InitiatorToken>
            <wsp:Policy>
              <sp:X509Token
                  sp:IncludeToken="http://.../IncludeToken/AlwaysToRecipient"/>
            </wsp:Policy>
          </sp:InitiatorToken>
          <sp:RecipientToken>
            <wsp:Policy>
              <sp:X509Token
                  sp:IncludeToken="http://.../IncludeToken/AlwaysToInitiator"/>
            </wsp:Policy>
          </sp:RecipientToken>
          <sp:AlgorithmSuite>
            <wsp:Policy>
              <sp:TripleDesRsa15/>
            </wsp:Policy>
          </sp:AlgorithmSuite>
          <sp:Layout>
            <wsp:Policy>
              <sp:Strict/>
            </wsp:Policy>
          </sp:Layout>
          <sp:IncludeTimestamp/>
          <sp:OnlySignEntireHeadersAndBody/>
        </wsp:Policy>
      </sp:AsymmetricBinding>
      <sp:SignedParts xmlns:sp="http://.../ws-securitypolicy/200702">
        <sp:Body/>
      </sp:SignedParts>

      <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
        <ramp:user>clientkey</ramp:user>
        <ramp:passwordCallbackClass
            >com.sosnoski.ws.library.adb.PWCBHandler</ramp:passwordCallbackClass>

        <ramp:signatureCrypto>
          <ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin">
            <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type"
                >JKS</ramp:property>
            <ramp:property name="org.apache.ws.security.crypto.merlin.file"
                >client.keystore</ramp:property>
            <ramp:property
                name="org.apache.ws.security.crypto.merlin.keystore.password"
                >nosecret</ramp:property>
          </ramp:crypto>
        </ramp:signatureCrypto>

      </ramp:RampartConfig>

    </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>

В листинге 1 элемент политики <sp:AsymmetricBinding> выдает сведения о базовой конфигурации для использования криптографии с открытым ключом при обмене сообщениями. В этом элементе для задания конфигурации используются несколько вложенных элементов. <sp:InitiatorToken> идентифицирует пару ключей, используемую для подписи сообщений клиента (отправитель) серверу (получатель), в данном случае открытый ключ будет представлен в форме сертификата X.509 и будет передаваться с каждым сообщением клиента (sp:IncludeToken=".../AlwaysToRecipient"). <sp:RecipientToken> идентифицирует пару ключей, используемых для подписи ответных сообщений сервера клиенту, опять же с использованием сертификата X.509 и с сертификатом, включенном в каждое сообщение сервера (sp:IncludeToken=".../AlwaysToInitiator").

Элемент <sp:AlgorithmSuite> идентифицирует набор алгоритмов, используемых для подписи. <sp:IncludeTimestamp> указывает, что с каждым сообщением будет использоваться метка времени (это полезно для предотвращения атак типа replay, когда сообщение перехватывается в пути и передается вновь в попытке запутать службу или вывести ее из строя). Элемент <sp:OnlySignEntireHeadersAndBody> указывает на то, что подпись относится ко всему заголовку или телу сообщения, а не к отдельным вложенным элементам (еще одна мера безопасности, предотвращающая определенные типы атак с целью переписать сообщение). Элемент <sp:SignedParts> идентифицирует части сообщения, подлежащие подписанию – в данном случае это Body (тело) SOAP-сообщения.

Последняя часть листинга 1 WS-Policy содержит специфичные для Rampart сведения о конфигурации. Это более сложная версия конфигурации Rampart, чем та, что используется в Основах стандарта Axis2 WS-Security — теперь в нее входят элемент <ramp:user> для идентификации ключа, который будет использоваться для подписания сообщений, и элемент <ramp:signatureCrypto> для настройки хранилища ключей, содержащего закрытый ключ клиента и сертификат сервера. Во время выполнения указанный файл хранилища ключей должен находиться в classpath. В примере приложения файл хранилища ключей копируется в каталог client/bin во время сборки.

Классы password-callback немного отличаются от тех, которые используются в Основах стандарта Axis2 WS-Security. Для той статьи обратный вызов пароля был нужен только на сервере и только для проверки (для обычного текста UsernameToken) или задания (для хеша UsernameToken) пароля, который соответствует конкретному имени пользователя. Для криптографии с открытым ключом, используемой в этой статье, функция обратного вызова должна обеспечить пароль для защиты закрытого ключа в хранилище ключей. К тому же отдельные обратные вызовы необходимы для клиента (чтобы обеспечить пароль для закрытого ключа клиента) и для сервера (чтобы обеспечить пароль для закрытого ключа сервера). В листинге 2 показана версия клиентской стороны обратного вызова:

Листинг 2. Обратный вызов пароля для клиента
/**
 * Простой обработчик обратного вызова пароля. Он всего лишь проверяет, зарегистрирован 
  * ли пароль для запрашиваемого закрытого ключа, и если да, то устанавливает это значение.
 */
public class PWCBHandler implements CallbackHandler
{
    public void handle(Callback[] callbacks) throws IOException {
        for (int i = 0; i < callbacks.length; i++) {
            WSPasswordCallback pwcb = (WSPasswordCallback)callbacks[i];
            String id = pwcb.getIdentifer();
            int usage = pwcb.getUsage();
            if (usage == WSPasswordCallback.DECRYPT ||
                usage == WSPasswordCallback.SIGNATURE) {

                // используется для извлечения пароля закрытого ключа
                if ("clientkey".equals(id)) {
                    pwcb.setPassword("clientpass");
                }

            }
        }
    }
}

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

Наряду с WS-Policy из листинга 1 для клиента существует такая же для сервера (sign-policy-server.xml в примере кода), которая отличается только деталями конфигурации Rampart. Аналогично, серверная версия класса password-callback в листинге 2 отличается только значением идентификатора и возвращаемым паролем.

Запуск примера приложения

Файл build.properties содержит строки, указывающие на файлы client.policy и server.policy для использования в примере приложения. В прилагаемой версии файла они настроены соответственно на sign-policy-client.xml и sign-policy-server.xml, поэтому достаточно всего лишь собрать приложение. Это можно сделать с помощью Ant, открыв консоль в каталоге jws05code и набрав ant. Если всё настроено правильно, вы должны получить архив службы Axis2 library-signencr.aar в каталоге jws05code. Разверните службу в своей установке сервера Axis2, загрузив файл .aar с помощью страницы администрирования Axis2, а затем попробуйте клиент, набрав на консоли ant run. Если все настроено правильно, вы должны увидеть результат, показанный на рисунке 1.

Рисунок 1. Вывод на консоль при запуске приложения
Вывод на консоль при запуске приложения

Чтобы увидеть фактические данные WS-Security в сообщениях, необходимо использовать такой инструмент, как TCPMon (см. Ресурсы). Сначала нужно настроить TCPMon, чтобы он принимал соединения от клиента на один порт, а затем направлял их на сервер, работающий через другой порт (или другой узел). После этого можно отредактировать файл build.properties и изменить значение host-port для прослушивания порта инструментом TCPMon. Если теперь снова набрать на консоли ant run, вы должны видеть передаваемые сообщения. Пример перехвата сообщений клиента приведен в листинге 3.

Листинг 3. Первое сообщение клиента серверу
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Header>
    <wsse:Security xmlns:wsse=".../oasis-200401-wss-wssecurity-secext-1.0.xsd"
        soapenv:mustUnderstand="1">
      <wsu:Timestamp xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
          wsu:Id="Timestamp-3753023">
        <wsu:Created>2009-04-18T19:26:14.779Z</wsu:Created>
        <wsu:Expires>2009-04-18T19:31:14.779Z</wsu:Expires>
      </wsu:Timestamp>
      <wsse:BinarySecurityToken
          xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
          EncodingType=".../oasis-200401-wss-soap-message-security-1.0#Base64Binary"
          ValueType=".../oasis-200401-wss-x509-token-profile-1.0#X509v1"
          wsu:Id="CertId-2650016">MIICoDC...0n33w==</wsse:BinarySecurityToken>
      <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
          Id="Signature-29086271">
        <ds:SignedInfo>
          <ds:CanonicalizationMethod
              Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
          <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
          <ds:Reference URI="#Id-14306161">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
            <ds:DigestValue>SiU8LTnBL10/mDCPTFETs+ZNL3c=</ds:DigestValue>
          </ds:Reference>
          <ds:Reference URI="#Timestamp-3753023">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
            <ds:DigestValue>2YopLipLgBFJi5Xdgz+harM9hO0=</ds:DigestValue>
          </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>TnUQtz...VUpZcm3Nk=</ds:SignatureValue>
        <ds:KeyInfo Id="KeyId-3932167">
          <wsse:SecurityTokenReference
              xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
              wsu:Id="STRId-25616143">
            <wsse:Reference URI="#CertId-2650016"
              ValueType=".../oasis-200401-wss-x509-token-profile-1.0#X509v1"/>
          </wsse:SecurityTokenReference>
        </ds:KeyInfo>
      </ds:Signature>
    </wsse:Security>
  </soapenv:Header>
  <soapenv:Body
      xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Id-14306161">
    <ns2:getBook xmlns:ns2="http://ws.sosnoski.com/library/wsdl">
      <ns2:isbn>0061020052</ns2:isbn>
    </ns2:getBook>
  </soapenv:Body>
</soapenv:Envelope>

Заголовок <wsse:Security> внутри SOAP-сообщения содержит все сведения о конфигурации безопасности во время выполнения и данные подписи. Первый представленный элемент – это <wsu:Timestamp>, как требует конфигурация WS-SecurityPolicy. Метка времени содержит два значения времени: время создания и срок действия. В данном случае эти два значения отстоят друг от друга на пять минут – значение по умолчанию для Rampart. (Эти значения можно изменить в политике конфигурации Rampart.) Промежуток времени между этими двумя значениями несколько произвольный, но пять минут — это разумное значение – достаточное, чтобы устранить разумное рассогласование часов (различия в системах измерения времени) между клиентом и сервером, и в то же время это достаточно короткий интервал, чтобы ограничить возможности по организации replay-атак с использованием сообщения. Следующий после метки времени элемент в заголовке безопасности — <wsse:BinarySecurityToken>. Этот маркер безопасности представляет собой сертификат клиента в виде двоичной кодировки base64.

Третий элемент в заголовке безопасности — это блок <ds:Signature>, с тремя дочерними элементами. Первый дочерний элемент, <ds:SignedInfo>, представляет собой единственную часть сообщения, которая непосредственно подписывается. Первые дочерние элементы <ds:SignedInfo> определяют алгоритмы, используемые для своей собственной канонизации и подписи. Затем следует дочерний элемент <ds:Reference> для каждого компонента сообщения, включенного в подпись. Каждый дочерний элемент <ds:Reference> содержит ссылку на конкретный компонент сообщения через идентификатор, а также алгоритмы канонизации и дайджеста, применяемые к этому компоненту, наряду с результирующим значением дайджеста. Оставшиеся дочерние элементы <ds:SignedInfo> содержат фактическое значение подписи и ссылку на открытый ключ, используемый для проверки подписи (в данном случае, сертификат, включенный ранее в <wsse:BinarySecurityToken> в заголовке, как указано выражением wsu:Id="CertId-2650016").


Шифрование и подписание сообщений

Добавить шифрование к обмену подписанными сообщениями очень легко, достаточно добавить к политике элемент <sp:EncryptedParts> для указания подписываемых компонентов и некоторые дополнительные сведения по конфигурации Rampart. В листинге 4 показана версия политики для этой цели (опять же, отредактированная по ширине страницы — полный текст см. в файле signencr-policy-client.xml из кода примера) с дополнениями к политике из листинга 1, выделенными жирным шрифтом.

Листинг 4. WS-Policy/WS-SecurityPolicy для подписи с последующим шифрованием (клиент)
<!-- Client policy for first signing and then encrypting all messages, with the
 certificate included in the message from client to server but only a thumbprint
 on messages from the server to the client. -->
<wsp:Policy wsu:Id="SignEncr"
    xmlns:wsu="http://.../oasis-200401-wss-wssecurity-utility-1.0.xsd"
    xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
  <wsp:ExactlyOne>
    <wsp:All>
      <sp:AsymmetricBinding
          xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
        <wsp:Policy>
          <sp:InitiatorToken>
            <wsp:Policy>
              <sp:X509Token
                  sp:IncludeToken="http://.../IncludeToken/AlwaysToRecipient"/>
            </wsp:Policy>
          </sp:InitiatorToken>
          <sp:RecipientToken>
            <wsp:Policy>
              <sp:X509Token
                  sp:IncludeToken="http://.../IncludeToken/Never">
                <wsp:Policy>
                  <sp:RequireThumbprintReference/>
                </wsp:Policy>
              </sp:X509Token>
            </wsp:Policy>
          </sp:RecipientToken>
          <sp:AlgorithmSuite>
            <wsp:Policy>
              <sp:TripleDesRsa15/>
            </wsp:Policy>
          </sp:AlgorithmSuite>
          <sp:Layout>
            <wsp:Policy>
              <sp:Strict/>
            </wsp:Policy>
          </sp:Layout>
          <sp:IncludeTimestamp/>
          <sp:OnlySignEntireHeadersAndBody/>
        </wsp:Policy>
      </sp:AsymmetricBinding>
      <sp:SignedParts xmlns:sp="http://.../200702">
        <sp:Body/>
      </sp:SignedParts>
      <sp:EncryptedParts xmlns:sp="http://.../200702">
        <sp:Body/>
      </sp:EncryptedParts>

      <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
        <ramp:user>clientkey</ramp:user>
        <ramp:encryptionUser>serverkey</ramp:encryptionUser>
        <ramp:passwordCallbackClass
            >com.sosnoski.ws.library.adb.PWCBHandler</ramp:passwordCallbackClass>

        <ramp:signatureCrypto>
          <ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin">
            <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type"
                >JKS</ramp:property>
            <ramp:property name="org.apache.ws.security.crypto.merlin.file"
                >client.keystore</ramp:property>
            <ramp:property
                name="org.apache.ws.security.crypto.merlin.keystore.password"
                >nosecret</ramp:property>
          </ramp:crypto>
        </ramp:signatureCrypto>

        <ramp:encryptionCrypto>
          <ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin">
            <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type"
                >JKS</ramp:property>
            <ramp:property name="org.apache.ws.security.crypto.merlin.file"
                >client.keystore</ramp:property>
            <ramp:property
                name="org.apache.ws.security.crypto.merlin.keystore.password"
                >nosecret</ramp:property>
          </ramp:crypto>
        </ramp:encryptionCrypto>

      </ramp:RampartConfig>

    </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>

Почему недостаточно одного шифрования?

Было бы неплохо, например, использовать только шифрование с помощью Axis2 и Rampart, но в Axis2 1.4 (и более ранних версиях) эта функциональность нарушена. Она исправлена в версии 1.5. Если вы используете Axis2 1,5 или более позднюю версию (с соответствующим выпуском Rampart), попробуйте применить файлы политики encr-policy-client.xml и encr-policy-server.xml для шифрования тела каждого сообщения без подписей.

Для использования шифрования первое изменение в политике из листинга 4не обязательно, но желательно. При отправке первоначального запроса с шифрованием у клиента должен быть доступ к сертификату сервера (потому что для шифрования используется открытый ключ сервера из сертификата). Так как клиенту в любом случае нужен сертификат сервера, нет причин передавать сертификат сервера клиенту. Изменение политики <sp:RecipientToken> в листинге 4 отражает такое использование, указывая, что сертификат передавать не следует (sp:IncludeToken=".../Never") и что вместо него нужно использовать ссылку thumbprint (по сути, хэш сертификата). Ссылка thumbprint гораздо компактнее, чем полный сертификат, поэтому использование ссылки уменьшает размер сообщения и нагрузку по его обработке.

Изменение, которое фактически выполняет шифрование, это добавленный элемент <sp:EncryptedParts>. Этот элемент указывает, что должно использоваться шифрование, а содержание элемента <sp:Body> указывает на то, что тело сообщения SOAP является частью самого сообщения, подлежащего шифрованию.

Добавленные в листинге 4 сведения о конфигурации Rampart состоят из элемента <ramp:encryptionUser>, который присваивает псевдоним открытому ключу (то есть, сертификат) для использования при шифровании сообщения, и элемента <ramp:encryptionCrypto>, указывающего, как получить доступ к хранилищу ключей, содержащему сертификат. В примере приложения одно и то же хранилище ключей используется как для закрытого ключа, применяемого для подписи, так и для открытого ключа, применяемого для шифрования, так что элемент <ramp:encryptionCrypto> — это просто переименованный дубликат существующего элемента <ramp:signatureCrypto>.

Во время выполнения Rampart должен получить пароль, используемый для защиты закрытого ключа, чтобы расшифровать зашифрованные данные. Обратный вызов пароля, используемый ранее для получения пароля закрытого ключа для подписи (показанный в листинге 2), обеспечивает пароль и для расшифровки, поэтому здесь никаких изменений не требуется.

Запуск примера приложения

Чтобы попробовать пример приложения с использованием подписи после шифрования, нужно сначала отредактировать файл build.properties. Замените строку политики клиента на client.policy=signencr-policy-client.xml, а политики сервера ― на server-policy=signencr-policy-server.xml. Затем можно заново скомпоновать приложение, запустив ant, развернуть сгенерированный файл library-signencr.aar в свою установку Axis2 и выполнить команду ant run.

В листинге 5 показан захват request-message при подписании, за которым используется шифрование, причем существенные отличия от версии с одним подписанием (листинг 3) выделены жирным шрифтом:

Листинг 5. Сообщение с использованием подписи и шифрования
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
  <soapenv:Header>
    <wsse:Security xmlns:wsse=".../oasis-200401-wss-wssecurity-secext-1.0.xsd"
        soapenv:mustUnderstand="1">
      <wsu:Timestamp xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
          wsu:Id="Timestamp-4067003">
        <wsu:Created>2009-04-21T23:15:47.701Z</wsu:Created>
        <wsu:Expires>2009-04-21T23:20:47.701Z</wsu:Expires>
      </wsu:Timestamp>
      <xenc:EncryptedKey Id="EncKeyId-urn:uuid:6E12E251E439C034FA12403557497352">
        <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
        <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
          <wsse:SecurityTokenReference>
            <wsse:KeyIdentifier
                EncodingType="http://...-wss-soap-message-security-1.0#Base64Binary"
                ValueType="http://.../oasis-wss-soap-message-security-1.1#ThumbprintSHA1"
                >uYn3PK2wXheN2lLZr4n2mJjoWE0=</wsse:KeyIdentifier>
          </wsse:SecurityTokenReference>
        </ds:KeyInfo>
        <xenc:CipherData>
          <xenc:CipherValue>OBUcMI...OIPQEUQaxkZps=</xenc:CipherValue>
        </xenc:CipherData>
        <xenc:ReferenceList>
          <xenc:DataReference URI="#EncDataId-28290629"/>
        </xenc:ReferenceList>
      </xenc:EncryptedKey>
      <wsse:BinarySecurityToken
          xmlns:wsu="http://.../oasis-200401-wss-wssecurity-utility-1.0.xsd"
          EncodingType="http://...-wss-soap-message-security-1.0#Base64Binary"
          ValueType="http://.../oasis-200401-wss-x509-token-profile-1.0#X509v1"
          wsu:Id="CertId-2650016">MIICo...QUBCPx+m8/0n33w==</wsse:BinarySecurityToken>
      <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
          Id="Signature-12818976">
        <ds:SignedInfo>
          <ds:CanonicalizationMethod
              Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
          <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
          <ds:Reference URI="#Id-28290629">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
            <ds:DigestValue>5RQy7La+tL2kyz/ae1Z8Eqw2qiI=</ds:DigestValue>
          </ds:Reference>
          <ds:Reference URI="#Timestamp-4067003">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
            <ds:DigestValue>GAt/gC/4mPbnKcfahUW0aWE43Y0=</ds:DigestValue>
          </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>DhamMx...+Umrnims=</ds:SignatureValue>
        <ds:KeyInfo Id="KeyId-31999426">
          <wsse:SecurityTokenReference
              xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
              wsu:Id="STRId-19267322">
            <wsse:Reference URI="#CertId-2650016"
                ValueType=".../oasis-200401-wss-x509-token-profile-1.0#X509v1"/>
          </wsse:SecurityTokenReference>
        </ds:KeyInfo>
      </ds:Signature>
    </wsse:Security>
  </soapenv:Header>
  <soapenv:Body
      xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Id-28290629">
    <xenc:EncryptedData Id="EncDataId-28290629"
        Type="http://www.w3.org/2001/04/xmlenc#Content">
      <xenc:EncryptionMethod
          Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
      <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <wsse:SecurityTokenReference
            xmlns:wsse="http://.../oasis-200401-wss-wssecurity-secext-1.0.xsd">
          <wsse:Reference URI="#EncKeyId-urn:uuid:6E12E251E439C034FA12403557497352"/>
        </wsse:SecurityTokenReference>
      </ds:KeyInfo>
      <xenc:CipherData>
        <xenc:CipherValue>k9IzAEG...3jBmonCsk=</xenc:CipherValue>
      </xenc:CipherData>
    </xenc:EncryptedData>
  </soapenv:Body>
</soapenv:Envelope>

Первое отличие заключается в наличии элемента <xenc:EncryptedKey> в заголовке безопасности. Этот элемент содержит секретный ключ в зашифрованном виде, для шифрования которого используется открытый ключ сервера. Второе отличие – это фактическое содержимое Body SOAP, которое заменено элементом <xenc:EncryptedData>. Этот зашифрованный элемент данных указывает на значение <xenc:EncryptedKey> из заголовка безопасности как на ключ для симметричного шифрования, используемый для содержания Body.


Применение своих собственных самозаверяющихся сертификатов

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

Для целей тестирования или внутреннего использования вместо этого можно создать свои собственные самозаверяющиеся сертификаты. В примере кода для этой статьи используются два таких самозаверяющихся сертификата, один для клиента и один для сервера. Хранилище ключей client.keystore, используемое на стороне клиента, содержит закрытый ключ и сертификат клиента, а также сертификат сервера (который должен храниться на клиентском компьютере, чтобы он принимался как действительный без одобрения органа сертификации при его использовании для подписи, а также чтобы его можно было использовать непосредственно для шифрования). Хранилище ключей server.keystore, используемое на стороне сервера, содержит закрытый ключ и сертификат сервера, а также сертификат клиента (опять же, это необходимо, чтобы сертификат принимался как действительный).

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

keytool -genkey -alias serverkey -keypass serverpass -keyalg RSA -sigalg SHA1withRSA 
   -keystore server.keystore -storepass nosecret

Эта команда создает ключ сервера и сертификат с псевдонимом serverkey в новом хранилище ключей с именем server.keystore. (Если у вас уже есть server.keystore в этом каталоге, сначала нужно удалить существующую пару ключей, использующую этот псевдоним.) Keytool запрашивает информационные элементы, используемые для создания сертификата (ни один из них не оказывает никакого реального влияния на тестирование) и затем просит подтвердить информацию. Как только вы сделали это, набрав yes, keytool создает хранилище с закрытым ключом и сертификатом и завершается.

Затем выполните ту же процедуру для создания клиентской пары ключей и хранилища ключей, на этот раз с помощью следующей командной строки (введенной в одну строку):

keytool -genkey -alias clientkey -keypass clientpass -keyalg RSA -sigalg SHA1withRSA 
   -keystore client.keystore -storepass nosecret

Следующий шаг заключается в том, чтобы экспортировать сертификат из хранилища ключей сервера и импортировать его в хранилище ключей клиента. Для экспорта используйте следующую командную строку (здесь она разделена по ширине страницы; необходимо ввести все в одну строку):

keytool -export -alias serverkey -keystore server.keystore -storepass nosecret 
   -file servercert.cer

В результате экспорта создается файл сертификата с именем servercert.cer, который затем можно импортировать в хранилище ключей клиента с помощью следующей командной строки (введенной в одну строку):

keytool -import -alias serverkey -keystore client.keystore -storepass nosecret 
   -file servercert.cer

При выполнении команды import keytool выводит сведения о сертификате и спрашивает, доверяете ли вы этому сертификату. Когда ключ принят (yes), программа добавляет сертификат в хранилище ключей и завершается.

Последним шагом мы экспортируем клиентский сертификат и импортируем его в хранилище сервера, сначала выполнив следующую команду(введенную в одну строку):

keytool -export -alias clientkey -keystore client.keystore -storepass nosecret 
   -file clientcert.cer

Затем выполните следующую команду (здесь она разделена по ширине страницы; необходимо ввести все в одну строку):

keytool -import -alias clientkey -keystore server.keystore -storepass nosecret 
   -file clientcert.cer

Зачем экспортировать/импортировать оба сертификата?

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

Этот же подход можно использовать для работы с несколькими клиентами, применяющими самозаверяющиеся сертификаты, просто импортировав сертификат каждого клиента в хранилище сервера. Как альтернативный вариант, вместо того чтобы работать с самозаверяющимися сертификатами, можно завести свой собственный центр сертификации (с помощью такого инструмента, как OpenSSL) и требовать, чтобы каждый клиент получал сертификат, подписанный этим органом. Таким образом, можно добавить центр сертификации ключей к своему хранилищу ключей сервера, и любой клиент, представивший сертификат, подписанный этим органом, будет приниматься. Или можно просто использовать официальные сертификаты, подписанные признанным органом.

Чтобы использовать новые ключи и сертификаты, необходимо скопировать файл client.keystore в каталог client/src кода примера перед запуском сборки клиента (или просто скопировать его в каталог client/bin, чтобы получить немедленный эффект), а файл server.keystore — в каталог server/src кода примера перед запуском сборки сервера.

В командных строках keytool в этом разделе используются те же имена файлов и пароли, что и в прилагаемом примере кода. При создании собственных ключей и сертификатов эти значения можно изменить, но тогда придется соответствующим образом изменить код примера. Пароль и имя файла хранилища ключей – это параметры в разделе RampartConfig файла политики каждой стороны. Пароль ключа клиента представляет собой жестко запрограммированную клиентскую версию класса com.sosnoski.ws.library.adb.PWCBHandler, а пароль ключа сервера — серверную версию того же класса.


Заключение

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


Загрузка

ОписаниеИмяРазмер
Исходный код для этой статьиj-jws5.zip36 КБ

Ресурсы

Научиться

Получить продукты и технологии

  • Apache Axis2: загрузите последнюю версию Axis2.
  • Rampart: загрузите модуль Rampart для Axis2.
  • The Legion of the Bouncy Castle: загрузите подходящий файл .jar Bouncy Castle для своей среды выполнения.
  • TCPMon: загрузите эту утилиту с открытым исходным кодом для мониторинга TCP-соединений

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java, SOA и web-сервисы, Open source
ArticleID=777501
ArticleTitle=Web-сервисы Java: Подпись и шифрование с помощью Axis2 WS-Security
publish-date=06162009