microsoft / WindowsAppSDK

The Windows App SDK empowers all Windows desktop apps with modern Windows UI, APIs, and platform features, including back-compat support, shipped via NuGet.
https://docs.microsoft.com/windows/apps/windows-app-sdk/
MIT License
3.78k stars 319 forks source link

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

Open benstevens48 opened 3 years ago

benstevens48 commented 3 years ago

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.

PylotLight commented 3 years ago

It took some time, but I was able to figure out how to make a code signing cert that VS accepted which traced back to our root CA, as I was able to get the publisher in the manifest to match the cert. Now I don't have to push any certs to deploy internally, so not sure I fully understand why you were saying you had to go buying certs and our company name isn't going to be changing anytime soon.. am I also missing something here? ;p

benstevens48 commented 3 years ago

@PylotLight - I'm talking about distributing apps to the general public, not internally within a company. In this case you'll need to buy a code-signing certificate from a recognised certificate authority in order for the certificate to be trusted on all Windows users' machines.

Well, it's nice that your not going to be changing your company name, but some people might decide to change their company name, and certainly their address might change. Again, this is mainly a problem when distributing to the general public, since it probably doesn't matter if your details are slightly incorrect on a certificate that's only used within your company.

jvintzel commented 3 years ago

This is an item we are actively looking into.

riverar commented 3 years ago

Note that no normalisation is performed and even the order in which things appear in the subject matter.

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

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 [...]

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.

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.

