Skip to content

Commit af52b41

Browse files
gpsheadtiranambv
authored
[3.11] gh-160040: Distrust compromised eMudhra intermediate CA keys (CVE-2026-115557) (GH-160042) (GH-160572)
The ssl module now rejects TLS certificate chains that contain any of a set of compromised eMudhra intermediate CA keys. This is an extraordinary measure and a last line of defense: removing trust is primarily the job of the platform root store, and the built-in denylist only blocks connections while that update has not yet reached the OS, a container image, or a bundled trust store in various Python packages. Entries are added to the denylist only under a very high threshold. The denylist matches on the public key (the SHA-256 digest of the RFC-5280 SubjectPublicKeyInfo), so it also covers the cross-signed certificates that reuse one of the distrusted keys. (cherry picked from commit 7dd8b47) Co-authored-by: Christian Heimes <christian@python.org> Co-authored-by: Łukasz Langa <lukasz@langa.pl>
1 parent 0fc0183 commit af52b41

9 files changed

Lines changed: 303 additions & 5 deletions

File tree

‎Lib/test/certdata/distrusted.zip‎

4.94 KB
Binary file not shown.

‎Lib/test/certdata/make_ssl_certs.py‎

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import pprint
66
import shutil
77
import tempfile
8+
import zipfile
89
from subprocess import *
910

1011
startdate = "20180829142316Z"
@@ -223,6 +224,107 @@ def print_cert(path):
223224
pprint.pprint(_ssl._test_decode_cert(path))
224225

225226

