locked
Using infocard without installing certificates on the client?

    Question

  • According to http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=311594&SiteID=1 it should be possible to use InfoCard without installing the service certificate on the client, but if I change the wsFederation InfoCard sample by removing the identity tag from the client endpoint configuration I get the error "The incoming policy could not be validated." (Without this change, the sample works correctly).

    I want to do this because I want to use InfoCard in a ClickOnce deployment scenario without having the need to burden the ClickOnce deployment with certificate installations (see my question http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=466325&SiteID=1, which hasn't been answered yet).

     

    What's missing from the sample to make this scenario work?

    BTW: I think ClickOnce + InfoCard is a major usage scenario that deserves it's own sample in the WCF SDK.

     

    Wednesday, June 14, 2006 5:59 AM

Answers

  • I just noticed that I had a typo in the ppid claimType. I've corrected it and now it works.

     

     

    Thursday, June 22, 2006 6:41 PM

All replies

  • The point of the certificate is to identify the Relying Party.

    You could request the certificate out-of-band from the Relying Party, and reference it that way (either with a base64 encoded cert or simply as a file). I've been thinking about crafting a sample around that too.

    There are some implications of trusting that certificate that I'd have to sketch out too.

    After Tech-ed, I'll try to post a sample around that (or at least get you the config!)

     

    Garrett

    g


    Garrett Serack | Program Manager | Federated Identity Team | Microsoft Corporation

    Wednesday, June 14, 2006 4:18 PM
  • Hi Garrett,

    Is this currently only possible via an out-of-band mechanism (I could embed the certificate in my client app since the client app is tied to this particular web service and construct the configuration programmatically) and not via a configuration file?

    I tried out using InfoCard to log in on a web site at identityblog.com and that works automagically without having to explicitly invent a mechanism to get the RP certificate on the client (or is this because IE7 does this internally?).

    Looking forward to your sample,

    Thanks,

    Nico

    Wednesday, June 14, 2006 5:07 PM
  • Try playing with the code I've crufted up at http://blogs.msdn.com/nigelwa/archive/2006/06/14/631672.aspx. I haven't tested it across two machines but if what Jan aswered is accurate it should give you a good starting point:

    "You don't need to install service certificate to the client certificate store if you use WCF standard bindings, because we automatically negotiate service certificate for you during the authentication phase. You only need to specify service certificate if you set the negotiateServiceCredential attribute to false on the security/message element in the configuration. You can do that by looking it up from the certificate store or you can set the certificate directly on the ClientCredentials.ClientCertificate property. You can use X509Certificate2 class to load the binary certificate blob from the file for example.

    Another issue that you might need to address is whether client trusts the service certificate. If the service certificate is issued by an issuer that is trusted on the client, you don't need to install anything on the client machine. If it is not trusted, you need to install the issuer certificate to the trusted certificate authorities store or you need to add service certificate to the TrustedPeople store. In the later case you need to change the certificate autentication mode to PeerOrChainTrust using the following element:

        <behaviors>
          <behavior name="ClientCertificateBehavior">
            <clientCredentials>
              <serviceCertificate>
                <authentication certificateValidationMode="PeerOrChainTrust"/>
              </serviceCertificate>
            </clientCredentials>
          </behavior>
        </behaviors>
    "

    So you may need to add the PeerOrChainTrust property in code as follows:

    credentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.PeerOrChainTrust;

    Let us know how you get on!

    Cheers,

    Nigel

    Thursday, June 15, 2006 1:16 AM
  • I have this _almost_ working.

    I started by modifying the Wsfederation InfoCard sample so that it programmatically builds the channel instead of using the config file. It also loads the certificate from a file instead of looking it up in the certificate store. With these modifications, the sample works correctly without having to install the server certificate.

    So I did the same thing in my application. My webservice is now hosted publicly on the internet and I'm using a SSL certificate bought from one of the root CA's as the server certificate. I had to set the CertificateValidationMode to ChainTrust so that this certificate is properly recognized. I can select a card from the InfoCard selection UI and submit it to the service. 

    However, I now get the following error:

    System.ServiceModel.Security.SecurityNegotiationException occurred
      Message="Soap security negotiation to 'http://www.xxxxxxxxxxx/service.svc/secure/' failed. See inner exception for more details."
      Source="mscorlib"
      StackTrace:
        Server stack trace:
           at System.ServiceModel.Security.IssuanceTokenProviderBase`1.DoNegotiation(TimeSpan timeout)
           at System.ServiceModel.Security.IssuanceTokenProviderBase`1.GetTokenCore(TimeSpan timeout)
           at System.IdentityModel.Selectors.SecurityTokenProvider.GetToken(TimeSpan timeout)
           at System.ServiceModel.Security.SecurityProtocol.GetToken(SecurityTokenProvider provider, EndpointAddress target, TimeSpan timeout)
           at System.ServiceModel.Security.MessageSecurityProtocol.GetTokenAndEnsureOutgoingIdentity(SecurityTokenProvider provider, Boolean isEncryptionOn, TimeSpan timeout, SecurityTokenAuthenticator authenticator)
           at System.ServiceModel.Security.SymmetricSecurityProtocol.TryGetTokenSynchronouslyForOutgoingSecurity(Message message, SecurityProtocolCorrelationState correlationState, Boolean isBlockingCall, TimeSpan timeout, SecurityToken& token, SecurityTokenParameters& tokenParameters, SecurityToken& prerequisiteWrappingToken, IList`1& supportingTokens, SecurityProtocolCorrelationState& newCorrelationState)
           at System.ServiceModel.Security.SymmetricSecurityProtocol.SecureOutgoingMessageCore(Message& message, TimeSpan timeout, SecurityProtocolCorrelationState correlationState)
           at System.ServiceModel.Security.MessageSecurityProtocol.SecureOutgoingMessage(Message& message, TimeSpan timeout, SecurityProtocolCorrelationState correlationState)
           at System.ServiceModel.Channels.SecurityChannelFactory`1.SecurityRequestChannel.Request(Message message, TimeSpan timeout)
           at System.ServiceModel.Security.SecuritySessionSecurityTokenProvider.DoOperation(SecuritySessionOperation operation, EndpointAddress target, Uri via, SecurityToken currentToken, TimeSpan timeout)
           at System.ServiceModel.Security.SecuritySessionSecurityTokenProvider.GetTokenCore(TimeSpan timeout)
           at System.IdentityModel.Selectors.SecurityTokenProvider.GetToken(TimeSpan timeout)
           at System.ServiceModel.Security.SecuritySessionClientSettings`1.ClientSecuritySessionChannel.OnOpen(TimeSpan timeout)
           at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
           at System.ServiceModel.Channels.DatagramAdapter.RequestDatagramAdapterChannel.Request(Message request, TimeSpan timeout)
           at System.ServiceModel.Dispatcher.RequestChannelBinder.Request(Message message, TimeSpan timeout)
           at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
           at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs)
           at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
           at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
        Exception rethrown at [0]:
           at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
           at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
     

    The inner exception is:

    X509 certificate (CN=computername) chain building failed. Unknown error.  

       at System.IdentityModel.Selectors.X509CertificateValidator.ChainTrustValidator.Validate(X509Certificate2 certificate)
       at System.IdentityModel.Selectors.X509SecurityTokenAuthenticator.ValidateTokenCore(SecurityToken token)
       at System.IdentityModel.Selectors.SecurityTokenAuthenticator.ValidateToken(SecurityToken token)
       at System.ServiceModel.Security.TlsnegoTokenProvider.ValidateSspiNegotiation(ISspiNegotiation sspiNegotiation)
       at System.ServiceModel.Security.SspiNegotiationTokenProvider.OnNegotiationComplete(SspiNegotiationTokenProviderState sspiState, RequestSecurityTokenResponse negotiationRstr, RequestSecurityTokenResponse authenticatorRstr)
       at System.ServiceModel.Security.SspiNegotiationTokenProvider.GetNextOutgoingMessageBody(Message incomingMessage, SspiNegotiationTokenProviderState sspiState)
       at System.ServiceModel.Security.IssuanceTokenProviderBase`1.GetNextOutgoingMessage(Message incomingMessage, T negotiationState)
       at System.ServiceModel.Security.IssuanceTokenProviderBase`1.DoNegotiation(TimeSpan timeout)

    'CN=computername' corresponds to the certificate that Windows 2003 apparently automatically creates during installation of the OS.

     

     

    Thursday, June 15, 2006 9:11 PM
  • Hi Nigel,

    I tried to use metadataexchange, but when I add the mex endpoint in my configuration file my web application doesn't start anymore and throws the following error:

    Configuration binding extension 'system.serviceModel/bindings/mexHttpBinding' could not be found. Verify that this binding extension is properly registered in system.serviceModel/extensions/bindingExtensions and that it is spelled correctly.

    I assume that this method only works in the upcoming June CTP (although it is documented in the beta 2 Platform SDK)?

     

    Saturday, June 17, 2006 6:24 PM
  • I've got a little bit of progress: This exception can be eliminated by setting negotiateServiceCredentials to false.

    But still, I got exceptions, so I tried to do a simple anonymous wsHttpBinding and that also doesn't work. Basically, the only WCF binding I got to work over the Internet up to now was basicHttpBinding. But on a single machine, all the samples I tried work ok.

    Saturday, June 17, 2006 6:36 PM
  • I think I'm going to give up until the next CTP is here.

    This is the current status with my experiments:

    - MEX works in the sense that the MetatdataResolver.Resolve call returns all my endpoint configurations.

    - I can only get basicHttpBinding to work.

    - I've experimented a bit with MEX because I think it gives me the best chance of not having a mismatch between the client and the server configuration and looks to be the easiest way to get the server certificate on the client withouth explicitly installing it on the client.

    - Creating a channel (wsHttpBinding, security mode=message, clientCredentialType="None" negotiateServiceCredential="false") with MEX works, but I get the following error returned by the sever on the first call:

    An unsecured or incorrectly secured fault was received from the other party. This fault may have been sent in response to an improperly secured request. See the inner FaultException for the fault code and detail.

    The inner exception just says "An error occurred when verifying security for the message."

    As far as I can see in the network trace, the message is properly encrypted, so I have no idea why I'd get this error.

    - Creating a channel (wsHttpFederationBinding, with negotiateServiceCredential="false") immediately return the following error:

    The incoming policy contains options which are not supported by this version of Infocard.

    There is no further information about what exactly is not supported.

     

    Sunday, June 18, 2006 11:54 AM
  • Nico,

    First make sure you are using the beta 2 version of the code. I have checked this works with services with a wsFederationHttpBinding. For example, here is a service App.config that works:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <system.serviceModel>

        <services>
          <service
            name="HelloService.Hello"
            behaviorConfiguration="helloServiceBehavior">
            <endpoint
              address="helloEndpoint"
              contract="HelloService.IHello"
              binding="wsFederationHttpBinding"
              bindingConfiguration="helloFederatedBinding">
              <identity>
                <certificateReference
                  findValue="
    www.fabrikam.com"
                  storeLocation="LocalMachine"
                  storeName="My"
                  x509FindType="FindBySubjectName" />
              </identity>
            </endpoint>
          </service>
        </services>

        <bindings>
          <wsFederationHttpBinding>
            <binding name='helloFederatedBinding' >
              <security mode='Message'>
                <message>
                  <claims>
                    <clear />
                    <add claimType='http://schemas.microsoft.com/ws/2005/05/identity/claims/emailaddress' />
                  </claims>
                </message>
              </security>
            </binding>
          </wsFederationHttpBinding>
        </bindings>

        <behaviors>
          <behavior name="helloServiceBehavior"
            returnUnknownExceptionsAsFaults="true" >
            <metadataPublishing enableGetWsdl='True' enableHelpPage='True' enableMetadataExchange='true' />
            <serviceCredentials>
              <serviceCertificate
                findValue="
    www.fabrikam.com"
                storeLocation="LocalMachine"
                storeName="My"
                x509FindType="FindBySubjectName" />
            </serviceCredentials>
          </behavior>
        </behaviors>

      </system.serviceModel>
    </configuration>

     

     

    Wednesday, June 21, 2006 12:02 AM
  • Thanks Nigel,

    I got a few differences in my binding (although they corresponded to what was shown in the samples). You may have read on the WCF forum that probably the biggest problem I was having was caused by the client and server not having their clocks synchronized.

    I can now make the first call, go trough the Infocard UI and get results back. However, on the second call, I get another exception, with this in the innerexception:

    +  InnerException {"The maximum array length (16384) has been exceeded. Line 1, position 94295."} System.Exception {System.Xml.XmlException}

    Is there some limit on the length of the messages that are exchanged? I have set

                        binding.MaxReceivedMessageSize = 1000000;
                        binding.MaxBufferPoolSize = 1000000;
    because my request can contain a lot of data.

    [Edit: I have the same error with a wsHttpBinding, so this may not be related to Infocard]

    Also, what is the threading model of the client proxies? I have a background worker thread and this also shows the Infocard login screen, as if the login info is stored in TLS. I assumed that these proxies were safe to use from multiple threads.

     

    Wednesday, June 21, 2006 6:40 AM
  • One step further:

    The maximum array length can be solved by setting:

    binding.ReaderQuotas.MaxArrayLength = 1000000;

    I can now login using InfoCard without any errors.

    I can also get the first name and last name by adding more claim request in the config file.

    However, when I add

                     <add claimType  ="http://schemas.microsoft.com/ws/2005/05/identity/claims/privatepersonalidentifer"/>

    the InfoCard UI requires me to choose a card provided by a third party (which I don't have).

    How do I get the PPID? When I look at the ClaimTypes collection, there's no entry for PPID anymore? Has it been replaced by something else?

     

     

     

    Wednesday, June 21, 2006 8:37 PM
  • I just noticed that I had a typo in the ppid claimType. I've corrected it and now it works.

     

     

    Thursday, June 22, 2006 6:41 PM