Skip to content

Commit 751c68f

Browse files
authored
Merge pull request libgit2#6877 from yerseg/ability_to_add_custom_x509_certs
ssl: ability to add raw X509 certs to the cert store
2 parents 5d48749 + fa5b832 commit 751c68f

File tree

14 files changed

+241
-22
lines changed

14 files changed

+241
-22
lines changed

include/git2/common.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ typedef enum {
199199
GIT_OPT_GET_TEMPLATE_PATH,
200200
GIT_OPT_SET_TEMPLATE_PATH,
201201
GIT_OPT_SET_SSL_CERT_LOCATIONS,
202+
GIT_OPT_ADD_SSL_X509_CERT,
202203
GIT_OPT_SET_USER_AGENT,
203204
GIT_OPT_ENABLE_STRICT_OBJECT_CREATION,
204205
GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION,
@@ -335,8 +336,21 @@ typedef enum {
335336
* > - `path` is the location of a directory holding several
336337
* > certificates, one per file.
337338
* >
339+
* > Calling `GIT_OPT_ADD_SSL_X509_CERT` may override the
340+
* > data in `path`.
341+
* >
338342
* > Either parameter may be `NULL`, but not both.
339343
*
344+
* * opts(GIT_OPT_ADD_SSL_X509_CERT, const X509 *cert)
345+
*
346+
* > Add a raw X509 certificate into the SSL certs store.
347+
* > This certificate is only used by libgit2 invocations
348+
* > during the application lifetime and is not persisted
349+
* > to disk. This certificate cannot be removed from the
350+
* > application once is has been added.
351+
* >
352+
* > - `cert` is the raw X509 cert will be added to cert store.
353+
*
340354
* * opts(GIT_OPT_SET_USER_AGENT, const char *user_agent)
341355
*
342356
* > Set the value of the comment section of the User-Agent header.

src/libgit2/settings.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,18 @@ int git_libgit2_opts(int key, ...)
222222
#endif
223223
break;
224224

225+
case GIT_OPT_ADD_SSL_X509_CERT:
226+
#ifdef GIT_OPENSSL
227+
{
228+
X509 *cert = va_arg(ap, X509 *);
229+
error = git_openssl__add_x509_cert(cert);
230+
}
231+
#else
232+
git_error_set(GIT_ERROR_SSL, "TLS backend doesn't support adding of the raw certs");
233+
error = -1;
234+
#endif
235+
break;
236+
225237
case GIT_OPT_SET_USER_AGENT:
226238
{
227239
const char *new_agent = va_arg(ap, const char *);

src/libgit2/streams/openssl.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,30 @@ int git_openssl__set_cert_location(const char *file, const char *path)
722722
return 0;
723723
}
724724

725+
int git_openssl__add_x509_cert(X509 *cert)
726+
{
727+
X509_STORE *cert_store;
728+
729+
if (openssl_ensure_initialized() < 0)
730+
return -1;
731+
732+
if (!(cert_store = SSL_CTX_get_cert_store(git__ssl_ctx)))
733+
return -1;
734+
735+
if (cert && X509_STORE_add_cert(cert_store, cert) == 0) {
736+
git_error_set(GIT_ERROR_SSL, "OpenSSL error: failed to add raw X509 certificate");
737+
return -1;
738+
}
739+
740+
return 0;
741+
}
742+
743+
int git_openssl__reset_context(void)
744+
{
745+
shutdown_ssl();
746+
return openssl_init();
747+
}
748+
725749
#else
726750

727751
#include "stream.h"

src/libgit2/streams/openssl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ extern int git_openssl_stream_global_init(void);
2424

2525
#ifdef GIT_OPENSSL
2626
extern int git_openssl__set_cert_location(const char *file, const char *path);
27+
extern int git_openssl__add_x509_cert(X509 *cert);
28+
extern int git_openssl__reset_context(void);
2729
extern int git_openssl_stream_new(git_stream **out, const char *host, const char *port);
2830
extern int git_openssl_stream_wrap(git_stream **out, git_stream *in, const char *host);
2931
#endif

src/libgit2/streams/openssl_dynamic.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ int (*X509_NAME_get_index_by_NID)(X509_NAME *name, int nid, int lastpos);
8080
void (*X509_free)(X509 *a);
8181
void *(*X509_get_ext_d2i)(const X509 *x, int nid, int *crit, int *idx);
8282
X509_NAME *(*X509_get_subject_name)(const X509 *x);
83+
int (*X509_STORE_add_cert)(X509_STORE *ctx, X509 *x);
8384

8485
int (*i2d_X509)(X509 *a, unsigned char **ppout);
8586

@@ -209,6 +210,7 @@ int git_openssl_stream_dynamic_init(void)
209210
X509_free = (void (*)(X509 *))openssl_sym(&err, "X509_free", true);
210211
X509_get_ext_d2i = (void *(*)(const X509 *x, int nid, int *crit, int *idx))openssl_sym(&err, "X509_get_ext_d2i", true);
211212
X509_get_subject_name = (X509_NAME *(*)(const X509 *))openssl_sym(&err, "X509_get_subject_name", true);
213+
X509_STORE_add_cert = (int (*)(X509_STORE *ctx, X509 *x))openssl_sym(&err, "X509_STORE_add_cert", true);
212214

213215
i2d_X509 = (int (*)(X509 *a, unsigned char **ppout))openssl_sym(&err, "i2d_X509", true);
214216

src/libgit2/streams/openssl_dynamic.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ extern int (*X509_NAME_get_index_by_NID)(X509_NAME *name, int nid, int lastpos);
326326
extern void (*X509_free)(X509 *a);
327327
extern void *(*X509_get_ext_d2i)(const X509 *x, int nid, int *crit, int *idx);
328328
extern X509_NAME *(*X509_get_subject_name)(const X509 *x);
329+
extern int (*X509_STORE_add_cert)(X509_STORE *ctx, X509 *x);
329330

330331
extern int (*i2d_X509)(X509 *a, unsigned char **ppout);
331332

tests/libgit2/online/customcert.c

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include "clar.h"
12
#include "clar_libgit2.h"
23

34
#include "path.h"
@@ -6,45 +7,52 @@
67
#include "remote.h"
78
#include "futils.h"
89
#include "refs.h"
10+
#include "str.h"
11+
#include "streams/openssl.h"
12+
13+
#ifdef GIT_OPENSSL
14+
# include <openssl/ssl.h>
15+
# include <openssl/err.h>
16+
# include <openssl/x509v3.h>
17+
#endif
918

1019
/*
11-
* Certificate one is in the `certs` folder; certificate two is in the
12-
* `self-signed.pem` file.
20+
* Certificates for https://test.libgit2.org/ are in the `certs` folder.
1321
*/
22+
#define CUSTOM_CERT_DIR "certs"
23+
1424
#define CUSTOM_CERT_ONE_URL "https://test.libgit2.org:1443/anonymous/test.git"
15-
#define CUSTOM_CERT_ONE_PATH "certs"
25+
#define CUSTOM_CERT_ONE_PATH "one"
1626

1727
#define CUSTOM_CERT_TWO_URL "https://test.libgit2.org:2443/anonymous/test.git"
18-
#define CUSTOM_CERT_TWO_FILE "self-signed.pem"
28+
#define CUSTOM_CERT_TWO_FILE "two.pem"
29+
30+
#define CUSTOM_CERT_THREE_URL "https://test.libgit2.org:3443/anonymous/test.git"
31+
#define CUSTOM_CERT_THREE_FILE "three.pem.raw"
1932

2033
#if (GIT_OPENSSL || GIT_MBEDTLS)
2134
static git_repository *g_repo;
22-
static int initialized = false;
2335
#endif
2436

2537
void test_online_customcert__initialize(void)
2638
{
2739
#if (GIT_OPENSSL || GIT_MBEDTLS)
28-
g_repo = NULL;
40+
git_str path = GIT_STR_INIT, file = GIT_STR_INIT;
41+
char cwd[GIT_PATH_MAX];
2942

30-
if (!initialized) {
31-
git_str path = GIT_STR_INIT, file = GIT_STR_INIT;
32-
char cwd[GIT_PATH_MAX];
43+
g_repo = NULL;
3344

34-
cl_fixture_sandbox(CUSTOM_CERT_ONE_PATH);
35-
cl_fixture_sandbox(CUSTOM_CERT_TWO_FILE);
45+
cl_fixture_sandbox(CUSTOM_CERT_DIR);
3646

37-
cl_must_pass(p_getcwd(cwd, GIT_PATH_MAX));
38-
cl_git_pass(git_str_joinpath(&path, cwd, CUSTOM_CERT_ONE_PATH));
39-
cl_git_pass(git_str_joinpath(&file, cwd, CUSTOM_CERT_TWO_FILE));
47+
cl_must_pass(p_getcwd(cwd, GIT_PATH_MAX));
48+
cl_git_pass(git_str_join_n(&path, '/', 3, cwd, CUSTOM_CERT_DIR, CUSTOM_CERT_ONE_PATH));
49+
cl_git_pass(git_str_join_n(&file, '/', 3, cwd, CUSTOM_CERT_DIR, CUSTOM_CERT_TWO_FILE));
4050

41-
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS,
42-
file.ptr, path.ptr));
43-
initialized = true;
51+
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS,
52+
file.ptr, path.ptr));
4453

