Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
This file describes changes in the curlInterface package.

Next
- Fix inverted `verifyCert` check which disabled TLS certificate
verification by default

2.4.4 (2026-07-19)
- Remove dependency on GAPDoc
- Prefer a Homebrew libcurl installation on macOS when available
Expand Down
2 changes: 1 addition & 1 deletion src/curl.c
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ Obj FuncCURL_REQUEST(Obj self, Obj input_list)
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, typebuf);
}

if (verifyCert == True) {
if (verifyCert == False) {
//
// If you want to connect to a site who isn't using a certificate
// that is signed by one of the certs in the CA bundle you have,
Expand Down
125 changes: 125 additions & 0 deletions tst/https-server.g
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#############################################################################
##
## An HTTPS server with a self-signed certificate for the curlInterface
## tests, used to check that certificates really are verified unless
## `verifyCert` is false. Implemented via `openssl s_server`; if openssl is
## missing, the tests using it are skipped.
##

BindGlobal( "CURLINTERFACE_OpenSSL", function()
local openssl;

openssl := Filename( DirectoriesSystemPrograms(), "openssl" );
if openssl = fail or not IsExecutableFile( openssl ) then
return fail;
fi;
return openssl;
end );

# Bind port 0 to have the kernel pick a free port, then release it again.
BindGlobal( "CURLINTERFACE_FreePort", function()
local listener, address, port;

listener := IO_socket( IO.PF_INET, IO.SOCK_STREAM, "tcp" );
if listener = fail then
return fail;
fi;
if IO_bind( listener, IO_MakeIPAddressPort( "127.0.0.1", 0 ) ) = fail then
IO_close( listener );
return fail;
fi;
address := IO_getsockname( listener );
port := 256 * INT_CHAR( address[3] ) + INT_CHAR( address[4] );
IO_close( listener );
return port;
end );

# Waits for the server to accept requests. Tries both settings of
# `verifyCert`, so that this succeeds no matter which of them rejects a
# self-signed certificate.
BindGlobal( "CURLINTERFACE_WaitForHTTPSServer", function( url )
local i;

for i in [ 1 .. 10 ] do
if DownloadURL( url, rec( verifyCert := false ) ).success or
DownloadURL( url ).success then
return true;
fi;
Sleep( 1 );
od;
return false;
end );

# Returns a record with components `pid` and `url`, or `fail` if no HTTPS
# server could be started.
BindGlobal( "CURLINTERFACE_StartHTTPSTestServer", function()
local openssl, sh, dir, cert, key, port, url, pid, devnull;

openssl := CURLINTERFACE_OpenSSL();
sh := Filename( DirectoriesSystemPrograms(), "sh" );
if openssl = fail or sh = fail then
return fail;
fi;

# LibreSSL's `req` has no `-quiet`, so silence it via the shell
dir := DirectoryTemporary();
cert := Filename( dir, "cert.pem" );
key := Filename( dir, "key.pem" );
if Process( dir, sh, InputTextNone(), OutputTextNone(),
[ "-c", Concatenation(
"'", openssl, "' req -x509 -newkey rsa:2048 -nodes",
" -keyout '", key, "' -out '", cert, "'",
" -days 1 -subj /CN=localhost 2>/dev/null" ) ] ) <> 0 then
return fail;
fi;

port := CURLINTERFACE_FreePort();
if port = fail then
return fail;
fi;
url := Concatenation( "https://localhost:", String( port ), "/" );

pid := IO_fork();
if pid = 0 then
devnull := IO_open( "/dev/null", IO.O_WRONLY, 0 );
if devnull <> fail then
IO_dup2( devnull, 1 );
IO_dup2( devnull, 2 );
fi;
IO_execv( openssl,
[ "s_server", "-quiet", "-www", "-accept", String( port ),
"-cert", cert, "-key", key ] );
IO_exit( 1 );
elif pid < 0 then
return fail;
fi;

if CURLINTERFACE_WaitForHTTPSServer( url ) <> true then
IO_kill( pid, IO.SIGTERM );
IO_WaitPid( pid, true );
return fail;
fi;

return rec( pid := pid, url := url );
end );

BindGlobal( "CURLINTERFACE_StopHTTPSTestServer", function( server )
if server <> fail then
IO_kill( server.pid, IO.SIGTERM );
IO_WaitPid( server.pid, true );
fi;
end );

# Downloads from the test server once with the default options and once with
# `verifyCert := false`, and returns the two `success` values.
BindGlobal( "CURLINTERFACE_VerifyCertResults", function( server )
if server = fail then
if CURLINTERFACE_OpenSSL() = fail then
# skip the test on systems without openssl
return [ false, true ];
fi;
return "could not start the HTTPS test server";
fi;
return [ DownloadURL( server.url ).success,
DownloadURL( server.url, rec( verifyCert := false ) ).success ];
end );
13 changes: 13 additions & 0 deletions tst/https.tst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#@local server
gap> LoadPackage( "curlInterface", false );
true
gap> LoadPackage( "io", false );
true
gap> ReadPackage( "curlInterface", "tst/https-server.g" );;
gap> server := CURLINTERFACE_StartHTTPSTestServer();;

# A self-signed certificate is rejected by default, and accepted only if
# verification is turned off explicitly
gap> CURLINTERFACE_VerifyCertResults( server );
[ false, true ]
gap> CURLINTERFACE_StopHTTPSTestServer( server );
Loading