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