WindowsAppSDK: Serious flaws in the way publisher Id/package family name are generated means packaged apps cannot be used outside the Store and LOB scenarios

This is a long post but the headline is this - if you distribute a packaged app outside the Store then the lifetime of your app is tied to the lifetime of your company’s registered address. Actually, the situation is even worse than this, as I’ll explain below. In fact your app’s guaranteed lifetime is 1-3 years even if you don’t change name or address. This is simply not a tenable situation, and needs urgently fixing by Microsoft.

Disclaimer: this is based on my limited testing and reading of documentation. I am happy to be corrected if I have misinterpreted anything. Also, apologies if this is the wrong issue type - for me this is too serious for to be put under the ‘Discussion’ tab.

This applies to the situation where you have a packaged app that you want to distribute to users. It includes the Store and internal line-of-business apps as well, but there are mitigations for those. The most serious implication is for companies or individuals wanting to distribute packages apps via their own website instead of the Store. It also does not apply so much to Microsoft themselves since they have control over trusting their own certificates.

Background

In order for the end user to be able to install a packaged app it needs to be signed using a code-signing certificate. This can be done with Visual Studio or SignTool. However, one important restriction is that the Publisher string that you specify in the Identity section of the package manifest must exactly match the subject. This is not so much of a problem in itself, but is when combined with the rest of the process. Note that no normalisation is performed and even the order in which things appear in the subject matter. For example, if your certificate subject says this

CN=MyPublisherCommonName, O=MyOrganization, C=MyCountry, L=MyCity2

and your Publisher entry in the package manifest says this

CN=MyPublisherCommonName, O=MyOrganization, L=MyCity2, C=MyCountry 

then SignTool will fail with the error

SignTool Error: An unexpected internal error has occurred.
Error information: "Error: SignerSign() failed." (-2147024885/0x8007000b)

The certificate can be self-signed, however if so the certificate will need to be manually added to a trusted machine-wide certificate store on the user’s computer (which requires admin access I think), and this is not feasible outside internal line-of-business apps. The only way to get an automatically trusted code-signing certificate that will allow the end-user to install the app easily is to sign it with an EV code-signing certificate issued by a recognised certificate authority (unless the app is installed from the Store). Now, how do you get such a certificate? Well, you have to request one from a certificate authority, for a minimum charge of $250 a year, and the certificate lifetime is between 1-3 years – it cannot be longer than 3 years. Also, the information you enter on the certificate has to correspond to your company’s official details and is subject to rigorous checking. Therefore you have no choice but to have your company’s name, country, state and city in the certificate subject. Moreover, the certificate authority may choose to include other details such as street address in the subject, and you don’t really have control over this.

Now, for the final piece of the story. Every package ends up being associated with a PublisherId on install. Here is the most serious major almost unbelievable flaw. It is computed using a hash of the exact Publisher string that you entered in the package manifest. It doesn’t even perform any normalization accounting for order. Here are some examples of the PublisherId for different publisher strings.

Publisher: CN=MyPublisherCommonName, O=MyOrganization, C=MyCountry, L=MyCity2
PublisherId: 0rxggyxen88sc

Publisher: CN=MyPublisherCommonName, O=MyOrganization, L=MyCity2, C=MyCountry
PublisherId: 6jx1svrqfke3r

Publisher: CN=MyPublisherCommonName, O=MyOrganization, C=MyCountry, L=MyCity2, STREET=MyStreet
PublisherId: 30tj3s0hg646y

What is the PublisherId used for? Well, it’s used to compute the PackageFamilyName and PackageFullName of your package (along with the PackageName you specify), which determines the install directory and app data directory, and the PackageFamilyName is effectively the identity of your app package for most purposes. So what happens if I publish an update to my app that has a different Publisher string, and hance a different PublsiherId? Well, when the user tries to install it, it is essentially treated as a different app. After double clicking the msix/msixbundle, the user will see a dialog offering to install the app (not to update the existing one). After they click install, they will get an error. For example, here is what happens for various Publisher string changes to an otherwise identical app upon install (assume v1.0.6.0 is already installed).

