Hack TwinUI to force Windows Store Apps run on low resolution screens

Windows Store Apps on Lumia 640 XL.

Windows 8 and Windows 8.1 has a minimum screen resolution constraint for Windows Store Apps (aka. Metro Apps or whatever). If the screen resolution doesn’t meet requirement, user will see a prompt indicating the resolution is too low for these applications.

However, on certain platforms (like phones and single-board computers), it is not convenient to change resolution. Recently I am trying Windows RT 8.1 on Lumia 640 XL. Qualcomm has the resolution hard-coded in platform configuration, so I was unable to change the resolution. 1280 * 720 is not sufficient for Store Apps.

But there was an exception – the PC settings (aka. Immersive Control Panel) app. It always opens regardless of current resolution settings. So how can I force other applications to launch?

Let’s turn to TwinUI.dll. It’s one of the core components of shell infrastructure. Start IDA Pro, load TwinUI with symbols from Microsoft. Go ahead and search the existence of PC settings app. All Windows Store Apps are associated with a package family identifier. Let’s search it. In this case, it’s windows.immersivecontrolpanel_cw5n1h2txyewy.

Bingo. We found it in some functions.

PC Settings Package Family ID is hardcoded in TwinUI.dll. This function has been patched by me, so it doesn't reflect actual situation you get from official Microsoft binary.
PC Settings Package Family ID is hardcoded in TwinUI.dll. This function has been patched by me, so it doesn’t reflect actual situation you get from official Microsoft binary.

By checking it’s references, we learned that layout checking routine verifies whether it is a desktop application, or PC settings app when resolution doesn’t meet requirements. Either you can patch layout checking routine or PC settings PFN verification routine. I decided to patch the second one, however patching the first is probably a better idea.

On ARMv7-A platform, I simply patched initial register store operation and the branch. Instruction BLX call was replaced with a simple NOP(MOV R0, R0).

Patched function
Patched function

There are two version of the PC settings check routines, so I need to patch both. The other one is similar to this one. Patching the layout verification routine (actually a better idea, as this patch will have some trouble when launch files from desktop) / patching on other architectures should be similar to this one.

A case that Hyper-V freezes host operating system

Two days ago I set up a new virtual machine for applications that require isolated security. When I put my computer into Connected Standby mode, I noticed SoC fan didn’t stop. To verify whether it was OS inconsistency or persistent issue, I manually initiated a system restart. However, it froze at login screen then. Nothing responded, including Ctrl + Alt + Delete key sequence. Attempting to force shutdown and start the computer almost didn’t improve the situation. After about ten attempts I managed to enter my desktop. There was no clue in event viewer: everything went well then suddenly the system froze.

I remembered that an alternative OS loader entry was configured to bypass Hypervisor launch at startup. I selected this entry to see whether it was related to Hyper-V. Test results indicated the freeze issue was strongly related to Hyper-V.

I tried to remember what I did before virtual machine configuration. I removed a virtual switch bridged to Surface Ethernet Adapter on my Surface Dock, then added a virtual switch bridged to VMware NAT adapter (which works better with Wireless network). Then I checked adapters in Network and Sharing Center, the old virtual switch didn’t get removed at all – and the “Remove” menu entry was unavailable. At last, I removed this adapter from Device Manager, and the issue was resolved.

This issue is likely related to OS inconsistency. When Hyper-V infrastructure attempts to initialize (bring up) all enabled network adapters including these Hyper-V virtual switches, the “removed” adapter is brought up, then enters failure state due to inconsistency in configuration. Because host operating system is a virtual machine on Hyper-V (with privileges), the host OS didn’t even get a chance to record what happened at that point.

The good thing is that I fixed it by myself; The bad thing is I missed an opportunity to report this bug and let Microsoft fix it.

Migrate legacy UWP project system to MSBuild-based

When Microsoft decided to adopt MSBuild on .NET Core platform, project.json was not dropped immediately until first toolchain RTM arrives. Dotnet Development on Universal Windows Platform Development leverages .NET Core too, but the depreciation progress is significantly slower than other .NET Core platforms due to historical reasons. UWP uses project.json for package management and MSBuild for builds.

In Visual Studio 2017 April Update, Microsoft finally migrates new UWP projects to full MSBuild-based project system. But our projects, which creates on early 2015, doesn’t get an auto migration as expected. Hence we decided to migrate them manually for additional benefits like better toolchain stability and advanced customization features.

Reminder: Do not attempt to use “dotnet migrate” CLI command, it won’t work for UWP projects.

Migration Prerequisites

  • Notify all your team members. Make sure everyone has Visual Studio 2017 with April update installed.
  • If you have continuous integration environment configured, make sure build agents have NuGet 4.1 or higher installed (3.5 or 4.0 won’t work).
  • Lock VCS during migration to prevent additional incidents. (We’re using TFVC for source management so that it will be easy)

Migration

  • Clean up all projects (including bin and obj directories)
  • Iterate all project directories
  • Find C# project file, open with your favorite editor.
  • Add following property group before project file lists:
<PropertyGroup>
    <RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>

Okay, you’ve completed the first step. Then open your project.json file. Migrate all NuGet packages references as the picture below.

Package Reference
Package Reference

Finally, remove project.json and additional files like project.lock.json, *.nuget.targets, *.nuget.props. (Or your will get lots of warning that may lead .NET Native compilation fail)

