xrpld
Loading...
Searching...
No Matches
GRPCServerTLS_test.cpp
1#include <test/jtx/Env.h>
2#include <test/jtx/envconfig.h>
3
4#include <xrpl/basics/FileUtilities.h>
5#include <xrpl/beast/unit_test/suite.h>
6#include <xrpl/config/Constants.h>
7#include <xrpl/proto/org/xrpl/rpc/v1/get_ledger.pb.h>
8#include <xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
9
10#include <grpcpp/client_context.h>
11#include <grpcpp/create_channel.h>
12#include <grpcpp/grpcpp.h>
13#include <grpcpp/security/credentials.h>
14#include <grpcpp/support/status.h>
15
16#include <chrono>
17#include <filesystem>
18#include <fstream>
19#include <ios>
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 {
257 tempDir_ = xrpl::uniqueRandomPath(
258 std::filesystem::temp_directory_path(), std::string(kCertsDirPrefix));
260
261 writeFile(tempDir_ / kCaCertFilename, kCaCertContent);
262 writeFile(tempDir_ / kServerCertFilename, kServerCertContent);
263 writeFile(tempDir_ / kServerKeyFilename, kServerKeyContent);
264 writeFile(tempDir_ / kClientCertFilename, kClientCertContent);
265 writeFile(tempDir_ / kClientKeyFilename, kClientKeyContent);
266 }
267
268 virtual ~TemporaryTLSCertificates()
269 {
270 std::error_code ec;
271 std::filesystem::remove_all(tempDir_, ec);
272 }
273
274 TemporaryTLSCertificates(TemporaryTLSCertificates const&) = delete;
275 TemporaryTLSCertificates&
276 operator=(TemporaryTLSCertificates const&) = delete;
277 TemporaryTLSCertificates(TemporaryTLSCertificates&&) = delete;
278 TemporaryTLSCertificates&
279 operator=(TemporaryTLSCertificates&&) = delete;
280
281 [[nodiscard]] std::filesystem::path
282 getCACertPath() const
283 {
284 return tempDir_ / kCaCertFilename;
285 }
286
287 [[nodiscard]] std::filesystem::path
288 getServerCertPath() const
289 {
290 return tempDir_ / kServerCertFilename;
291 }
292
293 [[nodiscard]] std::filesystem::path
294 getServerKeyPath() const
295 {
296 return tempDir_ / kServerKeyFilename;
297 }
298
299 [[nodiscard]] std::filesystem::path
300 getClientCertPath() const
301 {
302 return tempDir_ / kClientCertFilename;
303 }
304
305 [[nodiscard]] std::filesystem::path
306 getClientKeyPath() const
307 {
308 return tempDir_ / kClientKeyFilename;
309 }
310
311 [[nodiscard]] std::filesystem::path
312 getTempDir() const
313 {
314 return tempDir_;
315 }
316
317private:
318 static void
319 writeFile(std::filesystem::path const& path, std::string_view content)
320 {
321 std::ofstream file(path);
322 if (!file)
323 throw std::runtime_error("Failed to create file: " + path.string());
324 file << content;
325 if (!file)
326 throw std::runtime_error("Failed to write file: " + path.string());
327 }
328
329 std::filesystem::path tempDir_;
330};
331
332} // namespace
333
334namespace xrpl::test {
339bool
341{
342 grpc::ClientContext context;
343 org::xrpl::rpc::v1::GetLedgerRequest const request;
344 org::xrpl::rpc::v1::GetLedgerResponse response;
345
346 // Set a short deadline to avoid hanging on failed connections
347 context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(2));
348
349 grpc::Status const status = stub->GetLedger(&context, request, &response);
350 return status.ok();
351}
352
353class GRPCServerTLS_test : public beast::unit_test::Suite, public TemporaryTLSCertificates
354{
355public:
356 void
358 {
359 testcase("GRPCServer without TLS");
360
361 using namespace jtx;
362
363 // Create config without TLS settings
364 auto cfg = envconfig(addGrpcConfig);
365 Env env(*this, std::move(cfg));
366
367 // Verify the server actually started by checking the port
368 auto const grpcPort =
369 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
370 BEAST_EXPECT(grpcPort.has_value());
371 // NOLINTBEGIN(bugprone-unchecked-optional-access) grpcPort.has_value() checked above
372 BEAST_EXPECT(*grpcPort > 0);
373
374 // Test 1: Plaintext client should connect successfully
375 std::string const serverAddress = "localhost:" + std::to_string(*grpcPort);
376 // NOLINTEND(bugprone-unchecked-optional-access)
377 auto plaintextStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
378 grpc::CreateChannel(serverAddress, grpc::InsecureChannelCredentials()));
379 BEAST_EXPECT(makeTestGRPCCall(plaintextStub));
380 }
381
382 void
384 {
385 testcase("GRPCServer with valid TLS configuration (no mutual TLS)");
386
387 using namespace jtx;
388
389 // Test with just server cert and key (no client verification)
390 auto cfg = envconfig(
391 addGrpcConfigWithTLS, getServerCertPath().string(), getServerKeyPath().string());
392 Env env(*this, std::move(cfg));
393
394 // Verify the server actually started by checking the port
395 auto const grpcPort =
396 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
397 BEAST_EXPECT(grpcPort.has_value());
398 // NOLINTBEGIN(bugprone-unchecked-optional-access) grpcPort.has_value() checked above
399 BEAST_EXPECT(*grpcPort > 0);
400
401 std::string const serverAddress = "localhost:" + std::to_string(*grpcPort);
402 // NOLINTEND(bugprone-unchecked-optional-access)
403
404 // Test 1: Plaintext client should FAIL against TLS server
405 auto plaintextStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
406 grpc::CreateChannel(serverAddress, grpc::InsecureChannelCredentials()));
407 BEAST_EXPECT(!makeTestGRPCCall(plaintextStub));
408
409 // Test 2: TLS client with server CA should succeed
410 grpc::SslCredentialsOptions sslOpts;
411 sslOpts.pem_root_certs = std::string(kCaCertContent);
412 auto tlsStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
413 grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOpts)));
414 BEAST_EXPECT(makeTestGRPCCall(tlsStub));
415 }
416
417 void
419 {
420 testcase("GRPCServer with mutual TLS (client verification enabled)");
421
422 using namespace jtx;
423
424 // Test with server cert, key, and CA for client verification
425 auto cfg = envconfig(
427 getServerCertPath().string(),
428 getServerKeyPath().string(),
429 getCACertPath().string());
430 Env env(*this, std::move(cfg));
431
432 // Verify the server actually started by checking the port
433 auto const grpcPort =
434 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
435 BEAST_EXPECT(grpcPort.has_value());
436 // NOLINTBEGIN(bugprone-unchecked-optional-access) grpcPort.has_value() checked above
437 BEAST_EXPECT(*grpcPort > 0);
438
439 auto const serverAddress = "localhost:" + std::to_string(*grpcPort);
440 // NOLINTEND(bugprone-unchecked-optional-access)
441
442 // Test 1: TLS client WITHOUT client certificate should FAIL (mTLS requires client cert)
443 grpc::SslCredentialsOptions sslOptsNoClient;
444 sslOptsNoClient.pem_root_certs = std::string(kCaCertContent);
445 auto tlsStubNoClient = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
446 grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOptsNoClient)));
447 BEAST_EXPECT(!makeTestGRPCCall(tlsStubNoClient));
448
449 // Test 2: TLS client WITH client certificate should succeed
450 grpc::SslCredentialsOptions sslOptsWithClient;
451 sslOptsWithClient.pem_root_certs = std::string(kCaCertContent);
452 sslOptsWithClient.pem_cert_chain = std::string(kClientCertContent);
453 sslOptsWithClient.pem_private_key = std::string(kClientKeyContent);
454 auto tlsStubWithClient = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
455 grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOptsWithClient)));
456 BEAST_EXPECT(makeTestGRPCCall(tlsStubWithClient));
457 }
458
459 void
461 {
462 testcase("GRPCServer with cert but no key");
463
464 using namespace jtx;
465
466 // Create config with only cert (missing key)
467 auto cfg = envconfig();
468 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
469 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
470 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string());
471 // Intentionally omit ssl_key
472
473 try
474 {
475 Env const env(*this, std::move(cfg));
476 fail("Should have thrown exception for incomplete TLS config");
477 }
478 catch (std::runtime_error const& e)
479 {
480 BEAST_EXPECT(std::string(e.what()).contains("Incomplete TLS configuration"));
481 }
482 }
483
484 void
486 {
487 testcase("GRPCServer with key but no cert");
488
489 using namespace jtx;
490
491 // Create config with only key (missing cert)
492 auto cfg = envconfig();
493 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
494 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
495 (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string());
496 // Intentionally omit ssl_cert
497
498 try
499 {
500 Env const env(*this, std::move(cfg));
501 fail("Should have thrown exception for incomplete TLS config");
502 }
503 catch (std::runtime_error const& e)
504 {
505 BEAST_EXPECT(std::string(e.what()).contains("Incomplete TLS configuration"));
506 }
507 }
508
509 void
511 {
512 testcase("GRPCServer with ssl_client_ca but without both ssl_cert and ssl_key");
513
514 using namespace jtx;
515
516 // Test 1: ssl_client_ca specified without any TLS config
517 {
518 auto cfg = envconfig();
519 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
520 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
521 (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, getCACertPath().string());
522 // Intentionally omit both ssl_cert and ssl_key
523
524 try
525 {
526 Env const env(*this, std::move(cfg));
527 fail("Should have thrown exception for ssl_client_ca without TLS config");
528 }
529 catch (std::runtime_error const& e)
530 {
531 BEAST_EXPECT(
532 std::string(e.what()).contains(
533 "ssl_client_ca requires both ssl_cert and ssl_key"));
534 }
535 }
536
537 // Test 2: ssl_client_ca with only ssl_cert (missing ssl_key)
538 {
539 auto cfg = envconfig();
540 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
541 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
542 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string());
543 (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, getCACertPath().string());
544 // Intentionally omit ssl_key
545
546 try
547 {
548 Env const env(*this, std::move(cfg));
549 fail("Should have thrown exception for ssl_client_ca with only ssl_cert");
550 }
551 catch (std::runtime_error const& e)
552 {
553 // This should fail with "Incomplete TLS configuration" first
554 // because ssl_cert is specified without ssl_key
555 BEAST_EXPECT(std::string(e.what()).contains("Incomplete TLS configuration"));
556 }
557 }
558
559 // Test 3: ssl_client_ca with only ssl_key (missing ssl_cert)
560 {
561 auto cfg = envconfig();
562 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
563 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
564 (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string());
565 (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, getCACertPath().string());
566 // Intentionally omit ssl_cert
567
568 try
569 {
570 Env const env(*this, std::move(cfg));
571 fail("Should have thrown exception for ssl_client_ca with only ssl_key");
572 }
573 catch (std::runtime_error const& e)
574 {
575 // This should fail with "Incomplete TLS configuration" first
576 // because ssl_key is specified without ssl_cert
577 BEAST_EXPECT(std::string(e.what()).contains("Incomplete TLS configuration"));
578 }
579 }
580 }
581
582 void
584 {
585 testcase("GRPCServer with ssl_cert_chain but without both ssl_cert and ssl_key");
586
587 using namespace jtx;
588
589 // Test 1: ssl_cert_chain specified without any TLS config
590 {
591 auto cfg = envconfig();
592 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
593 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
594 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCertChain, getCACertPath().string());
595 // Intentionally omit both ssl_cert and ssl_key
596
597 try
598 {
599 Env const env(*this, std::move(cfg));
600 fail("Should have thrown exception for ssl_cert_chain without TLS config");
601 }
602 catch (std::runtime_error const& e)
603 {
604 BEAST_EXPECT(
605 std::string(e.what()).contains(
606 "ssl_cert_chain requires both ssl_cert and ssl_key"));
607 }
608 }
609
610 // Test 2: ssl_cert_chain with only ssl_cert (missing ssl_key)
611 {
612 auto cfg = envconfig();
613 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
614 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
615 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string());
616 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCertChain, getCACertPath().string());
617 // Intentionally omit ssl_key
618
619 try
620 {
621 Env const env(*this, std::move(cfg));
622 fail("Should have thrown exception for ssl_cert_chain with only ssl_cert");
623 }
624 catch (std::runtime_error const& e)
625 {
626 // This should fail with "Incomplete TLS configuration" first
627 // because ssl_cert is specified without ssl_key
628 BEAST_EXPECT(std::string(e.what()).contains("Incomplete TLS configuration"));
629 }
630 }
631 }
632
633 void
635 {
636 testcase("GRPCServer with ssl_cert_chain for intermediate CA certificates");
637
638 using namespace jtx;
639
640 // Test with server cert, key, and cert chain (intermediate CA)
641 // In this test, we use the CA cert as a stand-in for an intermediate CA cert
642 auto cfg = envconfig(
644 getServerCertPath().string(),
645 getServerKeyPath().string(),
646 getCACertPath().string());
647 Env env(*this, std::move(cfg));
648
649 // Verify the server actually started by checking the port
650 auto const grpcPort =
651 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
652 BEAST_EXPECT(grpcPort.has_value());
653 // NOLINTBEGIN(bugprone-unchecked-optional-access) grpcPort.has_value() checked above
654 BEAST_EXPECT(*grpcPort > 0);
655
656 auto const serverAddress = "localhost:" + std::to_string(*grpcPort);
657 // NOLINTEND(bugprone-unchecked-optional-access)
658
659 // Test: TLS client should be able to connect (no client cert required)
660 grpc::SslCredentialsOptions sslOpts;
661 sslOpts.pem_root_certs = std::string(kCaCertContent);
662 auto tlsStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
663 grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOpts)));
664 BEAST_EXPECT(makeTestGRPCCall(tlsStub));
665
666 // Insecure client should fail
667 auto insecureStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
668 grpc::CreateChannel(serverAddress, grpc::InsecureChannelCredentials()));
669 BEAST_EXPECT(!makeTestGRPCCall(insecureStub));
670 }
671
672 void
674 {
675 testcase("GRPCServer with invalid/non-existent certificate file");
676
677 using namespace jtx;
678
679 auto cfg = envconfig();
680 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
681 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
682 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, "/nonexistent/path/to/cert.pem");
683 (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string());
684
685 Env env(*this, std::move(cfg));
686
687 // Server should fail to start - verify port is 0
688 auto const grpcPort =
689 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
690 BEAST_EXPECT(grpcPort.has_value());
691 BEAST_EXPECT(*grpcPort == 0); // NOLINT(bugprone-unchecked-optional-access)
692 }
693
694 void
696 {
697 testcase("GRPCServer with invalid/non-existent key file");
698
699 using namespace jtx;
700
701 auto cfg = envconfig();
702 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
703 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
704 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string());
705 (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, "/nonexistent/path/to/key.pem");
706
707 Env env(*this, std::move(cfg));
708
709 // Server should fail to start - verify port is 0
710 auto const grpcPort =
711 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
712 BEAST_EXPECT(grpcPort.has_value());
713 BEAST_EXPECT(*grpcPort == 0); // NOLINT(bugprone-unchecked-optional-access)
714 }
715
716 void
718 {
719 testcase("GRPCServer with invalid/non-existent cert chain file");
720
721 using namespace jtx;
722
723 auto cfg = envconfig();
724 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
725 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
726 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string());
727 (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string());
728 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCertChain, "/nonexistent/path/to/chain.pem");
729
730 Env env(*this, std::move(cfg));
731
732 // Server should fail to start - verify port is 0
733 auto const grpcPort =
734 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
735 BEAST_EXPECT(grpcPort.has_value());
736 BEAST_EXPECT(*grpcPort == 0); // NOLINT(bugprone-unchecked-optional-access)
737 }
738
739 void
741 {
742 testcase("GRPCServer with invalid/non-existent client CA file");
743
744 using namespace jtx;
745
746 auto cfg = envconfig();
747 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
748 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
749 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string());
750 (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string());
751 (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, "/nonexistent/path/to/ca.pem");
752
753 Env env(*this, std::move(cfg));
754
755 // Server should fail to start - verify port is 0
756 auto const grpcPort =
757 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
758 BEAST_EXPECT(grpcPort.has_value());
759 BEAST_EXPECT(*grpcPort == 0); // NOLINT(bugprone-unchecked-optional-access)
760 }
761
762 void
764 {
765 testcase("GRPCServer with empty client CA file");
766
767 using namespace jtx;
768
769 // Create an empty file for client CA
770 auto emptyCAPath = getTempDir() / "empty_ca.pem";
771 std::ofstream emptyFile(emptyCAPath);
772 emptyFile.close();
773
774 auto cfg = envconfig();
775 (*cfg)[Sections::kPortGrpc].set(Keys::kIp, "127.0.0.1");
776 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
777 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string());
778 (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string());
779 (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, emptyCAPath.string());
780
781 Env env(*this, std::move(cfg));
782
783 // Server should fail to start due to empty CA file
784 auto const grpcPort =
785 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
786 BEAST_EXPECT(grpcPort.has_value());
787 BEAST_EXPECT(*grpcPort == 0); // NOLINT(bugprone-unchecked-optional-access)
788 }
789
790 void
792 {
793 testcase("GRPCServer with both cert chain and client CA (full mTLS with intermediates)");
794
795 using namespace jtx;
796
797 // Test with all TLS features enabled: cert, key, cert_chain, and client_ca
798 auto cfg = envconfig();
800 (*cfg)[Sections::kPortGrpc].set(Keys::kPort, "0");
801 (*cfg)[Sections::kPortGrpc].set(Keys::kSslCert, getServerCertPath().string());
802 (*cfg)[Sections::kPortGrpc].set(Keys::kSslKey, getServerKeyPath().string());
803 (*cfg)[Sections::kPortGrpc].set(
804 Keys::kSslCertChain, getCACertPath().string()); // Using CA as intermediate
805 (*cfg)[Sections::kPortGrpc].set(Keys::kSslClientCa, getCACertPath().string());
806
807 Env env(*this, std::move(cfg));
808
809 // Verify the server started successfully
810 auto const grpcPort =
811 env.app().config()[Sections::kPortGrpc].get<unsigned int>(Keys::kPort);
812 BEAST_EXPECT(grpcPort.has_value());
813 // NOLINTBEGIN(bugprone-unchecked-optional-access) grpcPort.has_value() checked above
814 BEAST_EXPECT(*grpcPort > 0);
815
816 auto const serverAddress = "localhost:" + std::to_string(*grpcPort);
817 // NOLINTEND(bugprone-unchecked-optional-access)
818
819 // Test 1: TLS client WITHOUT client certificate should FAIL (mTLS requires client cert)
820 grpc::SslCredentialsOptions sslOptsNoClient;
821 sslOptsNoClient.pem_root_certs = std::string(kCaCertContent);
822 auto tlsStubNoClient = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
823 grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOptsNoClient)));
824 BEAST_EXPECT(!makeTestGRPCCall(tlsStubNoClient));
825
826 // Test 2: TLS client WITH client certificate should succeed
827 grpc::SslCredentialsOptions sslOptsWithClient;
828 sslOptsWithClient.pem_root_certs = std::string(kCaCertContent);
829 sslOptsWithClient.pem_cert_chain = std::string(kClientCertContent);
830 sslOptsWithClient.pem_private_key = std::string(kClientKeyContent);
831 auto tlsStubWithClient = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(
832 grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOptsWithClient)));
833 BEAST_EXPECT(makeTestGRPCCall(tlsStubWithClient));
834 }
835
836 void
854};
855
856BEAST_DEFINE_TESTSUITE(GRPCServerTLS, app, xrpl);
857
858} // namespace xrpl::test
A testsuite class.
Definition suite.h:52
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:554
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
virtual Config & config()=0
void run() override
Runs the suite.
A transaction testing environment.
Definition Env.h:161
Application & app()
Definition Env.h:300
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:37
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:15
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::filesystem::path uniqueRandomPath(std::filesystem::path const &base, std::string const &prefix="", std::size_t maxAttempts=100)
Generate a unique, non-existing path under base whose filename starts with prefix and ends with a ran...
T remove_all(T... args)
static constexpr auto kSslCertChain
Definition Constants.h:161
static constexpr auto kSslKey
Definition Constants.h:165
static constexpr auto kPort
Definition Constants.h:145
static constexpr auto kSslClientCa
Definition Constants.h:164
static constexpr auto kSslCert
Definition Constants.h:160
static constexpr auto kIp
Definition Constants.h:114
static constexpr auto kPortGrpc
Definition Constants.h:45
T temp_directory_path(T... args)
T to_string(T... args)
T what(T... args)