v1.0.6.0, Publisher: CN=MyPublisherCommonName, O=MyOrganization, C=MyCountry, L=MyCity2
v1.0.7.0, Publisher: CN=MyPublisherCommonName, O=MyOrganization, L=MyCity2, C=MyCountry
v 1.0.8.0, Publisher: CN=MyPublisherCommonName, O=MyOrganization, C=MyCountry, L=MyCity2, STREET=MyStreet

Trying to install v1.0.7.0:
App installation failed with error message: Windows cannot install package MyPackageName_1.0.7.0_neutral_~_6jx1svrqfke3r because a different package MyPackageName_1.0.6.0_neutral_~_0rxggyxen88sc with the same name is already installed. Remove package MyPackageName_1.0.6.0_neutral_~_0rxggyxen88sc before installing. (0x80073cf3)

Trying to install v1.0.8.0:
App installation failed with error message: Windows cannot install package MyPackageName_1.0.8.0_neutral_~_30tj3s0hg646y because a different package MyPackageName_1.0.6.0_neutral_~_0rxggyxen88sc with the same name is already installed. Remove package MyPackageName_1.0.6.0_neutral_~_0rxggyxen88sc before installing. (0x80073cf3)

In addition, some APIs such as SystemIdentification.GetSystemIdForPublisher and HardwareIdentification.GetPackageSpecificToken, which may be used for licensing or similar scenarios, will return different values if the PublisherId changes.

Implications

You cannot guarantee your app lifetime to be longer than the lifetime of the code signing certificate, which is 1-3 years. In particular, if your company moves city during this period, when you come to renew the certificate, you will need to specify a different city, which will result in a different subject, thus a different Publisher string in your package manifest, thus a different PublisherId and thus your app will now be treated as a different app. The same applies if you change your company name. If the certificate authority includes the street address on your certificate, then the same applies if that changes. Moreover, since you cannot guarantee exactly which information the certificate authority will include in the subject, and they may change this in 3 years’ time or put the information in a different order, even if your company details stay the same, you cannot guarantee the subject on the certificate will be the same when you renew it, in which case you end up in the same position where your app is now treated as a different app.

It goes without saying that being treated as a different app is very bad. The user will have to manually update, and you will lose access to user settings and data unless you instruct them to migrate the data or if your app has sufficient permission to read the old app’s data. Also, if you issued any device licenses based on SystemIdentification.GetSystemIdForPublisher or HardwareIdentification.GetPackageSpecificToken, you will need to reissue them, which could be very awkward. Ultimately this means that it is currently not really possible to distribute packaged apps to users outside of the Store, or LOB scenarios where the certificate can be self-signed. If you do, you are making a big gamble on being able to sign the app with a certificate with exactly the same Publisher string in 1-3 year’s time.

Suggested solutions

Note that another scenario that should be supported that I didn’t mention above is where one company takes over an app from another company. This obviously involves a publisher name change so is affected by the above issues. The solutions here will try to address that as well.

Option 1 - A SigningId that is carefully issued

Instead of generating the PublisherId from a hash of the Publisher string, we should replace the PublisherId with a SigningId. The SigningId can be placed in the subject of the signing certificate, for example as the OU (organisational unit), or use a custom OID or whatever seems appropriate (maybe a specially designated OID is best). The Publisher string in the package manifest can be used as it is now, although I would suggest that it would be better to only require it to match the certificate subject up to normalization, but this is not really important with this method. The main point is that the PublisherId is simply set to be equal to the SigningId as specified in the Publisher string/signing certificate subject. Now the problem becomes how to trust this Id. Well, I suggest that this SigningId is treated a little bit like a domain name. After you register an account with a certificate authority, you can request SigningIds. These are globally unique Ids. You can also add an existing SigningId, but to do so requires a transfer process, much like domain names currently, so you transfer it from one certificate authority to another with authorization from both parties. Unlike domain names, we probably should allow a copy of SigningId to remain with the old account as well in case a company transfers only one of its apps, and it has more than one app that shares a SigningId. Using this method we can trust that the SigningId is authorized by the publisher. This allows all other company details to change without the app being treated as a different app, as long as the SigningId stays the same.

