Skip to content

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

@benstevens48

Description

@benstevens48

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-DeploymentIssues related to packaging, installation, runtime (e.g., SelfContained, Unpackaged)feature proposal

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions