1#!/usr/bin/env python3
2
3# Copyright 2022 gRPC authors.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17# Generator script for src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h and test/core/security/grpc_tls_credentials_options_comparator_test.cc
18# Should be executed from grpc's root directory.
19
20from __future__ import print_function
21
22import collections
23from dataclasses import dataclass
24import difflib
25import filecmp
26import os
27import sys
28import tempfile
29
30
31@dataclass
32class DataMember:
33    name: str  # name of the data member without the trailing '_'
34    type: str  # Type (eg. std::string, bool)
35    test_name: str  # The name to use for the associated test
36    test_value_1: str  # Test-specific value to use for comparison
37    test_value_2: str  # Test-specific value (different from test_value_1)
38    default_initializer: str = ''  # If non-empty, this will be used as the default initialization of this field
39    getter_comment: str = ''  # Comment to add before the getter for this field
40    special_getter_return_type: str = ''  # Override for the return type of getter (eg. const std::string&)
41    override_getter: str = ''  # Override for the entire getter method. Relevant for certificate_verifier and certificate_provider
42    setter_comment: str = ''  # Commend to add before the setter for this field
43    setter_move_semantics: bool = False  # Should the setter use move-semantics
44    special_comparator: str = ''  # If non-empty, this will be used in `operator==`
45
46
47_DATA_MEMBERS = [
48    DataMember(name='cert_request_type',
49               type='grpc_ssl_client_certificate_request_type',
50               default_initializer='GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE',
51               test_name="DifferentCertRequestType",
52               test_value_1="GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE",
53               test_value_2="GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_AND_VERIFY"),
54    DataMember(name='verify_server_cert',
55               type='bool',
56               default_initializer='true',
57               test_name="DifferentVerifyServerCert",
58               test_value_1="false",
59               test_value_2="true"),
60    DataMember(name='min_tls_version',
61               type='grpc_tls_version',
62               default_initializer='grpc_tls_version::TLS1_2',
63               test_name="DifferentMinTlsVersion",
64               test_value_1="grpc_tls_version::TLS1_2",
65               test_value_2="grpc_tls_version::TLS1_3"),
66    DataMember(name='max_tls_version',
67               type='grpc_tls_version',
68               default_initializer='grpc_tls_version::TLS1_3',
69               test_name="DifferentMaxTlsVersion",
70               test_value_1="grpc_tls_version::TLS1_2",
71               test_value_2="grpc_tls_version::TLS1_3"),
72    DataMember(
73        name='certificate_verifier',
74        type='grpc_core::RefCountedPtr<grpc_tls_certificate_verifier>',
75        override_getter="""grpc_tls_certificate_verifier* certificate_verifier() {
76    return certificate_verifier_.get();
77  }""",
78        setter_move_semantics=True,
79        special_comparator=
80        '(certificate_verifier_ == other.certificate_verifier_ || (certificate_verifier_ != nullptr && other.certificate_verifier_ != nullptr && certificate_verifier_->Compare(other.certificate_verifier_.get()) == 0))',
81        test_name="DifferentCertificateVerifier",
82        test_value_1="MakeRefCounted<HostNameCertificateVerifier>()",
83        test_value_2="MakeRefCounted<XdsCertificateVerifier>(nullptr, \"\")"),
84    DataMember(name='check_call_host',
85               type='bool',
86               default_initializer='true',
87               test_name="DifferentCheckCallHost",
88               test_value_1="false",
89               test_value_2="true"),
90    DataMember(
91        name='certificate_provider',
92        type='grpc_core::RefCountedPtr<grpc_tls_certificate_provider>',
93        getter_comment=
94        'Returns the distributor from certificate_provider_ if it is set, nullptr otherwise.',
95        override_getter=
96        """grpc_tls_certificate_distributor* certificate_distributor() {
97    if (certificate_provider_ != nullptr) { return certificate_provider_->distributor().get(); }
98    return nullptr;
99  }""",
100        setter_move_semantics=True,
101        special_comparator=
102        '(certificate_provider_ == other.certificate_provider_ || (certificate_provider_ != nullptr && other.certificate_provider_ != nullptr && certificate_provider_->Compare(other.certificate_provider_.get()) == 0))',
103        test_name="DifferentCertificateProvider",
104        test_value_1=
105        "MakeRefCounted<StaticDataCertificateProvider>(\"root_cert_1\", PemKeyCertPairList())",
106        test_value_2=
107        "MakeRefCounted<StaticDataCertificateProvider>(\"root_cert_2\", PemKeyCertPairList())"
108    ),
109    DataMember(
110        name='watch_root_cert',
111        type='bool',
112        default_initializer='false',
113        setter_comment=
114        'If need to watch the updates of root certificates with name |root_cert_name|. The default value is false. If used in tls_credentials, it should always be set to true unless the root certificates are not needed.',
115        test_name="DifferentWatchRootCert",
116        test_value_1="false",
117        test_value_2="true"),
118    DataMember(
119        name='root_cert_name',
120        type='std::string',
121        special_getter_return_type='const std::string&',
122        setter_comment=
123        'Sets the name of root certificates being watched, if |set_watch_root_cert| is called. If not set, an empty string will be used as the name.',
124        setter_move_semantics=True,
125        test_name="DifferentRootCertName",
126        test_value_1="\"root_cert_name_1\"",
127        test_value_2="\"root_cert_name_2\""),
128    DataMember(
129        name='watch_identity_pair',
130        type='bool',
131        default_initializer='false',
132        setter_comment=
133        'If need to watch the updates of identity certificates with name |identity_cert_name|. The default value is false. If used in tls_credentials, it should always be set to true unless the identity key-cert pairs are not needed.',
134        test_name="DifferentWatchIdentityPair",
135        test_value_1="false",
136        test_value_2="true"),
137    DataMember(
138        name='identity_cert_name',
139        type='std::string',
140        special_getter_return_type='const std::string&',
141        setter_comment=
142        'Sets the name of identity key-cert pairs being watched, if |set_watch_identity_pair| is called. If not set, an empty string will be used as the name.',
143        setter_move_semantics=True,
144        test_name="DifferentIdentityCertName",
145        test_value_1="\"identity_cert_name_1\"",
146        test_value_2="\"identity_cert_name_2\""),
147    DataMember(name='tls_session_key_log_file_path',
148               type='std::string',
149               special_getter_return_type='const std::string&',
150               setter_move_semantics=True,
151               test_name="DifferentTlsSessionKeyLogFilePath",
152               test_value_1="\"file_path_1\"",
153               test_value_2="\"file_path_2\""),
154    DataMember(
155        name='crl_directory',
156        type='std::string',
157        special_getter_return_type='const std::string&',
158        setter_comment=
159        ' gRPC will enforce CRLs on all handshakes from all hashed CRL files inside of the crl_directory. If not set, an empty string will be used, which will not enable CRL checking. Only supported for OpenSSL version > 1.1.',
160        setter_move_semantics=True,
161        test_name="DifferentCrlDirectory",
162        test_value_1="\"crl_directory_1\"",
163        test_value_2="\"crl_directory_2\""),
164    DataMember(
165        name="send_client_ca_list",
166        type="bool",
167        default_initializer="false",
168        test_name="DifferentSendClientCaListValues",
169        test_value_1="false",
170        test_value_2="true",
171    ),
172]
173
174
175# print copyright notice from this file
176def put_copyright(f, year):
177    print("""//
178//
179// Copyright %s gRPC authors.
180//
181// Licensed under the Apache License, Version 2.0 (the "License");
182// you may not use this file except in compliance with the License.
183// You may obtain a copy of the License at
184//
185//     http://www.apache.org/licenses/LICENSE-2.0
186//
187// Unless required by applicable law or agreed to in writing, software
188// distributed under the License is distributed on an "AS IS" BASIS,
189// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190// See the License for the specific language governing permissions and
191// limitations under the License.
192//
193//
194""" % (year),
195          file=f)
196
197
198# Prints differences between two files
199def get_file_differences(file1, file2):
200    with open(file1) as f1:
201        file1_text = f1.readlines()
202    with open(file2) as f2:
203        file2_text = f2.readlines()
204    return difflib.unified_diff(file1_text,
205                                file2_text,
206                                fromfile=file1,
207                                tofile=file2)
208
209
210# Is this script executed in test mode?
211test_mode = False
212if len(sys.argv) > 1 and sys.argv[1] == "--test":
213    test_mode = True
214
215HEADER_FILE_NAME = 'src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h'
216# Generate src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h
217header_file_name = HEADER_FILE_NAME
218if (test_mode):
219    header_file_name = tempfile.NamedTemporaryFile(delete=False).name
220H = open(header_file_name, 'w')
221
222put_copyright(H, '2018')
223print(
224    '// Generated by tools/codegen/core/gen_grpc_tls_credentials_options.py\n',
225    file=H)
226print(
227    """#ifndef GRPC_SRC_CORE_LIB_SECURITY_CREDENTIALS_TLS_GRPC_TLS_CREDENTIALS_OPTIONS_H
228#define GRPC_SRC_CORE_LIB_SECURITY_CREDENTIALS_TLS_GRPC_TLS_CREDENTIALS_OPTIONS_H
229
230#include <grpc/support/port_platform.h>
231
232#include "absl/container/inlined_vector.h"
233
234#include <grpc/grpc_security.h>
235
236#include "src/core/lib/gprpp/ref_counted.h"
237#include "src/core/lib/security/credentials/tls/grpc_tls_certificate_distributor.h"
238#include "src/core/lib/security/credentials/tls/grpc_tls_certificate_provider.h"
239#include "src/core/lib/security/credentials/tls/grpc_tls_certificate_verifier.h"
240#include "src/core/lib/security/security_connector/ssl_utils.h"
241
242// Contains configurable options specified by callers to configure their certain
243// security features supported in TLS.
244// TODO(ZhenLian): consider making this not ref-counted.
245struct grpc_tls_credentials_options
246    : public grpc_core::RefCounted<grpc_tls_credentials_options> {
247 public:
248  ~grpc_tls_credentials_options() override = default;
249""",
250    file=H)
251
252# Print out getters for all data members
253print("  // Getters for member fields.", file=H)
254for data_member in _DATA_MEMBERS:
255    if data_member.getter_comment != '':
256        print("  // " + data_member.getter_comment, file=H)
257    if data_member.override_getter:
258        print("  " + data_member.override_getter, file=H)
259    else:
260        print(
261            "  %s %s() const { return %s; }" %
262            (data_member.special_getter_return_type if
263             data_member.special_getter_return_type != '' else data_member.type,
264             data_member.name, data_member.name + '_'),
265            file=H)
266
267# Print out setters for all data members
268print("", file=H)
269print("  // Setters for member fields.", file=H)
270for data_member in _DATA_MEMBERS:
271    if data_member.setter_comment != '':
272        print("  // " + data_member.setter_comment, file=H)
273    if (data_member.setter_move_semantics):
274        print("  void set_%s(%s %s) { %s_ = std::move(%s); }" %
275              (data_member.name, data_member.type, data_member.name,
276               data_member.name, data_member.name),
277              file=H)
278    else:
279        print("  void set_%s(%s %s) { %s_ = %s; }" %
280              (data_member.name, data_member.type, data_member.name,
281               data_member.name, data_member.name),
282              file=H)
283
284# Write out operator==
285print("\n  bool operator==(const grpc_tls_credentials_options& other) const {",
286      file=H)
287operator_equal_content = "    return "
288for i in range(len(_DATA_MEMBERS)):
289    if (i != 0):
290        operator_equal_content += "      "
291    if (_DATA_MEMBERS[i].special_comparator != ''):
292        operator_equal_content += _DATA_MEMBERS[i].special_comparator
293    else:
294        operator_equal_content += _DATA_MEMBERS[
295            i].name + "_ == other." + _DATA_MEMBERS[i].name + "_"
296    if (i != len(_DATA_MEMBERS) - 1):
297        operator_equal_content += ' &&\n'
298print(operator_equal_content + ";\n  }", file=H)
299
300#Print out data member declarations
301print("\n private:", file=H)
302for data_member in _DATA_MEMBERS:
303    if data_member.default_initializer == '':
304        print("  %s %s_;" % (
305            data_member.type,
306            data_member.name,
307        ), file=H)
308    else:
309        print("  %s %s_ = %s;" % (data_member.type, data_member.name,
310                                  data_member.default_initializer),
311              file=H)
312
313# Print out file ending
314print("""};
315
316#endif  // GRPC_SRC_CORE_LIB_SECURITY_CREDENTIALS_TLS_GRPC_TLS_CREDENTIALS_OPTIONS_H""",
317      file=H)
318
319H.close()
320
321# Generate test/core/security/grpc_tls_credentials_options_comparator_test.cc
322TEST_FILE_NAME = 'test/core/security/grpc_tls_credentials_options_comparator_test.cc'
323test_file_name = TEST_FILE_NAME
324if (test_mode):
325    test_file_name = tempfile.NamedTemporaryFile(delete=False).name
326T = open(test_file_name, 'w')
327
328put_copyright(T, '2022')
329print('// Generated by tools/codegen/core/gen_grpc_tls_credentials_options.py',
330      file=T)
331print("""
332#include <grpc/support/port_platform.h>
333
334#include <string>
335
336#include <gmock/gmock.h>
337
338#include "src/core/lib/security/credentials/xds/xds_credentials.h"
339#include "src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h"
340#include "test/core/util/test_config.h"
341
342namespace grpc_core {
343namespace {
344""",
345      file=T)
346
347# Generate negative test for each negative member
348for data_member in _DATA_MEMBERS:
349    print("""TEST(TlsCredentialsOptionsComparatorTest, %s) {
350  auto* options_1 = grpc_tls_credentials_options_create();
351  auto* options_2 = grpc_tls_credentials_options_create();
352  options_1->set_%s(%s);
353  options_2->set_%s(%s);
354  EXPECT_FALSE(*options_1 == *options_2);
355  EXPECT_FALSE(*options_2 == *options_1);
356  delete options_1;
357  delete options_2;
358}""" % (data_member.test_name, data_member.name, data_member.test_value_1,
359        data_member.name, data_member.test_value_2),
360          file=T)
361
362# Print out file ending
363print("""
364} // namespace
365} // namespace grpc_core
366
367int main(int argc, char** argv) {
368  testing::InitGoogleTest(&argc, argv);
369  grpc::testing::TestEnvironment env(&argc, argv);
370  grpc_init();
371  auto result = RUN_ALL_TESTS();
372  grpc_shutdown();
373  return result;
374}""",
375      file=T)
376T.close()
377
378if (test_mode):
379    header_diff = get_file_differences(header_file_name, HEADER_FILE_NAME)
380    test_diff = get_file_differences(test_file_name, TEST_FILE_NAME)
381    os.unlink(header_file_name)
382    os.unlink(test_file_name)
383    header_error = False
384    for line in header_diff:
385        print(line)
386        header_error = True
387    if header_error:
388        print(
389            HEADER_FILE_NAME +
390            ' should not be manually modified. Please make changes to tools/distrib/gen_grpc_tls_credentials_options.py instead.'
391        )
392    test_error = False
393    for line in test_diff:
394        print(line)
395        test_error = True
396    if test_error:
397        print(
398            TEST_FILE_NAME +
399            ' should not be manually modified. Please make changes to tools/distrib/gen_grpc_tls_credentials_options.py instead.'
400        )
401    if (header_error or test_error):
402        sys.exit(1)
403