Custom Policy Assertions in WSE 3 - Part 3

In part 2, I described the major components and started showing how they fit together.  In this part I'll show how to use policy files to describe the behaviors necessary to tie everything together into a working system.  The system has 3 services, so there are 3 policies but the client only communicates directly with 2 services so it only needs 2 policies, by the end I'll show how 2 = 3.  Let's start with the services.

 

Ballot Issuer Policy

The requirements for the ballot issuer are the lightest, it distributes data that has no confidentiality requirements but is signed to ensure the data integrity.  One way to express this in policy:

 

<anonymousForCertificateSecurity establishSecurityContext="false" renewExpiredSecurityContext="true" signatureConfirmation="false" protectionOrder="SignBeforeEncrypting" deriveKeys="true" ttlInSeconds="300">
      <serviceToken>
        <x509 storeLocation="LocalMachine" storeName="My" findValue="CN=WSE2QuickStartServer" findType="FindBySubjectDistinguishedName" />
      </serviceToken>
      <protection>
        <request signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="false" />
        <response signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="false" />
        <fault signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="false" />
      </protection>
</anonymousForCertificateSecurity>

 

Only interesting point here is encryptBody="false", generally this will be true, but since we don't need any encryption it is turned off. The client policy looks the same except it looks for the x509 certificate in a different place.

 

Blind Signature Issuer

The policy for the blind signature issuer is actually very straightforward as well:

 

<usernameForCertificateSecurity establishSecurityContext="true" renewExpiredSecurityContext="true" signatureConfirmation="false" protectionOrder="SignBeforeEncrypting" deriveKeys="true" ttlInSeconds="300">
      <serviceToken>
        <x509 storeLocation="LocalMachine" storeName="My" findValue="CN=WSE2QuickStartServer" findType="FindBySubjectDistinguishedName" />
      </serviceToken>
      <protection>
        <request signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="true" />
        <response signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="true" />
        <fault signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="false" />
      </protection>
</usernameForCertificateSecurity>

 

On the client side, the blind signature protocol is handled inside a policy assertion, so it's behavior has to be expressed in policy.  On the service side, we have a service that issues blind signatures so it's policy just has to ask how to secure that message exchange.  In this case we're using user name tokens for client authentication and authorization and x509 certificate to provide confidentiality and integrity.  Note encryptBody is now true.  Also notice establishSecurityContext is true.  Since these conversations are 2 messages exchanges long, this is a borderline case for any performance benefits from using SCT's.  If the conversation could be longer, SCT's are definitely a good idea to improve performance.

 

Polling Place Policy - Service

This is where we first see the new custom blind signature assertion:

 

<blindSignature>
       <x509 storeLocation="LocalMachine" storeName="My" findValue="CN=WSE2QuickStartServer" findType="FindBySubjectDistinguishedName" />
      <signatureMessageConverter type="VotingServices.BallotConverter, Service" />
</blindSignature>

<anonymousForCertificateSecurity establishSecurityContext="false" renewExpiredSecurityContext="true" signatureConfirmation="false" protectionOrder="SignBeforeEncrypting" deriveKeys="true" ttlInSeconds="300">
      <serviceToken>
        <x509 storeLocation="LocalMachine" storeName="My" findValue="CN=WSE2QuickStartServer" findType="FindBySubjectDistinguishedName" />
      </serviceToken>
      <protection>
        <request signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="true" >
          <signedHeader name="blindSignature" namespace="urn:BlindSignature" />
         </request>

        <response signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="true" />
        <fault signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="false" />
      </protection>
</anonymousForCertificateSecurity>

 

First notice the anonymousForCertificateSecurity assertion is very similar to the one for the ballot issuer, only encryptBody is now true and we're checking for a signature on our blind signature header (the signedHeader element under the request element).  Second we have a new assertion, blindSignature.  In order for WSE to know how to handle this, you'll need to add an element to the extensions section of the policy file:

 

<extension name="blindSignature" type="BlindSignature.BlindSignatureAssertion, BlindSignatureAssertion" />

 

