xrpld
Loading...
Searching...
No Matches
GRPCServerTLS_test.cpp
1#include <test/jtx/Env.h>
2#include <test/jtx/envconfig.h>
3
4#include <xrpl/beast/unit_test/suite.h>
5#include <xrpl/config/Constants.h>
6#include <xrpl/proto/org/xrpl/rpc/v1/get_ledger.pb.h>
7#include <xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
8
9#include <boost/filesystem/operations.hpp>
10
11#include <grpcpp/client_context.h>
12#include <grpcpp/create_channel.h>
13#include <grpcpp/grpcpp.h>
14#include <grpcpp/security/credentials.h>
15#include <grpcpp/support/status.h>
16
17#include <chrono>
18#include <filesystem>
19#include <fstream>
20#include <memory>
21#include <stdexcept>
22#include <string>
23#include <string_view>
24#include <system_error>
25#include <utility>
26
27namespace {
28
29constexpr std::string_view kCaCertContent =
30 "-----BEGIN CERTIFICATE-----\n"
31 "MIIFhjCCA26gAwIBAgIJAL9P70zX30oiMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV\n"
32 "BAYTAlVTMQ0wCwYDVQQIDARUZXN0MQ0wCwYDVQQHDARUZXN0MRgwFgYDVQQKDA9S\n"
33 "aXBwbGVkIFRlc3QgQ0ExEDAOBgNVBAMMB1Rlc3QgQ0EwIBcNMjYwNDA5MTMyNTA2\n"
34 "WhgPMjEyNjAzMTYxMzI1MDZaMFcxCzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARUZXN0\n"
35 "MQ0wCwYDVQQHDARUZXN0MRgwFgYDVQQKDA9SaXBwbGVkIFRlc3QgQ0ExEDAOBgNV\n"
36 "BAMMB1Rlc3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzOJ5s\n"
37 "dy1O0GN/kmbeWf5DmFbQBSS9FRKxh6/o9V9BqRBQfECrVK9T5Y4FYrGGtmUW3YEV\n"
38 "uMDZ5q6rvBT2zrrzPXnWA5Pb4I4mKqC/yk5L7Mm8A9xQsNoRzgTyl/NuHiXKn+yQ\n"
39 "FuidA6U36qwIAcDR7gLqrJ1ud/ng9f9Q4k6+IItY/XGhcz4nKlQq9jpzmfdSlBkU\n"
40 "hXsIsdNtC+UGlQMCMX2jwysIFfjjCOMlH7KFQ3dKodhsW+Ym6AsPwyRGCgNXO/zd\n"
41 "Fqt1MIMs1F7r40DtfVO3R7w4/2SblcceZlsDrYQUbJnH+sEPWO0SGGo6Y7Ohs09+\n"
42 "aJSOAGGQVgTSLuAcFtR4BXD0GLn39+10PDvHGOsMJKL1de1f96s8kPlifQ5AGWuc\n"
43 "xy6XsupGSa0F8LozwQmKD7hVkyladUTWFPknz5tsPEVApTO0U8Vuknuhyovo6+mx\n"
44 "qBoSD32NwHveFz3jWqfj0CGX9BwL9AOpMabDhROVQfyM5GrLeLOOdgOnsBXJYYdW\n"
45 "MeJwz6BH30q9yvEd9Ti26jSk3fM8WPuEkZzNNp8STEMyDrfhaKOe5fGPWLnqMQAf\n"
46 "yMCDLwB1WqIN1Q6gOELb3rxyYDVH/5x6/JXosdUe1qx/tzvRoSWxxssRRd2Em+e+\n"
47 "MUFLXz+9D6kZ9XCuP/mLyRGW6LEiwwQkGKMnzwIDAQABo1MwUTAPBgNVHRMBAf8E\n"
48 "BTADAQH/MB0GA1UdDgQWBBQPK5hXxLdTj3QqfVzGpfTga6IF3zAfBgNVHSMEGDAW\n"
49 "gBQPK5hXxLdTj3QqfVzGpfTga6IF3zANBgkqhkiG9w0BAQsFAAOCAgEAa06whkqv\n"
50 "KmdT1HVhkV7AkWEAeHMWPLLaaFbcwble7a1Vizh6GjCyNpLtoN+mtwqwiOdsIlRE\n"
51 "42pWILc6CuuX0ae0nHSrcQS5mq8ZKSMr1xTo9RSfBq7CDfdyquxzG83HhpdApViZ\n"
52 "87Bjy3WoRuomM+YiONfUVdCbC5ZmXW/z+xrXJ+JqIXrtv66sZxpQIR0+ShnWT0DE\n"
53 "w9jB5fxjydPFwEudYi4z9XjEZaZJ1f8VNWDuUvi3yTJtTlNaWnKveudtDZBw/fA+\n"
54 "MBFd9ccYVhGQPxOs6S0Ev6q5IjcnzGeEBNZOjgjQk9aFrAs2Iiy018AbYQj5XD64\n"
55 "hHyiNgyPjl/VgXJE1Xl3lXGpiiJlXctgnCd3UGMfKznhBIpDT13i2CmHFyR3uk7o\n"
56 "UOZUXCnbnmgthejmFxB35Wf5TmGaYubtRMfCPHGNbQD+7Kg2+8eel3J3JSuG6RQ8\n"
57 "hwNyHHQnaPVUSANItJ4cMe5DutM0vUCMkJbajL+fjC5SdsTcGfR2VmAFqulNDXjH\n"
58 "sGWBiWVNsgddax63m6kL9UOeE+8pu8yStKZ4mVn2EjE9eJk4vyZt4BaI6sDUMlke\n"
59 "S9OjcI5iYlxXNgbRQBtwK70+c3D3JoRPREkTRPPwC4NiAFed7UwXSMh5nWbpt/dq\n"
60 "fAbAYqu0rfMFHUYjzIVnu8WRCC56qYHO5tU=\n"
61 "-----END CERTIFICATE-----\n";
62
63constexpr std::string_view kServerCertContent =
64 "-----BEGIN CERTIFICATE-----\n"
65 "MIIFizCCA3OgAwIBAgIJAIErcpMflkrRMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV\n"
66 "BAYTAlVTMQ0wCwYDVQQIDARUZXN0MQ0wCwYDVQQHDARUZXN0MRgwFgYDVQQKDA9S\n"
67 "aXBwbGVkIFRlc3QgQ0ExEDAOBgNVBAMMB1Rlc3QgQ0EwIBcNMjYwNDA5MTMyNTA3\n"
68 "WhgPMjEyNjAzMTYxMzI1MDdaMFExCzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARUZXN0\n"
69 "MQ0wCwYDVQQHDARUZXN0MRAwDgYDVQQKDAdSaXBwbGVkMRIwEAYDVQQDDAlsb2Nh\n"
70 "bGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCv+Lj9LJfPuSOE\n"
71 "yZqTn2gmG5tJt02ywnuIQet7N5tduxnNs50yXQ00Jeb40dth0HwI5I+AsEVNPIG3\n"
72 "7tJ9RtCwwTyltaZ4bXuL9ujEVr6TAKY6rlU9bL+Zmr62Lm8B0SLouxfPtyzKhBv6\n"
73 "7bGUrdIX7o9DbTQ3/2mQTc7KjPCJTEutmpVyD3dABN1qDM9Qzac0NtK1nixvYGd4\n"
74 "SpbK95BRXby3X9um0dVXoUMbc2gDV9uUZw1xLSjuKDJtQ/rleqe0mmS6JSoagwbb\n"
75 "DmPX/GbIf21IWUsg3m7AyIwYf9FtIJArB/j3iVBsY9lTB0mXSLxiyN6KM822+QjH\n"
76 "/VeHKDUWdQB6N3smmi1OLQasukRpSUTmTsoucn30dUqS6qdTtkHzvqGEN+CXBWgG\n"
77 "i1AS2CacOjYSVHRppC10r/3kEChYY/9rqYBz7GedFRJ6VzQzrwFYZleLJvX6GWfe\n"
78 "4gNvgwLfo/q2Af1HkCY2aHO+19eAghVsy1MRUDnm/GbZAhHSrX10iEfRjs+GhfxY\n"
79 "v0xMrvGBCm/2CiJ8RAvdRPpNkM/3u9fjOmqdKvE9NTqDOX1HUBoqa/UguIzi6o/k\n"
80 "BlBtohfaeL6ZeYXl6MefIIs2pipR7S1VQ1RY9OSdnN5nIJidyn1l85P9vLn49QVw\n"
81 "2OAT+TcEZnxyaiHCKU6nWtusuMt3wQIDAQABo14wXDAaBgNVHREEEzARgglsb2Nh\n"
82 "bGhvc3SHBH8AAAEwHQYDVR0OBBYEFO9bPc31jmMlMVNhOd+eXgZPD/+pMB8GA1Ud\n"
83 "IwQYMBaAFA8rmFfEt1OPdCp9XMal9OBrogXfMA0GCSqGSIb3DQEBCwUAA4ICAQCm\n"
84 "+hnvRdr9N9a260yOD53b/Gs0c4viAOU3WmxAa89upLHnpPEi7/GlKlw+ed6SwYoX\n"
85 "CSopDw8AG2Ub/oHM3uIrONjfdHBwUl/SUS8wNhiELuQjKm0qGjkh/n/FHY903flc\n"
86 "0VP2ciLnqhSS2NY+KH0O8uny3yR4FVH7Byqtk648Z7LfIhe02TjTIjhXDrGwn5dS\n"
87 "tuTKEAGaxxPJuINCR1BZlwfk+10ipJK59rSpCW//P1YJVr16sdnyh3YJXoAJ5qxP\n"
88 "P8QWHiRIl2ZGs7KB5SU9fX1dVEU5gwrl/KF3oP+iS01wfNZGvnR+eHMPJsl/IwoC\n"
89 "SOZAMjgkTZh06cprfEXne8bcidiHvETbF9szMAofA91PbXi0lcwMqpkHG2AElOXI\n"
90 "by4ejjs9RZJF2Ef38qZPb8RuT+gLORFH5SuPQUwXKlszjpzpxkQ6IKYjFJY+j8CS\n"
91 "XlXhdkzK5h18cf7J2i5SQdIzE1btQqdcaMb9DzX+drCqqD8JZd1Vczua7Q5tbZ/g\n"
92 "Bq19Zzo1KQL0xXPdomWv+sP6eUMiW+3J5oFN2hJpilKuFSCAhDmgcmLooFy5t6rR\n"
93 "kW0n1P3iTWvgQHNzB/3msanvC4/hHyrHHOVGQtAjhxuoRioBJ+hg4RKDptSUcHJX\n"
94 "YSyd81wvumIpP+I7BDkQLgTb+NzMmoBIjRg3aVvXSg==\n"
95 "-----END CERTIFICATE-----\n";
96
97constexpr std::string_view kServerKeyContent =
98 "-----BEGIN PRIVATE KEY-----\n"
99 "MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCv+Lj9LJfPuSOE\n"
100 "yZqTn2gmG5tJt02ywnuIQet7N5tduxnNs50yXQ00Jeb40dth0HwI5I+AsEVNPIG3\n"
101 "7tJ9RtCwwTyltaZ4bXuL9ujEVr6TAKY6rlU9bL+Zmr62Lm8B0SLouxfPtyzKhBv6\n"
102 "7bGUrdIX7o9DbTQ3/2mQTc7KjPCJTEutmpVyD3dABN1qDM9Qzac0NtK1nixvYGd4\n"
103 "SpbK95BRXby3X9um0dVXoUMbc2gDV9uUZw1xLSjuKDJtQ/rleqe0mmS6JSoagwbb\n"
104 "DmPX/GbIf21IWUsg3m7AyIwYf9FtIJArB/j3iVBsY9lTB0mXSLxiyN6KM822+QjH\n"
105 "/VeHKDUWdQB6N3smmi1OLQasukRpSUTmTsoucn30dUqS6qdTtkHzvqGEN+CXBWgG\n"
106 "i1AS2CacOjYSVHRppC10r/3kEChYY/9rqYBz7GedFRJ6VzQzrwFYZleLJvX6GWfe\n"
107 "4gNvgwLfo/q2Af1HkCY2aHO+19eAghVsy1MRUDnm/GbZAhHSrX10iEfRjs+GhfxY\n"
108 "v0xMrvGBCm/2CiJ8RAvdRPpNkM/3u9fjOmqdKvE9NTqDOX1HUBoqa/UguIzi6o/k\n"
109 "BlBtohfaeL6ZeYXl6MefIIs2pipR7S1VQ1RY9OSdnN5nIJidyn1l85P9vLn49QVw\n"
110 "2OAT+TcEZnxyaiHCKU6nWtusuMt3wQIDAQABAoICAQCZilzm0uT3Y2RBdaMBUaKP\n"
111 "NaFONbl+00D0SAhOr9tJcnp2SFVN33Eo4jVhP8K62y2OmNc5gxRE6xmIQsK4enSW\n"
112 "9VSUhiXliCm3m03IGqQYIgXox7oqaVvYi/QBhAxpunBKPwzsubhET/cWABXlU7Ew\n"
113 "HoA0ZfGdNqeGOM3JYCZ0tfSGWo4xQptbaaND6D9wErDk1z0NKSE+YRCHHhXqrQ3o\n"
114 "YPDL08EVEpui5VtndU/5Msyt9Sj+alf/TWWKfzlIx7fS1rAy10Cgd1khA7JMf7ez\n"
115 "E7Rn3zm1ST+7yICs08IJBNOmKEOswMxCdvDmCELG1LlDPF8omUDSeQKXdU7M6GFA\n"
116 "b5PQ11Ik6xZVw1NUESf4d9g0VhEJRXSdGwA3KepAkwRejkB5jI56C8z9dB0LWdWH\n"
117 "2r3dX2ZpbJv0XVNxAELRgKwyfqWxYrF3caGLrxxWAiyPFvD9FgZJB1ftBU3D+HZZ\n"
118 "bltdfHJBgZe3pwoCr3X2JPhcA6ecITsset14dvsXHSi9IAXTHbeXxjrHCRcXs6xV\n"
119 "v4ZSL5r43dv6qk7XiFONCmV8diIwJOxcaSvoBgeeCykX4RKGSk/6Atlo4C9hXb47\n"
120 "BAuXu3Y+SkS98EljsdeNKCr013Tvt0p4H2QfeoDTKuzC+j3hu9fCkEP3oak2nWFl\n"
121 "bOkrYMJCc6yxu20G58vzrQKCAQEA1y93gNuNa7Z+VrZCSEcJX2BZl1mTyhLEa9mN\n"
122 "QOmKlW10VrfCsJxLu+dTGWccy0c6Q8wk6uGjgYJHsdyFPIdSroPR2ysJKSP/5Vzu\n"
123 "xNymgbeLPnWoivC9TctovWY/15fdboYNUO54jOpFheCC1wq9ZP6CyJmw5O96Y7tJ\n"
124 "1l5Dq7Fe4iQbIQHPt54wVVHsm7G1ZNywgSbt0HXHeP43YN3mRawJ51++MaEksCXv\n"
125 "rW+vOxPdiW8djE0tqcK0tqFMhI6p+WcUu8128aRHd0iHlKsVsFU4OLLZr10zwy9i\n"
126 "COHoF4Fh53pGp05jv+5eMtuEiem87ZUmpJn7whHZt8sKSE71AwKCAQEA0Vkwr4KA\n"
127 "kRRCUPvor5mdNil05N1mLrYgr/4UAHg3tbeTGxOjSX65KnJWi5dsDmZUdGTL4StD\n"
128 "8H6uLzzjX88gQkpKvtRYPYKBFtTRsI+ItOvIIo8czK/Kv8dwC2WXZbZBjsCAhrCm\n"
129 "0fKL2jx7rgdjaqvQeqSRtcHiyiYJG/jC7Iqwm4CyPr+nkVUWKZUWXopw0QXZXHWp\n"
130 "Glz9TXreEI7Xb/R+RXYU21exBqg0SfHq9pA//aNTQWxWGlNVwqO/KUao9HZupKHb\n"
131 "mA73oxFJTKhVNNNdC5cC91pxDeDTUzpIEjCGeLI3Aa35CD0WFqEbELJphr5HGkGo\n"
132 "VkYod6P79+Ta6wKCAQAadFpzvAop2Ni1XljNu/X6BMVe5wNVT3NYcvl7pnqEHl20\n"
133 "H4lO3xgsdKbxs4yFrS8LkLhlK/JHBLY9toemxlgy3j/ZevP4W9Wk5ATyrNHHlsIG\n"
134 "nr5mvmv3eW9aAY0Nuzzczpwqe/bUFCUR7WUIfOiF1whLEyH9MzfPtQHB2frly7uH\n"
135 "f7raFvfrcgYtJxI4neNYEA2fAyMvgptQU6iJPx6FKD5bdJjUTyRMh41svBNF5w5Q\n"
136 "TBnM2twnR6mh3jii/0sEP1j8MalS0ch7cK5CZ7oV4JQ13D8I4SNw9o1N3EAFS8G2\n"
137 "jIDNJsT6npp0FCq6LcMtTi3fBJM/66PhhZOxCgvzAoIBAH1LnE/vE3PBZE+D9afj\n"
138 "kKwx87xmphme98FdmCsPyIgB7xFtl3UNW1WESTgS0KFtrW5cRYnmkysFJssu7gcR\n"
139 "uIT0YfgErythSFGZ3kaGIZPm6kmEzf/T1s0hWHX5v7soceQ2YrY6VB2jxQBA4uUt\n"
140 "ltrpKkW86ViXSl0ilqEfKcrY1wq64/OaUXgyLKmGiXTb9tmjXoxv/12/+fq9ZtsS\n"
141 "Iu7mrgx0t9bvjQwm7+Sx3abkfugXMGUfqgjnh5SO3IKfv89QcrgmB3/itWPrnKs8\n"
142 "tIKBXlbpcuUIRFHCFbjiUPBSCqmCQFnI/htoNCgnFEPSBEaY64VTdqTsKJwykUO0\n"
143 "vTECggEAEAB8vyHHk7fpU+IOwD8TP7MCMHwoJzoHQp35So7TlhmO7oDranNhg3nl\n"
144 "jhTOeISLG2dmPkT49vhsO30tal4CgSXVZo1bPbOK83UvgeLH5Rhji44Dmah+ohKy\n"
145 "wCuVLuF6YSSp5rD7VIrahhegBFXEYdW5+ZBFbDpE5EXp0WeHc7IRPwWvm+ixr1m8\n"
146 "VqLeeh1xkMG5WdTTwGjgKWIFXZQ3bOIdVK7uya8wFDAtftkswXiBxAlb9L6Id+Dp\n"
147 "bKfMAHNouU1TQn5duFgPnCbSU1Js74HkkC0NEEIjQX8k2UCPrhV0VfLfViPuPFax\n"
148 "S/RYUSUkZ4VvqFUfo7wT8x18urb87w==\n"
149 "-----END PRIVATE KEY-----\n";
150
151constexpr std::string_view kClientCertContent =
152 "-----BEGIN CERTIFICATE-----\n"
153 "MIIFeDCCA2CgAwIBAgIJAIErcpMflkrSMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV\n"
154 "BAYTAlVTMQ0wCwYDVQQIDARUZXN0MQ0wCwYDVQQHDARUZXN0MRgwFgYDVQQKDA9S\n"
155 "aXBwbGVkIFRlc3QgQ0ExEDAOBgNVBAMMB1Rlc3QgQ0EwIBcNMjYwNDA5MTMyNTA3\n"
156 "WhgPMjEyNjAzMTYxMzI1MDdaMFoxCzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARUZXN0\n"
157 "MQ0wCwYDVQQHDARUZXN0MRcwFQYDVQQKDA5SaXBwbGVkIENsaWVudDEUMBIGA1UE\n"
158 "AwwLdGVzdC1jbGllbnQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDP\n"
159 "QHttw3TLjOqYS3VkLF3KMRaP2ZtO6A1mXfTbqbKvD41Fazf/cM/v9lPMAlRd2SEY\n"
160 "3MeE8KVddKJwsbF0kNgDkKB5D5V42WrTw5biFNMOeHAZMR/oWChIvZbbGbDxIdIO\n"
161 "2+W3X0kjpa2eKcK9qBk8xoyIeilyXtleGWuWvHxiZP9iGHxaTWB+wIKUIK6vrEOb\n"
162 "iO3P/9XPpHzsBt0HdTDh4V7fwnr2UndVeQyBwUwLn6pd73sTKBfA26YppRwDjPIj\n"
163 "6NYtF3I28lRCUo+47TAVZM97gjN9oEwyHIVtOc7fnZPwtN26M5v5083SGXU1k/PN\n"
164 "3xGAlDUiCF3RSMbBRylGgUVtsAD57i8tI2SqCr+ZG233VFiOdwTRKKVNTyMC9TCJ\n"
165 "dCFFEDFDHTTSimKRQKy0Cm2qoL6JBkziAIiu/0Zzv9YjAAnRoJ2cweMXQ/9z1oWe\n"
166 "EUZBLRsggYQ8FbM13FOkOs8IlzacSuhwrYKOq8LsMX4cH2mnn783FtXXqrL/xfL7\n"
167 "11KhzGpZNrz187ilJ+ZsmP9D6vCBP/tR7V52dgtB6I291o8zxdH8GheIGenEFaZa\n"
168 "oAwyN2FuJgXZqx9319I9gYerZ/BbUzA2MuOxFd0ywtdcTPqKiyAQ9rxQVCVQyYWj\n"
169 "kfBEYRzWxjfj3XhNprxdm3cauz01NAoTDiz52dZhGQIDAQABo0IwQDAdBgNVHQ4E\n"
170 "FgQUXVKwiGRrXC1sjK2D86jsjMVV0XgwHwYDVR0jBBgwFoAUDyuYV8S3U490Kn1c\n"
171 "xqX04GuiBd8wDQYJKoZIhvcNAQELBQADggIBACpHTm9GZMZ7OPhqVo4VltVOW9a9\n"
172 "LLDsVYmvpAF9+yjZGims6+p3f7eY+o+TRdUE4HEBCmH0UiFVODXCZSoqXo6y9xq7\n"
173 "TS1dmXll1Sajbfi7YXsM8CAUb+cSsHtmT57JtbGicDiVXAqIOlT65yXkuujdcEa0\n"
174 "OAw45vJDkWk/6nneFJKdTs7aT3fvIGTlMAxgMJngVsA8BRsX8TWoo05Lum8ClNgi\n"
175 "s6mtl+nUvjOaM0omFL/K9kqLy7OJAbmE5xuhkC9q6Kn0pHBL4u0YSWaWTpyrvAX7\n"
176 "BuOE0G1JezcCAcqJvXbKFvhnOSHTvzdlMgXhteGW8Uwgf8cGKtVLSwh6YTjI1XaL\n"
177 "DkNZfJabAyH7BsGGbAd9Jts4h+4auPqHgcpEz16280oCgZdcfLSP0UKrfwYuXOar\n"
178 "8KWlVRFl2NBpEJwRf2KjZFQUqYoX1MmfX0gyy+kk0ZP12L7oGNqAxkaWySfb4PSv\n"
179 "Hsnb8iD6sIJQjZvZ/2wLV8xwFTbFjvGbmSx+XLnMUVV8cVAMUpZz5X2R9pBvpVi4\n"
180 "KfUccTvIVA0p1wFSdWYQ0+QNxHxZGX1rin6KVUdV1z8K6J3FgGlRqzfz4bruGpXs\n"
181 "6vX5vqF9KTFpwLTOxDU+kAoIfHowHeu/LQX1l+rk1ww2UZQ1zvgKb6fxWMtviq3F\n"
182 "cTe8jkzRqYdUfAoV\n"
183 "-----END CERTIFICATE-----\n";
184
185constexpr std::string_view kClientKeyContent =
186 "-----BEGIN PRIVATE KEY-----\n"
187 "MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDPQHttw3TLjOqY\n"
188 "S3VkLF3KMRaP2ZtO6A1mXfTbqbKvD41Fazf/cM/v9lPMAlRd2SEY3MeE8KVddKJw\n"
189 "sbF0kNgDkKB5D5V42WrTw5biFNMOeHAZMR/oWChIvZbbGbDxIdIO2+W3X0kjpa2e\n"
190 "KcK9qBk8xoyIeilyXtleGWuWvHxiZP9iGHxaTWB+wIKUIK6vrEObiO3P/9XPpHzs\n"
191 "Bt0HdTDh4V7fwnr2UndVeQyBwUwLn6pd73sTKBfA26YppRwDjPIj6NYtF3I28lRC\n"
192 "Uo+47TAVZM97gjN9oEwyHIVtOc7fnZPwtN26M5v5083SGXU1k/PN3xGAlDUiCF3R\n"
193 "SMbBRylGgUVtsAD57i8tI2SqCr+ZG233VFiOdwTRKKVNTyMC9TCJdCFFEDFDHTTS\n"
194 "imKRQKy0Cm2qoL6JBkziAIiu/0Zzv9YjAAnRoJ2cweMXQ/9z1oWeEUZBLRsggYQ8\n"
195 "FbM13FOkOs8IlzacSuhwrYKOq8LsMX4cH2mnn783FtXXqrL/xfL711KhzGpZNrz1\n"
196 "87ilJ+ZsmP9D6vCBP/tR7V52dgtB6I291o8zxdH8GheIGenEFaZaoAwyN2FuJgXZ\n"
197 "qx9319I9gYerZ/BbUzA2MuOxFd0ywtdcTPqKiyAQ9rxQVCVQyYWjkfBEYRzWxjfj\n"
198 "3XhNprxdm3cauz01NAoTDiz52dZhGQIDAQABAoICADTppZmUeVEunQZc3Y/BtABX\n"
199 "IAeB6yDuJd2ox0b9wFzpf4vln9pblvsQzLwdLCT5tnV+iIHsXovJp19WPpQgFsZy\n"
200 "OkYuMF82Qwvlt7Po1Smwng4QeLD9MOvBW658lKw7kkGw6qkybp3nQrhKuSlqrWbS\n"
201 "2jZN2h8VEDHyE4HchXUpi/ojfjwf3S7/P1dKMM8xD+G5x91+17u3px0rc2rgBKbm\n"
202 "vy4pnPMegtETopnOG/grv3dUGPv/FHFsorOnL8vIRFnerC+++K4GmHSGV6NDCy+r\n"
203 "GT3TNAoyzsFMftQwGh0FQiwGQUW0v3G9HaMyVLZlG63H8dP+AsK5mBpCllvqKyMb\n"
204 "TQcS8mTBYAvBgiKqZBy6cwbnLaN5hYftDTg4zS62LVZzNlaMeTFGGYINDrth2S6X\n"
205 "+qH2GcXAUqd8aYIz/BLimCGhZMFQ0hAFCcq72Lh8UJsvvf9ng8Di/6oiZFJeN4nf\n"
206 "/LHUjlOyBqj8prTh0UCBjM0hdzzs96K+e3eBGjFHdVrdK5QytKZh1KTBSu0t64b2\n"
207 "0MSW5+2vFbdaQT5jed2lyh9YMmtGJV07T+5LKjWQGcJcc53DLA6uQ8lQuckQReE4\n"
208 "VzTWoG0eKEvk8ahltbl+0Gk7+fQlsMD5VizbET7EDOoiFPT3SpA/5dybXglNSuH4\n"
209 "9T65s7Xj2/zD8khLb3CxAoIBAQDwV3OQ66kqIC554Emz7F9ZNInMx4Vjuatd4wxe\n"
210 "WMT1Vlyg0ZeNSgdfggPmfntDW56NZ/h7q9F9feGfF3OogfZXVv2NzsynAOS4DR1c\n"
211 "0JR8/y7NG8vxHmDkNVJ3YkHfNYqK3x+sMCoXF0jDdaILXaP0nzAdcnLrRLyU9F3r\n"
212 "RVJpyaMWt9mtnRzlf1PTlc9WQ99MYuMfqxFj/zBFddnNFiI0FaG3/3Xdg6EH9x42\n"
213 "/2GXT/TlSUQo4e6Dh9mGhupUYzJt+AqjCnFA2n+D68QIdVq8ykOqGvnpwmfF94qt\n"
214 "8xfrKhI4zskj4N0X/xwByfEBOkU8nI8zP8PdVqKCbCRG1Z93AoIBAQDcwSRpPD5J\n"
215 "dmfXY2MGHvGQiJme/3YGPhcA15fQVRzWuZtn1PHULlI2V62NintzTUhjmv6SkGyX\n"
216 "6ze4RSCxrRFJumJwev5HtohQ7DH/nDtg+Y9Ewn32ehSEotycz1HUskOtgtLOQjwY\n"
217 "m66gTx6OzG4T6G2YRHcK8hFk9eLR0t2fIqPtu6APfRuo5OowiuYVzRKOplzh2J05\n"
218 "Q1TCJ4QL6geJQ/MzxVx33yopXWfRxZekG7ri4OJTIv8zj1Ocrytgz4hxAc8xJEf5\n"
219 "Z50k4JaWGBy+O/mKZ9sOGsolNv/FMUauE2EjSeNWNgvCFFvh4hDUciIakPzEeslp\n"
220 "hZdZCG9IV8fvAoIBAQDoDbfSbAc4Wjwlhq4C362sJrMKGnarNADGtMsjaRg6PTlQ\n"
221 "OS3XyGtYBuOXL/X5skNjCsj7N4kcXmdywST1xQ3BhIdp3QryEEXFgzwfenB0Q7q/\n"
222 "ZSBDXW51yRonlKI/TqXGseoVyadKBjxGJJTh3nbIYM8HD5Lvn71pIIxx9cu9wmcK\n"
223 "L1cobvMQjyCzwQigpQW77hqXYAd5glHsLv6tKrq5iU1Mp4X46/eWBj6RIYDrpNKy\n"
224 "c0wxIPu22XrojelAs0pkrUIv64wv7weBqyjqdcy3TZ+JZWR5FDA4D2tByt4EO+m+\n"
225 "GcJRNvKiEbnL7FwbMFTbUdpdxCpr0hM0VA+uqOG/AoIBAB24JuXABYawWSSHLdKq\n"
226 "Ic1ahowASmxmuYQUgky62KoTzNc6tN/i6JCGV0gh56LLOb6nJDSpGuWM9jBpphAl\n"
227 "g5lQbWZFOKyA53M1iTmnV9sjXeVc5cZkAxUkM90skBC5eyEF5sl740lQ1D6iyDNj\n"
228 "VEJ73R1NwlUH582WyNWEtO9yo20jAFZ1el7PirPET1uKA0CPJxwEpI4MAYIt/bn4\n"
229 "5NDXBAvpOxysP6nX+F0mY9blINDgg7e7k23mktQaRRXAetbz7mfoQYRTLbXEQqGs\n"
230 "V1pJCrxWZQhOFP7Tm7V5f9F5rG8qyF9X4VdclE4huDBRuUOoV09AVJNPN+P1nb24\n"
231 "i6MCggEBAIHUb8G0QKM4LPfdUmv575YmbnYY+Y3O982+jjRg4uAkYHnEkNfL6FKE\n"
232 "6ot7vcwDTN2Ccw6UKZU8GvyAQOGotmj6Nkgny2wFnEfoTzJaENjhPlnCHD9LDCps\n"
233 "w/tuoCHOUyyEb/Ygc+4xTsc0W3y2dbaYcg1qvLeIFuVZBNvY1XNlVf40/sVoiyet\n"
234 "Abh2yPwqOgOu8FpK4gcM8iSwL/xhEJJgT2wE+1MyHOd8KKklFHR7dF2WX1dF0Sif\n"
235 "cerPwqKXCvWh7og0RIJXe24fymMxtIsURBer9a3bPzUPVQoOXki4/u/kdEGH66GH\n"
236 "+6f4hsbp29hg+BUZ+UPdk7QyCKpZD1A=\n"
237 "-----END PRIVATE KEY-----\n";
238
245class TemporaryTLSCertificates
246{
247public:
248 static constexpr std::string_view kCaCertFilename = "ca.pem";
249 static constexpr std::string_view kServerCertFilename = "server_cert.pem";
250 static constexpr std::string_view kServerKeyFilename = "server_key.pem";
251 static constexpr std::string_view kClientCertFilename = "client_cert.pem";
252 static constexpr std::string_view kClientKeyFilename = "client_key.pem";
253 static constexpr std::string_view kCertsDirPrefix = "grpc_tls_test_";
254
255 TemporaryTLSCertificates()
256 {
258 auto uniqueDirName =
259 boost::filesystem::unique_path(std::string(kCertsDirPrefix) + "%%%%%%%%");
260 tempDir_ = tmpDir / uniqueDirName.string();
262
263 writeFile(tempDir_ / kCaCertFilename, kCaCertContent);
264 writeFile(tempDir_ / kServerCertFilename, kServerCertContent);
265 writeFile(tempDir_ / kServerKeyFilename, kServerKeyContent);
266 writeFile(tempDir_ / kClientCertFilename, kClientCertContent);
267 writeFile(tempDir_ / kClientKeyFilename, kClientKeyContent);
268 }
269
270 virtual ~TemporaryTLSCertificates()
271 {
272 std::error_code ec;
273 std::filesystem::remove_all(tempDir_, ec);
274 }
275
276 TemporaryTLSCertificates(TemporaryTLSCertificates const&) = delete;
277 TemporaryTLSCertificates&
278 operator=(TemporaryTLSCertificates const&) = delete;
279 TemporaryTLSCertificates(TemporaryTLSCertificates&&) = delete;
280 TemporaryTLSCertificates&
281 operator=(TemporaryTLSCertificates&&) = delete;
282
283 [[nodiscard]] std::filesystem::path
284 getCACertPath() const
285 {
286 return tempDir_ / kCaCertFilename;
287 }
288
289 [[nodiscard]] std::filesystem::path
290 getServerCertPath() const
291 {
292 return tempDir_ / kServerCertFilename;
293 }
294
295 [[nodiscard]] std::filesystem::path
296 getServerKeyPath() const
297 {
298 return tempDir_ / kServerKeyFilename;
299 }
300
301 [[nodiscard]] std::filesystem::path
302 getClientCertPath() const
303 {
304 return tempDir_ / kClientCertFilename;
305 }
306
307 [[nodiscard]] std::filesystem::path
308 getClientKeyPath() const
309 {
310 return tempDir_ / kClientKeyFilename;
311 }
312
313 [[nodiscard]] std::filesystem::path
314 getTempDir() const
315 {
316 return tempDir_;
317 }
318
319private:
320 static void
321 writeFile(std::filesystem::path const& path, std::string_view content)
322 {
323 std::ofstream file(path);
324 if (!file)
325 throw std::runtime_error("Failed to create file: " + path.string());
326 file << content;
327 if (!file)
328 throw std::runtime_error("Failed to write file: " + path.string());
329 }
330
331 std::filesystem::path tempDir_;
332};
333
334} // namespace
335
336namespace xrpl::test {
341bool
343{
344 grpc::ClientContext context;
345 org::xrpl::rpc::v1::GetLedgerRequest const request;
346 org::xrpl::rpc::v1::GetLedgerResponse response;
347
348 // Set a short deadline to avoid hanging on failed connections
349 context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(2));
350
351 grpc::Status const status = stub->GetLedger(&context, request, &response);
352 return status.ok();
353}
354
355class GRPCServerTLS_test : public beast::unit_test::Suite, public TemporaryTLSCertificates
356{
357public:
358 void
360 {
361 testcase("GRPCServer without TLS");
362
363 using namespace jtx;
364
365 // Create config without TLS settings
366 auto cfg = envconfig(addGrpcConfig);
367 Env env(*this, std::move(cfg));
368
369 // Verify the server actually started by checking the port
370 auto const grpcPort =
371 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
372 BEAST_EXPECT(grpcPort.has_value());
373 // NOLINTBEGIN(bugprone-unchecked-optional-access) grpcPort.has_value() checked above
374 BEAST_EXPECT(*grpcPort > 0);
375
376 // Test 1: Plaintext client should connect successfully
377 std::string const serverAddress = "localhost:" + std::to_string(*grpcPort);
378 // NOLINTEND(bugprone-unchecked-optional-access)
379 auto plaintextStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
380 grpc::CreateChannel(serverAddress, grpc::InsecureChannelCredentials()));
381 BEAST_EXPECT(makeTestGRPCCall(plaintextStub));
382 }
383
384 void
386 {
387 testcase("GRPCServer with valid TLS configuration (no mutual TLS)");
388
389 using namespace jtx;
390
391 // Test with just server cert and key (no client verification)
392 auto cfg = envconfig(
393 addGrpcConfigWithTLS, getServerCertPath().string(), getServerKeyPath().string());
394 Env env(*this, std::move(cfg));
395
396 // Verify the server actually started by checking the port
397 auto const grpcPort =
398 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
399 BEAST_EXPECT(grpcPort.has_value());
400 // NOLINTBEGIN(bugprone-unchecked-optional-access) grpcPort.has_value() checked above
401 BEAST_EXPECT(*grpcPort > 0);
402
403 std::string const serverAddress = "localhost:" + std::to_string(*grpcPort);
404 // NOLINTEND(bugprone-unchecked-optional-access)
405
406 // Test 1: Plaintext client should FAIL against TLS server
407 auto plaintextStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
408 grpc::CreateChannel(serverAddress, grpc::InsecureChannelCredentials()));
409 BEAST_EXPECT(!makeTestGRPCCall(plaintextStub));
410
411 // Test 2: TLS client with server CA should succeed
412 grpc::SslCredentialsOptions sslOpts;
413 sslOpts.pem_root_certs = std::string(kCaCertContent);
414 auto tlsStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
415 grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOpts)));
416 BEAST_EXPECT(makeTestGRPCCall(tlsStub));
417 }
418
419 void
421 {
422 testcase("GRPCServer with mutual TLS (client verification enabled)");
423
424 using namespace jtx;
425
426 // Test with server cert, key, and CA for client verification
427 auto cfg = envconfig(
429 getServerCertPath().string(),
430 getServerKeyPath().string(),
431 getCACertPath().string());
432 Env env(*this, std::move(cfg));
433
434 // Verify the server actually started by checking the port
435 auto const grpcPort =
436 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
437 BEAST_EXPECT(grpcPort.has_value());
438 // NOLINTBEGIN(bugprone-unchecked-optional-access) grpcPort.has_value() checked above
439 BEAST_EXPECT(*grpcPort > 0);
440
441 auto const serverAddress = "localhost:" + std::to_string(*grpcPort);
442 // NOLINTEND(bugprone-unchecked-optional-access)
443
444 // Test 1: TLS client WITHOUT client certificate should FAIL (mTLS requires client cert)
445 grpc::SslCredentialsOptions sslOptsNoClient;
446 sslOptsNoClient.pem_root_certs = std::string(kCaCertContent);
447 auto tlsStubNoClient = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
448 grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOptsNoClient)));
449 BEAST_EXPECT(!makeTestGRPCCall(tlsStubNoClient));
450
451 // Test 2: TLS client WITH client certificate should succeed
452 grpc::SslCredentialsOptions sslOptsWithClient;
453 sslOptsWithClient.pem_root_certs = std::string(kCaCertContent);
454 sslOptsWithClient.pem_cert_chain = std::string(kClientCertContent);
455 sslOptsWithClient.pem_private_key = std::string(kClientKeyContent);
456 auto tlsStubWithClient = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
457 grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOptsWithClient)));
458 BEAST_EXPECT(makeTestGRPCCall(tlsStubWithClient));
459 }
460
461 void
463 {
464 testcase("GRPCServer with cert but no key");
465
466 using namespace jtx;
467
468 // Create config with only cert (missing key)
469 auto cfg = envconfig();
470 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
471 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
472 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string());
473 // Intentionally omit ssl_key
474
475 try
476 {
477 Env const env(*this, std::move(cfg));
478 fail("Should have thrown exception for incomplete TLS config");
479 }
480 catch (std::runtime_error const& e)
481 {
482 BEAST_EXPECT(std::string(e.what()).contains("Incomplete TLS configuration"));
483 }
484 }
485
486 void
488 {
489 testcase("GRPCServer with key but no cert");
490
491 using namespace jtx;
492
493 // Create config with only key (missing cert)
494 auto cfg = envconfig();
495 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
496 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
497 (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string());
498 // Intentionally omit ssl_cert
499
500 try
501 {
502 Env const env(*this, std::move(cfg));
503 fail("Should have thrown exception for incomplete TLS config");
504 }
505 catch (std::runtime_error const& e)
506 {
507 BEAST_EXPECT(std::string(e.what()).contains("Incomplete TLS configuration"));
508 }
509 }
510
511 void
513 {
514 testcase("GRPCServer with ssl_client_ca but without both ssl_cert and ssl_key");
515
516 using namespace jtx;
517
518 // Test 1: ssl_client_ca specified without any TLS config
519 {
520 auto cfg = envconfig();
521 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
522 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
523 (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, getCACertPath().string());
524 // Intentionally omit both ssl_cert and ssl_key
525
526 try
527 {
528 Env const env(*this, std::move(cfg));
529 fail("Should have thrown exception for ssl_client_ca without TLS config");
530 }
531 catch (std::runtime_error const& e)
532 {
533 BEAST_EXPECT(
534 std::string(e.what()).contains(
535 "ssl_client_ca requires both ssl_cert and ssl_key"));
536 }
537 }
538
539 // Test 2: ssl_client_ca with only ssl_cert (missing ssl_key)
540 {
541 auto cfg = envconfig();
542 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
543 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
544 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string());
545 (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, getCACertPath().string());
546 // Intentionally omit ssl_key
547
548 try
549 {
550 Env const env(*this, std::move(cfg));
551 fail("Should have thrown exception for ssl_client_ca with only ssl_cert");
552 }
553 catch (std::runtime_error const& e)
554 {
555 // This should fail with "Incomplete TLS configuration" first
556 // because ssl_cert is specified without ssl_key
557 BEAST_EXPECT(std::string(e.what()).contains("Incomplete TLS configuration"));
558 }
559 }
560
561 // Test 3: ssl_client_ca with only ssl_key (missing ssl_cert)
562 {
563 auto cfg = envconfig();
564 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
565 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
566 (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string());
567 (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, getCACertPath().string());
568 // Intentionally omit ssl_cert
569
570 try
571 {
572 Env const env(*this, std::move(cfg));
573 fail("Should have thrown exception for ssl_client_ca with only ssl_key");
574 }
575 catch (std::runtime_error const& e)
576 {
577 // This should fail with "Incomplete TLS configuration" first
578 // because ssl_key is specified without ssl_cert
579 BEAST_EXPECT(std::string(e.what()).contains("Incomplete TLS configuration"));
580 }
581 }
582 }
583
584 void
586 {
587 testcase("GRPCServer with ssl_cert_chain but without both ssl_cert and ssl_key");
588
589 using namespace jtx;
590
591 // Test 1: ssl_cert_chain specified without any TLS config
592 {
593 auto cfg = envconfig();
594 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
595 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
596 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCertChain, getCACertPath().string());
597 // Intentionally omit both ssl_cert and ssl_key
598
599 try
600 {
601 Env const env(*this, std::move(cfg));
602 fail("Should have thrown exception for ssl_cert_chain without TLS config");
603 }
604 catch (std::runtime_error const& e)
605 {
606 BEAST_EXPECT(
607 std::string(e.what()).contains(
608 "ssl_cert_chain requires both ssl_cert and ssl_key"));
609 }
610 }
611
612 // Test 2: ssl_cert_chain with only ssl_cert (missing ssl_key)
613 {
614 auto cfg = envconfig();
615 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
616 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
617 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string());
618 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCertChain, getCACertPath().string());
619 // Intentionally omit ssl_key
620
621 try
622 {
623 Env const env(*this, std::move(cfg));
624 fail("Should have thrown exception for ssl_cert_chain with only ssl_cert");
625 }
626 catch (std::runtime_error const& e)
627 {
628 // This should fail with "Incomplete TLS configuration" first
629 // because ssl_cert is specified without ssl_key
630 BEAST_EXPECT(std::string(e.what()).contains("Incomplete TLS configuration"));
631 }
632 }
633 }
634
635 void
637 {
638 testcase("GRPCServer with ssl_cert_chain for intermediate CA certificates");
639
640 using namespace jtx;
641
642 // Test with server cert, key, and cert chain (intermediate CA)
643 // In this test, we use the CA cert as a stand-in for an intermediate CA cert
644 auto cfg = envconfig(
646 getServerCertPath().string(),
647 getServerKeyPath().string(),
648 getCACertPath().string());
649 Env env(*this, std::move(cfg));
650
651 // Verify the server actually started by checking the port
652 auto const grpcPort =
653 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
654 BEAST_EXPECT(grpcPort.has_value());
655 // NOLINTBEGIN(bugprone-unchecked-optional-access) grpcPort.has_value() checked above
656 BEAST_EXPECT(*grpcPort > 0);
657
658 auto const serverAddress = "localhost:" + std::to_string(*grpcPort);
659 // NOLINTEND(bugprone-unchecked-optional-access)
660
661 // Test: TLS client should be able to connect (no client cert required)
662 grpc::SslCredentialsOptions sslOpts;
663 sslOpts.pem_root_certs = std::string(kCaCertContent);
664 auto tlsStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
665 grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOpts)));
666 BEAST_EXPECT(makeTestGRPCCall(tlsStub));
667
668 // Insecure client should fail
669 auto insecureStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
670 grpc::CreateChannel(serverAddress, grpc::InsecureChannelCredentials()));
671 BEAST_EXPECT(!makeTestGRPCCall(insecureStub));
672 }
673
674 void
676 {
677 testcase("GRPCServer with invalid/non-existent certificate file");
678
679 using namespace jtx;
680
681 auto cfg = envconfig();
682 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
683 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
684 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, "/nonexistent/path/to/cert.pem");
685 (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string());
686
687 Env env(*this, std::move(cfg));
688
689 // Server should fail to start - verify port is 0
690 auto const grpcPort =
691 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
692 BEAST_EXPECT(grpcPort.has_value());
693 BEAST_EXPECT(*grpcPort == 0); // NOLINT(bugprone-unchecked-optional-access)
694 }
695
696 void
698 {
699 testcase("GRPCServer with invalid/non-existent key file");
700
701 using namespace jtx;
702
703 auto cfg = envconfig();
704 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
705 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
706 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string());
707 (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, "/nonexistent/path/to/key.pem");
708
709 Env env(*this, std::move(cfg));
710
711 // Server should fail to start - verify port is 0
712 auto const grpcPort =
713 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
714 BEAST_EXPECT(grpcPort.has_value());
715 BEAST_EXPECT(*grpcPort == 0); // NOLINT(bugprone-unchecked-optional-access)
716 }
717
718 void
720 {
721 testcase("GRPCServer with invalid/non-existent cert chain file");
722
723 using namespace jtx;
724
725 auto cfg = envconfig();
726 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
727 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
728 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string());
729 (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string());
730 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCertChain, "/nonexistent/path/to/chain.pem");
731
732 Env env(*this, std::move(cfg));
733
734 // Server should fail to start - verify port is 0
735 auto const grpcPort =
736 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
737 BEAST_EXPECT(grpcPort.has_value());
738 BEAST_EXPECT(*grpcPort == 0); // NOLINT(bugprone-unchecked-optional-access)
739 }
740
741 void
743 {
744 testcase("GRPCServer with invalid/non-existent client CA file");
745
746 using namespace jtx;
747
748 auto cfg = envconfig();
749 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
750 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
751 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string());
752 (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string());
753 (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, "/nonexistent/path/to/ca.pem");
754
755 Env env(*this, std::move(cfg));
756
757 // Server should fail to start - verify port is 0
758 auto const grpcPort =
759 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
760 BEAST_EXPECT(grpcPort.has_value());
761 BEAST_EXPECT(*grpcPort == 0); // NOLINT(bugprone-unchecked-optional-access)
762 }
763
764 void
766 {
767 testcase("GRPCServer with empty client CA file");
768
769 using namespace jtx;
770
771 // Create an empty file for client CA
772 auto emptyCAPath = getTempDir() / "empty_ca.pem";
773 std::ofstream emptyFile(emptyCAPath);
774 emptyFile.close();
775
776 auto cfg = envconfig();
777 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
778 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
779 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string());
780 (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string());
781 (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, emptyCAPath.string());
782
783 Env env(*this, std::move(cfg));
784
785 // Server should fail to start due to empty CA file
786 auto const grpcPort =
787 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
788 BEAST_EXPECT(grpcPort.has_value());
789 BEAST_EXPECT(*grpcPort == 0); // NOLINT(bugprone-unchecked-optional-access)
790 }
791
792 void
794 {
795 testcase("GRPCServer with both cert chain and client CA (full mTLS with intermediates)");
796
797 using namespace jtx;
798
799 // Test with all TLS features enabled: cert, key, cert_chain, and client_ca
800 auto cfg = envconfig();
802 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
803 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string());
804 (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string());
805 (*cfg)[Sections::kPortGrpc].set(
806 Keys::kSslCertChain, getCACertPath().string()); // Using CA as intermediate
807 (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, getCACertPath().string());
808
809 Env env(*this, std::move(cfg));
810
811 // Verify the server started successfully
812 auto const grpcPort =
813 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
814 BEAST_EXPECT(grpcPort.has_value());
815 // NOLINTBEGIN(bugprone-unchecked-optional-access) grpcPort.has_value() checked above
816 BEAST_EXPECT(*grpcPort > 0);
817
818 auto const serverAddress = "localhost:" + std::to_string(*grpcPort);
819 // NOLINTEND(bugprone-unchecked-optional-access)
820
821 // Test 1: TLS client WITHOUT client certificate should FAIL (mTLS requires client cert)
822 grpc::SslCredentialsOptions sslOptsNoClient;
823 sslOptsNoClient.pem_root_certs = std::string(kCaCertContent);
824 auto tlsStubNoClient = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
825 grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOptsNoClient)));
826 BEAST_EXPECT(!makeTestGRPCCall(tlsStubNoClient));
827
828 // Test 2: TLS client WITH client certificate should succeed
829 grpc::SslCredentialsOptions sslOptsWithClient;
830 sslOptsWithClient.pem_root_certs = std::string(kCaCertContent);
831 sslOptsWithClient.pem_cert_chain = std::string(kClientCertContent);
832 sslOptsWithClient.pem_private_key = std::string(kClientKeyContent);
833 auto tlsStubWithClient = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
834 grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOptsWithClient)));
835 BEAST_EXPECT(makeTestGRPCCall(tlsStubWithClient));
836 }
837
838 void
856};
857
858BEAST_DEFINE_TESTSUITE(GRPCServerTLS, app, xrpl);
859
860} // namespace xrpl::test
A testsuite class.
Definition suite.h:50
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:522
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
virtual Config & config()=0
void run() override
Runs the suite.
A transaction testing environment.
Definition Env.h:143
Application & app()
Definition Env.h:280
T close(T... args)
T create_directories(T... args)
std::unique_ptr< Config > addGrpcConfigWithTLSAndCertChain(std::unique_ptr< Config >, std::string const &certPath, std::string const &keyPath, std::string const &certChainPath)
add a grpc address, port and TLS with server cert chain to config
std::unique_ptr< Config > addGrpcConfigWithTLSAndClientCA(std::unique_ptr< Config >, std::string const &certPath, std::string const &keyPath, std::string const &clientCAPath)
add a grpc address, port and TLS certificate/key/client CA paths to config
std::unique_ptr< Config > addGrpcConfigWithTLS(std::unique_ptr< Config >, std::string const &certPath, std::string const &keyPath)
add a grpc address, port and TLS certificate/key paths to config
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:28
std::unique_ptr< Config > addGrpcConfig(std::unique_ptr< Config >)
add a grpc address and port to config
bool makeTestGRPCCall(std::unique_ptr< org::xrpl::rpc::v1::XRPLedgerAPIService::Stub > const &stub)
Helper function to make a simple gRPC call to test connectivity.
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
char const * getEnvLocalhostAddr()
Definition envconfig.h:10
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
T remove_all(T... args)
static constexpr auto kSslCertChain
Definition Constants.h:158
static constexpr auto kSslKey
Definition Constants.h:162
static constexpr auto kPort
Definition Constants.h:142
static constexpr auto kSslClientCa
Definition Constants.h:161
static constexpr auto kSslCert
Definition Constants.h:157
static constexpr auto kIp
Definition Constants.h:113
static constexpr auto kPortGrpc
Definition Constants.h:44
T temp_directory_path(T... args)
T to_string(T... args)
T what(T... args)