Do it for every project. Then open Visual Studio, restore NuGet packages for all projects, build to validate and submit changes.

The Windows “Gatekeeper” Internals

"Rickrolling" in Windows SmartScreen

Windows 10 Insider Preview 15046 introduces the Windows-flavor “Gatekeeper“. It is similar to Gatekeeper in macOS, with some minor differences.

First of all, Windows “Gatekeeper” doesn’t block the execution of applications that don’t require installation. I tried to run PuTTY, a popular tool on Windows and it works.

Secondly, Windows “Gatekeeper” is based on Microsoft SmartScreen, which means disabling SmartScreen will turn it off too. Prior to application execution, SmartScreen will send file hash and publisher information(including certificate thumbprint) to Microsoft’s server, then SmartScreen server send back metadata including application reputation. Response is signed with a specific key that will be checked in client side for message integrity.

Unlike macOS, attempt to start application from console(e.g. Command Prompt and PowerShell) will trigger “Gatekeeper”.

Attempt to start application from PowerShell
Attempt to start application from PowerShell

The window is web-based. Although you can’t modify the response directly(no one wants to deal with sha256RSA unless the key leaks), you can attach a debugger to have some fun with it.

"Rickrolling" in Windows SmartScreen
“Rickrolling” in Windows SmartScreen

Microsoft claims that this feature is opt-in for most Windows SKUs (except Windows 10 Cloud AFAIK), and it is not revalent to UMCI (User-mode Code Integrity), which is enforced in Windows 10 Cloud.

Changing Microsoft Account alias is painful

Access deined for my new alias

A short update: The recent MSDN subscription migration kills my migrated account alias too. After contacting Microsoft support, I removed legacy alias from my account, create a new Microsoft Account using my legacy alias and restored my access to the new Visual Studio Subscription portal. In the same way, I removed legacy Microsoft Account in my Azure AD, linked two separated Microsoft Accounts(legacy and new alias) and resolved my issue accessing Visual Studio Team Services.

Such inconsistency always happens, and usually remove & add will be the universal solution in most cases.

After using legacy alias for almost 7 years, I decided to replace my Microsoft Account alias with a new Outlook.com email address due to increasing security concern of Netease Mail (my previous email service provider). Though I changed alternative recovery email to my domain email after several major security incidents, it looks weird to have an @163.com email alias linked to my Microsoft account.

Okay, I changed my alias the day before yesterday. It works. I didn’t delete the old one because I want to maintain some sort of backward compatibility. It works across my personal devices without any pain.

Annoying things came afterward days later.

Let’s talk about SSO/Federated Logon

Before talking about terrible things after switching to the new alias, let’s talk about Federated Logon. Technically speaking, Federated login is an authentication workflow based on trust relationships. Suppose Identity Provider A and Application B have successfully established two-way trust relationship by service provision. When a new user login attempt occurs, B redirects authentication challenges to Identity Provider A, with necessary metadata, like secure token ID, timestamp, nonce and finally something that validates the request, for example, digital signature, even token encryption. Since Application B has its own approach to understand Identity Provider A’s payload(so does B), the communication will be secured.

When Identity Provider A completes user authentication challenges(password, client certificate, fingerprint, etc.), it signs (encrypts maybe) authenticated user claims (user ID, user name and something else) and posts to B. The workflow image of WS-Federation below represents such workflow. OAuth and OpenID Connect have similar workflow with slight differences(multiple modes to retrieve user claims).

WS-Fed workflow from docs.oasis-open.org
WS-Fed workflow from docs.oasis-open.org

Microsoft Azure, Visual Studio Team Services and most Microsoft services use OpenID Connect. Believe it or not, you use Federated Logon and SSO every day.

Microsoft Account and Azure AD Account

They are two separated systems though they have something in common. Each Microsoft Account has a CID, a unique identifier in Microsoft Account system. All Microsoft Consumer services use CID to recognize your identity. For example, your Outlook.com email account is identified using your CID.

Azure AD Account handles it differently. Each Azure Active Directory have a tenant ID to identify AAD in AAD system. Each AAD contains objects: users, groups, computers, trust relationships….and more. Each AAD user has a unique alias in a specific AAD tenant. So the coexistence of 2ea6c0b4-cc49-42b8-9f1b-3f4aa653c719\imbushuo and b5093785-af31-4819-bf75-728d4474769c\imbushuo is possible.

Microsoft Accounts can be linked into Azure AD too: during the linking procedure, a new external user from Microsoft Account will be created in an AAD tenant, so you may have 2ea6c0b4-cc49-42b8-9f1b-3f4aa653c719\bill@live.com. When Bill wants to access resources in his tenant’s AAD, he will type bill@live.com in AAD Federation Service(Work and school account), a single sign on portal for Azure AD. Later, AAD FS will redirects the authentication challenges to Microsoft Account login portal. If Bill is authenticated in Microsoft Account login portal, he will be redirected back to AAD FS, with claims provided by Microsoft Account. Finally, AAD FS will tell the application that the user is Bill.

My blog uses such login mechanism too. See my management portal to get some idea about this if you don’t understand.

But…there’s no CID in Azure AD

