Skip to content

Commit ce448e5

Browse files
laramielcopybara-github
authored andcommitted
Generate self-signed certificates for test http server.
PiperOrigin-RevId: 738868850 Change-Id: I5ec8e69e67e2eb9351f4927606a53abee8768e8e
1 parent 74aec3e commit ce448e5

8 files changed

+322
-8
lines changed

tensorstore/internal/curl/http2_test.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ TestHttpServer& GetHttpServer() {
5454

5555
std::shared_ptr<HttpTransport> GetTransport() {
5656
auto config = DefaultCurlHandleFactory::Config();
57-
config.ca_bundle = absl::StrCat(GetHttpServer().root_path(), "/test.crt");
57+
config.ca_bundle = GetHttpServer().GetCertPath();
5858
config.verify_host = false;
5959
return std::make_shared<CurlTransport>(
6060
std::make_shared<DefaultCurlHandleFactory>(std::move(config)));

tensorstore/internal/http/BUILD

+28
Original file line numberDiff line numberDiff line change
@@ -144,17 +144,20 @@ tensorstore_cc_library(
144144
srcs = ["test_httpserver.cc"],
145145
hdrs = ["test_httpserver.h"],
146146
deps = [
147+
":self_signed_cert",
147148
":transport_test_utils",
148149
"//tensorstore/internal:path",
149150
"//tensorstore/internal/os:file_util",
150151
"//tensorstore/internal/os:subprocess",
152+
"//tensorstore/internal/testing:scoped_directory",
151153
"//tensorstore/util:result",
152154
"//tensorstore/util:span",
153155
"//tensorstore/util:status",
154156
"@com_google_absl//absl/flags:flag",
155157
"@com_google_absl//absl/log:absl_check",
156158
"@com_google_absl//absl/log:absl_log",
157159
"@com_google_absl//absl/status",
160+
"@com_google_absl//absl/strings",
158161
"@com_google_absl//absl/strings:str_format",
159162
"@com_google_absl//absl/time",
160163
"@com_google_re2//:re2",
@@ -179,3 +182,28 @@ tensorstore_cc_library(
179182
],
180183
alwayslink = 1,
181184
)
185+
186+
tensorstore_cc_library(
187+
name = "self_signed_cert",
188+
srcs = ["self_signed_cert.cc"],
189+
hdrs = ["self_signed_cert.h"],
190+
deps = [
191+
"//tensorstore/internal:source_location",
192+
"//tensorstore/util:result",
193+
"//tensorstore/util:status",
194+
"@com_google_absl//absl/log:absl_check",
195+
"@com_google_absl//absl/status",
196+
"@com_google_absl//absl/strings",
197+
"@com_google_boringssl//:crypto",
198+
],
199+
)
200+
201+
tensorstore_cc_test(
202+
name = "self_signed_cert_test",
203+
srcs = ["self_signed_cert_test.cc"],
204+
deps = [
205+
":self_signed_cert",
206+
"//tensorstore/util:status_testutil",
207+
"@com_google_googletest//:gtest_main",
208+
],
209+
)

tensorstore/internal/http/py/h2_server.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -315,13 +315,14 @@ def window_updated(self, stream_id, delta):
315315
self.flow_control_futures = {}
316316

317317

318-
def run(port):
319-
path = os.path.dirname(__file__)
318+
def run(port, cert_path):
319+
if cert_path is None:
320+
cert_path = os.path.dirname(__file__)
320321
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
321322
ssl_context.options |= ssl.OP_NO_COMPRESSION
322323
ssl_context.load_cert_chain(
323-
certfile=os.path.join(path, 'test.crt'),
324-
keyfile=os.path.join(path, 'test.key'),
324+
certfile=os.path.join(cert_path, 'test.crt'),
325+
keyfile=os.path.join(cert_path, 'test.key'),
325326
)
326327
ssl_context.set_alpn_protocols(['h2'])
327328

@@ -357,6 +358,7 @@ def run(port):
357358
if __name__ == '__main__':
358359
p = argparse.ArgumentParser()
359360
p.add_argument('--port', type=int, default=0)
361+
p.add_argument('--cert_path', type=str, default=None)
360362
v, _ = p.parse_known_args(sys.argv[1:])
361363
print(f'Starting h2_server with --port={v.port}')
362-
sys.exit(run(v.port))
364+
sys.exit(run(v.port, v.cert_path))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// Copyright 2025 The TensorStore Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "tensorstore/internal/http/self_signed_cert.h"
16+
17+
#include <stdint.h>
18+
19+
#include <memory>
20+
#include <string>
21+
#include <utility>
22+
#include <vector>
23+
24+
#include "absl/log/absl_check.h"
25+
#include "absl/status/status.h"
26+
#include "absl/strings/str_cat.h"
27+
#include <openssl/asn1.h>
28+
#include <openssl/bio.h>
29+
#include <openssl/bn.h>
30+
#include <openssl/err.h>
31+
#include <openssl/evp.h>
32+
#include <openssl/pem.h>
33+
#include <openssl/rsa.h>
34+
#include <openssl/x509.h>
35+
#include "tensorstore/internal/source_location.h"
36+
#include "tensorstore/util/result.h"
37+
#include "tensorstore/util/status.h"
38+
39+
namespace tensorstore {
40+
namespace internal_http {
41+
namespace {
42+
43+
struct EVP_PKEYFree {
44+
void operator()(EVP_PKEY* pkey) { EVP_PKEY_free(pkey); }
45+
};
46+
47+
struct X509Free {
48+
void operator()(X509* x509) { X509_free(x509); }
49+
};
50+
51+
struct X509NameFree {
52+
void operator()(X509_NAME* name) { X509_NAME_free(name); }
53+
};
54+
55+
struct BIOFree {
56+
void operator()(BIO* bio) { BIO_free(bio); }
57+
};
58+
59+
struct BIGNUMFree {
60+
void operator()(BIGNUM* bn) { BN_free(bn); }
61+
};
62+
63+
struct RSAFree {
64+
void operator()(RSA* rsa) { RSA_free(rsa); }
65+
};
66+
67+
std::unique_ptr<X509_NAME, X509NameFree> CreateName(
68+
const std::vector<std::pair<std::string, std::string>>& name_parts) {
69+
std::unique_ptr<X509_NAME, X509NameFree> name(X509_NAME_new());
70+
for (const auto& part : name_parts) {
71+
X509_NAME_add_entry_by_txt(name.get(), part.first.c_str(), MBSTRING_ASC,
72+
(unsigned char*)part.second.c_str(), -1, -1, 0);
73+
}
74+
return name;
75+
}
76+
77+
// Append the OpenSSL error strings to the status message.
78+
absl::Status OpenSslError(std::string message,
79+
SourceLocation loc = SourceLocation::current()) {
80+
for (int line = 0; line < 4; ++line) {
81+
absl::StrAppend(&message, "\n");
82+
const char* extra_data = nullptr;
83+
const char* file_name = nullptr;
84+
int line_number = 0;
85+
int flags = 0;
86+
// This extracts the top error code from the error codes stack.
87+
const uint32_t error_code =
88+
ERR_get_error_line_data(&file_name, &line_number, &extra_data, &flags);
89+
if (error_code == 0) { // No more error codes.
90+
break;
91+
}
92+
if (file_name != nullptr) {
93+
absl::StrAppend(&message, file_name, ":", line_number, " ");
94+
}
95+
const char* reason_error_string = ERR_reason_error_string(error_code);
96+
if (reason_error_string != nullptr) {
97+
absl::StrAppend(&message, reason_error_string);
98+
} else {
99+
absl::StrAppend(&message, error_code);
100+
}
101+
if (extra_data != nullptr && (flags & ERR_TXT_STRING)) {
102+
absl::StrAppend(&message, " - ", extra_data);
103+
}
104+
}
105+
auto status = absl::InternalError(message);
106+
MaybeAddSourceLocation(status, loc);
107+
return status;
108+
}
109+
110+
} // namespace
111+
112+
Result<SelfSignedCertificate> GenerateSelfSignedCerts() {
113+
std::unique_ptr<EVP_PKEY, EVP_PKEYFree> pkey(EVP_PKEY_new());
114+
115+
{
116+
std::unique_ptr<BIGNUM, BIGNUMFree> bn(BN_new());
117+
std::unique_ptr<RSA, RSAFree> rsa(RSA_new());
118+
119+
ABSL_CHECK(BN_set_word(bn.get(), RSA_F4));
120+
ABSL_CHECK(RSA_generate_key_ex(rsa.get(), 2048, bn.get(), nullptr));
121+
ABSL_CHECK(EVP_PKEY_assign_RSA(pkey.get(), rsa.release()));
122+
}
123+
124+
std::unique_ptr<X509, X509Free> x509(X509_new());
125+
126+
// TODO: Random serial number?
127+
ABSL_CHECK(ASN1_INTEGER_set(X509_get_serialNumber(x509.get()), 1));
128+
ABSL_CHECK(X509_gmtime_adj(X509_get_notBefore(x509.get()), 0));
129+
ABSL_CHECK(X509_gmtime_adj(X509_get_notAfter(x509.get()), 3600L)); // 1 hour
130+
131+
if (!X509_set_pubkey(x509.get(), pkey.get())) {
132+
return OpenSslError("Failed to set public key on certificate");
133+
}
134+
135+
auto name = CreateName({
136+
{"C", "CA"},
137+
{"O", "Tensorstore Test"},
138+
{"CN", "localhost"},
139+
});
140+
if (!X509_set_issuer_name(x509.get(), name.get())) {
141+
return OpenSslError("Failed to set issuer name on certificate");
142+
}
143+
144+
// Sign the certificate.
145+
if (!X509_sign(x509.get(), pkey.get(), EVP_sha256())) {
146+
return OpenSslError("Failed to sign x509 certificate");
147+
}
148+
149+
// Output the certificate and private key to PEM.
150+
std::unique_ptr<BIO, BIOFree> bio_pem(BIO_new(BIO_s_mem()));
151+
if (!PEM_write_bio_PrivateKey(bio_pem.get(), pkey.get(), nullptr, nullptr, 0,
152+
0, nullptr)) {
153+
return OpenSslError("Failed to generate certificate");
154+
}
155+
156+
SelfSignedCertificate result;
157+
auto& key_pem = result.key_pem;
158+
159+
key_pem.resize(BIO_pending(bio_pem.get()));
160+
if (BIO_read(bio_pem.get(), key_pem.data(), key_pem.size()) !=
161+
key_pem.size()) {
162+
return OpenSslError("Failed to generate certificate");
163+
}
164+
165+
std::unique_ptr<BIO, BIOFree> bio_cert(BIO_new(BIO_s_mem()));
166+
if (!PEM_write_bio_X509(bio_cert.get(), x509.get())) {
167+
return OpenSslError("Failed to generate certificate");
168+
}
169+
170+
auto& cert_pem = result.cert_pem;
171+
cert_pem.resize(BIO_pending(bio_cert.get()));
172+
if (BIO_read(bio_cert.get(), cert_pem.data(), cert_pem.size()) !=
173+
cert_pem.size()) {
174+
return OpenSslError("Failed to generate certificate");
175+
}
176+
return result;
177+
}
178+
179+
} // namespace internal_http
180+
} // namespace tensorstore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2025 The TensorStore Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef TENSORSTORE_INTERNAL_HTTP_SELF_SIGNED_CERT_H_
16+
#define TENSORSTORE_INTERNAL_HTTP_SELF_SIGNED_CERT_H_
17+
18+
#include <string>
19+
20+
#include "tensorstore/util/result.h"
21+
22+
namespace tensorstore {
23+
namespace internal_http {
24+
25+
// A self-signed certificate and private key.
26+
struct SelfSignedCertificate {
27+
std::string key_pem;
28+
std::string cert_pem;
29+
};
30+
Result<SelfSignedCertificate> GenerateSelfSignedCerts();
31+
32+
} // namespace internal_http
33+
} // namespace tensorstore
34+
35+
#endif // TENSORSTORE_INTERNAL_HTTP_SELF_SIGNED_CERT_H_
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2025 The TensorStore Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "tensorstore/internal/http/self_signed_cert.h"
16+
17+
#include <gtest/gtest.h>
18+
#include "tensorstore/util/status_testutil.h"
19+
20+
namespace {
21+
22+
using ::tensorstore::internal_http::GenerateSelfSignedCerts;
23+
24+
TEST(SelfSignedCertTest, GenerateSelfSignedCerts) {
25+
TENSORSTORE_ASSERT_OK_AND_ASSIGN(auto cert, GenerateSelfSignedCerts());
26+
EXPECT_FALSE(cert.key_pem.empty());
27+
EXPECT_FALSE(cert.cert_pem.empty());
28+
}
29+
30+
} // namespace

0 commit comments

Comments
 (0)