From 0e239d4c2d07e9c76e66db1f9b824ecda1babb47 Mon Sep 17 00:00:00 2001 From: evilbert Date: Sun, 12 Apr 2026 14:36:10 +1200 Subject: [PATCH] fix: attribute decryption for nodes with multiple keys and correctly detect share root Also, don't send authenticated sid for folder-share node listings or download URL requests; authenticated requests caused Mega API to return ResourceAdministrativelyBlocked (-16) --- MegaApiClient/MegaApiClient.cs | 6 +- MegaApiClient/Node.cs | 96 +++++++++++++--------- MegaApiClient/Serialization/DownloadUrl.cs | 1 + MegaApiClient/Serialization/GetNodes.cs | 5 ++ MegaApiClient/Serialization/RequestBase.cs | 4 + 5 files changed, 70 insertions(+), 42 deletions(-) diff --git a/MegaApiClient/MegaApiClient.cs b/MegaApiClient/MegaApiClient.cs index ccbe813..bfabef6 100644 --- a/MegaApiClient/MegaApiClient.cs +++ b/MegaApiClient/MegaApiClient.cs @@ -1112,7 +1112,7 @@ private TResponse RequestCore(RequestBase request, byte[] key) where TResponse : class { var dataRequest = JsonConvert.SerializeObject(new object[] { request }); - var uri = GenerateUrl(request.QueryArguments); + var uri = GenerateUrl(request.QueryArguments, request.UseSession); object jsonData = null; var attempt = 0; var apiCode = ApiResultCode.Ok; @@ -1182,7 +1182,7 @@ private void Wait(TimeSpan retryDelay) #endif } - private Uri GenerateUrl(Dictionary queryArguments) + private Uri GenerateUrl(Dictionary queryArguments, bool useSession = true) { var query = new Dictionary(queryArguments) { @@ -1190,7 +1190,7 @@ private Uri GenerateUrl(Dictionary queryArguments) ["ak"] = _options.ApplicationKey }; - if (!string.IsNullOrEmpty(_sessionId)) + if (useSession && !string.IsNullOrEmpty(_sessionId)) { query["sid"] = _sessionId; } diff --git a/MegaApiClient/Node.cs b/MegaApiClient/Node.cs index d90a3e4..660ff90 100644 --- a/MegaApiClient/Node.cs +++ b/MegaApiClient/Node.cs @@ -136,54 +136,72 @@ public void OnDeserialized(StreamingContext ctx) return; } - // There are cases where the SerializedKey property contains multiple keys separated with / - // This can occur when a folder is shared and the parent is shared too. - // Both keys are working so we use the first one - var serializedKey = SerializedKey.Split('/')[0]; - var splitPosition = serializedKey.IndexOf(":", StringComparison.Ordinal); - var encryptedKey = serializedKey.Substring(splitPosition + 1).FromBase64(); - - // If node is shared, we need to retrieve shared masterkey - if (_sharedKeys != null) + // The SerializedKey property can contain multiple keys separated with / + // This occurs when a folder is shared and the parent is shared too, or for + // shared folder links where the owner's key and share key are both present. + // Try each key and use the first one that produces valid attributes. + var serializedKeys = SerializedKey.Split('/'); + + foreach (var serializedKey in serializedKeys) { + var splitPosition = serializedKey.IndexOf(":", StringComparison.Ordinal); + if (splitPosition < 0) + { + continue; + } + var handle = serializedKey.Substring(0, splitPosition); - var sharedKey = _sharedKeys.FirstOrDefault(x => x.Id == handle); - if (sharedKey != null) + var encryptedKey = serializedKey.Substring(splitPosition + 1).FromBase64(); + + // If node is shared, we need to retrieve shared masterkey + var usedMasterKey = _masterKey; + byte[] sharedKeyValue = null; + if (_sharedKeys != null) { - _masterKey = Crypto.DecryptKey(sharedKey.Key.FromBase64(), _masterKey); - if (Type == NodeType.Directory) - { - SharedKey = _masterKey; - } - else + var sharedKey = _sharedKeys.FirstOrDefault(x => x.Id == handle); + if (sharedKey != null) { - SharedKey = Crypto.DecryptKey(encryptedKey, _masterKey); + usedMasterKey = Crypto.DecryptKey(sharedKey.Key.FromBase64(), _masterKey); + sharedKeyValue = Type == NodeType.Directory + ? usedMasterKey + : Crypto.DecryptKey(encryptedKey, usedMasterKey); } } - } - if (encryptedKey.Length != 16 && encryptedKey.Length != 32) - { - // Invalid key size - return; - } + if (encryptedKey.Length != 16 && encryptedKey.Length != 32) + { + continue; + } - FullKey = Crypto.DecryptKey(encryptedKey, _masterKey); + var fullKey = Crypto.DecryptKey(encryptedKey, usedMasterKey); + byte[] nodeKey; + byte[] iv = null; + byte[] metaMac = null; - if (Type == NodeType.File) - { - Crypto.GetPartsFromDecryptedKey(FullKey, out var iv, out var metaMac, out var fileKey); + if (Type == NodeType.File) + { + Crypto.GetPartsFromDecryptedKey(fullKey, out iv, out metaMac, out nodeKey); + } + else + { + nodeKey = fullKey; + } + + var attrs = Crypto.DecryptAttributes(SerializedAttributes.FromBase64(), nodeKey); + FullKey = fullKey; + Key = nodeKey; Iv = iv; MetaMac = metaMac; - Key = fileKey; - } - else - { - Key = FullKey; + SharedKey = sharedKeyValue; + Attributes = attrs; + + if (attrs?.Name != null && !attrs.Name.StartsWith("Attribute deserialization failed")) + { + break; + } } - Attributes = Crypto.DecryptAttributes(SerializedAttributes.FromBase64(), Key); FileAttributes = DeserializeFileAttributes(SerializedFileAttributes); } } @@ -278,12 +296,12 @@ private bool IsShareRoot { return true; } - else + + return _node.SerializedKey.Split('/').Any(key => { - var serializedKey = _node.SerializedKey.Split('/')[0]; - var splitPosition = serializedKey.IndexOf(":", StringComparison.Ordinal); - return serializedKey.Substring(0, splitPosition) == Id; - } + var splitPosition = key.IndexOf(":", StringComparison.Ordinal); + return splitPosition >= 0 && key.Substring(0, splitPosition) == Id; + }); } } } diff --git a/MegaApiClient/Serialization/DownloadUrl.cs b/MegaApiClient/Serialization/DownloadUrl.cs index 68bf569..1e37505 100644 --- a/MegaApiClient/Serialization/DownloadUrl.cs +++ b/MegaApiClient/Serialization/DownloadUrl.cs @@ -12,6 +12,7 @@ public DownloadUrlRequest(INode node) if (node is PublicNode publicNode) { QueryArguments["n"] = publicNode.ShareId; + UseSession = false; } } diff --git a/MegaApiClient/Serialization/GetNodes.cs b/MegaApiClient/Serialization/GetNodes.cs index 732f755..f2c43f7 100644 --- a/MegaApiClient/Serialization/GetNodes.cs +++ b/MegaApiClient/Serialization/GetNodes.cs @@ -12,6 +12,8 @@ public GetNodesRequest(string shareId = null) : base("f") { C = 1; + Ca = 1; + UseSession = false; if (shareId != null) { @@ -27,6 +29,9 @@ public GetNodesRequest(string shareId = null) [JsonProperty("r")] public int R { get; private set; } + + [JsonProperty("ca")] + public int Ca { get; private set; } } internal class GetNodesResponse diff --git a/MegaApiClient/Serialization/RequestBase.cs b/MegaApiClient/Serialization/RequestBase.cs index b14eae9..98bd539 100644 --- a/MegaApiClient/Serialization/RequestBase.cs +++ b/MegaApiClient/Serialization/RequestBase.cs @@ -9,6 +9,7 @@ protected RequestBase(string action) { Action = action; QueryArguments = new Dictionary(); + UseSession = true; } [JsonProperty("a")] @@ -16,5 +17,8 @@ protected RequestBase(string action) [JsonIgnore] public Dictionary QueryArguments { get; } + + [JsonIgnore] + public bool UseSession { get; protected set; } } }