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
14 changes: 7 additions & 7 deletions .github/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@ run_matrix_for_current_compiler() {

# Build some tests with sanitizers
if [[ ${build_sanitizers} -ne 0 ]]; then
run_single --compiler "${compiler}" --build-dir build-asan --sanitizer asan --targets test_connection test_crypto --run-tests
run_single --compiler "${compiler}" --build-dir build-asan --sanitizer asan --targets test_connection test_crypto test_pki --run-tests
run_single --compiler "${compiler}" --build-dir build-asan --sanitizer asan \
--run-tests --tests test_connection:soak \
--phase test
run_single --compiler "${compiler}" --build-dir build-ubsan --sanitizer ubsan --targets test_connection test_crypto --run-tests
run_single --compiler "${compiler}" --build-dir build-ubsan --sanitizer ubsan --targets test_connection test_crypto test_pki --run-tests
if [[ ${CXX} == *clang* ]]; then
run_single --compiler "${compiler}" --build-dir build-tsan --sanitizer tsan --targets test_connection test_crypto --run-tests
run_single --compiler "${compiler}" --build-dir build-tsan --sanitizer tsan --targets test_connection test_crypto test_pki --run-tests
fi
fi

Expand All @@ -127,7 +127,7 @@ run_matrix_for_current_compiler() {
run_single --compiler "${compiler}" --build-dir build-tsan \
--sanitizer tsan \
--crypto libsodium --crypto25519 libsodium \
--targets test_connection test_crypto \
--targets test_connection test_crypto test_pki \
--run-tests
fi

Expand All @@ -151,14 +151,14 @@ run_matrix_for_current_compiler() {
fi

# Build binaries with reference ed25519/curve25519
run_single --compiler "${compiler}" --build-dir build-cmake-ref --build-type RelWithDebInfo --crypto25519 Reference --run-tests --tests test_crypto
run_single --compiler "${compiler}" --build-dir build-cmake-ref --build-type RelWithDebInfo --crypto25519 Reference --run-tests --tests test_crypto test_pki

# Build binaries with libsodium for ed25519/curve25519 only
run_single --compiler "${compiler}" --build-dir build-cmake-sodium25519 --build-type RelWithDebInfo --crypto25519 libsodium --run-tests --tests test_crypto
run_single --compiler "${compiler}" --build-dir build-cmake-sodium25519 --build-type RelWithDebInfo --crypto25519 libsodium --run-tests --tests test_crypto test_pki

# Build binaries with libsodium
if [[ ${build_libsodium} -ne 0 ]]; then
run_single --compiler "${compiler}" --build-dir build-cmake-sodium --build-type RelWithDebInfo --crypto libsodium --crypto25519 libsodium --run-tests --tests test_crypto
run_single --compiler "${compiler}" --build-dir build-cmake-sodium --build-type RelWithDebInfo --crypto libsodium --crypto25519 libsodium --run-tests --tests test_crypto test_pki
fi
}

Expand Down
2 changes: 1 addition & 1 deletion .github/run-single-config.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def compiler_env(compiler: str, base_env: dict[str, str]) -> dict[str, str]:

def parse_test_specs(specs: list[str] | None) -> list[list[str]]:
if specs is None:
specs = ["test_crypto", "test_connection:suite-quick"]
specs = ["test_crypto", "test_pki", "test_connection:suite-quick"]

commands: list[list[str]] = []
for spec in specs:
Expand Down
21 changes: 16 additions & 5 deletions .github/workflows/build-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,35 @@ jobs:
use_webrtc: false
crypto: default
crypto25519: default
targets: "test_connection test_p2p trivial_signaling_server publish_test_script test_crypto"
targets: "test_connection test_p2p trivial_signaling_server publish_test_script test_crypto test_pki"
- row: gcc-openssl-crypto
compiler: gcc
sanitizer: none
use_webrtc: false
crypto: default
crypto25519: default
targets: "test_connection test_p2p trivial_signaling_server publish_test_script test_crypto"
targets: "test_connection test_p2p trivial_signaling_server publish_test_script test_crypto test_pki"
- row: clang-libsodium
compiler: clang
sanitizer: none
use_webrtc: false
crypto: libsodium
crypto25519: libsodium
targets: "test_connection test_p2p trivial_signaling_server publish_test_script test_crypto"
targets: "test_connection test_p2p trivial_signaling_server publish_test_script test_crypto test_pki"
- row: clang-reference-25519
compiler: clang
sanitizer: none
use_webrtc: false
crypto: default
crypto25519: Reference
targets: "test_connection test_p2p trivial_signaling_server publish_test_script test_crypto"
targets: "test_connection test_p2p trivial_signaling_server publish_test_script test_crypto test_pki"
- row: clang-openssl-webrtc
compiler: clang
sanitizer: none
use_webrtc: true
crypto: default
crypto25519: default
targets: "test_connection test_p2p trivial_signaling_server publish_test_script test_crypto"
targets: "test_connection test_p2p trivial_signaling_server publish_test_script test_crypto test_pki"
env:
CI_BUILD: 1
IMAGE: ubuntu
Expand Down Expand Up @@ -100,6 +100,17 @@ jobs:
--run-tests \
--tests test_crypto

- name: Test pki (RelWithDebInfo)
run: |
set -euo pipefail
python3 .github/run-single-config.py \
--compiler "${{ matrix.compiler }}" \
--build-dir build \
--sanitizer "${{ matrix.sanitizer }}" \
--phase test \
--run-tests \
--tests test_pki

- name: Test connection (RelWithDebInfo)
run: |
set -euo pipefail
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/build-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ jobs:
run: test_crypto.exe
shell: cmd

- name: Test pki
working-directory: '${{ github.workspace }}/build/bin'
run: test_pki.exe
shell: cmd

- name: Test connection
working-directory: '${{ github.workspace }}/build/bin'
run: test_connection.exe suite-quick
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ jobs:
run: ./test_crypto
shell: bash

- name: Test pki
working-directory: ${{github.workspace}}/build/bin
run: ./test_pki
shell: bash

- name: Test connection
working-directory: ${{github.workspace}}/build/bin
run: ./test_connection suite-quick
Expand Down
28 changes: 28 additions & 0 deletions include/steam/steamnetworkingsockets.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,34 @@ STEAMNETWORKINGSOCKETS_INTERFACE bool GameNetworkingSockets_Init( const SteamNet
// Close all connections and listen sockets and free all resources
STEAMNETWORKINGSOCKETS_INTERFACE void GameNetworkingSockets_Kill();

/// Add a certificate to the trust store used to verify peer certificates.
/// Accepts a PEM-like blob ("STEAMDATAGRAM CERT") as emitted by the certificate
/// tool, or just the raw base64 body of one. Call it once per certificate:
/// install your root CA cert, plus any intermediate certs needed to complete
/// the chain to certs that peers will present. (Certs on the wire only carry
/// the ID of the CA key that signed them, not the chain itself, so the
/// verifying side must have the whole chain installed.) The trust store is
/// global to the process and shared by all interfaces. A self-signed cert
/// installed here becomes a trusted root, in addition to the hardcoded root
/// CA key, which remains trusted. Do not install certs bound to a particular
/// identity (e.g. a server's own cert) -- only CA certs go in the trust store.
///
/// Note that installing certs does not, by itself, *require* peers to
/// authenticate. To reject peers without a valid certificate, set
/// k_ESteamNetworkingConfig_IP_AllowWithoutAuth to 0. Also, a certificate
/// proves that the peer's identity was certified by a trusted CA, but for
/// connections initiated by IP address the library has no expected identity
/// to compare against -- check SteamNetConnectionInfo_t::m_identityRemote
/// after connecting if you need to know *which* certified peer you are
/// talking to.
STEAMNETWORKINGSOCKETS_INTERFACE bool GameNetworkingSockets_AddTrustedCert( const char *pszCert, SteamNetworkingErrMsg &errMsg );

/// Set the App ID used for certificate validation. Certificates are
/// authorized for particular App IDs, and both ends check certs against
/// their own App ID. If you use certificate authentication, call this
/// before SetCertificate, on clients as well as servers.
STEAMNETWORKINGSOCKETS_INTERFACE void GameNetworkingSockets_SetAppID( AppId_t nAppID );

/// Custom memory allocation methods. If you call this, you MUST call it exactly once,
/// before calling any other API function. *Most* allocations will pass through these,
/// especially all allocations that are per-connection. A few allocations
Expand Down
12 changes: 12 additions & 0 deletions src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2531,6 +2531,18 @@ STEAMNETWORKINGSOCKETS_INTERFACE void GameNetworkingSockets_Kill()
}
}

STEAMNETWORKINGSOCKETS_INTERFACE bool GameNetworkingSockets_AddTrustedCert( const char *pszCert, SteamNetworkingErrMsg &errMsg )
{
SteamNetworkingGlobalLock lock( "GameNetworkingSockets_AddTrustedCert" );
return CertStore_AddTrustedCertFromPEM( pszCert, errMsg );
}

STEAMNETWORKINGSOCKETS_INTERFACE void GameNetworkingSockets_SetAppID( AppId_t nAppID )
{
SteamNetworkingGlobalLock lock( "GameNetworkingSockets_SetAppID" );
( (CSteamNetworkingUtils *)SteamNetworkingUtils() )->SetAppID( nAppID );
}

STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingSockets *SteamNetworkingSockets_LibV12()
{
return s_pSteamNetworkingSockets;
Expand Down
55 changes: 46 additions & 9 deletions src/steamnetworkingsockets/steamnetworkingsockets_certstore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ struct Cert
CertAuthScope m_authScope;
time_t m_timeCreated;

// Was this cert explicitly installed by the app? (As opposed to being
// received over the network, etc.) Only app-installed certs may act as
// self-signed trusted roots when a hardcoded root CA key is in use.
bool m_bTrustedByApp = false;

bool Setup( const CMsgSteamDatagramCertificateSigned &msgCertSigned, CECSigningPublicKey &outPublicKey, SteamNetworkingErrMsg &errMsg )
{
m_signed_data = msgCertSigned.cert();
Expand Down Expand Up @@ -333,13 +338,13 @@ void CertStore_AddKeyRevocation( uint64 key_id )
s_bTrustValid = false;
}

bool CertStore_AddCertFromBase64( const char *pszBase64, SteamNetworkingErrMsg &errMsg )
static bool CertStore_AddCert( const char *pszBase64, size_t cchBase64, bool bTrustedByApp, SteamNetworkingErrMsg &errMsg )
{
CertStore_OneTimeInit();

// Decode
CMsgSteamDatagramCertificateSigned msgSignedCert;
if ( !ParseCertFromBase64( pszBase64, V_strlen( pszBase64 ), msgSignedCert, errMsg ) )
if ( !ParseCertFromBase64( pszBase64, cchBase64, msgSignedCert, errMsg ) )
return false;

CECSigningPublicKey publicKey;
Expand All @@ -348,6 +353,7 @@ bool CertStore_AddCertFromBase64( const char *pszBase64, SteamNetworkingErrMsg &
Cert cert;
if ( !cert.Setup( msgSignedCert, publicKey, errMsg ) )
return false;
cert.m_bTrustedByApp = bTrustedByApp;

uint64 key_id = CalculatePublicKeyID( publicKey );
PublicKey *pKey = FindPublicKey( key_id );
Expand All @@ -369,13 +375,21 @@ bool CertStore_AddCertFromBase64( const char *pszBase64, SteamNetworkingErrMsg &

// Check if we already have this exact cert,
// using the signature as as hash/fingerprint.
for ( const Cert &c: pKey->m_vecCerts )
for ( Cert &c: pKey->m_vecCerts )
{
if ( cert.m_signature == c.m_signature )
{
Assert( cert.m_signed_data == c.m_signed_data );
Assert( cert.m_ca_key_id == c.m_ca_key_id );
Assert( cert.m_timeCreated == c.m_timeCreated );

// Upgrade trust if the app is now vouching for a
// cert we had previously received some other way
if ( bTrustedByApp && !c.m_bTrustedByApp )
{
c.m_bTrustedByApp = true;
s_bTrustValid = false;
}
return true;
}
}
Expand All @@ -397,6 +411,25 @@ bool CertStore_AddCertFromBase64( const char *pszBase64, SteamNetworkingErrMsg &
return true;
}

bool CertStore_AddCertFromBase64( const char *pszBase64, SteamNetworkingErrMsg &errMsg )
{
return CertStore_AddCert( pszBase64, V_strlen( pszBase64 ), false, errMsg );
}

bool CertStore_AddTrustedCertFromPEM( const char *pszCert, SteamNetworkingErrMsg &errMsg )
{
// Locate the base64 body if this is a PEM-like blob. If it isn't,
// assume we were given the raw base64 body directly.
uint32 cchBody = (uint32)V_strlen( pszCert );
const char *pszBody = CCrypto::LocatePEMBody( pszCert, &cchBody, "STEAMDATAGRAM CERT" );
if ( !pszBody )
{
pszBody = pszCert;
cchBody = (uint32)V_strlen( pszCert );
}
return CertStore_AddCert( pszBody, cchBody, true, errMsg );
}

template< int kMaxSize = 1024 >
std::string V_sprintf_stdstring( const char *pszFmt, ... )
{
Expand Down Expand Up @@ -457,12 +490,16 @@ static void RecursiveEvaluateKeyTrust( PublicKey *pKey )
if ( pSignerKey == pKey )
{
#ifdef STEAMNETWORKINGSOCKETS_HARDCODED_ROOT_CA_KEY
// If hardcoded root cert is in use, only trust the
// one hardcoded root key. (We've already tagged it
// as trusted by hardcoded, so we don't get this far
// for those keys).
cert.m_status_msg = "Trusted root is hardcoded, cannot add more self-signed certs";
continue;
// If hardcoded root cert is in use, only trust self-signed
// certs that the app has explicitly installed. (We've
// already tagged the hardcoded key as trusted by hardcoded,
// so we don't get this far for that key.)
if ( !cert.m_bTrustedByApp )
{
cert.m_status_msg = "Trusted root is hardcoded, cannot add more self-signed certs";
continue;
}
cert.m_status_msg = "(Self-signed root, installed by app)";
#else
// Self signed is OK.
cert.m_status_msg = "(Self-signed root)";
Expand Down
7 changes: 7 additions & 0 deletions src/steamnetworkingsockets/steamnetworkingsockets_certstore.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ extern void CertStore_Reset();
/// only if there was a parse error. DOES NOT check for expiry or validate any signatures, etc.
extern bool CertStore_AddCertFromBase64( const char *pszBase64, SteamNetworkingErrMsg &errMsg );

/// Add a cert that the application has explicitly asked us to trust. Accepts either a
/// PEM-like blob or the raw base-64 body of one. Unlike certs added by
/// CertStore_AddCertFromBase64, if such a cert is self-signed, it is accepted as a trusted
/// root, even when a hardcoded root CA key is in use. (The hardcoded key remains trusted.)
/// Returns false only if there was a parse error.
extern bool CertStore_AddTrustedCertFromPEM( const char *pszCert, SteamNetworkingErrMsg &errMsg );

/// Adds a key revocation entry.
extern void CertStore_AddKeyRevocation( uint64 key_id );

Expand Down
10 changes: 10 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ add_test(NAME crypto COMMAND test_crypto WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPU
# Test data for the crypto test when the project is built
file(COPY aesgcmtestvectors DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})

add_executable(
test_pki
test_pki.cpp
)
set_target_common_gns_properties( test_pki )
target_include_directories(test_pki PRIVATE ../src ../src/public ../src/common ../include "${CMAKE_BINARY_DIR}/src")
target_link_libraries(test_pki GameNetworkingSockets::static)
add_sanitizers(test_pki)
add_test(NAME pki COMMAND test_pki WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})

# P2P test
if(ENABLE_ICE)
add_executable(
Expand Down
Loading