1 //====== Copyright Valve Corporation, All rights reserved. ====================
2
3 #include <crypto.h>
4 #include <crypto_25519.h>
5 #include "steamnetworkingsockets_internal.h"
6
7 // Must be the last include
8 #include <tier0/memdbgon.h>
9
10 namespace SteamNetworkingSocketsLib {
11
CalculatePublicKeyID_Ed25519(const void * pPubKey,size_t cbPubKey)12 uint64 CalculatePublicKeyID_Ed25519( const void *pPubKey, size_t cbPubKey )
13 {
14 if ( cbPubKey != 32 )
15 return 0;
16
17 SHA256Digest_t digest;
18 CCrypto::GenerateSHA256Digest( pPubKey, cbPubKey, &digest );
19
20 // First 8 bytes
21 return LittleQWord( *(uint64*)&digest );
22 }
23
CalculatePublicKeyID(const CECSigningPublicKey & pubKey)24 uint64 CalculatePublicKeyID( const CECSigningPublicKey &pubKey )
25 {
26 if ( !pubKey.IsValid() )
27 return 0;
28
29 // SHA over the whole public key.
30 uint8 data[32];
31 uint32 cbPubKey = pubKey.GetRawData( data );
32 Assert( cbPubKey == sizeof(data) );
33 return CalculatePublicKeyID_Ed25519( data, cbPubKey );
34 }
35
36 // Returns:
37 // -1 Bogus data
38 // 0 Unknown type
39 // 1 OK
SteamNetworkingIdentityFromLegacyBinaryProtobufMsg(SteamNetworkingIdentity & identity,const CMsgSteamNetworkingIdentityLegacyBinary & msgIdentity,SteamDatagramErrMsg & errMsg)40 static int SteamNetworkingIdentityFromLegacyBinaryProtobufMsg( SteamNetworkingIdentity &identity, const CMsgSteamNetworkingIdentityLegacyBinary &msgIdentity, SteamDatagramErrMsg &errMsg )
41 {
42 if ( msgIdentity.has_steam_id() )
43 {
44 if ( !IsValidSteamIDForIdentity( msgIdentity.steam_id() ) )
45 {
46 V_sprintf_safe( errMsg, "Invalid SteamID %llu", (unsigned long long)msgIdentity.steam_id() );
47 return -1;
48 }
49 identity.SetSteamID64( msgIdentity.steam_id() );
50 return 1;
51 }
52 if ( msgIdentity.has_generic_string() )
53 {
54 if ( !identity.SetGenericString( msgIdentity.generic_string().c_str() ) )
55 {
56 V_sprintf_safe( errMsg, "Invalid generic string '%s'", msgIdentity.generic_string().c_str() );
57 return -1;
58 }
59 return 1;
60 }
61 if ( msgIdentity.has_generic_bytes() )
62 {
63 if ( !identity.SetGenericBytes( msgIdentity.generic_bytes().c_str(), msgIdentity.generic_bytes().length() ) )
64 {
65 V_sprintf_safe( errMsg, "Invalid generic bytes (len=%d)", len( msgIdentity.generic_bytes() ) );
66 return -1;
67 }
68 return 1;
69 }
70 if ( msgIdentity.has_ipv6_and_port() )
71 {
72 const std::string &ip_and_port = msgIdentity.ipv6_and_port();
73 COMPILE_TIME_ASSERT( sizeof( identity.m_ip ) == 18 ); // 16-byte IPv6 + 2-byte port
74 if ( ip_and_port.length() != 18 )
75 {
76 V_sprintf_safe( errMsg, "ip_and_port field has invalid length %d", len( ip_and_port ) );
77 return -1;
78 }
79 const uint8 *b = (const uint8 *)msgIdentity.ipv6_and_port().c_str();
80 SteamNetworkingIPAddr tmpAddr;
81 tmpAddr.SetIPv6( b, BigWord( *(uint16*)(b+16) ) );
82 identity.SetIPAddr( tmpAddr );
83 return 1;
84 }
85
86 // Unknown type
87 return 0;
88 }
89
BSteamNetworkingIdentityFromLegacyBinaryProtobuf(SteamNetworkingIdentity & identity,const CMsgSteamNetworkingIdentityLegacyBinary & msgIdentity,SteamDatagramErrMsg & errMsg)90 bool BSteamNetworkingIdentityFromLegacyBinaryProtobuf( SteamNetworkingIdentity &identity, const CMsgSteamNetworkingIdentityLegacyBinary &msgIdentity, SteamDatagramErrMsg &errMsg )
91 {
92 // Parse it
93 int r = SteamNetworkingIdentityFromLegacyBinaryProtobufMsg( identity, msgIdentity, errMsg );
94 if ( r > 0 )
95 return true;
96 if ( r < 0 )
97 {
98 identity.Clear();
99 return false;
100 }
101
102 if ( msgIdentity.unknown_fields().field_count() > 0 )
103 {
104 V_sprintf_safe( errMsg, "Unrecognized identity format. (%d unknown field(s), first ID=%d)", msgIdentity.unknown_fields().field_count(), msgIdentity.unknown_fields().field(0).number() );
105 }
106 else if ( ProtoMsgByteSize( msgIdentity ) == 0 )
107 {
108 V_strcpy_safe( errMsg, "Empty identity msg" );
109 }
110 else
111 {
112 AssertMsg( false, "SteamNetworkingIdentityFromProtobufMsg returned 0, but but we don't have any unknown fields?" );
113 V_strcpy_safe( errMsg, "Unrecognized identity format" );
114 }
115
116 identity.Clear();
117 return false;
118 }
119
BSteamNetworkingIdentityFromLegacySteamID(SteamNetworkingIdentity & identity,uint64 legacy_steam_id,SteamDatagramErrMsg & errMsg)120 bool BSteamNetworkingIdentityFromLegacySteamID( SteamNetworkingIdentity &identity, uint64 legacy_steam_id, SteamDatagramErrMsg &errMsg )
121 {
122 if ( !IsValidSteamIDForIdentity( legacy_steam_id ) )
123 {
124 V_sprintf_safe( errMsg, "Invalid SteamID %llu (in legacy field)", legacy_steam_id );
125 return false;
126 }
127 identity.SetSteamID64( legacy_steam_id );
128 return true;
129 }
130
131
BSteamNetworkingIdentityFromLegacyBinaryProtobuf(SteamNetworkingIdentity & identity,const std::string & bytesMsgIdentity,SteamDatagramErrMsg & errMsg)132 bool BSteamNetworkingIdentityFromLegacyBinaryProtobuf( SteamNetworkingIdentity &identity, const std::string &bytesMsgIdentity, SteamDatagramErrMsg &errMsg )
133 {
134 // Assume failure
135 identity.Clear();
136
137 // New format blob not present?
138 if ( bytesMsgIdentity.empty() )
139 {
140 V_strcpy_safe( errMsg, "No identity data is present" );
141 return false;
142 }
143
144 // Parse it
145 CMsgSteamNetworkingIdentityLegacyBinary msgIdentity;
146 if ( !msgIdentity.ParseFromString( bytesMsgIdentity ) )
147 {
148 V_strcpy_safe( errMsg, "Protobuf failed to parse" );
149 return false;
150 }
151
152 // Parse it
153 int r = SteamNetworkingIdentityFromLegacyBinaryProtobufMsg( identity, msgIdentity, errMsg );
154 if ( r > 0 )
155 return true;
156 if ( r < 0 )
157 {
158 identity.Clear();
159 return false;
160 }
161
162 // Hm, unknown identity type. Include the first few bytes for debugging
163 const size_t kMaxBytes = 8;
164 char szBytes[kMaxBytes*2 + 4];
165 size_t l = std::min( bytesMsgIdentity.length(), kMaxBytes );
166 for ( size_t i = 0 ; i < l ; ++i )
167 sprintf( szBytes + i*2, "%02x", uint8(bytesMsgIdentity[i]) );
168 szBytes[l*2] = '\0';
169 V_sprintf_safe( errMsg, "Parse failure. Length=%d, data begins %s", (int)bytesMsgIdentity.length(), szBytes );
170 return false;
171 }
172
SteamNetworkingIdentityFromSignedCert(SteamNetworkingIdentity & result,const CMsgSteamDatagramCertificateSigned & msgCertSigned,SteamDatagramErrMsg & errMsg)173 int SteamNetworkingIdentityFromSignedCert( SteamNetworkingIdentity &result, const CMsgSteamDatagramCertificateSigned &msgCertSigned, SteamDatagramErrMsg &errMsg )
174 {
175 // !SPEED! We could optimize this by hand-parsing the protobuf.
176 // This would avoid some memory allocations and dealing with
177 // fields we don't care about.
178 CMsgSteamDatagramCertificate cert;
179 if ( !cert.ParseFromString( msgCertSigned.cert() ) )
180 {
181 V_strcpy_safe( errMsg, "Cert failed protobuf parse" );
182 return -1;
183 }
184 return SteamNetworkingIdentityFromCert( result, cert, errMsg );
185 }
186
BSteamNetworkingIdentityToProtobufInternal(const SteamNetworkingIdentity & identity,std::string * strIdentity,CMsgSteamNetworkingIdentityLegacyBinary * msgIdentityLegacyBinary,SteamDatagramErrMsg & errMsg)187 bool BSteamNetworkingIdentityToProtobufInternal( const SteamNetworkingIdentity &identity, std::string *strIdentity, CMsgSteamNetworkingIdentityLegacyBinary *msgIdentityLegacyBinary, SteamDatagramErrMsg &errMsg )
188 {
189 switch ( identity.m_eType )
190 {
191 case k_ESteamNetworkingIdentityType_Invalid:
192 V_strcpy_safe( errMsg, "Identity is blank" );
193 return false;
194
195 case k_ESteamNetworkingIdentityType_SteamID:
196 Assert( identity.m_cbSize == sizeof(identity.m_steamID64) );
197 if ( !IsValidSteamIDForIdentity( identity.m_steamID64 ) )
198 {
199 V_sprintf_safe( errMsg, "Invalid SteamID %llu", identity.m_steamID64 );
200 return false;
201 }
202 msgIdentityLegacyBinary->set_steam_id( identity.m_steamID64 );
203 break;
204
205 case k_ESteamNetworkingIdentityType_IPAddress:
206 {
207 COMPILE_TIME_ASSERT( sizeof( SteamNetworkingIPAddr ) == 18 );
208 Assert( identity.m_cbSize == sizeof( SteamNetworkingIPAddr ) );
209 SteamNetworkingIPAddr tmpAddr( identity.m_ip );
210 tmpAddr.m_port = BigWord( tmpAddr.m_port );
211 msgIdentityLegacyBinary->set_ipv6_and_port( &tmpAddr, sizeof(tmpAddr) );
212 break;
213 }
214
215 case k_ESteamNetworkingIdentityType_GenericString:
216 Assert( identity.m_cbSize == (int)V_strlen( identity.m_szGenericString ) + 1 );
217 Assert( identity.m_cbSize > 1 );
218 Assert( identity.m_cbSize <= sizeof( identity.m_szGenericString ) );
219 msgIdentityLegacyBinary->set_generic_string( identity.m_szGenericString );
220 break;
221
222 case k_ESteamNetworkingIdentityType_GenericBytes:
223 Assert( identity.m_cbSize > 1 );
224 Assert( identity.m_cbSize <= sizeof( identity.m_genericBytes ) );
225 msgIdentityLegacyBinary->set_generic_bytes( identity.m_genericBytes, identity.m_cbSize );
226 break;
227
228 default:
229 // Any other format was added after we switched everything to the string format,
230 // and does not have a representation in the legacy format.
231 break;
232 }
233
234 // And return string format
235 char buf[ SteamNetworkingIdentity::k_cchMaxString ];
236 SteamNetworkingIdentity_ToString( &identity, buf, sizeof(buf) );
237 *strIdentity = buf;
238
239 return true;
240 }
241
BSteamNetworkingIdentityToProtobufInternal(const SteamNetworkingIdentity & identity,std::string * strIdentity,std::string * bytesMsgIdentityLegacyBinary,SteamDatagramErrMsg & errMsg)242 bool BSteamNetworkingIdentityToProtobufInternal( const SteamNetworkingIdentity &identity, std::string *strIdentity, std::string *bytesMsgIdentityLegacyBinary, SteamDatagramErrMsg &errMsg )
243 {
244 CMsgSteamNetworkingIdentityLegacyBinary msgIdentity;
245 if ( !BSteamNetworkingIdentityToProtobufInternal( identity, strIdentity, &msgIdentity, errMsg ) )
246 return false;
247
248 if ( !msgIdentity.SerializeToString( bytesMsgIdentityLegacyBinary ) )
249 {
250 // WAT
251 V_strcpy_safe( errMsg, "protobuf serialization failed?" );
252 return false;
253 }
254
255 return true;
256 }
257
258 /// Check an arbitrary signature against a public key.
BCheckSignature(const std::string & signed_data,CMsgSteamDatagramCertificate_EKeyType eKeyType,const std::string & public_key,const std::string & signature,SteamDatagramErrMsg & errMsg)259 bool BCheckSignature( const std::string &signed_data, CMsgSteamDatagramCertificate_EKeyType eKeyType, const std::string &public_key, const std::string &signature, SteamDatagramErrMsg &errMsg )
260 {
261
262 // Quick check for missing values
263 if ( signature.empty() )
264 {
265 V_strcpy_safe( errMsg, "No signature" );
266 return false;
267 }
268 if ( public_key.empty() )
269 {
270 V_strcpy_safe( errMsg, "No public key" );
271 return false;
272 }
273
274 // Only one key type supported right now
275 if ( eKeyType != CMsgSteamDatagramCertificate_EKeyType_ED25519 )
276 {
277 V_sprintf_safe( errMsg, "Unsupported key type %d", eKeyType );
278 return false;
279 }
280
281 // Make sure signature length is the right size
282 if ( signature.length() != sizeof(CryptoSignature_t) )
283 {
284 V_strcpy_safe( errMsg, "Signature has invalid length" );
285 return false;
286 }
287
288 // Put the public key into our object
289 CECSigningPublicKey keyPublic;
290 if ( !keyPublic.SetRawDataWithoutWipingInput( public_key.c_str(), public_key.length() ) )
291 {
292 V_strcpy_safe( errMsg, "Invalid public key" );
293 return false;
294 }
295
296 // Do the crypto work to check the signature
297 if ( !keyPublic.VerifySignature( signed_data.c_str(), signed_data.length(), *(const CryptoSignature_t *)signature.c_str() ) )
298 {
299 V_strcpy_safe( errMsg, "Invalid signature" );
300 return false;
301 }
302
303 // OK
304 return true;
305 }
306
ParseCertFromBase64(const char * pBase64Data,size_t cbBase64Data,CMsgSteamDatagramCertificateSigned & outMsgSignedCert,SteamNetworkingErrMsg & errMsg)307 bool ParseCertFromBase64( const char *pBase64Data, size_t cbBase64Data, CMsgSteamDatagramCertificateSigned &outMsgSignedCert, SteamNetworkingErrMsg &errMsg )
308 {
309
310 std_vector<uint8> buf;
311 uint32 cbDecoded = CCrypto::Base64DecodeMaxOutput( (uint32)cbBase64Data );
312 buf.resize( cbDecoded );
313 if ( !CCrypto::Base64Decode( pBase64Data, (uint32)cbBase64Data, &buf[0], &cbDecoded, false ) )
314 {
315 V_strcpy_safe( errMsg, "Failed to Base64 decode cert" );
316 return false;
317 }
318
319 if ( !outMsgSignedCert.ParseFromArray( &buf[0], cbDecoded ) )
320 {
321 V_strcpy_safe( errMsg, "Protobuf failed to parse CMsgSteamDatagramCertificateSigned" );
322 return false;
323 }
324 if ( !outMsgSignedCert.has_cert() )
325 {
326 V_strcpy_safe( errMsg, "No cert data" );
327 return false;
328 }
329
330 return true;
331 }
332
ParseCertFromPEM(const void * pCert,size_t cbCert,CMsgSteamDatagramCertificateSigned & outMsgSignedCert,SteamNetworkingErrMsg & errMsg)333 bool ParseCertFromPEM( const void *pCert, size_t cbCert, CMsgSteamDatagramCertificateSigned &outMsgSignedCert, SteamNetworkingErrMsg &errMsg )
334 {
335 uint32 cbCertBody = (uint32)cbCert;
336 const char *pszCertBody = CCrypto::LocatePEMBody( (const char *)pCert, &cbCertBody, "STEAMDATAGRAM CERT" );
337 if ( !pszCertBody )
338 {
339 V_strcpy_safe( errMsg, "Cert isn't a valid PEM-like text block" );
340 return false;
341 }
342
343 return ParseCertFromBase64( pszCertBody, cbCertBody, outMsgSignedCert, errMsg );
344 }
345
346 }
347