(Bonus suggestion. There is a slight issue to be overcome in the case where the app is transferred from one company to another. In this case the SigningId will be different to the one used for other apps by the new company. This is fine or most purposes, except if they are relying on the value of an API like SystemIdentification.GetSystemIdForPublisher to be the same for all their apps (which is probably quite rare). In order to support this scenario, I would propose that SystemIdentification.GetSystemIdForPublisher can have an overload where you specify what identifying information you would like to use for the publisher. For example, we can have a None option that allows a publisher-independent result and is limited to desktop apps or UWP apps with a capability. We can have the default SigningId, and we can have one or more other options such as OrganizationAndCountry, which should verified by the signing process, then the back-end service for the app can store more than one value to be resilient to changes in the SigningId or company details.)

Option 2 - A SigningId tied to a private key

This is much like Option 1 except might be easier to implement. The idea is that the PublisherId is replaced by a SigningId which is tied to a specific private key. The downside is that a lost or compromised key means the app signed with the new key will now have to be treated as a different app. The idea is that when you request your certificate from the certificate authority, you require them to use the public key associated with a private key that you own. (At the moment I think the certificate authority will generate the key-pair for you in most cases). Then, the SigningId is computed as a hash of the public key in the certificate. For convenience, we should probably put this SingingId in the app manifest, but ultimately it can by computed and verified from the public key of the certificate. We set the PublisherId to the SigningId. This is really very similar to option 1. If we want to sell the app to another company, we given them the private key (if we agree on that).

Option 3 – mitigations to the current system

This isn’t really a solution. It’s simply how to mitigate the current system. The package manifest should have a field specifying which parts of the certificate subject it would like to use to generate the PublisherId. In fact, we could just let this be the Publisher field and remove the requirement that it is an exact match for the certificate subject, but for now let’s consider a separate field. It’s OK for the PublisherId to be computed as a hash of the exact contents of this new field. However, there are some verification requirements depending on the source of the package etc. If the package is from the Store, this field should be CN=xxxxxxxx, where xxxxx is the Store’s publisher id. If the Package is from outside the Store, then it must either be a match of the certificate subject up to reordering, or it must contain at least the organization name and county (which should be enough to be a unique identifier). (Possibly it should require the CN as well as/instead of the Organization name but I think these are usually the same). These should match the info on the certificate. We should also consider making the checks case-insensitive where appropriate.

This mitigation is still not ideal, but it would allow the company to change address within the same country, and it would also account for any minor changes to the subject of the certificate issued by the certificate authority (e.g. reordering or including extra information).

A note on Store apps

Store apps already use the form CN=xxxxx where xxxx is some sort of GUID, for the Publisher string, so are largely immune to this problem. However, there is still a problem if you want to transfer an app to another account. Note that xxxx is associated with a particular Store account. I have not transferred an app myself, but I imagine this string might be updated to a new value if you transfer it to a new account. If not, then the following paragraph does not apply, and I apologise.