227+
# The distrusted test material (compromised eMudhra CA certs and a leaf signed
228+
# by them) lives in a password-protected zip so it doesn't trip security
229+
# scanners that cannot tell internal test fixtures from production secrets and
230+
# flag any certificate or private key they find. It is encrypted, not merely
231+
# zipped, because such scanners read inside plain archives. The password is
232+
# fixed, public, and not a secret.
233+
DISTRUSTED_ZIP = 'distrusted.zip'
234+
DISTRUSTED_ZIP_DIR = 'distrusted'
235+
DISTRUSTED_ZIP_CHOSEN_KNIGHT = 'Brave Sir Robin'
236+
237+
# ASN.1 template for the intermediate's compromised key: secp384r1 scalar 4.
238+
# openssl derives the public key when the SEC1 structure omits it.
239+
distrusted_key_template = """
240+
asn1 = SEQUENCE:eckey
241+
[eckey]
242+
version = INTEGER:1
243+
priv = FORMAT:HEX,OCTETSTRING:{scalar}
244+
params = EXPLICIT:0,OID:secp384r1
245+
"""
246+
247+
248+
def make_distrusted_spki_cert():
249+
# leaf signed by the distrusted intermediate, chains to the distrusted root
250+
print("creating distrusted-SPKI test cert")
251+
# The standard library can read but not write encrypted zip files, so we
252+
# need the Info-ZIP "zip" command to repackage the archive.
253+
if shutil.which('zip') is None:
254+
raise SystemExit(
255+
f"the 'zip' command is required to write the encrypted {DISTRUSTED_ZIP} "
256+
"(the zipfile module cannot create encrypted archives); "
257+
"please install Info-ZIP's zip utility"
258+
)
259+
260+
# the root and intermediate are real certs we cannot re-mint; carry them
261+
# over from the existing archive
262+
pw = DISTRUSTED_ZIP_CHOSEN_KNIGHT.encode()
263+
with zipfile.ZipFile(DISTRUSTED_ZIP) as z:
264+
root = z.read(DISTRUSTED_ZIP_DIR + '/distrusted_root.pem', pw)
265+
intermediate = z.read(DISTRUSTED_ZIP_DIR + '/distrusted_intermediate.pem', pw)
266+
267+
with tempfile.TemporaryDirectory() as workdir:
268+
stage = os.path.join(workdir, DISTRUSTED_ZIP_DIR)
269+
os.mkdir(stage)
270+
req_file = os.path.join(workdir, 'req.cnf')
271+
key_file = os.path.join(workdir, 'leaf.key')
272+
csr_file = os.path.join(workdir, 'leaf.csr')
273+
cert_file = os.path.join(workdir, 'leaf.pem')
274+
cakey_conf = os.path.join(workdir, 'cakey.cnf')
275+
cakey_der = os.path.join(workdir, 'cakey.der')
276+
cakey_file = os.path.join(workdir, 'cakey.pem')
277+
278+
inter_file = os.path.join(stage, 'distrusted_intermediate.pem')
279+
with open(os.path.join(stage, 'distrusted_root.pem'), 'wb') as f:
280+
f.write(root)
281+
with open(inter_file, 'wb') as f:
282+
f.write(intermediate)
283+
284+
# recreate the intermediate's key (secp384r1 scalar 4) on the fly
285+
with open(cakey_conf, 'w') as f:
286+
f.write(distrusted_key_template.format(scalar='00' * 47 + '04'))
287+
check_call(['openssl', 'asn1parse', '-genconf', cakey_conf,
288+
'-out', cakey_der, '-noout'])
289+
check_call(['openssl', 'pkey', '-in', cakey_der,
290+
'-inform', 'DER', '-out', cakey_file])
291+
292+
req = req_template.format(
293+
hostname='localhost',
294+
extra_san='',
295+
startdate=startdate,
296+
enddate=enddate
297+
)
298+
with open(req_file, 'w') as f:
299+
f.write(req)
300+
# ECC leaf to match the ECC intermediate
301+
check_call(['openssl', 'req', '-new', '-nodes', '-days', '7000',
302+
'-newkey', 'param:secp384r1.pem', '-keyout', key_file,
303+
'-config', req_file, '-out', csr_file])
304+
# sign with the distrusted intermediate, not the test CA
305+
check_call(['openssl', 'ca', '-config', req_file,
306+
'-extensions', 'req_x509_extensions_full',
307+
'-cert', inter_file, '-keyfile', cakey_file,
308+
'-out', cert_file, '-outdir', 'cadir',
309+
'-policy', 'policy_anything', '-batch', '-infiles', csr_file])
310+
with open(key_file) as f:
311+
key = f.read()
312+
with open(cert_file) as f:
313+
cert = f.read()
314+
# server sends leaf + intermediate, client trusts the root
315+
with open(os.path.join(stage, 'distrusted_keycert.pem'), 'w') as f:
316+
f.write(key)
317+
f.write(cert)
318+
f.write(intermediate.decode())
319+
320+
# repackage the encrypted archive (zipfile cannot write encryption)
321+
zippath = os.path.join(here, DISTRUSTED_ZIP)
322+
if os.path.exists(zippath):
323+
os.remove(zippath)
324+
check_call(['zip', '-q', '-X', '-P', DISTRUSTED_ZIP_CHOSEN_KNIGHT, '-r',
325+
zippath, DISTRUSTED_ZIP_DIR], cwd=workdir)
326+
327+
226328
if __name__ == '__main__':
227329
os.chdir(here)
228330
cert, key = make_cert_key('localhost', ext='req_x509_extensions_simple')
@@ -306,6 +408,8 @@ def print_cert(path):
306408
f.write(key)
307409
f.write(cert)
308410

411+
make_distrusted_spki_cert()
412+
309413
unmake_ca()
310414
print("update Lib/test/test_ssl.py and Lib/test/test_asyncio/utils.py")
311415
print_cert('keycert.pem')

‎Lib/test/test_ssl.py‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
import socket
1616
import select
1717
import struct
18+
import tempfile
1819
import time
20+
import zipfile
1921
import enum
2022
import gc
2123
import http.client
@@ -132,6 +134,19 @@ def data_file(*name):
132134
SIGNED_CERTFILE_ECC = data_file("keycertecc.pem")
133135
SIGNED_CERTFILE_ECC_HOSTNAME = 'localhost-ecc'
134136