But there’s something just works like CID: user alias. Another mapping! Microsoft Account will be mapped to Azure AD account, then the application will use the Azure AD account identity. After changing my alias in Microsoft Account, my Azure AD user alias remains the same. So I can login into my blog management portal with the same identity:

Logged in with the same ID
Logged in with the same ID

Do you remember that federation logon can carry multiple attributes at one time? So here’s the problem. My team’s source control service, Visual Studio Team Services, seems to use email address (which changes after rotating my primary Microsoft Account alias) to identity user. After logging in with my organization account, I found that my email address didn’t change after the rotation. To make the whole thing worse, I am the account creator, hence I cannot remove my Microsoft Account in VSTS to address the issue.

In short, the primary alias rotation didn’t change my user alias in Azure AD, but applications’ behavior vary based on how they deal with user claims.

 

Seems that I have to change my alias back. Yuck.

Using WASAPI Exclusive mode in Universal Windows Apps (Desktop)

Windows Audio Session API (WASAPI) was first introduced in Windows Vista. It offers advanced audio control and playback features for Windows Apps. Since it mitigates SRC issue in some aspects, WASAPI Exclusive Mode gains its popularity among music lovers. Windows Runtime supports a small subset of WASAPI APIs, including WASAPI Exclusive mode (in Windows 10).

However, here is one thing you should know: Windows 10 Mobile doesn’t support WASAPI Exclusive mode (by design).  As far as I know, only desktop platform is supported yet. Luckily, modern Windows Phone devices can choose the best format for shared mode using input wave’s format, so you don’t have to worry that (at least on Lumia 950 and Lumia 950 XL).

Audio Format

There’s no IMMDevice available in Windows Runtime. Try to create it via CLSID & IID will throw HRESULT Class Not Registered. It will cause some trouble getting all natively supported formats for Exclusive mode. A possible solution is using Properties property in DeviceInformation class in Windows Runtime instead of IMMDevice class & OpenPropertyStore method. Then, query the format using IsFormatSupported in IAudioClient2. Remember that in Exclusive Mode, system won’t return the best-fit wave format in this method, so you have to try all formats and select the best wave format by yourself. In my sample, I specified 44.1kHz / 16Bit / WaveFormat = 0x1, which is supported by my Surface Pro’s audio subsystem.

Initialize Audio Client

Then you can initialize IAudioClient2 in exclusive mode. Simply pass AUDCLNT_SHAREMODE_EXCLUSIVE in, specify Buffer Length and Wave Format. You may get some HRESULTs like AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED or AUDCLNT_E_BUFFER_SIZE_ERROR. Just check out this page and find solutions.

Perform Playback

Like HW-Offload mode, you don’t have to calculate available frames by yourself In event-based playback mode. The value for padding frames is exactly the same as available frames. Then return audio samples as what you do in shared mode.

Notes

It should supports Windows 8.1 desktop, but I haven’t test it on Windows 8.1 since I don’t have a Windows 8.1 desktop device.

I often noticed weird noise during playback when the system average load is high. I believe the root cause is process priority.

Windows 10 removed Background Audio category in WASAPI headers. In order to implement background playback, a customized out-of-process COM server or Media Foundation extension is required.

I don’t offer a demo program here, because it’s pretty easy to adapt the official sample to WASAPI Exclusive mode. 🙂

玩物丧志 · Yubikey 4 (智能卡登录)

Yubikey 是很早就有的东西了,然而受限于经济能力还有海关,直到最近有人提出来要团购我才买了个一个玩玩。我主要拿来做智能卡登录。

为什么呢?

虽然我用的电脑都装备有了 TPM ,但是这样还是有一些不方便的:

  • TPM 的数据基本上只留在这台电脑了(这也是优点)
  • 虽然我的电脑很轻,但是我不一定天天带出去,万一有事情需要连接到服务器上还是比较麻烦
  • 虚拟智能卡基本上一直在域账号里使用比较有效,然而我平时还是以登录个人账号为主(虽然电脑已经加入域)
 Yubikey 可以解决移动性的问题,而且出门不需要输入一个几十位复杂脑残的密码。
也许我哪天要玩玩 U2F 呢?这大概是以后的文章了。

准备智能卡证书

假定电脑已经加入域,域环境中已经部署了 Active Directory Certificate Services,并且 CS 服务器已经发布到 AD 中。

登录域账号,打开 Certificate Management,并 Request New Certificate。
一路往下,选择 Smart Card User / Smart Card  Logon 模板(我选的是前者)。
在 Private Key Configuration 下把私钥长度设置为 2048/4096,然后 CSP 不用动。
注:如果你选择了 Smart Card Crypto Service Provider,那么默认情况下在有 TPM 而且虚拟智能卡开启的情况下,会选择的是虚拟智能卡,在 Windows 证书选择里表示为“安全设备凭据”而不是“智能卡凭据”,但是功能类似。然后勾选允许导出私钥。
申请完证书,导出证书。
打开 Yubikey PIV Manager,并导入证书。
Screenshot of Yubikey PIV Manager
Screenshot of Yubikey PIV Manager

基本工作到此结束。下面来试试智能卡登录。

如果是要给 Active Directory Federation Services 打开智能卡登录支持呢?

很简单。进入 AD CS 管理界面,找到认证选项,在对应的 Intranet / External 认证模式里勾选证书登录,AD FS Web Proxy 启用49443端口即可。