Assuming that the PublisherId of the app is updated when moved from one account to another, this will cause the same issue with APIs like SystemIdentification.GetSystemIdForPublisher` to return a different value, which is a problem, so the Store could also benefit from Option 1 or 2 above. Note that the Store currently does not allow transferring apps to another account if they have In-app purchases (which is most apps that make money). I am wondering if this is a technical limitation due to the PublisherId issue, in which case Option 1 or Option 2 could solve this, or if it’s just a general deficiency with the Store, which wouldn’t surprise me. (I am currently in the situation where I want to transfer an app but can’t because it has In-app purchases).

Call to action

If I am wrong about any of this, please let me know. However, if I am correct, I implore Microsoft to seriously consider solutions to this problem. I am on the verge of releasing my packaged app for download outside the Store. However, this major flaw has made me question whether I can actually do so.

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 7
  • Comments: 28 (3 by maintainers)

Most upvoted comments

@riverar - to answer your points

The string representation of Distinguished Names is codified in standards. I don’t believe this is a real concern (e.g. rfc1779).

The same subject may be written in multiple ways and still conform to standards. See the example I gave above

CN=MyPublisherCommonName, O=MyOrganization, C=MyCountry, L=MyCity2
CN=MyPublisherCommonName, O=MyOrganization, L=MyCity2, C=MyCountry

EV certificates are not required in both Store or non-Store scenarios as far as I know. But you’ll probably be hard-pressed to find a non-EV code signing certificate because CAs want to charge you more money.

I haven’t actually tried it because it’s costly to experiment. But according to most of the CAs websites, if you want the installation smart screen message to say ‘Trusted publisher’ with a green tick instead of ‘Unverified’ or something like that, you need an EV certificate. But anyway, most of what I said also applies to non-EV certificates.

This is easily solved with timestamping.

Yes, I’m only talking about updating apps, I know that existing apps without any updates will continue to function.

True. But moving a registered business is very laborious and probably not common.

Moving a registered business is not very laborious, at least here in the UK. You just have to fill in an online form (plus change your address in a few other places just like you would if moving house). For a business that only sells virtual products it’s quite easy. Also, it is probably quite common for small businesses to change address. For example, I just set up a Limited company and am unsure my address will be the same in 3 years.

I don’t see this as a flaw because again, this string representation should be one form that is aligned with specifications.

This combines with the previous requirement of exact matching to produce a flaw, since

CN=MyPublisherCommonName, O=MyOrganization, C=MyCountry, L=MyCity
CN=MyPublisherCommonName, O=MyOrganization, L=MyCity, C=MyCountry

produce different results for the PublisherId.

Yep, different publisher = different app

Do you really think that these publisher strings constitute a different publisher in each case?

CN=MyPublisherCommonName, O=MyOrganization, C=MyCountry, L=MyCity
CN=MyPublisherCommonName, O=MyOrganization, L=MyCity, C=MyCountry
CN=MyPublisherCommonName, O=MyOrganization, C=MyCountry, L=MyCity, STREET=MyStreet

You have to take all the requirements together and then look at the end restriction that you get. This means that to update your app in 3 years time with a new signing certificate, the subject of the certificate must be exactly the same. As you can see, if you change your address this is not possible (unless perhaps you can reach an agreement with the CA to use your old address which seems doubtful plus your address will then be wrong on the certificate). Why should small companies who are more likely to change their address be penalized? Also, can you guarantee that the CA will put exactly the same information in the subject of the certificate? For example, maybe due to popular demand, they decide to start including the STREET in the subject, having previously not included it. Maybe you can get the CA to put exactly the information you want in the certificate, but I am slightly doubtful they would respond to an individual request. These things are quite hard to investigate, but whether or not I can update my app certainly shouldn’t rely on the outcome of that question.

I would like to emphasize that this situation directly impacts me. I have just set up a Limited company and was planning to make my packaged app available outside the Store, but now I’m very uncertain whether that is a good idea due to the high chance of not being able to update in 3 years’ time (or 1 year’s time if I get a 1 year certificate). I would say the chances of the business address changing are around 50%. Then, as I said, there’s also a large uncertainty around whether the CA will keep the subject exactly the same even if my company details stay the same. So it looks to my like the chances of me having to release my app as a completely separate app in 3 years time are > 50% as things stand. And forcing users to install a new app loses a lot of users, so I don’t really consider it to be a sensible option.

Just to give back to the community, this is what I’ve done. Firstly, I wrote and debugged a Powershell script, just to remember that by default script execution is restricted 😄 So I wrote a program that does more things (scans the system for “our” packages, reporting, etc) and included the script inside it. Here’s it:

           # This script backs up user data, removes and installs the app
           # It's needed for a workaround for MS bug - the publisher's ID gets lost when the code signing certificate's Subject changes
           # https://github.com/microsoft/WindowsAppSDK/issues/650
           # https://github.com/microsoft/msix-packaging/issues/365

           $ProgressPreference = 'SilentlyContinue' # This disables xml-formatted output to the console

           # These variables are unique to the app
           $pkgName = "{{pkgName}}"
           $newPkgUrl = "{{newPkgUrl}}"
           
           echo "Re-installation script is started for package [$pkgName] from [$newPkgUrl]"

           $tempDir = "$env:LOCALAPPDATA\TempSettingsMigration"

           $pkg = Get-AppxPackage -Name $pkgName
           $settingsPath = "$env:LOCALAPPDATA\Packages\$($pkg.PackageFamilyName)\LocalCache\Roaming\<your folder>\"
           $migrationDir = "$tempDir\<your folder>"

           # This directory might not exist if settings are saved in a non-containerized path
           $isDirExists = Test-Path -Path $settingsPath -PathType Container

           echo "Directory $settingsPath exists: $isDirExists"

           if ($isDirExists)
           {
             Copy-Item -Path $settingsPath -Destination $migrationDir -Force -Recurse
             echo "Settings are backed up to $migrationDir"
           }

           Remove-AppxPackage -Package $pkg

           echo "Package $pkgName is removed, installing a new one from $newPkgUrl. This can take a couple of minutes."

           Add-AppxPackage -AppInstallerFile $newPkgUrl

           echo "Package $pkgName is installed"

           if ($isDirExists)
           {
             Copy-Item -Path $migrationDir -Destination $settingsPath -Force -Recurse
             Remove-Item $tempDir -Recurse
             echo "Settings are restored from $migrationDir"
           }

           echo "Done"

It turned out, that embedding this script into an executable is the simplest way to deal with the problem. You can reference Windows SDK and try to remove and install packages directly, but that brings A LOT of dependencies and breaks AOT compilation. The AOTed app with this script in its core takes only 2 MB.

This is an item we are actively looking into.

@jvintzel - I see that you have made some progress on this, which is documented here - https://docs.microsoft.com/en-us/windows/msix/package/persistent-identity. That’s great, although it would have been nice to have an update here.

Essentially the solution you have implemented is to specify an ‘old’ publisher (as well as the ‘new’ publisher, which is used in the publisher field etc), and presumably the publisher ID is always generated from the ‘old’ publisher (it would be good to confirm this). Then, in order to verify this, the old -> new publisher mapping must be signed by both the certificate associated with the old publisher and the certificate associated with the new publisher.

I have a couple of concerns with this, and a couple of potential ways of addressing them. It would be great to get some feedback on whether these concerns can be addressed.

The first concern is that this mechanism requires the old certificate to still be valid at the time of signing the ‘mapping’ to the new publisher. I am concerned that, for example, if I bought a 1 year certificate, then only tried to renew it just after 1 year, and found that my address had changed (so the publisher string changed), then I wouldn’t be able to sign this mapping. I have a potential workaround. In this case, either Microsoft or a CA should offer a service to sign the mapping on my behalf if I can prove that I have authority from the ‘old’ publisher (I guess this would be a manual check). So basically they would generate a temporary trusted cert with my old publisher string, do the signing with timestamping then delete the cert. Does this sound like something Microsoft or a third-party CA could offer? Alternatively they could issue a temporary very short lifetime cert with the old publisher details to me and I could sign the mapping myself, but they may not be willing to do that.

The second concern is what if my publisher details change twice? E.g. I have a 1-year cert, I move address in that year, get another 1-year cert before it expires and sign the mapping, then I move address again and get another 1-year cert. Does the current implementation support chaining mappings together? If it doesn’t support chaining, I have an alternative workaround. Microsoft should offer a service where you can upload a signed chain of old/new publishers and it will collapse it into a start and end publisher. It can do this because, as mentioned above, it could generate a temporary trusted cert for the oldest publisher and then use that to sign the mapping to the new publisher. This could be completely automated since the chain would be signed with trusted certificates all the way through (taking into account timestamping) so no manual verification of identify would be required. Does this sound like a reasonable suggestion?

@orcmid That’s exactly right. My complaint with that is: How my app is supposed to gain the “reputation” it needs to stop those warnings from appearing if no one launches my app because of the scary warnings?

@wjk It seems that (1) the warnings are nowadays even scarier, (2) finding a way to over-ride the warning in the Edge downloader is difficult and casual users will not know there is such an avenue, let alone a way to find it, along with (3) File Explorer is now even more emphatic about warning attempts to execute such downloaded content (for .exe at least), (4) and this all seems more extreme than simply indicating lack of a (trusted/recognized) signature (reported as not a known producer). And the tendency for reports/messages to reflect inferences by report-authors that extend beyond the facts of the matter is very disappointing.

I suspect that the only feasible avenue for indy developers is the Store, even though that might not be safe against non-MS-app counterfeits. (I haven’t checked that lately.)