45-
git_str_dispose(&file);
46-
git_str_dispose(&path);
47-
}
54+
git_str_dispose(&file);
55+
git_str_dispose(&path);
4856
#endif
4957
}
5058

@@ -57,8 +65,11 @@ void test_online_customcert__cleanup(void)
5765
}
5866

5967
cl_fixture_cleanup("./cloned");
60-
cl_fixture_cleanup(CUSTOM_CERT_ONE_PATH);
61-
cl_fixture_cleanup(CUSTOM_CERT_TWO_FILE);
68+
cl_fixture_cleanup(CUSTOM_CERT_DIR);
69+
#endif
70+
71+
#ifdef GIT_OPENSSL
72+
git_openssl__reset_context();
6273
#endif
6374
}
6475

@@ -77,3 +88,34 @@ void test_online_customcert__path(void)
7788
cl_assert(git_fs_path_exists("./cloned/master.txt"));
7889
#endif
7990
}
91+
92+
void test_online_customcert__raw_x509(void)
93+
{
94+
#ifdef GIT_OPENSSL
95+
X509* x509_cert = NULL;
96+
char cwd[GIT_PATH_MAX];
97+
git_str raw_file = GIT_STR_INIT,
98+
raw_file_data = GIT_STR_INIT,
99+
raw_cert = GIT_STR_INIT;
100+
const unsigned char *raw_cert_bytes = NULL;
101+
102+
cl_must_pass(p_getcwd(cwd, GIT_PATH_MAX));
103+
104+
cl_git_pass(git_str_join_n(&raw_file, '/', 3, cwd, CUSTOM_CERT_DIR, CUSTOM_CERT_THREE_FILE));
105+
106+
cl_git_pass(git_futils_readbuffer(&raw_file_data, git_str_cstr(&raw_file)));
107+
cl_git_pass(git_str_decode_base64(&raw_cert, git_str_cstr(&raw_file_data), git_str_len(&raw_file_data)));
108+
109+
raw_cert_bytes = (const unsigned char *)git_str_cstr(&raw_cert);
110+
x509_cert = d2i_X509(NULL, &raw_cert_bytes, git_str_len(&raw_cert));
111+
cl_git_pass(git_libgit2_opts(GIT_OPT_ADD_SSL_X509_CERT, x509_cert));
112+
X509_free(x509_cert);
113+
114+
cl_git_pass(git_clone(&g_repo, CUSTOM_CERT_THREE_URL, "./cloned", NULL));
115+
cl_assert(git_fs_path_exists("./cloned/master.txt"));
116+
117+
git_str_dispose(&raw_cert);
118+
git_str_dispose(&raw_file_data);
119+
git_str_dispose(&raw_file);
120+
#endif
121+
}