Smartcard and ADFS
Smartcard and ADFS

效果大概如图所示。

未完待续

自制 Federation STS: MediaWiki x ASP.NET OWIN Identity

最近在做一个和 MediaWiki 扩展管理的项目时遇到一个问题:如何安全地把身份凭据传递给ASP.NET MVC的后端,而且共用一套账号系统。

这篇文章将简要讲述完成的过程。

了解 WS-Federation

在 ASP.NET OWIN Identity 里,最方便的实现 Claim Identity 凭据的方法也就是 WS-Federation。WS-Federation 最典型的一个例子就是Active Directory Federation Service,其具体工作流程可以简化为以下的图表:

WS-Fed workflow from docs.oasis-open.org
WS-Fed workflow from docs.oasis-open.org

在本文的场景中,MediaWiki 将充当 IdP 的角色。

获得 Federation Metadata

Federation Metadata是一个XML-Dsig签名后的XML文件,包含了 SP 需要的所有信息,诸如提供的 Claim Identity 类型,公钥,访问端点。一个简化的 Federation Metadata 如下文所示:

<?xml version="1.0"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" ID="_3ef47b02-f5a9-4a32-a48d-3ba56d6b270f" entityID="">
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
  <!-- 这里是签名,是 enveloped-signature + xml-exc-c14n -->
  <!-- 推荐 sha256RSA -->
  <!-- 记得带上公钥 -->
    </ds:Signature>
    <RoleDescriptor xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:fed="http://docs.oasis-open.org/wsfed/federation/200706" 
 xsi:type="fed:SecurityTokenServiceType" 
 protocolSupportEnumeration="http://docs.oasis-open.org/wsfed/federation/200706">
        <KeyDescriptor use="signing">
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <X509Data>
     <!-- 用于STS签名的公钥, base64 encoded -->
                    <X509Certificate></X509Certificate>
                </X509Data>
            </KeyInfo>
        </KeyDescriptor>
        <fed:ClaimTypesOffered>
   <!-- 提供的 Claim Identity Types,下面举例四个 -->
            <auth:ClaimType xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706" Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" Optional="true">
                <auth:DisplayName>UPN</auth:DisplayName>
                <auth:Description>User Principal Name</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706" Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" Optional="true">
                <auth:DisplayName>User Name</auth:DisplayName>
                <auth:Description>The mutable display name of the user.</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706" Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" Optional="true">
                <auth:DisplayName>Email</auth:DisplayName>
                <auth:Description>Email address of the user.</auth:Description>
            </auth:ClaimType>
            <auth:ClaimType xmlns:auth="http://docs.oasis-open.org/wsfed/authorization/200706" Uri="http://schemas.microsoft.com/ws/2008/06/identity/claims/groups" Optional="true">
                <auth:DisplayName>Groups</auth:DisplayName>
                <auth:Description>Groups of the user.</auth:Description>
            </auth:ClaimType>
        </fed:ClaimTypesOffered>
  <!-- 服务Endpoint -->
        <fed:PassiveRequestorEndpoint>
            <EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
                <Address></Address>
            </EndpointReference>
        </fed:PassiveRequestorEndpoint>
        <fed:SecurityTokenServiceEndpoint>
            <EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
                <Address></Address>
            </EndpointReference>
        </fed:SecurityTokenServiceEndpoint>
    </RoleDescriptor>
    <RoleDescriptor xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:fed="http://docs.oasis-open.org/wsfed/federation/200706" xsi:type="fed:SecurityTokenServiceType" protocolSupportEnumeration="http://docs.oasis-open.org/wsfed/federation/200706">
        <KeyDescriptor use="signing">
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <X509Data>
     <!-- 用于STS签名的公钥, base64 encoded -->
                    <X509Certificate></X509Certificate>
                </X509Data>
            </KeyInfo>
        </KeyDescriptor>
  <!-- 服务Scope -->
  <!-- 确保Scope和后续的Audience匹配 -->
        <TargetScopes>
            <EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
                <Address></Address>
            </EndpointReference>
        </TargetScopes>
  <!-- 服务Endpoint -->
        <fed:ApplicationServiceEndpoint>
            <EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
                <Address></Address>
            </EndpointReference>
        </fed:ApplicationServiceEndpoint>
        <fed:PassiveRequestorEndpoint>
            <EndpointReference xmlns="http://www.w3.org/2005/08/addressing">
                <Address></Address>
            </EndpointReference>
        </fed:PassiveRequestorEndpoint>
    </RoleDescriptor>
    <IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
        <KeyDescriptor use="signing">
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <X509Data>
     <!-- 用于STS签名的公钥, base64 encoded -->
                    <X509Certificate></X509Certificate>
                </X509Data>
            </KeyInfo>
        </KeyDescriptor>
  <!-- 服务Endpoint -->
        <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location=""/>
        <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location=""/>
        <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location=""/>
    </IDPSSODescriptor>
</EntityDescriptor>

STS 服务

SP在解析 Federation Metadata 后会发起请求。一般在OWIN-based里会带上如下的参数:
– wa: 动作。可以是wsignin1.0 (登录) 和 wsignout1.0 (登出)
– wctx: 上下文。附上即可
– wp: 可能有
– wreply: 如果手动指定,则返回到这个页面;如果不指定,根据应用默认注册情况来
– wtrealm: 应用ID

