GRPC на Android с использованием TLS

RedDeveloper
05.01.2023 04:48
GRPC на Android с использованием TLS

GRPC - это относительно новая концепция взаимодействия между клиентом и сервером, но не более.

gRPC client-server

Что такое удаленный вызов процедур (RPC)?

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

RPC использует традиционную модель "клиент-сервер". Сервер определяет процедуры, а клиент запрашивает удаленные серверы для применения бизнес-логики и возврата результата.

Rest vs gRPC:

Передача репрезентативного состояния (REST) - это коммуникационный протокол взаимодействия между клиентом и сервером, построенный на основе HTTP 1.1/HTTPS 1.1 с такими глаголами, как (PUT, POST, GET и т.д.).

GRPC - это удаленный вызов протокола от Google, кроссплатформенный коммуникационный протокол с открытым исходным кодом, основанный на контрактах и упрощающий межсервисное взаимодействие.

GRPC использует двунаправленную функцию HTTP/2.0 и TLS. gRPC также обеспечивает более эффективную коммуникацию через сериализованные двоичные полезные нагрузки. В качестве механизма управления и сериализации полезной нагрузки он использует буфер протокола, подобно JSON в Rest.

В отличие от JSON буфер протокола также состоит из трех основных частей:

  1. Язык определения контракта, т.е. proto3 (последняя спецификация языка буфера протокола).
  2. Сгенерированный функциональный код
  3. Библиотеки времени выполнения, специфичные для конкретного языка

В отличие от HTTP/1.1, который последовательно загружает ресурсы, HTTP/2.0 использует TCP-соединение и одно соединение для передачи многих потоков данных, поэтому блокировка ресурсов отсутствует. В HTTP/1.1 мы можем столкнуться с проблемой, если ресурс не сможет загрузиться, последовательность будет заблокирована.

Что такое proto3?

syntax = 'proto3';

package bookStorePackage;

// Book service definition. defines all the rpc methods here
service Book {
    rpc createBook (BookItemReq) returns (BookItem);
    rpc readBook (BookRequest) returns (BookItem);
    rpc readBooks (Empty) returns (BooksList);
}

// inputs / request , e.g. BookItemReq 
// outputs / response BookItem must be defined
// they are defined with a keyword of struct

message BookItemReq {
    string name = 1;
    string description = 2;
    string author = 3;
}

message BookItem {
    int32 id = 1;
    string name = 2;
    string description = 3;
    string author = 4;
}

message BookRequest {
    int32 id = 1;
}

message BooksList {
    repeated BookItem books = 1;
}

message Empty {}

Файл `.proto` содержит структуру нашего gRPC и определяет процедуры и запросы/ответы для каждого вызова. Файлы .proto транслируются в файлы буферов proto с помощью плагина protoc.

protoc --proto_path=src --java_out=build/gen PROTO_PATH

Давайте теперь подробнее рассмотрим код. Вы можете найти много ресурсов для реализации gRPC без TLS. Мы рассмотрим создание клиента gRPC на android с TLS.

Некоторые требования перед началом работы: безопасный gRPC-сервер, написанный на любом языке.

Как это сделать для Android Studio:

Во-первых, нам нужно создать новый проект в android. В этом проекте мы будем использовать пустую активность. Затем установите зависимости и добавьте следующее в build.gradle.

plugins {
    id 'com.google.protobuf' version '0.8.18'
}

// this is used to generate proto buf file when you build the project
protobuf {
    protoc {
        artifact="com.google.protobuf:protoc:3.21.7"
    }
    plugins {
        grpc {
            artifact = 'io.grpc:protoc-gen-grpc-java:1.51.0'
        }
    }
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java { option 'lite' }
            }
            task.plugins {
                grpc { option 'lite' }
            }
        }
    }
}

dependencies {
    // for TLS based connection we need okhttp
    implementation 'io.grpc:grpc-okhttp:1.51.1'
    implementation 'io.grpc:grpc-protobuf-lite:1.51.1'
    implementation 'io.grpc:grpc-stub:1.51.1'
    // these are used to set up a secure client
    implementation 'com.squareup.okhttp3:okhttp-tls:4.10.0'
    implementation 'com.squareup.okhttp3:okhttp:4.10.0'
}

Теперь нам нужно добавить файл proto в наш проект.

Теперь нам нужно добавить файл proto в наш проектFile Structure Section in Android Studio

Нам нужно изменить структуру с android на проект. Теперь мы можем добавить наш файл proto в app/src/main/proto. Нам нужно создать каталог с именем proto и поместить туда наш файл .proto.

Нам нужно изменить структуру с android на проект Теперь мы можем добавить наш файл protoProto file destination app/src/main/proto

Теперь нам нужно собрать наш проект с помощью панели инструментов > build > rebuild project.

Теперь нам нужно собрать наш проект с помощью панели инструментов > build > rebuildrebuild project to get generated code.

Вот сгенерированный файл, который вы получите.

Вот сгенерированный файл который вы получитеauto-generated protocol buffer files.Вот сгенерированный файл который вы получитеBookGrpc auto-generated class

Один важный момент, как мы знаем, в java мы имеем определенное имя пакета в нашем проекте. Как мы можем указать его в нашем proto файле?

Очень просто. Нам нужно добавить опцию для java, чтобы направлять компилятор (protoc) при генерации буферного файла proto.

Очень просто Нам нужно добавить опцию для java чтобы направлять компилятор (protoc) приproto file with java options

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

Теперь давайте разберемся, как настроить нашего клиента на взаимодействие с сервером gRPC через защищенное соединение.