This lets WSE know what to call when it encounters your assertion.  Also this assertion is using the same x509 element as the builtin assertions use.  It is very easy to reuse WSE's builtin extensions from your own assertions, in this case WSE does all the work of finding and loading the x509 certificate for us.  The signatureMessageConverter element is necessary because the blind signature assertion was designed to be application agnostic and deal only with byte arrays.  This element specifies a class that can convert this application's documents into byte arrays.

 

So how does all this work?  For incoming messages the assertions will be called in the opposite order they appear, so anonymousForCertificateSecurity will run first and decrypt the message and verify all the WS-Security signatures.  Now the blind signature assertion goes to work.  First it looks for the blind signature header that contains the signature.  After getting the signature, it verifies it with the private key of the certificate specified by the x509 element.  If everything succeeds, the Vote method is called on the polling place service and the vote is tallied.  Otherwise an exception is thrown and the Vote method is never called.

 

Polling Place Policy - Client

Here's how 2 client policies map to 3 service policies:

 

<blindSignature>
       <x509 storeLocation="LocalMachine" storeName="My" findValue="CN=WSE2QuickStartServer" findType="FindBySubjectDistinguishedName" />
      <signatureIssuer>soap.tcp://nathana-wse:8000/VotingServices/SignatureIssuer</signatureIssuer>

      <signatureProtection>

           <usernameForCertificateSecurity establishSecurityContext="true" renewExpiredSecurityContext="true" signatureConfirmation="false" protectionOrder="SignBeforeEncrypting" deriveKeys="true" ttlInSeconds="300">
              <serviceToken>
                 <x509 storeLocation="CurrentUser" storeName="AddressBook" findValue="CN=WSE2QuickStartServer" findType="FindBySubjectDistinguishedName" />
              </serviceToken>
              <protection>
                 <request signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="true" />
                 <response signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="true" />
                 <fault signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="false" />
              </protection>
            </usernameForCertificateSecurity>

      </signatureProtection>
</blindSignature>

<anonymousForCertificateSecurity establishSecurityContext="false" renewExpiredSecurityContext="true" signatureConfirmation="false" protectionOrder="SignBeforeEncrypting" deriveKeys="true" ttlInSeconds="300">
      <serviceToken>
        <x509 storeLocation="CurrentUser" storeName="AddressBook" findValue="CN=WSE2QuickStartServer" findType="FindBySubjectDistinguishedName" />
      </serviceToken>
      <protection>
         <request signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="true" >
          <signedHeader name="blindSignature" namespace="urn:BlindSignature" />
         </request>

        <response signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="true" />
        <fault signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="false" />
      </protection>
</anonymousForCertificateSecurity>

 

Again we have an anonymousForCertificate assertion that's essentially the same as the service assertion.  Also the only common element between the blindSignature assertions is the x509 element, this is necessary so the client can blind the ballots.  The new elements are signatureIssuer, which is the location of signature issuer and signatureProtection which contains the policy necessary to call the signature issuer.  The signatureProtection element is another example of how easy it is to reuse WSE assertions in your custom policies.  I'll show how to do this in the next part.

 

On the client side, the user fills out a ballot and the first assertion called is the blind signature assertion.  This assertion works in a similar manner to setting establishSecurityContext=true on a builtin assertion.  After creating a proxy with establishSecurityContext on, the first call with it notices it doesn't have an SCT so it gets one, then it makes the call.  The end result is the proxy made one request that is invisible to the client as well as the request the client initiated.  In this case when the client calls Vote, the blind signature assertion makes two calls to the signature isssuer to obtain the necessary signature.  After obtaining the signature the assertion inserts it into a header in the message and modifies the body to have the correct ballot id.  Now the anonymousForCertificateSecurity assertion runs does the proper encryption and signing of the entire message.

 

In the next part I'll describe how to actually implement the blind signature assertion.

 

The other parts:

Part 1

Part 2

Part 3

Part 4

Part 5