所有的参数都在 Query String 里。在收到 STS 请求后,IdP首先根据情况判断有无再次输入密码必要。然后判断身份,签发凭据。签发的凭据也是一个XML文档,里面包含有一个SAML文档(XML-Dsig),简化的格式如下:

<?xml version="1.0" encoding="utf-8"?>
<t:RequestSecurityTokenResponse xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
<t:Lifetime>
<!-- 需要标注有效时间 -->
<wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2016-02-12T05:13:30+0000</wsu:Created>
<wsu:Expires xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2016-02-12T06:13:30+0000</wsu:Expires>
</t:Lifetime>
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:Address>https://ligstd.com/STSTest</wsa:Address>
</wsa:EndpointReference>
</wsp:AppliesTo>
<t:RequestedSecurityToken xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<!-- 需要标注有效时间,AssertionID一般就是一个UUID -->
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" MajorVersion="1" MinorVersion="1" AssertionID="_7529070a-0300-4b65-bdee-df0e55c2c775" Issuer="签发者,参考metadata" IssueInstant="2016-02-12T05:13:30+0000">
<saml:Conditions NotBefore="2016-02-12T05:13:30+0000" NotAfter="2016-02-12T06:13:30+0000">
<saml:AudienceRestrictionCondition>
<!- 请注意这个必须和Scope匹配 -->
<saml:Audience>https://ligstd.com/STSTest</saml:Audience>
</saml:AudienceRestrictionCondition>
</saml:Conditions>
<saml:AttributeStatement>
<!-- 各种 Claim Identity结果 -->
<!-- 请注意不能有空的Attribute -->
<saml:Subject>
<saml:NameIdentifier>Imbushuo</saml:NameIdentifier>
<saml:SubjectConfirmation>
<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Attribute AttributeName="upn" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>Imbushuo@xxxx</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="emailaddress" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>i@xxxx</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute AttributeName="name" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
<saml:AttributeValue>Imbushuo</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
<saml:AuthenticationStatement AuthenticationMethod="urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" AuthenticationInstant="2016-02-12T05:13:30+0000">
<saml:Subject>
<saml:NameIdentifier>Imbushuo</saml:NameIdentifier>
<saml:SubjectConfirmation>
<saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
</saml:SubjectConfirmation>
</saml:Subject>
</saml:AuthenticationStatement>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<!-- 签名段,只需要对SAML签名。参考前面的XML签名 -->
</ds:Signature>
</saml:Assertion>
</t:RequestedSecurityToken>
<t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
<t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
</t:RequestSecurityTokenResponse>

然后将其进行 HTML 表单编码,封装到返回里。提供一段PHP代码作参考:

 $output->addHTML("<form method="POST" name="hiddenform" action="{$escapedToken}/">");
        $output->addHTML(' <input type="hidden" name="wa" value="wsignin1.0" />');
        $output->addHTML(" <input type="hidden" name="wresult" value="{$resultXml}" />");
        $output->addHTML(" <input type="hidden" name="wctx" value="{$this->wCtx}" />");
        if(isset($this->wp)){
            $output->addHTML(" <input type="hidden" name="wp" value="{$this->wp}" />");
        }
        $output->addHTML(' <noscript><p>Script is disabled. Click Submit to continue.</p><input type="submit" value="Submit" /></noscript>');
        $output->addHTML(' </form>');
        $output->addHTML(' <script language="javascript"> window.setTimeout('document.forms[0].submit()', 0); </script>');

在 MediaWiki 的工作流程

验证用户登录以及权限。如果不满足需求,返回权限错误。
通过 MediaWiki 的 User.php 里的函数获得必要的信息。
写 SOAP XML 和 SAML XML。用 xmlseclibs 对 SAML XML 签名,封入 SOAP XML,封入表单,返回特殊页面。
ASP.NET OWIN Identity 完成后续验证。

使用

新建 ASP.NET MVC v4.6 项目,认证模式选择 Work and School account,然后选择 On-Premise。输入 Federation Metadata 位置,输入 URI (如果实现了 App 注册,请输入对应的URI)
调试项目,已经可以使用。

备注

MediaWiki 的特殊页面输出非 text/html 有点麻烦,我选择了直接暴露一个在 extensions/文件夹/StsMetadata.php 的文件来暴露。
STS 所需的证书可以用 OpenSSL 生成。
由于 xmlseclib 在 URI 处的一些处理原因,目前似乎无法和 Azure ACS 直接工作,但是可以跟 ASP.NET OWIN Identity 工作。
一般来说,wsignout1.0 的处理就是销毁 Cookie ,注销 ST S这边的登录,ASP.NET 这儿会有 OWIN 自己处理,然后跟 wsignin1.0 的表单类似,但是不需要返回SAML数据。

推荐阅读

Understanding WS-Federation – MSDN
Web Services Federation Language (WS-Federation) Version 1.2

未完待续

Direct2D based blur effect in Windows Runtime Apps

Effects such as DropShadowEffect, BlurEffect were removed from Windows Runtime XAML. In order to achieve some goals, I need to write something like these.

Luckily Direct2D provides many useful effects for us, including Gaussian Blur, which is the effect I want.

