1 //====== Copyright Valve Corporation, All rights reserved. ====================
2 
3 // Ug, I didn't know ostream used exceptions.  Isn't there a decent
4 // stream implementation that won't bring in 10000000000 dependencies?
5 #ifdef _MSC_VER
6 #pragma warning( disable: 4530 )
7 #endif
8 
9 #include <time.h>
10 #include <ostream>
11 #include <memory>
12 #include <crypto.h>
13 #include <crypto_25519.h>
14 #include "steamnetworkingsockets_certstore.h"
15 #include <tier1/utlhashmap.h>
16 
17 #ifdef DBGFLAG_VALIDATE
18 	#include <tier0/validator_std.h>
19 #endif
20 
21 // Must be the last include
22 #include <tier0/memdbgon.h>
23 
24 #define MsgOrOK( s ) ( (s).empty() ? "OK" : (s).c_str() )
25 
26 namespace SteamNetworkingSocketsLib {
27 
28 template <typename T, T kInvalidItem >
SetIntersection(const CertAuthParameter<T,kInvalidItem> & a,const CertAuthParameter<T,kInvalidItem> & b)29 void CertAuthParameter<T,kInvalidItem>::SetIntersection( const CertAuthParameter<T,kInvalidItem> &a, const CertAuthParameter<T,kInvalidItem> &b )
30 {
31 	if ( a.IsAll() )
32 	{
33 		m_vecItems = b.m_vecItems;
34 		return;
35 	}
36 	if ( b.IsAll() )
37 	{
38 		m_vecItems = a.m_vecItems;
39 		return;
40 	}
41 	m_vecItems.clear();
42 	m_vecItems.reserve( std::min( a.m_vecItems.size(), b.m_vecItems.size() ) );
43 
44 	#define INC(x) ++it##x; if ( it##x == x.m_vecItems.end() ) break; Assert( *it##x > v##x );
45 
46 	// Scan lists in parallel, taking advantage of the fact that they should be sorted
47 	auto ita = a.m_vecItems.begin();
48 	auto itb = b.m_vecItems.begin();
49 	for (;;)
50 	{
51 		const T &va = *ita;
52 		const T &vb = *itb;
53 		if ( va < vb )
54 		{
55 			INC(a)
56 		}
57 		else if ( vb < va )
58 		{
59 			INC(b)
60 		}
61 		else
62 		{
63 			m_vecItems.push_back( va );
64 			INC(a)
65 			INC(b)
66 		}
67 	}
68 
69 	#undef INC
70 }
71 
72 template <typename T, T kInvalidItem >
Setup(const T * pItems,int n)73 void CertAuthParameter<T,kInvalidItem>::Setup( const T *pItems, int n )
74 {
75 	m_vecItems.assign( pItems, pItems+n);
76 
77 	// Sort, so that intersection can be computed efficiently
78 	std::sort( m_vecItems.begin(), m_vecItems.end() );
79 
80 	// Remove duplicates.  We assume both that duplicates are rare
81 	// and lists are small, so that O(n^2) is OK here.
82 	for ( int i = len(m_vecItems)-1 ; i > 1 ; --i )
83 	{
84 		if ( m_vecItems[i-1] == m_vecItems[i] )
85 			erase_at( m_vecItems, i );
86 	}
87 }
88 
89 template <typename T, T kInvalidItem >
Print(std::ostream & out,void (* ItemPrint)(std::ostream & out,const T & x)) const90 void CertAuthParameter<T,kInvalidItem>::Print( std::ostream &out, void (*ItemPrint)( std::ostream &out, const T &x ) ) const
91 {
92 	if ( IsEmpty() )
93 	{
94 		out << "(none)";
95 	}
96 	else if ( IsAll() )
97 	{
98 		out << "(any)";
99 	}
100 	else
101 	{
102 		for ( int i = 0 ; i < len( m_vecItems ) ; ++i )
103 		{
104 			if ( i > 0 )
105 				out << ',';
106 			(*ItemPrint)( out, m_vecItems[i] );
107 		}
108 	}
109 }
110 
Print(std::ostream & out,const char * pszIndent) const111 void CertAuthScope::Print( std::ostream &out, const char *pszIndent ) const
112 {
113 	auto PrintAppID = []( std::ostream &o, const AppId_t &x )
114 	{
115 		o << x;
116 	};
117 	auto PrintPOPID = []( std::ostream &o, const SteamNetworkingPOPID &x )
118 	{
119 		o << SteamNetworkingPOPIDRender( x ).c_str();
120 	};
121 
122 	out << pszIndent << "AppIDs . . : "; m_apps.Print( out, PrintAppID ); out << std::endl;
123 	out << pszIndent << "POPs . . . : "; m_pops.Print( out, PrintPOPID ); out << std::endl;
124 	out << pszIndent << "Expires. . : " << ctime( &m_timeExpiry );
125 }
126 
127 enum ETrust
128 {
129 	k_ETrust_Revoked = -3,
130 	k_ETrust_NotTrusted = -2,
131 	k_ETrust_UnknownWorking = -1,
132 	k_ETrust_Unknown = 0,
133 	k_ETrust_Trusted = 1,
134 	k_ETrust_Hardcoded = 2,
135 };
136 
137 // List of certs presented for this public key.
138 // We only actually ever use one, and it's in the first slot.
139 // But on some occasions we may have more than one cert for
140 // a key (e.g. key rotation)
141 struct Cert
142 {
143 	ETrust m_eTrust = k_ETrust_Unknown;
144 	std::string m_status_msg; // If it's not trusted, why?
145 	std::string m_signed_data;
146 	uint64 m_ca_key_id = 0;
147 	std::string m_signature;
148 	CertAuthScope m_authScope;
149 	time_t m_timeCreated;
150 
SetupSteamNetworkingSocketsLib::Cert151 	bool Setup( const CMsgSteamDatagramCertificateSigned &msgCertSigned, CECSigningPublicKey &outPublicKey, SteamNetworkingErrMsg &errMsg )
152 	{
153 		m_signed_data = msgCertSigned.cert();
154 		m_signature = msgCertSigned.ca_signature();
155 		m_ca_key_id = msgCertSigned.ca_key_id();
156 
157 		if ( m_signed_data.empty() )
158 		{
159 			V_strcpy_safe( errMsg, "No data" );
160 			return false;
161 		}
162 		if ( m_signature.length() != sizeof(CryptoSignature_t) )
163 		{
164 			V_strcpy_safe( errMsg, "Invalid signature length" );
165 			return false;
166 		}
167 
168 		CMsgSteamDatagramCertificate msgCert;
169 		if ( !msgCert.ParseFromString( m_signed_data ) )
170 		{
171 			V_strcpy_safe( errMsg, "Cert failed protobuf parse" );
172 			return false;
173 		}
174 
175 		// We don't store certs bound to a particular identity in the cert store
176 		if ( msgCert.has_identity_string() || msgCert.has_legacy_identity_binary() || msgCert.has_legacy_steam_id() )
177 		{
178 			V_strcpy_safe( errMsg, "Cert is bound to particular identity; doesn't go in the cert store" );
179 			return false;
180 		}
181 
182 		if ( msgCert.key_type() != CMsgSteamDatagramCertificate_EKeyType_ED25519 )
183 		{
184 			V_strcpy_safe( errMsg, "Only ED25519 public key supported" );
185 			return false;
186 		}
187 		if ( !outPublicKey.SetRawDataWithoutWipingInput( msgCert.key_data().c_str(), msgCert.key_data().length() ) )
188 		{
189 			V_strcpy_safe( errMsg, "Invalid public key" );
190 			return false;
191 		}
192 
193 		m_timeCreated = msgCert.time_created();
194 		m_authScope.m_timeExpiry = msgCert.time_expiry();
195 		if ( m_authScope.m_timeExpiry <= 0 )
196 		{
197 			V_strcpy_safe( errMsg, "Cert has no expiry" );
198 			return false;
199 		}
200 
201 		if ( msgCert.gameserver_datacenter_ids_size() > 0 )
202 		{
203 			m_authScope.m_pops.Setup( msgCert.gameserver_datacenter_ids().data(), msgCert.gameserver_datacenter_ids_size() );
204 		}
205 		else
206 		{
207 			m_authScope.m_pops.SetAll();
208 		}
209 
210 		if ( msgCert.app_ids_size() > 0 )
211 		{
212 			m_authScope.m_apps.Setup( msgCert.app_ids().data(), msgCert.app_ids_size() );
213 		}
214 		else
215 		{
216 			m_authScope.m_apps.SetAll();
217 		}
218 
219 		return true;
220 	}
221 
PrintSteamNetworkingSocketsLib::Cert222 	void Print( std::ostream &out, const char *pszIndent ) const
223 	{
224 		out << pszIndent << "Cert signed by CA " << (unsigned long long)m_ca_key_id << "  " << MsgOrOK(m_status_msg) << std::endl;
225 		out << pszIndent << "Created " << ctime( &m_timeCreated );
226 		m_authScope.Print( out, ( std::string( pszIndent ) + "  " ).c_str() );
227 	}
228 
229 };
230 
231 struct PublicKey
232 {
233 	ETrust m_eTrust = k_ETrust_Unknown;
234 	CECSigningPublicKey m_keyPublic;
235 	std::string m_status_msg; // If it's not trusted, why?
236 	std_vector<Cert> m_vecCerts;
237 	CertAuthScope m_effectiveAuthScope;
238 	int m_idxNewestValidCert = -1;
239 
CalculateKeyIDSteamNetworkingSocketsLib::PublicKey240 	uint64 CalculateKeyID() const { Assert( m_keyPublic.IsValid() ); return CalculatePublicKeyID( m_keyPublic ); }
241 
IsTrustedSteamNetworkingSocketsLib::PublicKey242 	inline bool IsTrusted() const
243 	{
244 		if ( m_eTrust >= k_ETrust_Trusted )
245 			return true;
246 		Assert( m_eTrust <= k_ETrust_NotTrusted );
247 		Assert( !m_status_msg.empty() ); // We should know the reason for any key we don't trust
248 		return false;
249 	}
250 
251 	#ifdef STEAMNETWORKINGSOCKETS_HARDCODED_ROOT_CA_KEY
SlamHardcodedRootCASteamNetworkingSocketsLib::PublicKey252 		void SlamHardcodedRootCA()
253 		{
254 			bool bOK = m_keyPublic.SetFromOpenSSHAuthorizedKeys( STEAMNETWORKINGSOCKETS_HARDCODED_ROOT_CA_KEY, sizeof(STEAMNETWORKINGSOCKETS_HARDCODED_ROOT_CA_KEY) );
255 			Assert( bOK );
256 			m_eTrust = k_ETrust_Hardcoded;
257 			m_effectiveAuthScope.SetAll();
258 		}
259 	#endif
260 
261 	#ifdef DBGFLAG_VALIDATE
ValidateSteamNetworkingSocketsLib::PublicKey262 		void Validate( CValidator &validator, const char *pchName ) const
263 		{
264 			ValidateRecursive( m_keyPublic );
265 			ValidateRecursive( m_status_msg );
266 			ValidateRecursive( m_vecCerts );
267 			ValidateRecursive( m_effectiveAuthScope );
268 		}
269 	#endif
270 };
271 static CUtlHashMap<uint64,std::unique_ptr<PublicKey>,std::equal_to<uint64>,std::hash<uint64> > s_mapPublicKeys;
272 static bool s_bTrustValid = false;
273 
FindPublicKey(uint64 nKeyID)274 static PublicKey *FindPublicKey( uint64 nKeyID )
275 {
276 	int idx = s_mapPublicKeys.Find( nKeyID );
277 	if ( idx == s_mapPublicKeys.InvalidIndex() )
278 		return nullptr;
279 	return s_mapPublicKeys[ idx ].get();
280 }
281 
CertStore_OneTimeInit()282 static void CertStore_OneTimeInit()
283 {
284 	#ifdef STEAMNETWORKINGSOCKETS_HARDCODED_ROOT_CA_KEY
285 		if ( s_mapPublicKeys.Count() == 0 )
286 		{
287 			PublicKey *pKey = new PublicKey;
288 			pKey->SlamHardcodedRootCA();
289 			uint64 nKeyID = pKey->CalculateKeyID();
290 
291 			// Make sure calculated ID matches what we expect!
292 			char checkID[64];
293 			V_sprintf_safe( checkID, "ID%llu", (unsigned long long)nKeyID );
294 			AssertFatal( V_stristr( STEAMNETWORKINGSOCKETS_HARDCODED_ROOT_CA_KEY, checkID ) != NULL );
295 
296 			s_mapPublicKeys.Insert( nKeyID, std::unique_ptr<PublicKey>( pKey ) );
297 		}
298 	#endif
299 }
300 
CertStore_Reset()301 void CertStore_Reset()
302 {
303 	s_mapPublicKeys.RemoveAll();
304 	s_bTrustValid = false;
305 }
306 
CertStore_AddKeyRevocation(uint64 key_id)307 void CertStore_AddKeyRevocation( uint64 key_id )
308 {
309 	PublicKey *pKey = FindPublicKey( key_id );
310 	if ( !pKey )
311 	{
312 		pKey = new PublicKey;
313 		pKey->m_eTrust = k_ETrust_Revoked;
314 		pKey->m_status_msg = "Revoked";
315 		s_mapPublicKeys.Insert( key_id, std::unique_ptr<PublicKey>( pKey ) );
316 		s_bTrustValid = false;
317 		return;
318 	}
319 
320 	if ( pKey->m_eTrust == k_ETrust_Revoked )
321 		return;
322 
323 	// What should we do if our hardcoded key ever shows up in a revocation list?
324 	// Probably just totally make all connections unable to connect, and force
325 	// people to update their software.  In reality it's probably a bad idea
326 	// for us to ever explicitly "revoke" root keys.  We should just remove them
327 	// from the dynamic list we are serving.
328 	AssertMsg( pKey->m_eTrust != k_ETrust_Hardcoded, "WARNING: Hardcoded trust key is in revocation list.  We won't be able to trust anything, ever!" );
329 	pKey->m_eTrust = k_ETrust_Revoked;
330 	pKey->m_status_msg = "Revoked";
331 
332 	s_bTrustValid = false;
333 }
334 
CertStore_AddCertFromBase64(const char * pszBase64,SteamNetworkingErrMsg & errMsg)335 bool CertStore_AddCertFromBase64( const char *pszBase64, SteamNetworkingErrMsg &errMsg )
336 {
337 	CertStore_OneTimeInit();
338 
339 	// Decode
340 	CMsgSteamDatagramCertificateSigned msgSignedCert;
341 	if ( !ParseCertFromBase64( pszBase64, V_strlen( pszBase64 ), msgSignedCert, errMsg ) )
342 		return false;
343 
344 	CECSigningPublicKey publicKey;
345 
346 	// Parse the basic properties of the cert without doing any auth checks
347 	Cert cert;
348 	if ( !cert.Setup( msgSignedCert, publicKey, errMsg ) )
349 		return false;
350 
351 	uint64 key_id = CalculatePublicKeyID( publicKey );
352 	PublicKey *pKey = FindPublicKey( key_id );
353 	if ( pKey )
354 	{
355 		if ( pKey->m_keyPublic != publicKey )
356 		{
357 			if ( pKey->m_keyPublic.IsValid() )
358 			{
359 				V_sprintf_safe( errMsg, "Key collision on key ID %lld!?  Almost certainly a bug.", (unsigned long long)key_id );
360 				AssertMsg1( false, "%s", errMsg );
361 				return false;
362 			}
363 
364 			// No key data, it was probably revoked.  We can continue on
365 			Assert( pKey->m_eTrust == k_ETrust_Revoked );
366 			pKey->m_keyPublic.CopyFrom( publicKey );
367 		}
368 
369 		// Check if we already have this exact cert,
370 		// using the signature as as hash/fingerprint.
371 		for ( const Cert &c: pKey->m_vecCerts )
372 		{
373 			if ( cert.m_signature == c.m_signature )
374 			{
375 				Assert( cert.m_signed_data == c.m_signed_data );
376 				Assert( cert.m_ca_key_id == c.m_ca_key_id );
377 				Assert( cert.m_timeCreated == c.m_timeCreated );
378 				return true;
379 			}
380 		}
381 	}
382 	else
383 	{
384 		pKey = new PublicKey;
385 		pKey->m_keyPublic.CopyFrom( publicKey );
386 		s_mapPublicKeys.Insert( key_id, std::unique_ptr<PublicKey>( pKey ) );
387 	}
388 
389 	// Add the cert
390 	pKey->m_vecCerts.emplace_back( std::move( cert ) );
391 
392 	// Invalidate trust, recompute it next time we ask for it
393 	s_bTrustValid = false;
394 
395 	// OK
396 	return true;
397 }
398 
399 template< int kMaxSize = 1024  >
V_sprintf_stdstring(const char * pszFmt,...)400 std::string V_sprintf_stdstring( const char *pszFmt, ... )
401 {
402 	char temp[kMaxSize];
403 	va_list ap;
404 	va_start( ap, pszFmt );
405 	V_vsprintf_safe( temp, pszFmt, ap );
406 	va_end( ap );
407 	return std::string( temp );
408 }
409 
RecursiveEvaluateKeyTrust(PublicKey * pKey)410 static void RecursiveEvaluateKeyTrust( PublicKey *pKey )
411 {
412 
413 	// Make sure we didn't already make a definitive determination
414 	if ( pKey->m_eTrust != k_ETrust_Unknown )
415 	{
416 		Assert( pKey->m_eTrust != k_ETrust_UnknownWorking );
417 		return;
418 	}
419 
420 	// Mark key as "working on it" so we can detect loops
421 	pKey->m_eTrust = k_ETrust_UnknownWorking;
422 	pKey->m_idxNewestValidCert = -1;
423 
424 	// No certs?  How did we get here?
425 	if ( pKey->m_vecCerts.empty() )
426 	{
427 		Assert( false );
428 		pKey->m_eTrust = k_ETrust_NotTrusted;
429 		pKey->m_status_msg = "No certs?";
430 		return;
431 	}
432 
433 	// Scan all certs, looking for the newest one that is valid
434 	for ( int i = 0 ; i < len( pKey->m_vecCerts ) ; ++i )
435 	{
436 		Cert &cert = pKey->m_vecCerts[ i ];
437 		Assert( !cert.m_signed_data.empty() );
438 		Assert( cert.m_signature.length() == sizeof(CryptoSignature_t) );
439 
440 		// Cert with empty auth scope shouldn't parse
441 		Assert( !cert.m_authScope.IsEmpty() );
442 
443 		// Assume failure
444 		cert.m_eTrust = k_ETrust_NotTrusted;
445 		cert.m_status_msg.clear();
446 
447 		// Locate the public key that they are claiming signed this
448 		PublicKey *pSignerKey = FindPublicKey( cert.m_ca_key_id );
449 		if ( pSignerKey == nullptr )
450 		{
451 			cert.m_status_msg = V_sprintf_stdstring( "CA key %llu is not known", (unsigned long long)cert.m_ca_key_id );
452 			continue;
453 		}
454 
455 		// Self-signed (root cert)?
456 		if ( pSignerKey == pKey )
457 		{
458 			#ifdef STEAMNETWORKINGSOCKETS_HARDCODED_ROOT_CA_KEY
459 				// If hardcoded root cert is in use, only trust the
460 				// one hardcoded root key.  (We've already tagged it
461 				// as trusted by hardcoded, so we don't get this far
462 				// for those keys).
463 				cert.m_status_msg = "Trusted root is hardcoded, cannot add more self-signed certs";
464 				continue;
465 			#else
466 				// Self signed is OK.
467 				cert.m_status_msg = "(Self-signed root)";
468 			#endif
469 		}
470 		else
471 		{
472 
473 			// Recursively check that the other key is trusted.
474 			// Protect against a cycle
475 			if ( pSignerKey->m_eTrust == k_ETrust_UnknownWorking )
476 			{
477 				cert.m_status_msg = V_sprintf_stdstring( "Cycle detected in trust chain!  (Cert for key %llu, signed by CA key %llu)", pKey->CalculateKeyID(), (unsigned long long)cert.m_ca_key_id );
478 				continue;
479 			}
480 
481 			RecursiveEvaluateKeyTrust( pSignerKey );
482 			Assert( pSignerKey->m_eTrust != k_ETrust_UnknownWorking ); // Should have made a determination!
483 
484 			// Not trusted?
485 			if ( !pSignerKey->IsTrusted() )
486 			{
487 				cert.m_status_msg = V_sprintf_stdstring( "CA key %llu not trusted.  ", (unsigned long long)cert.m_ca_key_id ) + pSignerKey->m_status_msg.c_str();
488 				continue;
489 			}
490 
491 			cert.m_status_msg.clear();
492 		}
493 
494 		// If we get here, we trust the signing CA's public key
495 		// Check signature.  For self-signed certs this is just
496 		// basically busywork, but it's a nice double-check.)
497 		if ( !pSignerKey->m_keyPublic.VerifySignature( cert.m_signed_data.c_str(), cert.m_signed_data.length(), *(const CryptoSignature_t *)cert.m_signature.c_str() ) )
498 		{
499 			cert.m_status_msg = V_sprintf_stdstring( "Failed signature verification (against CA key %llu)", (unsigned long long)cert.m_ca_key_id );
500 			continue;
501 		}
502 
503 		// Calculate effective auth scope, make sure it isn't empty
504 		CertAuthScope authScope;
505 		if ( pSignerKey == pKey )
506 		{
507 			authScope = cert.m_authScope;
508 		}
509 		else
510 		{
511 			authScope.SetIntersection( pSignerKey->m_effectiveAuthScope, cert.m_authScope );
512 		}
513 
514 		if ( authScope.m_apps.IsEmpty() )
515 		{
516 			cert.m_status_msg = V_sprintf_stdstring( "All apps excluded by auth chain!" );
517 			continue;
518 		}
519 		if ( authScope.m_pops.IsEmpty() )
520 		{
521 			cert.m_status_msg = V_sprintf_stdstring( "All pops excluded by auth chain!" );
522 			continue;
523 		}
524 		Assert( authScope.m_timeExpiry > 0 );
525 
526 		// OK, we're trusted.  Is this the best cert so far?
527 		if ( pKey->m_idxNewestValidCert < 0 || pKey->m_vecCerts[ pKey->m_idxNewestValidCert ].m_timeCreated < cert.m_timeCreated )
528 		{
529 			if ( pSignerKey == pKey )
530 			{
531 				pKey->m_effectiveAuthScope = cert.m_authScope;
532 			}
533 			else
534 			{
535 				pKey->m_effectiveAuthScope = std::move( authScope );
536 			}
537 			pKey->m_idxNewestValidCert = i;
538 		}
539 	}
540 
541 	// Did we find a valid cert?
542 	if ( pKey->m_idxNewestValidCert < 0 )
543 	{
544 		pKey->m_eTrust = k_ETrust_NotTrusted;
545 		pKey->m_effectiveAuthScope.SetEmpty();
546 		const std::string &sFirstCertMsg = pKey->m_vecCerts[0].m_status_msg;
547 		Assert( !sFirstCertMsg.empty() );
548 		if ( pKey->m_vecCerts.size() == 1 )
549 		{
550 			pKey->m_status_msg = sFirstCertMsg;
551 		}
552 		else
553 		{
554 			pKey->m_status_msg = V_sprintf_stdstring( "None of %d certs trusted.  (E.g.: ", len( pKey->m_vecCerts ) )  + sFirstCertMsg + ")";
555 		}
556 		return;
557 	}
558 
559 	// Trusted!
560 	pKey->m_eTrust = k_ETrust_Trusted;
561 	pKey->m_status_msg.clear();
562 	Assert( !pKey->m_effectiveAuthScope.IsEmpty() );
563 }
564 
CertStore_EnsureTrustValid()565 static void CertStore_EnsureTrustValid()
566 {
567 	CertStore_OneTimeInit();
568 	if ( s_bTrustValid )
569 		return;
570 
571 	// Mark everything not in a "terminal" state as unknown
572 	for ( const std::unique_ptr<PublicKey> &pKey: s_mapPublicKeys.IterValues() )
573 	{
574 		if ( pKey->m_eTrust != k_ETrust_Revoked && pKey->m_eTrust != k_ETrust_Hardcoded )
575 			pKey->m_eTrust = k_ETrust_Unknown;
576 	}
577 
578 	// Now scan all keys, and recursively calculate their trust
579 	for ( const std::unique_ptr<PublicKey> &pKey: s_mapPublicKeys.IterValues() )
580 	{
581 		RecursiveEvaluateKeyTrust( pKey.get() );
582 	}
583 
584 	// Mark trust as having been calculated
585 	s_bTrustValid = true;
586 }
587 
CertStore_CheckCASignature(const std::string & signed_data,uint64 nCAKeyID,const std::string & signature,time_t timeNow,SteamNetworkingErrMsg & errMsg)588 const CertAuthScope *CertStore_CheckCASignature( const std::string &signed_data, uint64 nCAKeyID, const std::string &signature, time_t timeNow, SteamNetworkingErrMsg &errMsg )
589 {
590 	CertStore_EnsureTrustValid();
591 
592 	// Make sure they actually presented any data
593 	if ( signed_data.empty() )
594 	{
595 		V_strcpy_safe( errMsg, "No signed data" );
596 		return nullptr;
597 	}
598 
599 	// Check that signature appears valid.
600 	if ( signature.empty() )
601 	{
602 		V_strcpy_safe( errMsg, "No signature" );
603 		return nullptr;
604 	}
605 
606 	// Locate the cert
607 	if ( nCAKeyID == 0 )
608 	{
609 		V_strcpy_safe( errMsg, "Missing CA Key ID" );
610 		return nullptr;
611 	}
612 	PublicKey *pKey = FindPublicKey( nCAKeyID );
613 	if ( pKey == nullptr )
614 	{
615 		V_sprintf_safe( errMsg, "CA key %llu is not known to us", (unsigned long long)nCAKeyID );
616 		return nullptr;
617 	}
618 
619 	// Check the status of the cert
620 	Assert( pKey->m_eTrust != k_ETrust_UnknownWorking && pKey->m_eTrust != k_ETrust_Unknown );
621 	if ( pKey->m_eTrust < k_ETrust_Trusted )
622 	{
623 		V_sprintf_safe( errMsg, "CA key %llu is not trusted.  %s", (unsigned long long)nCAKeyID, pKey->m_status_msg.c_str() );
624 		return nullptr;
625 	}
626 
627 	// Is any part of the chain expired?
628 	if ( pKey->m_effectiveAuthScope.m_timeExpiry < timeNow )
629 	{
630 		V_sprintf_safe( errMsg, "CA key %llu (or an antecedent) expired %lld seconds ago!", (unsigned long long)nCAKeyID, (long long)( timeNow - pKey->m_effectiveAuthScope.m_timeExpiry ) );
631 		return nullptr;
632 	}
633 
634 	// We only support one crypto method right now.
635 	if ( signature.length() != sizeof(CryptoSignature_t) )
636 	{
637 		V_strcpy_safe( errMsg, "Signature has invalid length" );
638 		return nullptr;
639 	}
640 
641 	// Do the crypto work to check the signature
642 	if ( !pKey->m_keyPublic.VerifySignature( signed_data.c_str(), signed_data.length(), *(const CryptoSignature_t *)signature.c_str() ) )
643 	{
644 		V_strcpy_safe( errMsg, "Signature verification failed" );
645 		return nullptr;
646 	}
647 
648 	return &pKey->m_effectiveAuthScope;
649 }
650 
CertStore_CheckCert(const CMsgSteamDatagramCertificateSigned & msgCertSigned,CMsgSteamDatagramCertificate & outMsgCert,time_t timeNow,SteamNetworkingErrMsg & errMsg)651 const CertAuthScope *CertStore_CheckCert( const CMsgSteamDatagramCertificateSigned &msgCertSigned, CMsgSteamDatagramCertificate &outMsgCert, time_t timeNow, SteamNetworkingErrMsg &errMsg )
652 {
653 	const CertAuthScope *pResult = CertStore_CheckCASignature( msgCertSigned.cert(), msgCertSigned.ca_key_id(), msgCertSigned.ca_signature(), timeNow, errMsg );
654 	if ( !pResult )
655 		return nullptr;
656 	if ( !outMsgCert.ParseFromString( msgCertSigned.cert() ) )
657 	{
658 		V_strcpy_safe( errMsg, "Cert failed protobuf parse" );
659 		return nullptr;
660 	}
661 
662 	// Check expiry
663 	if ( (time_t)outMsgCert.time_expiry() < timeNow )
664 	{
665 		V_sprintf_safe( errMsg, "Cert expired %lld seconds ago", (long long)( timeNow - outMsgCert.time_expiry() ) );
666 		return nullptr;
667 	}
668 
669 	// Check if their key has specifically been revoked.
670 	if ( outMsgCert.key_type() != CMsgSteamDatagramCertificate_EKeyType_ED25519 )
671 	{
672 		V_sprintf_safe( errMsg, "Cert has invalid key type %d", (int)outMsgCert.key_type() );
673 		return nullptr;
674 	}
675 	uint64 nKeyID = CalculatePublicKeyID_Ed25519( outMsgCert.key_data().c_str(), outMsgCert.key_data().length() );
676 	if ( nKeyID == 0)
677 	{
678 		V_sprintf_safe( errMsg, "Cert has invalid public key" );
679 		return nullptr;
680 	}
681 	const PublicKey *pPubKey = FindPublicKey( nKeyID );
682 	if ( pPubKey )
683 	{
684 		if ( pPubKey->m_eTrust == k_ETrust_NotTrusted )
685 		{
686 			// Hm - this status doesn't mean "bad", it just means that the cert in the cert store
687 			// with this key was not able to be verified.  This is an an unusual situation, ordinarily
688 			// we should not have any certs in the cert store that we are not able to trust.  Still, we
689 			// just specific ally verified trust above.  So let's continue on, but without adding this
690 			// to the cert store.
691 		}
692 		else if ( !pPubKey->IsTrusted() )
693 		{
694 			// Key is revoked.
695 			Assert( pPubKey->m_eTrust == k_ETrust_Revoked );
696 			V_sprintf_safe( errMsg, "Cert has untrusted public key.  %s", pPubKey->m_status_msg.c_str() );
697 			return nullptr;
698 		}
699 	}
700 
701 	return pResult;
702 }
703 
CheckCertAppID(const CMsgSteamDatagramCertificate & msgCert,const CertAuthScope * pCACertAuthScope,AppId_t nAppID,SteamNetworkingErrMsg & errMsg)704 bool CheckCertAppID( const CMsgSteamDatagramCertificate &msgCert, const CertAuthScope *pCACertAuthScope, AppId_t nAppID, SteamNetworkingErrMsg &errMsg )
705 {
706 
707 	// Not bound to specific AppIDs?
708 	if ( msgCert.app_ids_size() == 0 )
709 	{
710 		if ( !pCACertAuthScope || pCACertAuthScope->m_apps.HasItem( nAppID ) )
711 			return true;
712 		V_sprintf_safe( errMsg, "Cert is not restricted by appid, but CA trust chain is, and does not authorize %u", nAppID );
713 		return true;
714 	}
715 
716 	// Search cert for the one they are trying
717 	for ( AppId_t nCertAppID: msgCert.app_ids() )
718 	{
719 		if ( nCertAppID == nAppID )
720 		{
721 			if ( !pCACertAuthScope || pCACertAuthScope->m_apps.HasItem( nAppID ) )
722 				return true;
723 			V_sprintf_safe( errMsg, "Cert allows appid %u, but CA trust chain does not", nAppID );
724 			return false;
725 		}
726 	}
727 
728 	// No good
729 	if ( msgCert.app_ids_size() == 1 )
730 	{
731 		V_sprintf_safe( errMsg, "Cert is not authorized for appid %u, only %u", nAppID, msgCert.app_ids(0) );
732 	}
733 	else
734 	{
735 		V_sprintf_safe( errMsg, "Cert is not authorized for appid %u, only %u (and %d more)", nAppID, msgCert.app_ids(0), msgCert.app_ids_size()-1 );
736 	}
737 	return false;
738 }
739 
CheckCertPOPID(const CMsgSteamDatagramCertificate & msgCert,const CertAuthScope * pCACertAuthScope,SteamNetworkingPOPID popID,SteamNetworkingErrMsg & errMsg)740 bool CheckCertPOPID( const CMsgSteamDatagramCertificate &msgCert, const CertAuthScope *pCACertAuthScope, SteamNetworkingPOPID popID, SteamNetworkingErrMsg &errMsg )
741 {
742 
743 	// Not bound to specific PopIDs?
744 	if ( msgCert.gameserver_datacenter_ids_size() == 0 )
745 	{
746 		if ( !pCACertAuthScope || pCACertAuthScope->m_pops.HasItem( popID ) )
747 			return true;
748 		V_sprintf_safe( errMsg, "Cert is not restricted by POPID, but CA trust chain is, and does not authorize %s", SteamNetworkingPOPIDRender( popID ).c_str() );
749 		return true;
750 	}
751 
752 	// Search cert for the one they are trying
753 	for ( SteamNetworkingPOPID certPOPID: msgCert.gameserver_datacenter_ids() )
754 	{
755 		if ( certPOPID == popID )
756 		{
757 			if ( !pCACertAuthScope || pCACertAuthScope->m_pops.HasItem( popID ) )
758 				return true;
759 			V_sprintf_safe( errMsg, "Cert allows POPID %s, but CA trust chain does not", SteamNetworkingPOPIDRender( popID ).c_str() );
760 			return false;
761 		}
762 	}
763 
764 	// No good
765 	SteamNetworkingPOPIDRender firstAuthorizedPopID( msgCert.gameserver_datacenter_ids(0) );
766 	if ( msgCert.app_ids_size() == 1 )
767 	{
768 		V_sprintf_safe( errMsg, "Cert is not authorized for POPID %s, only %s", SteamNetworkingPOPIDRender( popID ).c_str(), firstAuthorizedPopID.c_str() );
769 	}
770 	else
771 	{
772 		V_sprintf_safe( errMsg, "Cert is not authorized for POPID %s, only %s (and %d more)", SteamNetworkingPOPIDRender( popID ).c_str(), firstAuthorizedPopID.c_str(), msgCert.gameserver_datacenter_ids_size()-1 );
773 	}
774 	return false;
775 }
776 
CertStore_Check()777 void CertStore_Check()
778 {
779 	CertStore_EnsureTrustValid();
780 
781 	for ( auto item: s_mapPublicKeys.IterItems() )
782 	{
783 		const std::unique_ptr<PublicKey> &pKey = item.Element();
784 		AssertMsg2( pKey->IsTrusted() || pKey->m_eTrust == k_ETrust_Revoked, "Key %llu not trusted: %s", (unsigned long long)item.Key(), pKey->m_status_msg.c_str() );
785 	}
786 }
787 
CertStore_Print(std::ostream & out)788 void CertStore_Print( std::ostream &out )
789 {
790 	CertStore_EnsureTrustValid();
791 
792 	for ( auto item: s_mapPublicKeys.IterItems() )
793 	{
794 		const std::unique_ptr<PublicKey> &pKey = item.Element();
795 		out << "Public key " << (unsigned long long)item.Key() << " " << MsgOrOK(pKey->m_status_msg) << std::endl;
796 		pKey->m_effectiveAuthScope.Print( out, "  " );
797 		if ( pKey->m_idxNewestValidCert >= 0 )
798 		{
799 			pKey->m_vecCerts[ pKey->m_idxNewestValidCert ].Print( out, "  " );
800 		}
801 		else if ( !pKey->m_vecCerts.empty() )
802 		{
803 			for ( const Cert &c: pKey->m_vecCerts )
804 			{
805 				c.Print( out, "  " );
806 			}
807 		}
808 		else
809 		{
810 			out << "  (No valid certs)" << std::endl;
811 		}
812 	}
813 }
814 
815 #ifdef DBGFLAG_VALIDATE
CertStore_ValidateStatics(CValidator & validator)816 void CertStore_ValidateStatics( CValidator &validator )
817 {
818 	ValidateRecursive( s_mapPublicKeys );
819 }
820 #endif
821 
822 } // namespace SteamNetworkingSocketsLib
823