This is easily solved with timestamping. The idea is that you indicate to users that the signature was valid at the time it was signed and therefore should be trusted (if it's not explicitly revoked elsewhere, of course). Your existing signed code will be valid forever. Upon receipt of the new certificate, you would then cut new builds with the new certificate you were issued (typically CAs provide for an overlap to make this seamless).

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.

True. But moving a registered business is very laborious and probably not common. There are also a few escape hatches for Locality Presence Verification at a CA, like keeping your business registered in the original location and set up a DBA/fictional name elsewhere or submitting a Legal Opinion Letter.

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.

I don't see this as a flaw because again, this string representation should be one form that is aligned with specifications. Where are you getting these invalid DN strings?

[...] 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. [...]

Yep, different publisher = different app. Would agree this edge scenario is not covered at all.

benstevens48 commented 3 years ago

@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.

riverar commented 3 years ago

The same subject may be written in multiple ways and still conform to standards.

I was hoping to lean on RFC1779 for this. But it states that the components are to be presented in a little-endian order. (i.e. CN=L. Eagle, O="Sue, Grabbit and Runn", C=GB) completely undermining my argument 😂. Ultimately it's just one small piece of the bigger concern you brought up so I won't dwell on it.

I think your proposed solutions are a great start for discussion 👍 Another idea would be the inclusion of some sort of PreviousThumbprints element in our manifest to track previously owned/used certificate thumbprints. I believe this is very closely aligned with the signing ID concept you introduced.

Hope to hear more from @jvintzel and crew on this. I'll see if I can jab them during the currently on-going MVP Summit as well.

benstevens48 commented 3 years ago

@riverar - Actually, maybe you are right about the ordering being specified. However, there are still ambiguities between which things are 'bigger' than other things, and also, yes this is only a small and non essential part of the problem. However, for the sake of completeness, I include the 'Subject' from Comodo SSL's own website certificate, followed by SSL.com's one.

Comodo

Subject: serialNumber = L07000113321, jurisdictionC = US, jurisdictionST = Florida, businessCategory = Private Organization, C = US, postalCode = 33701, ST = Florida, L = Saint Petersburg, street = 146 Second Street N. Suite 201, O = "Rapid Web Services, LLC", CN = www.comodosslstore.com

SSL.com

Subject: C = US, ST = Texas, L = Houston, O = SSL Corp, serialNumber = NV20081614243, CN = www.ssl.com, businessCategory = Private Organization, jurisdictionST = Nevada, jurisdictionC = US

These were decoded using OpenSSL. I'd need to do more investigation to confirm what if any normalization OpenSSL does upon decode. But, as you can see, you don't really want to be relying on the exact value of these. (Also I appreciate that these are not code-signing certificates, but the principal is similar, unless there is some addition standard for code-signing certificates that I don't know about).

Perhaps there is some normalization being done in terms of spacing, and in fact the entire publisher string seems to be reversed in the certificate compared to the package manifest, but the reordering of individual terms certainly is not covered by the normalization process as I specifically tested that as I describe in the long initial post.

wjk commented 3 years ago

I definitely agree with all these points. I am trying to migrate from WiX to MSIX, as WiX-based installers cause scary-sounding but spurious app-might-be-malware warnings to be shown that make my business look bad. MSIX files don’t emit these warnings; only EXE-based installers and MSI files do. I have purchased an IV code-signing cert from SSL.com (being ineligible for an EV cert because I am not an incorporated entity).

The best workaround I can think of is to sign my app with a self-signed cert and deliver an installer EXE that would import the cert before installing the package (signing that with my commercial certificate), but doing this would cause those stupid might-be-malware warnings to return, and I am trying to avoid them. (It would also render my app ineligible for AppInstaller-based automatic updates AFAIK.)

Unfortunately, no matter how we decide to fix this, this fix can be made only in Windows itself, meaning that apps that run on any build older than the latest release (and probably an Insider-only release at that) cannot be distributed using MSIX without running into this problem. This directly conflicts with the goal of Reunion to be equally functional on downlevel versions of Windows.

orcmid commented 3 years ago

I definitely agree with all these points. I am trying to migrate from WiX to MSIX, as WiX-based installers cause scary-sounding but spurious app-might-be-malware warnings to be shown that make my business look bad. MSIX files don’t emit these warnings; only EXE-based installers and MSI files do....

Are you talking about Edge downloader warnings and warnings on attempts to execute the installer? My impression is there is also a kind of statistical tracking that comes into play.

wjk commented 3 years ago

@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? It seems like a chicken-and-egg situation. I even submitted my installers to Microsoft so they could pre-screen it for malware; got a clean bill of health but the warnings still didn’t go away.

sylveon commented 3 years ago

In my experience it goes away after a few days whenever I release a new update.

benstevens48 commented 3 years ago

@jvintzel - has there been any progress on this yet? Are you able to give any basic updates such as

Also, I had another thought about the simplest possible solution that doesn't really involve any changes to Windows. It goes as follows, and is a simplification of Option 1 above.

Option 4 - A SigningId that is carefully issued (simplified)

Certificates are issued directly by Microsoft, to avoid the complication of having to set up a system for transferring signing Ids between certificate authorities (but that could be done if needed). You register an account with a the Microsoft certificate authority and automatically get one SigningId, which is a globally unique Id. You can also request additional SigningIds to use with different apps, which will make it easier to transfer the ownership of an individual app to another account. To sign an app, you request a signing certificate associated with one of these signing Ids. Microsoft should obviously verify the individual or company credentials and could charge a fee, but it shouldn't be too expensive because these certificates may be suitable for signing MSIX only (due to the strange subject line as described below). Being suitable for MSIX only could also mollify existing certificate authorities.

The certificate subject looks similar to Store apps and has the form CN=SigningId. This means the publisher Id is generated from the signing Id as it is currently and there is no need to change that logic. (I'm assuming here that Windows will accept such a certificate as long as it has a trusted root). The only problem with this is if you try to install the app and you hover for more info about the publisher, then you will just see CN=SigningId. Therefore I suggest that the actual publisher info could be stored in other metadata on the certificate, such as the subjectAltName. Then the installer UI can be updated in future Windows builds (and perhaps backported) to display this information if the subject is of the form CN=SigningId or perhaps if a certain metadata flag in the certificate is set.

orcmid commented 3 years ago

@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.)

wjk commented 3 years ago

@orcmid Yeah… I am told that EV code-signing certificates are exempt from this “reputation” nonsense, but they are not sold to individuals. A one-person owned-and-operated LLC is in theory acceptable, but I haven’t been able to confirm that one actually would be with the CA I do business with. Further, creating an LLC opens a can of worms of legal compliance and tax headaches that I am just not prepared to deal with (not to mention annual company/DBA registration fees, which I understand are required by state law where I live, and which I cannot afford with currently very meager income). ☹️

I have not yet tried installing an MSIX signed with my non-EV cert, however. From what I understand there are fewer warnings when installing an MSIX than a traditional MSI or EXE installer. However, the changing PFN problem has stopped me from shipping any real-world apps via MSIX, due to how it would break HardwareIdentification.GetPackageSpecificToken.

benstevens48 commented 2 years ago

@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?

sylveon commented 2 years ago

Does the current implementation support chaining mappings together?

yes, from the docs

Each line must contain a pair of XML and CAT file paths. The artifacts must be ordered as they are applied. If you have two artifacts one for going Publisher1->Publisher2, and another for Publisher2->Publisher3, you must list first the one for Publisher1->Publisher2

One concern I have however, is I need the ability to do this from VS - not everybody builds their packages manually, and in some cases (packages with XAML for example) it's actually very hard to do manually.

benstevens48 commented 2 years ago

@sylveon - thanks for the info re chaining. Not sure how I missed that! Yes, VS tooling for MISX is currently very basic and needs improving (I haven't tried the new VS yet though).

mikehearn commented 2 years ago

A simple fix for the expiry issue would be to just not check expiry times on the old cert at all.

sylveon commented 2 years ago

This would completely defeat the point of certificates having an expiration date.

mikehearn commented 2 years ago

The original justification for expiry times, according to the oldest RFC I could find (some years ago), was to keep CA CRLs to a manageable size by allowing expired-and-revoked certs to be dropped. These days there is OCSP so this rationale is less relevant than it once was, and expiry causes many thorny problems along with frequent major outages. In theory you could e.g. not check expiry but force check OCSP when verifying a migration (which is at any rate a one time operation). In practice probably some CAs won't let you revoke an expired cert so that wouldn't work.

Given that the migration is a one off and the user is likely to discover the issue relatively quickly after getting a new cert, you could also just check the expiry time against a rolled backwards clock, creating a grace period of e.g. a few months.

The issue with the current implementation is that for occasionally updated app, the moment you discover your CA is changing your X.500 name may be the moment you buy a new cert because your old one expired, at which point it's too late. This seems like a plausible scenario and will cause some users to get stuck, even with the new support for migration. Fewer than before, true, but still not zero. The cost is clear, the benefit is less clear. If the user's key is compromised within such a grace period then someone could sign a bad transition file, but they'd still need to actually get access to the servers hosting your packages in order to upload the bad software update. And presumably if they can compromise the old cert they can probably compromise the new one and just sign it without faffing around with transition catalogue files anyway because the reason to buy a new cert was merely routine expiry policies, not any actual security incident.

LevYas commented 7 months ago

I don't believe this issue exists at all. I have an app, which was signed with a 1-year OV cert. The cert expired, we worked couple of months on a big update and did not update the cert. Now we are ready to publish a new version, we bought an EV cert with a huge "Publisher" string and now my app did not pick the update, and manual update results in the error

The appinstaller uri https://....azurestaticapps.net/appname.appinstaller is already being used by another package and cannot be applied to package appname_68dzga3d03kcg until the association is removed.

Looking in the event viewer, I also found this:

The package family name (appname_68dzga3d03kcg) from the .appinstaller file does not match the expected package family name (appname_m80w9zk83sp2a) that has previously been associated with the appinstaller uri. Please ensure the .appinstaller points to a package within the existing package family, or uninstall the existing package family, or move the .appinstaller to a different uri.

So, what should I do now? Ask all users to reinstall the app? But this will erase all their local settings, which are stored in the "container". So this is ridiculous. I can't change the previous or current Subject string of the cert, so it looks like I can't change the package family name. @jvintzel @BenJKuhn or anybody - any ideas? Highly appreciated.

On the "MSIX persistent identity" - how should I create the package if I don't use makeappx.exe but msbuild Installer.wapproj? In theory, I can manipulate the system time to sign using the old certificate, but do I need to do it every update? Also, the new cert is stored in Azure KeyVault HSM, so I will not be able to do that when the new cert expires. There should be a way to make this simpler, now it's an annoying complication that does not produce any value to anybody.

sylveon commented 7 months ago

Signing the persistent identity file now won't do much, it needs to be signed and time-stamped while the old certificate is still valid.

AFAIK VS doesn't support adding the persistent identity file from application packaging projects, only manually using makeappx does.

mikehearn commented 7 months ago

Unfortunately your only options are:

  1. Distribute an installer (win32 exe) that can remove the old package and install the new one. This is how Hydraulic Conveyor manages identity transitions for our customers, for example.
  2. Convince the CA to sell you a certificate with the old name. Unfortunately, my experience has been that CAs are rarely helpful, but it's possible yours is different.
sylveon commented 7 months ago

You could also theorically buy a separate OV cert with the original subject, then use it to sign (and timestamp) a persistent identity file. Since identity is based on the subject, not the thumbprint, it should work.

LevYas commented 7 months ago

Thank you guys for the support.

  1. Indeed, generating that stupid (sorry, I'm so frustrated) persistent identity won't do much even if I:

    • manage to sign that file with the expired cert with a changed system time
    • find a way to integrate that into .wapproj installer project

    since this "feature/hack" still exists only on Win11 and the majority of our users are still on Win10, so it's not even worth the effort (or money for buying a separate OV cert).

  2. Distribute an installer - looks like it might be the option. Even a powershell script might work I guess. BTW, @mikehearn - great product. If I knew MSIX would cause so much pain, I think we could switch to the Conveyor, although we still need to produce self-update for pre-Win 10 computers, so a lot of custom code anyway.

  3. It's impossible to convince CA because the cert type changed. Previously, it was OV and had only 4 fields in the Subject string, now it is EV cert and it has 9 including serial number and some codes.

riverar commented 7 months ago

It's also worth noting a portion of or all of MSIX is transitioning over to Microsoft India Development Center. So I wouldn't hold your breath for any major revelations here or any substantive updates to MSIX in the near future.

LevYas commented 7 months ago

Thanks for the addition, @riverar . Looks like it's gonna be dead just like WPF.

LevYas commented 7 months ago

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.