At first I tried SharpDX, it worked well on Intel platform devices, but not ARM-based devices. To make matters worse, SharpDX‘s performance was not so good as I thought. So I had to write a C++/CX Windows Runtime Component and use it in my own Windows Runtime XAML project.
Here’s the result.

Windows Runtime XAML Render to bitmap sample with blur effect
Windows Runtime XAML render to bitmap sample with blur effect

To use Direct2D, I need to create device resources first. Create the Direct3D 11 API device object, and then get the Direct2D device object.

Note: To convert stream, see here: http://blogs.msdn.com/b/win8devsupport/archive/2013/05/15/how-to-do-data-conversion-in-windows-store-app.aspx

Then receive the bitmap and create WIC object. Finally get things ready and draw, and generate output file.

Note: Set D2D1_GAUSSIANBLUR_PROP_BORDER_MODE to D2D1_BORDER_MODE_HARD, you will get the iOS 7-like blur style.

Here’s the main source code:

D2DEffect.cpp


#include "pch.h"
#include "D2DBlurEffect.h"

using namespace Light::UI::Effects::Direct2D::BlurEffect;
using namespace Platform;
using namespace concurrency;

using namespace Microsoft::WRL;
using namespace Windows::ApplicationModel;
using namespace Windows::System;
using namespace Windows::Foundation;
using namespace Windows::Graphics::Display;
using namespace Windows::Storage;
using namespace Windows::UI::Core;

// Initialize hardware-dependent resources.
void BlurEffectImageProcessor::CreateDeviceResources()
{
// This flag adds support for surfaces with a different color channel ordering
// than the API default. It is required for compatibility with Direct2D.
UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

#if defined(_DEBUG)
// If the project is in a debug build, enable debugging via SDK Layers.
creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

// This array defines the set of DirectX hardware feature levels this app will support.
// Note the ordering should be preserved.
// Don't forget to declare your application's minimum required feature level in its
// description. All applications are assumed to support 9.1 unless otherwise stated.
const D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1,
};

// Create the Direct3D 11 API device object.
DX::ThrowIfFailed(
D3D11CreateDevice(
nullptr, // Specify nullptr to use the default adapter.
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
creationFlags, // Set debug and Direct2D compatibility flags.
featureLevels, // List of feature levels this app can support.
ARRAYSIZE(featureLevels),
D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Windows Store apps.
&m_d3dDevice, // Returns the Direct3D device created.
nullptr,
nullptr
)
);

// Get the Direct3D 11.1 API device.
ComPtr dxgiDevice;
DX::ThrowIfFailed(
m_d3dDevice.As(&dxgiDevice)
);

// Create the Direct2D device object and a corresponding context.
DX::ThrowIfFailed(
D2D1CreateDevice(
dxgiDevice.Get(),
nullptr,
&m_d2dDevice
)
);

DX::ThrowIfFailed(
m_d2dDevice->CreateDeviceContext(
D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
&m_d2dContext
)
);
}

///
/// Internal method referred from Bing.
/// Convert IBuffer to IStream.
///

///The buffer to convert. IStream* createIStreamFromIBuffer(Streams::IBuffer ^buffer) {
// convert the IBuffer into an IStream to be used with WIC
IStream *fileContentsStream;
HRESULT res = CreateStreamOnHGlobal(NULL, TRUE, &fileContentsStream);
if (FAILED(res) || !fileContentsStream) {
throw ref new FailureException();
}
Streams::DataReader^ dataReader = Streams::DataReader::FromBuffer(buffer);
// read the data into the stream in chunks of 1MB to preserve memory
while (dataReader->UnconsumedBufferLength > 0) {
UINT chunkSize = min(1024 * 1024, dataReader->UnconsumedBufferLength);
auto data = ref new Platform::Array(chunkSize);
dataReader->ReadBytes(data);
ULONG written;
res = fileContentsStream->Write(data->Data, chunkSize, &written);
if (FAILED(res) || written != chunkSize) {
fileContentsStream->Release();
throw ref new FailureException();
}
}
return fileContentsStream;
}

BlurEffectImageProcessor::BlurEffectImageProcessor()
{
IsInitialized = false;
}

///
/// Render image but not get the final image.
/// REMEMBER call DataInitialize method first.
///

