diff --git a/cs/sdk/src/Proton.Drive.Sdk/Nodes/Cryptography/NodeCrypto.cs b/cs/sdk/src/Proton.Drive.Sdk/Nodes/Cryptography/NodeCrypto.cs index d2c3c559..e3ac6f6f 100644 --- a/cs/sdk/src/Proton.Drive.Sdk/Nodes/Cryptography/NodeCrypto.cs +++ b/cs/sdk/src/Proton.Drive.Sdk/Nodes/Cryptography/NodeCrypto.cs @@ -265,30 +265,36 @@ internal static class NodeCrypto return new ProtonDriveError("Cannot get node key", error); } + ArraySegment serializedExtendedAttributes; + AuthorshipVerificationFailure? authorshipVerificationFailure; try { - var serializedExtendedAttributes = DecryptMessage( + serializedExtendedAttributes = DecryptMessage( encryptedExtendedAttributes.Value, detachedSignature: null, nodeKey, authorshipClaim.GetKeyRing(nodeKey), out _, - out var author); - - try - { - var extendedAttributes = JsonSerializer.Deserialize(serializedExtendedAttributes, DriveApiSerializerContext.Default.ExtendedAttributes); - - return new DecryptionOutput(extendedAttributes, author); - } - catch (Exception e) - { - return new ProtonDriveError("Failed to deserialize extended attributes", e.ToProtonDriveError()); - } + out authorshipVerificationFailure); } catch (Exception e) { - return new ProtonDriveError("Failed to decrypt extended attributes", e.ToProtonDriveError()); + return new DecryptionError("Failed to decrypt extended attributes", e.ToProtonDriveError()); + } + + try + { + var extendedAttributes = JsonSerializer.Deserialize(serializedExtendedAttributes, DriveApiSerializerContext.Default.ExtendedAttributes); + + return new DecryptionOutput(extendedAttributes, authorshipVerificationFailure); + } + catch (JsonException e) + { + return new ExtendedAttributesDeserializationError(e.ToProtonDriveError()); + } + catch (Exception e) + { + return new ProtonDriveError("Unknown error while deserializing extended attributes", e.ToProtonDriveError()); } } diff --git a/cs/sdk/src/Proton.Drive.Sdk/Nodes/DtoToMetadataConverter.cs b/cs/sdk/src/Proton.Drive.Sdk/Nodes/DtoToMetadataConverter.cs index 19f2e216..4bef9ed2 100644 --- a/cs/sdk/src/Proton.Drive.Sdk/Nodes/DtoToMetadataConverter.cs +++ b/cs/sdk/src/Proton.Drive.Sdk/Nodes/DtoToMetadataConverter.cs @@ -299,17 +299,25 @@ internal static class DtoToMetadataConverter ShareMembershipSummaryDto? membershipDto) { Dictionary failedDecryptionFields = []; - List errors = []; + List nodeKeyErrors = []; if (decryptionResult.Link.Passphrase.TryGetError(out var passphraseError)) { - errors.Add(new DecryptionError("Passphrase decryption failed", passphraseError)); - failedDecryptionFields.Add(EncryptedField.NodeKey, passphraseError); + nodeKeyErrors.Add(passphraseError); + + if (passphraseError is DecryptionError) + { + failedDecryptionFields.Add(EncryptedField.NodeKey, passphraseError); + } } else if (decryptionResult.Link.NodeKey.TryGetError(out var nodeKeyError)) { - errors.Add(new DecryptionError("Node key decryption failed", nodeKeyError)); - failedDecryptionFields.Add(EncryptedField.NodeKey, nodeKeyError); + nodeKeyErrors.Add(nodeKeyError); + + if (passphraseError is DecryptionError) + { + failedDecryptionFields.Add(EncryptedField.NodeKey, nodeKeyError); + } } else if (decryptionResult.ContentKey.TryGetError(out var contentKeyError)) { @@ -324,8 +332,12 @@ internal static class DtoToMetadataConverter var revisionErrors = new List(); if (decryptionResult.ExtendedAttributes.TryGetError(out var extendedAttributesError)) { - revisionErrors.Add(new DecryptionError("Extended attributes decryption failed", extendedAttributesError)); - failedDecryptionFields.Add(EncryptedField.NodeExtendedAttributes, extendedAttributesError); + revisionErrors.Add(extendedAttributesError); + + if (extendedAttributesError is DecryptionError) + { + failedDecryptionFields.Add(EncryptedField.NodeExtendedAttributes, extendedAttributesError); + } } var nodeAuthor = decryptionResult.Link.Passphrase.Merge( @@ -369,7 +381,7 @@ internal static class DtoToMetadataConverter MediaType = fileDto.MediaType, ActiveRevision = degradedRevision, TotalStorageQuotaUsage = fileDto.TotalSizeOnStorage, - Errors = errors, + Errors = nodeKeyErrors, CaptureTime = linkDetailsDto.Photo.CaptureTime, AlbumUids = linkDetailsDto.Photo.AlbumInclusions.Select(a => new NodeUid(uid.VolumeId, a.Id)).ToList(), OwnedBy = ownedBy, @@ -386,7 +398,7 @@ internal static class DtoToMetadataConverter MediaType = fileDto.MediaType, ActiveRevision = degradedRevision, TotalStorageQuotaUsage = fileDto.TotalSizeOnStorage, - Errors = errors, + Errors = nodeKeyErrors, OwnedBy = ownedBy, }; @@ -495,25 +507,34 @@ internal static class DtoToMetadataConverter ShareMembershipSummaryDto? membershipDto) { Dictionary failedDecryptionFields = []; - List errors = []; + List nodeKeyAndHashKeyErrors = []; if (decryptionResult.Link.Passphrase.TryGetError(out var passphraseError)) { - errors.Add(new DecryptionError("Passphrase decryption failed", passphraseError)); - failedDecryptionFields.Add(EncryptedField.NodeKey, passphraseError); + nodeKeyAndHashKeyErrors.Add(passphraseError); + + if (passphraseError is DecryptionError) + { + failedDecryptionFields.Add(EncryptedField.NodeKey, passphraseError); + } } else if (decryptionResult.Link.NodeKey.TryGetError(out var nodeKeyError)) { - errors.Add(new DecryptionError("Node key decryption failed", nodeKeyError)); - failedDecryptionFields.Add(EncryptedField.NodeKey, nodeKeyError); + nodeKeyAndHashKeyErrors.Add(nodeKeyError); + + if (nodeKeyError is DecryptionError) + { + failedDecryptionFields.Add(EncryptedField.NodeKey, nodeKeyError); + } } else if (decryptionResult.HashKey.TryGetError(out var hashKeyError)) { - errors.Add(new DecryptionError("Hash key decryption failed", hashKeyError)); + nodeKeyAndHashKeyErrors.Add(hashKeyError); + failedDecryptionFields.Add(EncryptedField.NodeHashKey, hashKeyError); } - if (nameResult.TryGetError(out var nameError)) + if (nameResult.TryGetError(out var nameError) && nameError is DecryptionError) { failedDecryptionFields.Add(EncryptedField.NodeName, nameError); } @@ -541,7 +562,7 @@ internal static class DtoToMetadataConverter CreationTime = linkDto.CreationTime, TrashTime = linkDto.TrashTime, Author = nodeAuthor, - Errors = errors, + Errors = nodeKeyAndHashKeyErrors, OwnedBy = MapOwnedBy(linkDto.OwnedBy), }; diff --git a/cs/sdk/src/Proton.Drive.Sdk/Nodes/ExtendedAttributesDeserializationError.cs b/cs/sdk/src/Proton.Drive.Sdk/Nodes/ExtendedAttributesDeserializationError.cs new file mode 100644 index 00000000..33b9c8fb --- /dev/null +++ b/cs/sdk/src/Proton.Drive.Sdk/Nodes/ExtendedAttributesDeserializationError.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace Proton.Drive.Sdk.Nodes; + +[method: JsonConstructor] +public sealed class ExtendedAttributesDeserializationError(ProtonDriveError? innerError = null) + : ProtonDriveError("Failed to deserialize extended attributes", innerError) +{ +}