137+
# gh-160040: a leaf signed by a compromised eMudhra intermediate whose public
138+
# key is on the _ssl distrusted list. The chain to distrusted_root.pem is
139+
# otherwise valid.
140+
# The PEMs live in a password-protected zip so the compromised eMudhra certs
141+
# don't trip security scanners that cannot tell internal test fixtures from
142+
# production secrets and flag any certificate or private key they find. It is
143+
# encrypted, not merely zipped, because such scanners read inside plain archives.
144+
# The password is fixed, public, and not a secret; it only keeps the archive
145+
# from storing the PEMs in the clear.
146+
DISTRUSTED_SPKI_ZIP = data_file("distrusted.zip")
147+
DISTRUSTED_SPKI_CHOSEN_KNIGHT = b"Brave Sir Robin" # not a secret, see comment
148+
DISTRUSTED_SPKI_HOSTNAME = "localhost"
149+
135150
# Same certificate as pycacert.pem, but without extra text in file
136151
SIGNING_CA = data_file("capath", "ceff1710.0")
137152
# cert with all kinds of subject alt names
@@ -2133,6 +2148,28 @@ def test_unwrap(self):
21332148
c_in.write(s_out.read())
21342149
client.unwrap()
21352150

2151+
def test_distrusted_spki(self):
2152+
# gh-160040: the server chain is otherwise valid, but the eMudhra
2153+
# intermediate's public key is on the _ssl distrusted list, so
2154+
# verification rejects it.
2155+
client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
2156+
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
2157+
# the contexts load the PEMs now, so the files are only needed here
2158+
with tempfile.TemporaryDirectory() as tmpdir:
2159+
with zipfile.ZipFile(DISTRUSTED_SPKI_ZIP) as z:
2160+
z.extractall(tmpdir, pwd=DISTRUSTED_SPKI_CHOSEN_KNIGHT)
2161+
certdir = os.path.join(tmpdir, "distrusted")
2162+
client_context.load_verify_locations(
2163+
os.path.join(certdir, "distrusted_root.pem"))
2164+
server_context.load_cert_chain(
2165+
os.path.join(certdir, "distrusted_keycert.pem"))
2166+
2167+
with self.assertRaises(ssl.SSLCertVerificationError) as cm:
2168+
connected_bio_pair(client_context, server_context,
2169+
DISTRUSTED_SPKI_HOSTNAME)
2170+
# X509_V_ERR_CERT_REJECTED
2171+
self.assertEqual(cm.exception.verify_code, 28)
2172+
21362173
def test_sni_callback_context_released_and_callback_raises(self):
21372174
# Variant of the test below without a HelloRetryRequest: the callback
21382175
# switches the connection to another context, drops the last

‎Makefile.pre.in‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2611,7 +2611,7 @@ MODULE__SHA256_DEPS=$(srcdir)/Modules/hashlib.h
26112611
MODULE__SHA3_DEPS=$(srcdir)/Modules/_sha3/sha3.c $(srcdir)/Modules/_sha3/sha3.h $(srcdir)/Modules/hashlib.h
26122612
MODULE__SHA512_DEPS=$(srcdir)/Modules/hashlib.h
26132613
MODULE__SOCKET_DEPS=$(srcdir)/Modules/socketmodule.h $(srcdir)/Modules/addrinfo.h $(srcdir)/Modules/getaddrinfo.c $(srcdir)/Modules/getnameinfo.c
2614-
MODULE__SSL_DEPS=$(srcdir)/Modules/_ssl.h $(srcdir)/Modules/_ssl/cert.c $(srcdir)/Modules/_ssl/debughelpers.c $(srcdir)/Modules/_ssl/misc.c $(srcdir)/Modules/_ssl_data.h $(srcdir)/Modules/_ssl_data_111.h $(srcdir)/Modules/_ssl_data_300.h $(srcdir)/Modules/socketmodule.h
2614+
MODULE__SSL_DEPS=$(srcdir)/Modules/_ssl.h $(srcdir)/Modules/_ssl/cert.c $(srcdir)/Modules/_ssl/debughelpers.c $(srcdir)/Modules/_ssl/misc.c $(srcdir)/Modules/_ssl/distrusted_spki.h $(srcdir)/Modules/_ssl_data.h $(srcdir)/Modules/_ssl_data_111.h $(srcdir)/Modules/_ssl_data_300.h $(srcdir)/Modules/socketmodule.h
26152615
MODULE__TESTCAPI_DEPS=$(srcdir)/Modules/testcapi_long.h
26162616
MODULE__SQLITE3_DEPS=$(srcdir)/Modules/_sqlite/connection.h $(srcdir)/Modules/_sqlite/cursor.h $(srcdir)/Modules/_sqlite/microprotocols.h $(srcdir)/Modules/_sqlite/module.h $(srcdir)/Modules/_sqlite/prepare_protocol.h $(srcdir)/Modules/_sqlite/row.h $(srcdir)/Modules/_sqlite/util.h
26172617

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.. cve: CVE-2026-115557
2+
3+
:mod:`ssl` now rejects TLS certificate chains that contain any of a set of
4+
compromised eMudhra intermediate CA keys. This is an extraordinary measure and
5+
a last line of defense: removing trust is primarily the job of the platform root
6+
store, and the built-in denylist only blocks connections while that update has
7+
not yet reached the OS, a container image, or a bundled trust store in various
8+
Python packages. Entries are added to the denylist only under a very high
9+
threshold.
10+
The denylist matches on the public key (the SHA-256 digest of the :rfc:`5280`
11+
``SubjectPublicKeyInfo``), so it also covers the cross-signed certificates that
12+
reuse one of the distrusted keys.

‎Modules/_ssl.c‎

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
/* Include OpenSSL header files */
5353
#include "openssl/rsa.h"
5454
#include "openssl/crypto.h"
55+
#include "openssl/evp.h"
5556
#include "openssl/x509.h"
5657
#include "openssl/x509v3.h"
5758
#include "openssl/pem.h"
@@ -878,6 +879,7 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
878879
int (*verify_cb)(int, X509_STORE_CTX *) = NULL;
879880
verify_cb = SSL_get_verify_callback(self->ssl);
880881
mode |= SSL_VERIFY_POST_HANDSHAKE;
882+
/* Keep ctx verify callback (distrusted SPKI check) */
881883
SSL_set_verify(self->ssl, mode, verify_cb);
882884
}
883885
} else {
@@ -2990,11 +2992,70 @@ static PyType_Spec PySSLSocket_spec = {
29902992
* _SSLContext objects
29912993
*/
29922994

2995+
#include "_ssl/distrusted_spki.h"
2996+
2997+
/* Returns 1 if the SHA-256 of the cert's SPKI is on the distrusted list,
2998+
0 if not (okay), -1 on an internal error. */
2999+
static int
3000+
_cert_has_distrusted_spki(X509 *cert)
3001+
{
3002+
unsigned char *spki = NULL;
3003+
int spki_len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &spki);
3004+
if (spki_len <= 0 || spki == NULL) {
3005+
return -1;
3006+
}
3007+
3008+
unsigned char digest[EVP_MAX_MD_SIZE];
3009+
unsigned int digest_len = 0;
3010+
int ok = EVP_Digest(spki, (size_t)spki_len, digest, &digest_len,
3011+
EVP_sha256(), NULL);
3012+
OPENSSL_free(spki);
3013+
assert(sizeof(_distrusted_spki[0]) == SHA256_DIGEST_LENGTH);
3014+
if (!ok
3015+
|| digest_len != SHA256_DIGEST_LENGTH
3016+
|| sizeof(_distrusted_spki[0]) != SHA256_DIGEST_LENGTH) {
3017+
return -1;
3018+
}
3019+
3020+
for (size_t i = 0; i < Py_ARRAY_LENGTH(_distrusted_spki); i++) {
3021+
if (memcmp(digest, _distrusted_spki[i], SHA256_DIGEST_LENGTH) == 0) {
3022+
return 1;
3023+
}
3024+
}
3025+
return 0;
3026+
}
3027+
3028+
/* Verify callback, called once per cert. Reject distrusted SPKIs. */
3029+
static int
3030+
_ssl_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
3031+
{
3032+
/* Don't override an error OpenSSL already reported for this certificate. */
3033+
if (!preverify_ok) {
3034+
return preverify_ok;
3035+
}
3036+
X509 *cert = X509_STORE_CTX_get_current_cert(x509_ctx);
3037+
if (cert == NULL) {
3038+
return preverify_ok;
3039+
}
3040+
switch (_cert_has_distrusted_spki(cert)) {
3041+
case 0:
3042+
/* okay: not on the distrusted list */
3043+
return preverify_ok;
3044+
case 1:
3045+
/* rejected: public key is on the distrusted list */
3046+
X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CERT_REJECTED);
3047+
return 0;
3048+
default:
3049+
/* internal error */
3050+
X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_UNSPECIFIED);
3051+
return 0;
3052+
}
3053+
}
3054+
29933055
static int
29943056
_set_verify_mode(PySSLContext *self, enum py_ssl_cert_requirements n)
29953057
{
29963058
int mode;
2997-
int (*verify_cb)(int, X509_STORE_CTX *) = NULL;
29983059

29993060
switch(n) {
30003061
case PY_SSL_CERT_NONE:
@@ -3015,9 +3076,7 @@ _set_verify_mode(PySSLContext *self, enum py_ssl_cert_requirements n)
30153076
/* bpo-37428: newPySSLSocket() sets SSL_VERIFY_POST_HANDSHAKE flag for
30163077
* server sockets and SSL_set_post_handshake_auth() for client. */
30173078

3018-
/* keep current verify cb */
3019-
verify_cb = SSL_CTX_get_verify_callback(self->ctx);
3020-
SSL_CTX_set_verify(self->ctx, mode, verify_cb);
3079+
SSL_CTX_set_verify(self->ctx, mode, _ssl_verify_callback);
30213080
return 0;
30223081
}
30233082

‎Modules/_ssl/distrusted_spki.h‎

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/* Denylist of distrusted SubjectPublicKeyInfo (SPKI).
2+
*
3+
* Verification rejects any chain with a cert whose SPKI SHA-256 digest is in
4+
* the _distrusted_spki table. We match the key, not a cert fingerprint, so
5+
* cross-signs that reuse the key are blocked too.
6+
*
7+
* This is an extraordinary measure and last line of defense. Removing trust is
8+
* primarily the job of the platform root store. This list only blocks keys while
9+
* that update is still propagating to the OS, container images, or root CAs
10+
* bundled in Python packages. The threshold for adding an entry is very high.
11+
*
12+
* See https://github.com/python/cpython/issues/160040 for the eMudhra discussion.
13+
*
14+
* To block another key, add the SHA-256 digest bytes of its SPKI:
15+
*
16+
* $ openssl x509 -in cert.pem -noout -pubkey | openssl pkey -pubin -outform DER | openssl dgst -sha256 -binary | xxd -i
17+
*/
18+
#ifndef Py_SSL_DISTRUSTED_SPKI_H
19+
#define Py_SSL_DISTRUSTED_SPKI_H
20+
21+
#include "openssl/sha.h"
22+
23+
static const unsigned char _distrusted_spki[][SHA256_DIGEST_LENGTH] = {
24+
/* Compromised eMudhra intermediates, https://emudhra.we-have-a-problem.com/ */
25+
/* CN=EM OV TLS CA - G3B,O=eMudhra BV,C=NL */
26+
{
27+
0x43, 0xbf, 0xfa, 0x19, 0xd6, 0xef, 0x65, 0x14, 0x51, 0xf6, 0x84, 0x1e,
28+
0x01, 0x99, 0x31, 0xe9, 0x6f, 0x6f, 0x2e, 0xae, 0xff, 0x28, 0xab, 0x4b,
29+
0x6f, 0x1c, 0xe4, 0x57, 0x6d, 0xe2, 0x71, 0x7b,
30+
},
31+
/* CN=EM DV TLS CA - G3C,O=eMudhra Inc,C=US */
32+
{
33+
0x49, 0xdd, 0x35, 0x3d, 0xc2, 0xb9, 0x19, 0xa3, 0x77, 0x06, 0x45, 0x11,
34+
0x7d, 0x0e, 0x60, 0xc1, 0x61, 0x15, 0xe7, 0xd1, 0xbc, 0x25, 0x9e, 0xef,
35+
0xe7, 0x34, 0xb0, 0x0e, 0xa4, 0x34, 0xfe, 0x5f,
36+
},
37+
/* CN=EM EV TLS CA - G3C,O=eMudhra Inc,C=US */
38+
{
39+
0x5c, 0x06, 0x60, 0xa2, 0xb1, 0x64, 0x91, 0x0d, 0x8f, 0xf5, 0x89, 0xc1,
40+
0x8e, 0x14, 0x55, 0x2e, 0x9e, 0x0e, 0xc2, 0x37, 0xdc, 0xef, 0xe2, 0xcb,
41+
0x73, 0x6d, 0x41, 0xa4, 0x81, 0x1f, 0x81, 0xa0,
42+
},
43+
/* CN=EM OV TLS CA - G3C,O=eMudhra Inc,C=US */
44+
{
45+
0x63, 0xec, 0xef, 0xee, 0x43, 0x1d, 0x49, 0x52, 0x3b, 0xc1, 0x13, 0x6c,
46+
0x4e, 0x72, 0xd0, 0x20, 0x6a, 0x47, 0x70, 0x86, 0xca, 0xf6, 0x06, 0xcd,
47+
0x5c, 0xf0, 0x25, 0x68, 0x6c, 0x10, 0x9c, 0xf8,
48+
},
49+
/* CN=EM OV CS CA - G3C,O=eMudhra Inc,C=US */
50+
{
51+
0x92, 0xc0, 0x63, 0xd6, 0x7e, 0x8f, 0x14, 0xdb, 0x71, 0xd2, 0x88, 0xe1,
52+
0x12, 0x66, 0xff, 0xda, 0xf6, 0x9c, 0xbf, 0x11, 0x5f, 0x7b, 0xab, 0x14,
53+
0xf3, 0xbc, 0x2f, 0x68, 0xd2, 0x5e, 0x0e, 0x30,
54+
},
55+
/* CN=EM EV TLS CA - G3B,O=eMudhra BV,C=NL */
56+
{
57+
0xe0, 0x25, 0x4c, 0x7f, 0xcd, 0xc2, 0x65, 0xe2, 0xf0, 0xf8, 0xa4, 0x28,
58+
0xda, 0xbb, 0x75, 0xab, 0x4a, 0xe0, 0x39, 0xb4, 0x87, 0x2d, 0x62, 0xf5,
59+
0xee, 0x57, 0xf0, 0x87, 0x7d, 0x5a, 0x8a, 0x41,
60+
},
61+
/* CN=EM DV TLS CA - G3B,O=eMudhra BV,C=NL */
62+
{
63+
0xeb, 0xed, 0x64, 0x75, 0xc2, 0xc2, 0xd8, 0x0f, 0xcb, 0x19, 0x9f, 0x3f,
64+
0xe8, 0xdd, 0x6e, 0x6b, 0x3c, 0xe6, 0x0b, 0x77, 0xeb, 0x15, 0xd4, 0xe4,
65+
0x2c, 0x3d, 0xec, 0x0c, 0x6f, 0x5b, 0x51, 0x05,
66+
},
67+
/* CN=EM OV CS CA - G3B,O=eMudhra BV,C=NL */
68+
{
69+
0xec, 0x4f, 0xfa, 0x1a, 0xd0, 0xac, 0x88, 0x08, 0x13, 0x14, 0xb7, 0xaa,
70+
0xff, 0xef, 0x6a, 0x91, 0xf9, 0x7c, 0xd5, 0x72, 0x91, 0xbb, 0x5b, 0xa6,
71+
0x86, 0xc5, 0x41, 0x5f, 0x5e, 0xb4, 0xf6, 0xb6,
72+
},
73+
};
74+
75+
#endif /* !Py_SSL_DISTRUSTED_SPKI_H */

‎PCbuild/_ssl.vcxproj‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@
9797
<AdditionalDependencies>ws2_32.lib;crypt32.lib;%(AdditionalDependencies)</AdditionalDependencies>
9898
</Link>
9999
</ItemDefinitionGroup>
100+
<ItemGroup>
101+
<ClInclude Include="..\Modules\_ssl\distrusted_spki.h" />
102+
</ItemGroup>
100103
<ItemGroup>
101104
<ClCompile Include="..\Modules\_ssl.c" />
102105
<ClCompile Include="$(opensslIncludeDir)\applink.c">

0 commit comments

Comments
 (0)