///Indicates the the blur amount. ///Indicates the current display's DPI. IAsyncAction^ BlurEffectImageProcessor::RenderImage(float gaussianBlurStDev, float DPI){
return create_async([this, gaussianBlurStDev,DPI]{
if (!IsInitialized){
throw ref new Platform::Exception(1, "The class has not initialized.");
}

// Render it
UINT imageWidth;
UINT imageHeight;
m_wicFormatConverter->GetSize(&imageWidth, &imageHeight);

// Create a Bitmap Source Effect.
DX::ThrowIfFailed(m_d2dContext->CreateEffect(CLSID_D2D1BitmapSource, &m_bitmapSourceEffect));

// Set the BitmapSource Property to the BitmapSource generated earlier.
DX::ThrowIfFailed(
m_bitmapSourceEffect->SetValue(D2D1_BITMAPSOURCE_PROP_WIC_BITMAP_SOURCE, m_wicFormatConverter.Get())
);

// Create the Gaussian Blur Effect.
DX::ThrowIfFailed(m_d2dContext->CreateEffect(CLSID_D2D1GaussianBlur, &m_gaussianBlurEffect));

// Set the input to recieve the bitmap from the BitmapSourceEffect.
m_gaussianBlurEffect->SetInputEffect(0, m_bitmapSourceEffect.Get());

// Set the blur amount.
DX::ThrowIfFailed(m_gaussianBlurEffect->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, gaussianBlurStDev));
DX::ThrowIfFailed(m_gaussianBlurEffect->SetValue(D2D1_GAUSSIANBLUR_PROP_BORDER_MODE, D2D1_BORDER_MODE_HARD));

// Begin drawing.
m_d2dContext->BeginDraw();

m_d2dContext->Clear(D2D1::ColorF(D2D1::ColorF::CornflowerBlue));

// Draw the scaled and blurred image.
m_d2dContext->DrawImage(m_gaussianBlurEffect.Get());

// We ignore D2DERR_RECREATE_TARGET here. This error indicates that the device
// is lost. It will be handled during the next call to Present.
HRESULT hr = m_d2dContext->EndDraw();
if (hr != D2DERR_RECREATE_TARGET)
{
DX::ThrowIfFailed(hr);
}

});
}

///
/// Initializes all device resources and the image.
/// You need to call this method before doing other things.
///

IAsyncAction^ BlurEffectImageProcessor::DataInitialize(IRandomAccessStream^ ImageDataStream,float DPI){
// DirectXBase::Initialize(Window, DPI);
return create_async([this,ImageDataStream, DPI]{
// Initialize Devices
CreateDeviceResources();

DX::ThrowIfFailed(CoCreateInstance(
CLSID_WICImagingFactory1,
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&m_wicImagingFactory)
)
);

DX::ThrowIfFailed(
CoCreateInstance(
CLSID_WICImagingFactory,
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&m_wicImagingFactory2)
)
);

// Now we have the image source and we can decode it.
ImageBuffer = ref new Buffer(ImageDataStream->Size);
auto op = create_task(ImageDataStream->ReadAsync(ImageBuffer, ImageDataStream->Size, InputStreamOptions::None)).then([this,DPI](IBuffer^ ImageBufferData){
DX::ThrowIfFailed(
m_wicImagingFactory2->CreateDecoderFromStream(createIStreamFromIBuffer(ImageBufferData), nullptr, WICDecodeMetadataCacheOnDemand,
&m_wicDecoder)
);

// Get data ready
DX::ThrowIfFailed(
m_wicDecoder->GetFrame(0, &m_wicFrameDecode)
);
DX::ThrowIfFailed(
m_wicImagingFactory2->CreateFormatConverter(&m_wicFormatConverter)
);

DX::ThrowIfFailed(
m_wicFormatConverter->Initialize(
m_wicFrameDecode.Get(),
GUID_WICPixelFormat32bppBGRA,
WICBitmapDitherTypeNone,
nullptr,
0.0f,
WICBitmapPaletteTypeCustom
)
);

// Create output bitmap & get it ready
UINT Width;
UINT Height;
m_wicFrameDecode->GetSize(&Width, &Height);
m_wicImagingFactory2->CreateBitmap(Width, Height, GUID_WICPixelFormat32bppBGRA, WICBitmapCreateCacheOption::WICBitmapCacheOnDemand, &m_wicBitmap);
D2D1_SIZE_U bitmapSize = D2D1::SizeU(Width, Height);
D2D1_PIXEL_FORMAT bitmapPixelFormat = D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE);
D2D1_BITMAP_PROPERTIES1 bitmapProp1 = D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_TARGET,bitmapPixelFormat, DPI, DPI);
m_d2dContext->CreateBitmap(
D2D1::SizeU(Width, Height),
nullptr,
Width * 4, // 4 bytes for B8G8R8A8
bitmapProp1,
&m_d2dBitmap1
);

m_d2dContext->SetTarget(m_d2dBitmap1.Get());

IsInitialized = true;

return;
});

op.wait();
});
}

///
/// Get the final image.
/// REMEMBER call DataInitialize method first.
/// You can call this method before calling RenderImage, but you will get the original image.
///