tests/resources/certs/README

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
These are self-signed certificates for https://test.libgit2.org/.
2+
3+
* one/ - contains certificates for https://test.libgit2.org:1443/
4+
* two.pem - contains a certificate for https://test.libgit2.org:2443/
5+
* three.pem - contains a certificate for https://test.libgit2.org:3443/
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIFWzCCA0MCFESY816VkhBPUOsdp7djKW5q4ZVzMA0GCSqGSIb3DQEBCwUAMGox
3+
CzAJBgNVBAYTAlVTMRYwFAYDVQQIDA1NYXNzYWNodXNldHRzMRIwEAYDVQQHDAlD
4+
YW1icmlkZ2UxFDASBgNVBAoMC2xpYmdpdDIub3JnMRkwFwYDVQQDDBB0ZXN0Lmxp
5+
YmdpdDIub3JnMB4XDTIxMDgyNTE4NTExMVoXDTMxMDgyMzE4NTExMVowajELMAkG
6+
A1UEBhMCVVMxFjAUBgNVBAgMDU1hc3NhY2h1c2V0dHMxEjAQBgNVBAcMCUNhbWJy
7+
aWRnZTEUMBIGA1UECgwLbGliZ2l0Mi5vcmcxGTAXBgNVBAMMEHRlc3QubGliZ2l0
8+
Mi5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvaRUaM3IJh9N
9+
G6Yc7tHioUsIGU0MkzSvy/X6O/vONnuuioiJQyPIvfRSvZR2iQj8THTypDGhWn3r
10+
6h2wk5eOUGwJH2N9FrrlEBdpsMc7SKdiJXwTI30mkK3/qru8NzE71dgCkYp1xhKw
11+
edTkAFK+PkvyVLFL7K35cx8Bxfamyssdb+qGWa7g4P27CWUdvQgmurrzzPIMZiLD
12+
/cI1Kwer/N7nTY/6CSs9dcHTlanyZdf+mQ50+//vI4F6+OduGHJkxRF48jLUz1rz
13+
P3WGRMRbHjCmvWpX/9DLgqGk7XTy0hNgNUCit6kawwcv5y7SP/ii86MkynAHn5i8
14+
d+zhXjdrSSy8i0IbRJafnxmtrsmjGeIzraJSRqMlv7KKWEBz+alm6vlePnRUbWB7
15+
0po5uSsRPya6kJJCzMjIfKq1dgXq33m9jCG2wU+L4fEHVlEkFGXYTspMlIBNUjTc
16+
c45+e1EpamF8aHm32PP8gTF8fGZzQjOXmNW5g7t0joWMGZ+Ao2jYc1pG3SOARi36
17+
azrmB5/XJqbbfVZEzIue01fO/5R8RgabOP1qWUjH2KLb8zTDok+CW0ULNseU+MKf
18+
PHXG2OjxcR0vTqop2V6JlKTXXx3/TOD16/+mSrrPzNDejLrkvAH9oN38YpMBM8eg
19+
vfivHNRm0jjdGbv2OOPEBLEf1cNimQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQBZ
20+
znFta24sWoqdgKXKAK5RHAh/HyOvTInwcXi9RU4XjYlbqNVs0ODR74VRZINoyAL2
21+
bo+x/iUuAp9+b8fjr79fpVof3nSMU7UtMcT1nvzVmaUYSkKQ0f/9vK4yg0kao1bV
22+
WwhIc0slKgOJjEicPVs3kd+duv5vakQeUajLPGM8SiS1F/nF67rIuZLdJn2Qp+im
23+
w5Q3Pjgqw5VrJxyk3AaUcntKHpWy1POLyNV79tXra6BxbtQVlRS0+h1MHELARDFx
24+
1ZtgyAe5YbWM7WrIiFKD4mmKZu4GMnJDXVpfUub5g0U/e7L/gg6Z1UyYZuln6axw
25+
RojuAHo1uAWFUsjhWLYV/7P/l/dC+7gFjvSsUqb1+U7jXObzfKjXo/FwYcy4VsVv
26+
xNbglbhdVjAo/YBTJuf3L0UZjSbxvQIYS+v8u1ECeWE6SH6cHRzryeo5wO4h8NJR
27+
n30xsvocHFbs4LWy5BVfMUo6wGUy0Y+1gSwSqVMv3JPuLwxUsv0HPdeC00Ab9cHq
28+
kYXPNZXg3a6orTDa4hJLdAm2V/fn/2KKJYlNj7iCL664QgoCHl7LFyLMiwFVCu5h
29+
4JjGL3Q+8MondaLZlq5YDmvtj979AyM/7qL4XAE2oofQ4J5dqnKKpMkWdAM/fI/9
30+
N5DK/4zMXJWgIED0yo2SSZHQmuqZplacOhmfjjZigQ==
31+
-----END CERTIFICATE-----
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIFWzCCA0MCFESY816VkhBPUOsdp7djKW5q4ZVzMA0GCSqGSIb3DQEBCwUAMGox
3+
CzAJBgNVBAYTAlVTMRYwFAYDVQQIDA1NYXNzYWNodXNldHRzMRIwEAYDVQQHDAlD
4+
YW1icmlkZ2UxFDASBgNVBAoMC2xpYmdpdDIub3JnMRkwFwYDVQQDDBB0ZXN0Lmxp
5+
YmdpdDIub3JnMB4XDTIxMDgyNTE4NTExMVoXDTMxMDgyMzE4NTExMVowajELMAkG
6+
A1UEBhMCVVMxFjAUBgNVBAgMDU1hc3NhY2h1c2V0dHMxEjAQBgNVBAcMCUNhbWJy
7+
aWRnZTEUMBIGA1UECgwLbGliZ2l0Mi5vcmcxGTAXBgNVBAMMEHRlc3QubGliZ2l0
8+
Mi5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvaRUaM3IJh9N
9+
G6Yc7tHioUsIGU0MkzSvy/X6O/vONnuuioiJQyPIvfRSvZR2iQj8THTypDGhWn3r
10+
6h2wk5eOUGwJH2N9FrrlEBdpsMc7SKdiJXwTI30mkK3/qru8NzE71dgCkYp1xhKw
11+
edTkAFK+PkvyVLFL7K35cx8Bxfamyssdb+qGWa7g4P27CWUdvQgmurrzzPIMZiLD
12+
/cI1Kwer/N7nTY/6CSs9dcHTlanyZdf+mQ50+//vI4F6+OduGHJkxRF48jLUz1rz
13+
P3WGRMRbHjCmvWpX/9DLgqGk7XTy0hNgNUCit6kawwcv5y7SP/ii86MkynAHn5i8
14+
d+zhXjdrSSy8i0IbRJafnxmtrsmjGeIzraJSRqMlv7KKWEBz+alm6vlePnRUbWB7
15+
0po5uSsRPya6kJJCzMjIfKq1dgXq33m9jCG2wU+L4fEHVlEkFGXYTspMlIBNUjTc
16+
c45+e1EpamF8aHm32PP8gTF8fGZzQjOXmNW5g7t0joWMGZ+Ao2jYc1pG3SOARi36
17+
azrmB5/XJqbbfVZEzIue01fO/5R8RgabOP1qWUjH2KLb8zTDok+CW0ULNseU+MKf
18+
PHXG2OjxcR0vTqop2V6JlKTXXx3/TOD16/+mSrrPzNDejLrkvAH9oN38YpMBM8eg
19+
vfivHNRm0jjdGbv2OOPEBLEf1cNimQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQBZ
20+
znFta24sWoqdgKXKAK5RHAh/HyOvTInwcXi9RU4XjYlbqNVs0ODR74VRZINoyAL2
21+
bo+x/iUuAp9+b8fjr79fpVof3nSMU7UtMcT1nvzVmaUYSkKQ0f/9vK4yg0kao1bV
22+
WwhIc0slKgOJjEicPVs3kd+duv5vakQeUajLPGM8SiS1F/nF67rIuZLdJn2Qp+im
23+
w5Q3Pjgqw5VrJxyk3AaUcntKHpWy1POLyNV79tXra6BxbtQVlRS0+h1MHELARDFx
24+
1ZtgyAe5YbWM7WrIiFKD4mmKZu4GMnJDXVpfUub5g0U/e7L/gg6Z1UyYZuln6axw
25+
RojuAHo1uAWFUsjhWLYV/7P/l/dC+7gFjvSsUqb1+U7jXObzfKjXo/FwYcy4VsVv
26+
xNbglbhdVjAo/YBTJuf3L0UZjSbxvQIYS+v8u1ECeWE6SH6cHRzryeo5wO4h8NJR
27+
n30xsvocHFbs4LWy5BVfMUo6wGUy0Y+1gSwSqVMv3JPuLwxUsv0HPdeC00Ab9cHq
28+
kYXPNZXg3a6orTDa4hJLdAm2V/fn/2KKJYlNj7iCL664QgoCHl7LFyLMiwFVCu5h
29+
4JjGL3Q+8MondaLZlq5YDmvtj979AyM/7qL4XAE2oofQ4J5dqnKKpMkWdAM/fI/9
30+
N5DK/4zMXJWgIED0yo2SSZHQmuqZplacOhmfjjZigQ==
31+
-----END CERTIFICATE-----

0 commit comments

Comments
 (0)