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