///Indicates the current display's DPI. IAsyncOperation<IRandomAccessStream^>^ BlurEffectImageProcessor::GetImageAsBitmap(float DPI){
return create_async([this,DPI]{
if (!IsInitialized){
throw ref new Platform::Exception(1, "The class has not initialized.");
}
// Render the bitmap use WIC.
ComPtr m_iwicBitmap;
ComPtr m_iwicStream;
ComPtr m_iwicBitmapEncoder;
ComPtr m_iwicBitmapFrameEncode;
ComPtr m_iwicImageEncoder;
WICImageParameters* m_imageparm = new WICImageParameters();
D2D1_PIXEL_FORMAT m_pixel_format = D2D1_PIXEL_FORMAT();
ComPtr m_iStream;
ID2D1Image* m_id2d1image;
UINT height;
UINT width;

// Since we can't create IStream directly in Windows Runtime, we need creating InMemoryRandomAccessStream and convert it
IRandomAccessStream^ data = ref new InMemoryRandomAccessStream();

DX::ThrowIfFailed(
CreateStreamOverRandomAccessStream(data, IID_PPV_ARGS(&m_iStream))
);

// Get size, we need it later
DX::ThrowIfFailed(
m_wicFrameDecode->GetSize(&width, &height)
);

// Create bitmap
DX::ThrowIfFailed(
m_wicImagingFactory2->CreateBitmap(width, height, GUID_WICPixelFormat32bppBGRA, WICBitmapCreateCacheOption::WICBitmapCacheOnDemand, &m_iwicBitmap)
);

// Create WIC Stream
DX::ThrowIfFailed(
m_wicImagingFactory->CreateStream(&m_iwicStream)
);

// Initialize WIC Stream from IStream that we converted
DX::ThrowIfFailed(
m_iwicStream->InitializeFromIStream(m_iStream.Get())
);

// Create encoder
DX::ThrowIfFailed(
m_wicImagingFactory2->CreateEncoder(GUID_ContainerFormatPng, nullptr, &m_iwicBitmapEncoder)
);

// Create image encoder
DX::ThrowIfFailed(
m_wicImagingFactory2->CreateImageEncoder(m_d2dDevice.Get(), &m_iwicImageEncoder)
);

// Initialize
DX::ThrowIfFailed(
m_iwicBitmapEncoder->Initialize(m_iwicStream.Get(), WICBitmapEncoderCacheOption::WICBitmapEncoderNoCache)
);

// Create new frame for the bitmap
DX::ThrowIfFailed(
m_iwicBitmapEncoder->CreateNewFrame(&m_iwicBitmapFrameEncode,nullptr)
);

// Set properties
m_iwicBitmapFrameEncode->Initialize(nullptr);
m_iwicBitmapFrameEncode->SetSize(width, height);
WICPixelFormatGUID format = GUID_WICPixelFormat32bppBGRA;
m_iwicBitmapFrameEncode->SetPixelFormat(&format);
m_d2dContext->GetTarget(&m_id2d1image);
m_imageparm->DpiX = DPI;
m_imageparm->DpiY = DPI;
m_pixel_format.alphaMode = D2D1_ALPHA_MODE_IGNORE;
m_pixel_format.format = DXGI_FORMAT_B8G8R8A8_UNORM;
m_imageparm->PixelFormat = m_pixel_format;
m_imageparm->PixelHeight = height;
m_imageparm->PixelWidth = width;

// Write frmae
DX::ThrowIfFailed(
m_iwicImageEncoder->WriteFrame(m_id2d1image, m_iwicBitmapFrameEncode.Get(), m_imageparm)
);

// Commit
DX::ThrowIfFailed(
m_iwicBitmapFrameEncode->Commit()
);

DX::ThrowIfFailed(
m_iwicBitmapEncoder->Commit()
);

// Now we successfully got the image
// Convert it to stream.
// Reference: MSDN
Windows::Storage::Streams::IRandomAccessStream^ comRAS;
IUnknown* p11 = reinterpret_cast(comRAS);

static const GUID guidIRandomAccessStream =
{ 0x905a0fe1, 0xbc53, 0x11df, { 0x8c, 0x49, 0x00, 0x1e, 0x4f, 0xc6, 0x86, 0xda } };

DX::ThrowIfFailed(
CreateRandomAccessStreamOverStream(m_iwicStream.Get(), BSOS_DEFAULT, guidIRandomAccessStream, (void**)&p11)
);

// Return result
return reinterpret_cast<IRandomAccessStream^>(p11);
});
}


D2DEffects.h

#pragma once

#include "DirectXBase.h"

using namespace Windows::Storage::Streams;
using namespace Windows::Foundation;
using namespace Windows::UI::Core;

namespace Light{
namespace UI{
namespace Effects{
namespace Direct2D{
namespace BlurEffect{
public ref class BlurEffectImageProcessor sealed
{
public:
BlurEffectImageProcessor();
IAsyncAction^ DataInitialize(IRandomAccessStream^ ImageDataStream,float DPI);
IAsyncAction^ RenderImage(float gaussianBlurStDev, float DPI);
IAsyncOperation<IRandomAccessStream^>^ GetImageAsBitmap(float DPI);
void CreateDeviceResources();
private:
Microsoft::WRL::ComPtr m_bitmapSourceEffect;
Microsoft::WRL::ComPtr m_gaussianBlurEffect;

Microsoft::WRL::ComPtr m_wicDecoder;
Microsoft::WRL::ComPtr m_wicFrameDecode;
Microsoft::WRL::ComPtr m_wicFormatConverter;
Microsoft::WRL::ComPtr m_wicImagingFactory2;
Microsoft::WRL::ComPtr m_wicImagingFactory;
Microsoft::WRL::ComPtr m_d2ddevice1;
Microsoft::WRL::ComPtr m_d2ddevice;
Microsoft::WRL::ComPtr m_d3d11device;

// Direct3D device
Microsoft::WRL::ComPtr m_d3dDevice;

// Direct2D objects
Microsoft::WRL::ComPtr m_d2dDevice;
Microsoft::WRL::ComPtr m_d2dContext;
Microsoft::WRL::ComPtr m_d2dBitmap1;
Microsoft::WRL::ComPtr m_wicBitmap;

int m_width;
int m_height;
IBuffer^ ImageBuffer;
bool IsInitialized;
};
}
}
}
}
}


And don’t forget these input file: dxgi, dwrite, d2d1, d3d11, windowscodec, etc.