dotnet/wpf · error · InvalidOperationException

Cannot sign read-only file.

Error message

Cannot sign read-only file.

What it means

Sign throws InvalidOperationException (SR.CannotSignReadOnlyFile) when the package was opened in read-only mode, because signing requires writing the signature part and digital signature origin part back into the package.

Solutions

  1. Open the package with PackageOpenMode.ReadWrite (or FileAccess.ReadWrite) before signing
  2. Ensure the underlying file/stream and its directory grant write permission
  3. If the package is only for consumption, do not call Sign — check ReadOnly property first and skip signing
  4. Copy the read-only package to a writable location and reopen ReadWrite if you must sign it

Example fix

// before
using (Package pkg = Package.Open(path, FileMode.Open, FileAccess.Read))
{
    var mgr = new PackageDigitalSignatureManager(pkg);
    mgr.Sign(parts, cert); // throws: package is read-only
}
// after
using (Package pkg = Package.Open(path, FileMode.Open, FileAccess.ReadWrite))
{
    var mgr = new PackageDigitalSignatureManager(pkg);
    if (!mgr.ReadOnly)
        mgr.Sign(parts, cert);
}
Defensive patterns

Strategy: validation

Validate before calling

if (manager.ReadOnly)
    throw new InvalidOperationException("Open the package with ReadWrite access before signing.");

Try / catch

try { manager.Sign(parts, cert); }
catch (InvalidOperationException ex) when (manager.ReadOnly) { log.Error("Package is read-only", ex); }

Prevention

When it happens

Trigger: Creating a Package or the manager from a stream/file opened FileAccess.Read or PackageOpenMode.ReadOnly and then calling any Sign/Countersign overload.

Common situations: Opening a package from a stream that was opened with FileAccess.Read; consuming a signed package from disk without write permission and then attempting to re-sign; opening from a read-only network share or embedded resource stream.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/44232e0f57dfcca7. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/WindowsBase/System/IO/Packaging/PackageDigitalSignatureManager.cs:445

        /// tags, it follows that this Manifest tag must include at least one Reference tag.
        /// This means that every signature include at least one of a Part to sign (non-empty parts tag)
        /// or a Relationship to sign (non-empty relationshipSelectors) even if such a signature
        /// is only destined to sign signatureObjects and/or objectReferences.
        /// This overload provides support for generation of Xml signatures that require custom
        /// Object tags.  For any provided Object tag to be signed, a corresponding Reference
        /// tag must be provided with a Uri that targets the Object tag using local fragment 
        /// syntax.  If the object had an ID of "myObject" the Uri on the Reference would
        /// be "#myObject".  For unsigned objects, no reference is required.</remarks>
        public PackageDigitalSignature Sign(
            IEnumerable<Uri> parts, 
            X509Certificate certificate,
            IEnumerable<PackageRelationshipSelector> relationshipSelectors,
            String signatureId,
            IEnumerable<System.Security.Cryptography.Xml.DataObject> signatureObjects,
            IEnumerable<System.Security.Cryptography.Xml.Reference> objectReferences)
        {
            if (ReadOnly)
                throw new InvalidOperationException(SR.CannotSignReadOnlyFile);

            VerifySignArguments(parts, certificate, relationshipSelectors, signatureId, signatureObjects, objectReferences);

            // substitute default id if none given
            if (string.IsNullOrEmpty(signatureId))
            {
                signatureId = "packageSignature";   // default
            }

            // Make sure the list reflects what's in the package.
            // Do this before adding the new signature part because we don't want it included until it
            // is fully formed (and delaying the add saves us having to remove it in case there is an 
            // error during the Sign call).
            EnsureSignatures();

            Uri newSignaturePartName = GenerateSignaturePartName();
            if (_container.PartExists(newSignaturePartName))
                throw new ArgumentException(SR.DuplicateSignature);

View on GitHub (pinned to 81131a70a4)