Java 11 была выпущена 25 сентября 2018 г. Это первый выпуск с долгосрочной поддержкой, выпущенный в соответствии с моделью выпуска с периодичностью в шесть месяцев. Помимо огромного количества мелких улучшений и исправлений ошибок, новый релиз содержит 17 крупных улучшений, в том числе:

  • несколько обновлений в Hotspot и сборщиках мусора
  • новый HTTP-клиент
  • Юникод 10
  • отказ от Nashorn JavaScript Engine и инструмента Pack200
  • удаление модулей Java EE и CORBA
  • синтаксис локальной переменной для параметров Lambda
  • запускать однофайловые программы с исходным кодом
  • и, наконец, несколько функций безопасности

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

Безопасность транспортного уровня (TLS) 1.3

Спецификация TLS 1.3 была опубликована в августе 2018 года, а Java 11 уже поддерживает минимальную реализацию нового протокола. Вот что говорят авторы JEP:

Основной целью этого JEP является минимальная интероперабельная и совместимая реализация TLS 1.3. Минимальная реализация должна поддерживать:

- Согласование версии протокола

- Полное рукопожатие TLS 1.3

- Возобновление сеанса TLS 1.3

- Ключ TLS 1.3 и обновление iv

- TLS 1.3 обновил сшивание OCSP

- Режим обратной совместимости TLS 1.3

- Требуемые расширения и алгоритмы TLS 1.3

- Алгоритмы подписи RSASSA-PSS (8146293)

Для TLS 1.3 нет новых API, JEP вводит только несколько новых констант:

- Название версии протокола TLS: TLSv1.3

- Имя алгоритма javax.net.ssl.SSLContext: TLSv1.3

- Имена наборов шифров TLS для TLS 1.3: TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384.

Java Secure Sockets Extension (JSSE) был значительно переработан в Java 11. Раньше я работал над security-libs на Java, и могу сказать, что это был не простой проект. Тем не менее, команда Java security-libs реализовала TLS 1.3 в Java 11. Отличная работа!

Хотя кажется, что в реализации TLS 1.3 есть несколько ошибок, которые могут повлиять на взаимодействие с другими реализациями протокола, например:

Похоже, что большинство проблем уже исправлено в репозитории разработки. Надеемся, что исправления будут доставлены в следующем выпуске обновления для Java 11.

Если вы хотите попробовать TLS 1.3 с Java 11, здесь вы можете найти пример клиента и сервера TLS 1.3 на Java.

Алгоритмы шифрования ChaCha20 и Poly1305

В JSSE реализовано несколько новых функций, связанных с реализацией TLS 1.3. Это одна из них. ChaCha20 — это потоковый шифр, который должен заменить небезопасный шифр RC4 (отключен в Java), а Poly1305 — это код аутентификации сообщений (MAC). ChaCha20 можно использовать в режиме AEAD с аутентификатором Poly1305. Этот алгоритм AEAD обычно называется ChaCha20-Poly1305, и он также доступен в Java 11. Вот что говорит автор JEP:

Реализуйте шифры ChaCha20 и ChaCha20-Poly1305, как указано в RFC 7539. ChaCha20 — это относительно новый потоковый шифр, который может заменить старый небезопасный потоковый шифр RC4.

Цели

- Обеспечьте реализацию шифра ChaCha20 и ChaCha20-Poly1305. Эти алгоритмы будут реализованы в провайдере SunJCE.

- Обеспечьте реализацию KeyGenerator, которая создает ключи, подходящие для алгоритмов ChaCha20 и ChaCha20-Poly1305.

- Обеспечьте реализацию AlgorithmParameters для использования с алгоритмом ChaCha20-Poly1305.

TLS 1.3. спецификация определяет несколько новых наборов шифров, которые используют алгоритм ChaCha20-Poly1305, но, к сожалению, поддержка этих новых наборов шифров не была целью этого JEP. Надеюсь, мы получим их в следующих выпусках Java.

Вы можете найти более подробную информацию в JEP 329.

Ключевое соглашение с Curve25519 и Curve448

Это еще один JEP, связанный с реализацией TLS 1.3 в Java. Он добавляет пару новых алгоритмов обмена ключами, основанных на эллиптических кривых Curve25519 и Curve448. Давайте посмотрим, что говорит автор JEP:

Реализуйте согласование ключей, используя Curve25519 и Curve448, как описано в RFC 7748.

Цели

RFC 7748 определяет схему согласования ключей, которая является более эффективной и безопасной, чем существующая схема Диффи-Хеллмана на эллиптических кривых (ECDH). Основной целью этого JEP является API и реализация этого стандарта. Дополнительные цели реализации:

- Разработайте независимую от платформы реализацию полностью на Java с более высокой производительностью, чем существующий код ECC (собственный C), при той же степени безопасности.

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

Приятно видеть, что авторы явно упомянули тестирование против атак по сторонним каналам. Эти новые алгоритмы включены в спецификации TLS 1.3 и даже в более ранних версиях протокола. Подробнее в JEP 324.

Контроль доступа на основе гнезд

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

Вот пример. Рассмотрим следующий код Java:

package com.gypsyengineer.innerclass.field;
 
public class Outer {
 
    private int secret = 10;
 
    public void check() {
        if (secret < 0) {
            System.out.println("Oops");
        } else {
            System.out.println("Okay");
        }
    }
 
    public class Inner {
 
        public void go() {
            secret = 42;
        }
    }
 
}

Если мы скомпилируем его с помощью JDK 10 и запустим javap, то получим примерно следующее:

$ javac -d classes src/com/gypsyengineer/innerclass/field/*.java
$ javap -c -p classes/com/gypsyengineer/innerclass/field/Outer.class
Compiled from "Outer.java"
public class com.gypsyengineer.innerclass.field.Outer {
  private int secret;

  public com.gypsyengineer.innerclass.field.Outer();
    Code:
       0: aload_0
       1: invokespecial #2                  // Method java/lang/Object."":()V
       4: aload_0
       5: bipush        10
       7: putfield      #1                  // Field secret:I
      10: return

  public void check();
    Code:
       0: aload_0
       1: getfield      #1                  // Field secret:I
       4: ifge          18
       7: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: ldc           #4                  // String Oops
      12: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      15: goto          26
      18: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      21: ldc           #6                  // String Okay
      23: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      26: return

  static int access$002(com.gypsyengineer.innerclass.field.Outer, int);
    Code:
       0: aload_0
       1: iload_1
       2: dup_x1
       3: putfield      #1                  // Field secret:I
       6: ireturn
}

Обратите внимание на метод access$002, который мы не определили явно в исходном коде. Это синтетический метод, добавленный компилятором Java, который обеспечивает доступ к приватному полю. Вы можете найти более подробную информацию в разделе Доступ к закрытым полям с помощью синтетических методов в Java.

Java 11 ослабляет проверки доступа для таких случаев. Вот что говорят авторы JEP:

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

Если мы скомпилируем приведенный выше код с помощью JDK 11 и запустим javap, то получим следующее:

$ javac -d classes src/com/gypsyengineer/innerclass/field/*.java
$ /home/asmotrakov/tools/jdk/jdk-11b28/bin/javap -c -p classes/com/gypsyengineer/innerclass/field/Outer.class
Compiled from "Outer.java"
public class com.gypsyengineer.innerclass.field.Outer {
  private int secret;

  public com.gypsyengineer.innerclass.field.Outer();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: aload_0
       5: bipush        10
       7: putfield      #2                  // Field secret:I
      10: return

  public void check();
    Code:
       0: aload_0
       1: getfield      #2                  // Field secret:I
       4: ifge          18
       7: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: ldc           #4                  // String Oops
      12: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      15: goto          26
      18: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      21: ldc           #6                  // String Okay
      23: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      26: return
}

Метод access$002 больше не используется, но тем не менее JVM успешно выполняет код.

Вы можете найти более подробную информацию в JEP 181. Это довольно большое обновление в Java, которое затронуло спецификацию JVM, JVM Hotspot, компилятор Java, API дескрипторов методов, API отражения, JVM TI, API инструментария Java, JDWP, JDI и даже Pack200 (который устарел в Java 11 способ). Я надеюсь, что это обновление не имеет побочных эффектов безопасности :)

Первоначально опубликовано на сайте blog.gypsyengineer.com 26 сентября 2018 г.