public void TestCertMangling() { string certString = @"MIICSjCCAdECCQDje/no7mXkVzAKBggqhkjOPQQDAjCBjjELMAkGA1UEBhMCVVMx EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFDAS BgNVBAoMC0dvb2dsZSwgSW5jMRcwFQYDVQQDDA53d3cuZ29vZ2xlLmNvbTEjMCEG CSqGSIb3DQEJARYUZ29sYW5nLWRldkBnbWFpbC5jb20wHhcNMTIwNTIxMDYxMDM0 WhcNMjIwNTE5MDYxMDM0WjCBjjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlm b3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFDASBgNVBAoMC0dvb2dsZSwg SW5jMRcwFQYDVQQDDA53d3cuZ29vZ2xlLmNvbTEjMCEGCSqGSIb3DQEJARYUZ29s YW5nLWRldkBnbWFpbC5jb20wdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARRuzRNIKRK jIktEmXanNmrTR/q/FaHXLhWRZ6nHWe26Fw7Rsrbk+VjGy4vfWtNn7xSFKrOu5ze qxKnmE0h5E480MNgrUiRkaGO2GMJJVmxx20aqkXOk59U8yGA4CghE6MwCgYIKoZI zj0EAwIDZwAwZAIwBZEN8gvmRmfeP/9C1PRLzODIY4JqWub2PLRT4mv9GU+yw3Gr PU9A3CHMdEcdw/MEAjBBO1lId8KOCh9UZunsSMfqXiVurpzmhWd6VYZ/32G+M+Mh 3yILeYQzllt/g0rKVRk="; X509Certificate2 c = new X509Certificate2(); c.Import(Convert.FromBase64String(certString)); Assert.AreEqual("[email protected], CN=www.google.com, O=\"Google, Inc\", L=Mountain View, S=California, C=US", c.Issuer); //Assert.AreEqual("CN=Microsoft Corporate Root CA, O=Microsoft Corporation", c.Subject); Assert.AreEqual("X509", c.GetFormat()); Assert.AreEqual("1.2.840.10045.2.1", c.GetKeyAlgorithm()); Assert.AreEqual("06052B81040022", c.GetKeyAlgorithmParametersString()); Assert.AreEqual("ECC", c.PublicKey.Oid.FriendlyName); ECDiffieHellmanPublicKey certKey = ImportEccPublicKeyFromCertificate(c); //Console.WriteLine(certKey.ToXmlString()); // https://blogs.msdn.microsoft.com/shawnfa/2007/01/22/elliptic-curve-diffie-hellman/ // http://stackoverflow.com/questions/11266711/using-cngkey-to-generate-rsa-key-pair-in-pem-dkim-compatible-using-c-simi { string input = "eyJhbGciOiJFUzM4NCIsIng1dSI6Ik1IWXdFQVlIS29aSXpqMENBUVlGSzRFRUFDSURZZ0FFN25uWnBDZnhtQ3JTd0RkQnY3ZUJYWE10S2hyb3hPcmlFcjNobU1PSkF1dy9acFFYajFLNUdHdEhTNENwRk50dGQxSllBS1lvSnhZZ2F5a3BpZTBFeUF2M3FpSzZ1dElIMnFuT0F0M1ZOclFZWGZJWkpTL1ZSZTNJbDhQZ3U5Q0IifQo.eyJleHAiOjE0NjQ5ODM4NDUsImV4dHJhRGF0YSI6eyJkaXNwbGF5TmFtZSI6Imd1cnVueCIsImlkZW50aXR5IjoiYWY2ZjdjNWUtZmNlYS0zZTQzLWJmM2EtZTAwNWU0MDBlNTc4In0sImlkZW50aXR5UHVibGljS2V5IjoiTUhZd0VBWUhLb1pJemowQ0FRWUZLNEVFQUNJRFlnQUU3bm5acENmeG1DclN3RGRCdjdlQlhYTXRLaHJveE9yaUVyM2htTU9KQXV3L1pwUVhqMUs1R0d0SFM0Q3BGTnR0ZDFKWUFLWW9KeFlnYXlrcGllMEV5QXYzcWlLNnV0SUgycW5PQXQzVk5yUVlYZklaSlMvVlJlM0lsOFBndTlDQiIsIm5iZiI6MTQ2NDk4Mzg0NH0K.4OrvYYbX09iwOkz-7_N_5yEejuATcUogEbe69fB-kr7r6sH_qSu6bxp9L64SEgABb0rU7tyYCLVnaCSQjd9Dvb34WI9EducgOPJ92qHspcpXr7j716LDfhZE31ksMtWQ"; ECDiffieHellmanPublicKey rootKey = CryptoUtils.FromDerEncoded("MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V".DecodeBase64Url()); Console.WriteLine($"Root Public Key:\n{rootKey.ToXmlString()}"); CngKey key = CngKey.Import(rootKey.ToByteArray(), CngKeyBlobFormat.EccPublicBlob); Console.WriteLine("Key family: " + key.AlgorithmGroup); // "identityPublicKey": "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7nnZpCfxmCrSwDdBv7eBXXMtKhroxOriEr3hmMOJAuw/ZpQXj1K5GGtHS4CpFNttd1JYAKYoJxYgaykpie0EyAv3qiK6utIH2qnOAt3VNrQYXfIZJS/VRe3Il8Pgu9CB", ECDiffieHellmanCngPublicKey newKey = (ECDiffieHellmanCngPublicKey)CryptoUtils.FromDerEncoded("MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7nnZpCfxmCrSwDdBv7eBXXMtKhroxOriEr3hmMOJAuw/ZpQXj1K5GGtHS4CpFNttd1JYAKYoJxYgaykpie0EyAv3qiK6utIH2qnOAt3VNrQYXfIZJS/VRe3Il8Pgu9CB".DecodeBase64Url()); string decoded = JWT.Decode(input, newKey.Import()); //Assert.AreEqual("", decoded); //ECDsaCng t = new ECDsaCng(); //t.HashAlgorithm = CngAlgorithm.ECDiffieHellmanP384; //t.KeySize = 384; //byte[] test = t.Key.Export(CngKeyBlobFormat.EccPublicBlob); //Assert.AreEqual(test, newKey); //string decoded = JWT.Decode(input, t.Key); } // Private key (in reality this is not necessary since we will generate it) AsymmetricKeyParameter privKey = PrivateKeyFactory.CreateKey(Base64Url.Decode("MB8CAQAwEAYHKoZIzj0CAQYFK4EEACIECDAGAgEBBAEB")); PrivateKeyInfo privKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privKey); byte[] derKey = privKeyInfo.GetDerEncoded(); CngKey privCngKey = CngKey.Import(derKey, CngKeyBlobFormat.Pkcs8PrivateBlob); Console.WriteLine(privKeyInfo.PrivateKeyAlgorithm.Algorithm); Console.WriteLine(privCngKey.Algorithm.Algorithm); // Public key ECDiffieHellmanPublicKey clientKey = CryptoUtils.FromDerEncoded("MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEDEKneqEvcqUqqFMM1HM1A4zWjJC+I8Y+aKzG5dl+6wNOHHQ4NmG2PEXRJYhujyodFH+wO0dEr4GM1WoaWog8xsYQ6mQJAC0eVpBM96spUB1eMN56+BwlJ4H3Qx4TAvAs".DecodeBase64Url()); // EC key to generate shared secret ECDiffieHellmanCng ecKey = new ECDiffieHellmanCng(privCngKey); ecKey.HashAlgorithm = CngAlgorithm.Sha256; ecKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; ecKey.SecretPrepend = new byte[128]; // Server token //ecKey.SecretPrepend = new byte[0]; // Server token Console.WriteLine(ecKey.HashAlgorithm); Console.WriteLine(ecKey.KeyExchangeAlgorithm); byte[] secret = ecKey.DeriveKeyMaterial(clientKey); Console.WriteLine(Package.HexDump(secret)); Console.WriteLine(Package.HexDump(Base64Url.Decode("ZOBpyzki/M8UZv5tiBih048eYOBVPkQE3r5Fl0gmUP4="))); Console.WriteLine(Package.HexDump(Base64Url.Decode("DEKneqEvcqUqqFMM1HM1A4zWjJC+I8Y+aKzG5dl+6wNOHHQ4NmG2PEXRJYhujyod"))); //Console.WriteLine(Package.HexDump(Base64Url.Decode("DEKneqEvcqUqqFMM1HM1A4zWjJC+I8Y+aKzG5dl+6wNOHHQ4NmG2PEXRJYhujyod"))); }
protected void DecodeCert(McpeLogin message) { byte[] buffer = message.payload; if (message.payload.Length != buffer.Length) { Log.Debug($"Wrong lenght {message.payload.Length} != {message.payload.Length}"); throw new Exception($"Wrong lenght {message.payload.Length} != {message.payload.Length}"); } if (Log.IsDebugEnabled) { Log.Debug("Lenght: " + message.payload.Length + ", Message: " + buffer.EncodeBase64()); } string certificateChain; string skinData; try { var destination = new MemoryStream(buffer); destination.Position = 0; NbtBinaryReader reader = new NbtBinaryReader(destination, false); var countCertData = reader.ReadInt32(); certificateChain = Encoding.UTF8.GetString(reader.ReadBytes(countCertData)); if (Log.IsDebugEnabled) { Log.Debug($"Certificate Chain (Lenght={countCertData})\n{certificateChain}"); } var countSkinData = reader.ReadInt32(); skinData = Encoding.UTF8.GetString(reader.ReadBytes(countSkinData)); if (Log.IsDebugEnabled) { Log.Debug($"Skin data (Lenght={countSkinData})\n{skinData}"); } } catch (Exception e) { Log.Error("Parsing login", e); return; } try { { IDictionary <string, dynamic> headers = JWT.Headers(skinData); dynamic payload = JObject.Parse(JWT.Payload(skinData)); if (Log.IsDebugEnabled) { Log.Debug($"Skin JWT Header: {string.Join(";", headers)}"); } if (Log.IsDebugEnabled) { Log.Debug($"Skin JWT Payload:\n{payload.ToString()}"); } try { _playerInfo.ClientId = payload.ClientRandomId; _playerInfo.CurrentInputMode = payload.CurrentInputMode; _playerInfo.DefaultInputMode = payload.DefaultInputMode; _playerInfo.DeviceModel = payload.DeviceModel; _playerInfo.DeviceOS = payload.DeviceOS; _playerInfo.GameVersion = payload.GameVersion; _playerInfo.GuiScale = payload.GuiScale; _playerInfo.LanguageCode = payload.LanguageCode; _playerInfo.ServerAddress = payload.ServerAddress; _playerInfo.UIProfile = payload.UIProfile; _playerInfo.Skin = new Skin() { CapeData = Convert.FromBase64String((string)payload.CapeData), SkinId = payload.SkinId, SkinData = Convert.FromBase64String((string)payload.SkinData), SkinGeometryName = payload.SkinGeometryName, SkinGeometry = Encoding.UTF8.GetString(Convert.FromBase64String((string)payload.SkinGeometry)), }; Log.Warn($"Cape data lenght={_playerInfo.Skin.CapeData.Length}"); } catch (Exception e) { Log.Error("Parsing skin data", e); } } { dynamic json = JObject.Parse(certificateChain); if (Log.IsDebugEnabled) { Log.Debug($"Certificate JSON:\n{json}"); } JArray chain = json.chain; //var chainArray = chain.ToArray(); string validationKey = null; string identityPublicKey = null; foreach (JToken token in chain) { IDictionary <string, dynamic> headers = JWT.Headers(token.ToString()); if (Log.IsDebugEnabled) { Log.Debug("Raw chain element:\n" + token.ToString()); Log.Debug($"JWT Header: {string.Join(";", headers)}"); dynamic jsonPayload = JObject.Parse(JWT.Payload(token.ToString())); Log.Debug($"JWT Payload:\n{jsonPayload}"); } // Mojang root x5u cert (string): MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V if (!headers.ContainsKey("x5u")) { continue; } string x5u = headers["x5u"]; if (identityPublicKey == null) { if (CertificateData.MojangRootKey.Equals(x5u, StringComparison.InvariantCultureIgnoreCase)) { Log.Debug("Key is ok, and got Mojang root"); } else if (chain.Count > 1) { Log.Debug("Got client cert (client root)"); continue; } else if (chain.Count == 1) { Log.Debug("Selfsigned chain"); } } else if (identityPublicKey.Equals(x5u)) { Log.Debug("Derived Key is ok"); } ECDiffieHellmanCngPublicKey newKey = (ECDiffieHellmanCngPublicKey)CryptoUtils.FromDerEncoded(x5u.DecodeBase64Url()); CertificateData data = JWT.Decode <CertificateData>(token.ToString(), newKey.Import()); // Validate if (data != null) { identityPublicKey = data.IdentityPublicKey; if (Log.IsDebugEnabled) { Log.Debug("Decoded token success"); } if (CertificateData.MojangRootKey.Equals(x5u, StringComparison.InvariantCultureIgnoreCase)) { Log.Debug("Got Mojang key. Is valid = " + data.CertificateAuthority); validationKey = data.IdentityPublicKey; } else if (validationKey != null && validationKey.Equals(x5u, StringComparison.InvariantCultureIgnoreCase)) { _playerInfo.CertificateData = data; } else { if (data.ExtraData == null) { continue; } // Self signed, make sure they don't fake XUID if (data.ExtraData.Xuid != null) { Log.Warn("Received fake XUID from " + data.ExtraData.DisplayName); data.ExtraData.Xuid = null; } _playerInfo.CertificateData = data; } } else { Log.Error("Not a valid Identity Public Key for decoding"); } } //TODO: Implement disconnect here { _playerInfo.Username = _playerInfo.CertificateData.ExtraData.DisplayName; _session.Username = _playerInfo.Username; string identity = _playerInfo.CertificateData.ExtraData.Identity; if (Log.IsDebugEnabled) { Log.Debug($"Connecting user {_playerInfo.Username} with identity={identity}"); } _playerInfo.ClientUuid = new UUID(identity); _session.CryptoContext = new CryptoContext { UseEncryption = Config.GetProperty("UseEncryptionForAll", false) || (Config.GetProperty("UseEncryption", true) && !string.IsNullOrWhiteSpace(_playerInfo.CertificateData.ExtraData.Xuid)), }; if (_session.CryptoContext.UseEncryption) { ECDiffieHellmanPublicKey publicKey = CryptoUtils.FromDerEncoded(_playerInfo.CertificateData.IdentityPublicKey.DecodeBase64Url()); if (Log.IsDebugEnabled) { Log.Debug($"Cert:\n{publicKey.ToXmlString()}"); } // Create shared shared secret ECDiffieHellmanCng ecKey = new ECDiffieHellmanCng(384); ecKey.HashAlgorithm = CngAlgorithm.Sha256; ecKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; ecKey.SecretPrepend = Encoding.UTF8.GetBytes("RANDOM SECRET"); // Server token byte[] secret = ecKey.DeriveKeyMaterial(publicKey); if (Log.IsDebugEnabled) { Log.Debug($"SECRET KEY (b64):\n{secret.EncodeBase64()}"); } { RijndaelManaged rijAlg = new RijndaelManaged { BlockSize = 128, Padding = PaddingMode.None, Mode = CipherMode.CFB, FeedbackSize = 8, Key = secret, IV = secret.Take(16).ToArray(), }; // Create a decrytor to perform the stream transform. ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV); MemoryStream inputStream = new MemoryStream(); CryptoStream cryptoStreamIn = new CryptoStream(inputStream, decryptor, CryptoStreamMode.Read); ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV); MemoryStream outputStream = new MemoryStream(); CryptoStream cryptoStreamOut = new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write); _session.CryptoContext.Algorithm = rijAlg; _session.CryptoContext.Decryptor = decryptor; _session.CryptoContext.Encryptor = encryptor; _session.CryptoContext.InputStream = inputStream; _session.CryptoContext.OutputStream = outputStream; _session.CryptoContext.CryptoStreamIn = cryptoStreamIn; _session.CryptoContext.CryptoStreamOut = cryptoStreamOut; string b64Key = ecKey.PublicKey.ToDerEncoded().EncodeBase64(); var handshakeJson = new HandshakeData() { salt = ecKey.SecretPrepend.EncodeBase64() }; string val = JWT.Encode(handshakeJson, ecKey.Key, JwsAlgorithm.ES384, new Dictionary <string, object> { { "x5u", b64Key } }); var response = McpeServerToClientHandshake.CreateObject(); response.NoBatch = true; response.ForceClear = true; response.token = val; _session.SendPackage(response); if (Log.IsDebugEnabled) { Log.Warn($"Encryption enabled for {_session.Username}"); } } } } } if (!_session.CryptoContext.UseEncryption) { _session.MessageHandler.HandleMcpeClientToServerHandshake(null); } } catch (Exception e) { Log.Error("Decrypt", e); } }
public void TestJWTHandling() { CngKey newKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP384, null, new CngKeyCreationParameters() { ExportPolicy = CngExportPolicies.AllowPlaintextExport, KeyUsage = CngKeyUsages.AllUsages }); byte[] t = CryptoUtils.ImportECDsaCngKeyFromCngKey(newKey.Export(CngKeyBlobFormat.EccPrivateBlob)); CngKey tk = CngKey.Import(t, CngKeyBlobFormat.EccPrivateBlob); Assert.AreEqual(CngAlgorithmGroup.ECDsa, tk.AlgorithmGroup); ECDiffieHellmanCng ecKey = new ECDiffieHellmanCng(newKey); ecKey.HashAlgorithm = CngAlgorithm.Sha256; ecKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; var b64Key = Base64Url.Encode(ecKey.PublicKey.ToDerEncoded()); string test = $@" {{ ""exp"": 1464983845, ""extraData"": {{ ""displayName"": ""gurunx"", ""identity"": ""af6f7c5e -fcea-3e43-bf3a-e005e400e578"" }}, ""identityPublicKey"": ""{b64Key}"", ""nbf"": 1464983844 }}"; CertificateData certificateData = new CertificateData { Exp = 1464983845, ExtraData = new ExtraData { DisplayName = "gurun", Identity = "af6f7c5e -fcea-3e43-bf3a-e005e400e578", }, IdentityPublicKey = b64Key, Nbf = 1464983844, }; JWT.JsonMapper = new NewtonsoftMapper(); string val = JWT.Encode(certificateData, tk, JwsAlgorithm.ES384, new Dictionary <string, object> { { "x5u", b64Key } }); Console.WriteLine(val); Assert.AreEqual(b64Key, JWT.Headers(val)["x5u"]); //Assert.AreEqual("", string.Join(";", JWT.Headers(val))); //Assert.AreEqual(test, JWT.Payload(val)); Console.WriteLine(JWT.Payload(val)); IDictionary <string, dynamic> headers = JWT.Headers(val); if (headers.ContainsKey("x5u")) { string certString = headers["x5u"]; // Validate ECDiffieHellmanCngPublicKey importKey = (ECDiffieHellmanCngPublicKey)CryptoUtils.FromDerEncoded(certString.DecodeBase64Url()); CertificateData data = JWT.Decode <CertificateData>(val, importKey.Import()); Assert.NotNull(data); Assert.AreEqual(certificateData.Exp, data.Exp); Assert.AreEqual(certificateData.IdentityPublicKey, data.IdentityPublicKey); Assert.AreEqual(certificateData.Nbf, data.Nbf); Assert.NotNull(data.ExtraData); Assert.AreEqual(certificateData.ExtraData.DisplayName, data.ExtraData.DisplayName); Assert.AreEqual(certificateData.ExtraData.Identity, data.ExtraData.Identity); } }