Мы должны поместить наш сертификат подписи в категорию raw в папке res нашего приложения.

Мы должны поместить наш сертификат подписи в категорию raw в папке res нашего приложенияlocation to add the signing certificate

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

        try{
            Resources res = getResources();
            InputStream instream = res.openRawResource(R.raw.ca_cert);
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            Certificate ca = cf.generateCertificate(instream);
            KeyStore kStore = KeyStore.getInstance(KeyStore.getDefaultType());
            kStore.load(null, null);
            kStore.setCertificateEntry("ca", ca);
            TrustManagerFactory tmf = TrustManagerFactory
                    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(kStore);
            TrustManager[]  trustManagers = tmf.getTrustManagers();
            if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
            }
            SSLContext context = SSLContext.getInstance("TLS");
            context.init(null, tmf.getTrustManagers(), null);
            SSLSocketFactory sslSocketFactory = context.getSocketFactory();
            
            final ManagedChannel channel = OkHttpChannelBuilder
                    .forAddress("IP Address",PORT)
                    .useTransportSecurity()
                    .overrideAuthority("add same DNS / IP:PORT entry here")
                    .sslSocketFactory(sslSocketFactory)
                    .build();
            // BookGrpc is an auto generated class from the proto we defined.
            BookGrpc.BookBlockingStub stub = BookGrpc.newBlockingStub(channel);
            // create a request object
            BookItemReq requestData = BookItemReq
                    .newBuilder()
                    .setName("AnyName")
                    .setAuthor("AnyAuthor")
                    .setDescription("Any Description")
                    .build();
            BookItem response = stub.createBook(requestData);
            // handle the response
            System.out.println(response);
            channel.shutdown();
        } catch (Exception e){
            e.printStackTrace();
        }

Используя предоставленный пример кода, вы можете взаимодействовать с вашим защищенным сервером gRPC.

Что делает этот код?

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

Далее мы считываем сертификат с помощью следующих строк.

Resources res = getResources();
InputStream instream = res.openRawResource(R.raw.ca_cert);

Сначала мы получаем экземпляр ресурса, затем используем функцию openRawResource для чтения нашего сертификата. Затем мы генерируем сертификат из нашего файла и добавляем его в качестве доверенного сертификата. Используя доверенный сертификат, мы инициализируем экземпляр контекста SSL для инициализации фабрики сокетов SSL. Фабрика сокетов SSL действует как фактор защищенных сокетов.

Зашифрованный канал обрабатывает запросы и ответы от сервера. Мы использовали OkHttpChannelBuilder, который поддерживает использование SSLSocketFactor для обеспечения безопасной среды связи между клиентом и сервером.

Как получить сертификаты самостоятельно?

Для генерации сертификатов вы можете использовать следующий скрипт:

rm *.pem
rm *.srl
rm *.cnf

# 1. Generate CA's private key and self-signed certificate
openssl req -x509 -newkey rsa:4096 -days 365 -nodes -keyout ca-key.pem -out ca-cert.pem -subj "/C=FR/ST=Occitanie/L=Toulouse/O=Test Org/OU=Test/CN=*.test/[email protected]"

echo "CA's self-signed certificate"
openssl x509 -in ca-cert.pem -noout -text

# 2. Generate web server's private key and certificate signing request (CSR)
openssl req -newkey rsa:4096 -nodes -keyout server-key.pem -out server-req.pem -subj "/C=FR/ST=Ile de France/L=Paris/O=Server TLS/OU=Server/CN=*.tls/[email protected]"

# Remember that when we develop on localhost, It’s important to add the IP:0.0.0.0 as an Subject Alternative Name (SAN) extension to the certificate.
echo "subjectAltName=DNS:*.tls,DNS:example.com,IP:0.0.0.0" > server-ext.cnf
# Or you can use localhost DNS and grpc.ssl_target_name_override variable
# echo "subjectAltName=DNS:localhost" > server-ext.cnf

# 3. Use CA's private key to sign web server's CSR and get back the signed certificate
openssl x509 -req -in server-req.pem -days 60 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile server-ext.cnf

echo "Server's signed certificate"
openssl x509 -in server-cert.pem -noout -text

# 4. Generate client's private key and certificate signing request (CSR)
openssl req -newkey rsa:4096 -nodes -keyout client-key.pem -out client-req.pem -subj "/C=FR/ST=Alsace/L=Strasbourg/O=PC Client/OU=Computer/CN=*.client.com/[email protected]"

# Remember that when we develop on localhost, It’s important to add the IP:0.0.0.0 as an Subject Alternative Name (SAN) extension to the certificate.
echo "subjectAltName=DNS:eaple.com,IP:0.0.0.0" > client-ext.cnf

# 5. Use CA's private key to sign client's CSR and get back the signed certificate
openssl x509 -req -in client-req.pem -days 60 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -extfile client-ext.cnf

echo "Client's signed certificate"
openssl x509 -in client-cert.pem -noout -text

Примечание: Полный код вы можете найти на сайте https://github.com/AbdullahJanKhan/android-grpc-tls

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?

20.08.2023 18:21

Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в 2023-2024 годах? Или это полная лажа?".

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией

20.08.2023 17:46

В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.

Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox

19.08.2023 18:39

Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в частности, магию поплавков и гибкость flexbox.

Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest

19.08.2023 17:22

В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для чтения благодаря своей простоте. Кроме того, мы всегда хотим проверить самые последние возможности в наших проектах!

Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️

18.08.2023 20:33

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

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL

14.08.2023 14:49

Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип предназначен для представления неделимого значения.