Introduction
In previous blog CXF security: getting certificates from central PKI we have seen how to use Public Key Infrastructure and XKMS service to locate certificates in message encryption scenario. This blog is continuation of previous one and explains the integration of central PKI into SecurityTokenService (STS) for the authentication.
WS-Trust and SecurityTokenService
In authentication scenarios client generates or obtains security token and sends it to the service for verification inside the message. Security token can be either user name and password, SAML assertion or Kerberos ticket. Generation of security token sometimes is non-trivial task, requires access to external systems and using third party libraries. Therefore it makes a lot of sense to free service participants from implementing any security processing logic on their own. This logic can be delegated to SecurityTokenService (STS) offering functionality to issue, validate, renew or remove Security Tokens.The STS is defined within the OASIS WS-Trust specification.
The communication between client, service and STS is depicted on following figure:
The typical authentication scenario using WS-Trust requires the following steps:
- Client obtains credentials and requests security token from STS.
- STS verifies credentials using external IDM or other mechanism.
- STS generates security token (for instance SAML), signs it with own private key and sends it back to client.
- Client injects security token into protocol security header (SOAP or REST HTTP) and sends request message to the service.
- Service extracts security token and validates it locally or using remote call to STS. As far as service and STS are in trusted relationship, it is enough just to validate STS signature of the security token.
Typically STS call is transparent for the client and triggered by appropriate WS-Policy assertion (IssuedToken).
When STS validates user's X509 certificate?
In some scenarios STS receives user certificate and uses it for authentication and for generation of SAML assertion:
- Client sends user certificate in STS request as credentials for own
authentication. In this case client must additionally sign part of request with
private key to proof of possession:
<wsse:Security
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" soap:mustUnderstand="1">
<wsse:BinarySecurityToken EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"
wsu:Id="X509-3CBA5F87FE6EAABD0E13661135280161">...</wsse:BinarySecurityToken>
<wsu:Timestamp wsu:Id="TS-1">
<wsu:Created>2013-04-16T11:58:48.003Z</wsu:Created>
<wsu:Expires>2013-04-16T12:03:48.003Z</wsu:Expires>
</wsu:Timestamp>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
Id="SIG-2">
<ds:SignedInfo>
...
<ds:Reference URI="#TS-1">
...
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>...</ds:SignatureValue>
<ds:KeyInfo Id="KI-3CBA5F87FE6EAABD0E13661135280202">
<wsse:SecurityTokenReference
wsu:Id="STR-3CBA5F87FE6EAABD0E13661135280223">
<wsse:Reference URI="#X509-3CBA5F87FE6EAABD0E13661135280161"
ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" />
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
ds:KeyInfo>
</ds:Signature>
...
You can see that BinarySecurityToken has X509 type and contains user certificate. Timestamp is signed with KeyInfo referencing to this certificate URI="#X509-3CBA5F87FE6EAABD0E13661135280161". - Client sends user certificate to be included into SAML token as SubjectConfirmationData/KeyInfo element. This certificate can be referenced and used for validation of XML signatures:
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
ID="_C5CE27CE46751556E113662938382792" IssueInstant="2013-04-18T14:03:58.279Z"
Version="2.0" xsi:type="saml2:AssertionType">
...
<saml2:Subject>
<saml2:NameID
Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
NameQualifier="http://cxf.apache.org/sts">alice@EXAMPLE.COM</saml2:NameID>
<saml2:SubjectConfirmation
Method="urn:oasis:names:tc:SAML:2.0:cm:holder-of-key">
<saml2:SubjectConfirmationData
xsi:type="saml2:KeyInfoConfirmationDataType">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>...</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</saml2:SubjectConfirmationData>
</saml2:SubjectConfirmation>
</saml2:Subject>
</saml2:Assertion>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
Id="SIG-11">
<ds:SignedInfo>
...
<ds:KeyInfo Id="KI-CC7D54F0FC1CF34D4913662938383496">
<ns3:SecurityTokenReference
xmlns:ns3="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd"
wsse11:TokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0">
<ns3:KeyIdentifier
ValueType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID">_C5CE27CE46751556E113662938382792
</ns3:KeyIdentifier>
</ns3:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
...
You can see that KeyIdentifier into Signature references SAML token ID="_C5CE27CE46751556E113662938382792". That means, the certificate from SAML SubjectConfirmation must be used to verify this signature.
In both scenarios STS should validate the user certificate.
User Certificate Validation
Validation through local java keystore
STS validates client certificate using verifyTrust() method of WSS4J Crypto interface. Default implementation of Crypto interface is Merlin. Merlin provider is keystore based, that means, user certificate will be validated using local STS java keystore. Merlin checks first is user certificate itself in STS keystore. If yes, validation is successfully finished. If no, Merlin proves if keystore contains all trusted chain certificates.
The problem in this approach is that STS local java keystore should contain either every client certificate or all certificates from certificate trusted chain. Furthermore, administrator should manage revocation lists for all client certificates in STS keystore.
That is not really acceptable for enterprise environments hosting a lot of clients. One possibility to resolve this issue is using Crypto XKMS implementation accessing central Public Key Infrastructure (PKI) to validate user certificate.
Validation through PKI
Using of XKMS based Crypto implementation is depicted bellow:
You can see that STS uses XKMS based Crypto implementation instead Merlin. XKMS Crypto still gets STS private key from local java keystore, but it uses central PKI to validate user certificates. W3C specifies standard SOAP interface to access and administrate keys remotely: XML Key Management Specification (XKMS 2.0). XKMS Crypto provider invokes XKMS service to validate user certificate. Dependent on XKMS service implementation, it either validates user certificate locally or delegates validation call to remote PKI. Administrator can manage certificates and revocation lists either directly in PKI or through XKMS registration interface (XKRSS). Note, that beginning from CXF 3.0.0, XKMS service and client implementation as well as XKMS Crypto Provider are available in CXF distribution.
Configure STS to use XKMS Crypto Provider
How to say STS that it must use XKMS Crypto Provider instead default keystore based one (Merlin)?
The XKMS Crypto Provider should be instantiated and set as property either in StaticSTSProperties object or in STS jaxws:endpoint. That can be done either programmatic or via Spring/Blueprint configuration.
The finished tutorial source code for STS configured with XKMS crypto provider is prepared on xkms_symmetric_tutorial. It is slightly modified code from Glen Mazza blog.
Sample spring configuration looks like:
The finished tutorial source code for STS configured with XKMS crypto provider is prepared on xkms_symmetric_tutorial. It is slightly modified code from Glen Mazza blog.
Sample spring configuration looks like:
<!-- XKMS configuration -->
<bean id="xkmsExtensions"
class="org.apache.cxf.xkms.model.extensions.AdditionalClassesFactory" />
<jaxws:client xmlns:serviceNamespace="http://www.w3.org/2002/03/xkms#wsdl"
id="xkmsClient" serviceClass="org.w3._2002._03.xkms_wsdl.XKMSPortType"
serviceName="serviceNamespace:XKMSService" endpointName="serviceNamespace:XKMSPort"
address="http://localhost:8080/xkms/XKMS/">
<jaxws:properties>
<entry key="jaxb.additionalContextClasses">
<bean class="java.lang.Object" factory-bean="xkmsExtensions"
factory-method="create" />
</entry>
</jaxws:properties>
</jaxws:client>
<bean id="xkmsCryptoProviderFactory" class="org.apache.cxf.xkms.crypto.impl.XkmsCryptoProviderFactory">
<constructor-arg ref="xkmsClient"/>
</bean>
<bean id="xkmsCryptoProvider" class="org.apache.cxf.xkms.crypto.impl.XkmsCryptoProvider"
factory-bean="xkmsCryptoProviderFactory"
factory-method="create">
<constructor-arg type="java.lang.String" value="/stsKeystore.properties" />
</bean>
<!-- end of XKMS configuration -->
<bean id="xkmsExtensions"
class="org.apache.cxf.xkms.model.extensions.AdditionalClassesFactory" />
<jaxws:client xmlns:serviceNamespace="http://www.w3.org/2002/03/xkms#wsdl"
id="xkmsClient" serviceClass="org.w3._2002._03.xkms_wsdl.XKMSPortType"
serviceName="serviceNamespace:XKMSService" endpointName="serviceNamespace:XKMSPort"
address="http://localhost:8080/xkms/XKMS/">
<jaxws:properties>
<entry key="jaxb.additionalContextClasses">
<bean class="java.lang.Object" factory-bean="xkmsExtensions"
factory-method="create" />
</entry>
</jaxws:properties>
</jaxws:client>
<bean id="xkmsCryptoProviderFactory" class="org.apache.cxf.xkms.crypto.impl.XkmsCryptoProviderFactory">
<constructor-arg ref="xkmsClient"/>
</bean>
<bean id="xkmsCryptoProvider" class="org.apache.cxf.xkms.crypto.impl.XkmsCryptoProvider"
factory-bean="xkmsCryptoProviderFactory"
factory-method="create">
<constructor-arg type="java.lang.String" value="/stsKeystore.properties" />
</bean>
<!-- end of XKMS configuration -->
<bean id="stsProperties" class="org.apache.cxf.sts.StaticSTSProperties">
<property name="signatureUsername" value="sts" />
<property name="signatureCrypto" ref="xkmsCryptoProvider" />
<property name="callbackHandler" ref="pwdCallbackHandler" />
<property name="encryptionUsername" value="useReqSigCert" />
<property name="encryptionCrypto" ref="xkmsCryptoProvider" />
<property name="issuer" value="STS Issuer" />
</bean>
<bean id="transportSTSProvider" class="org.sopera.csg.tesbext.sts.factory.ProviderFactory" factory-method="create">
<argument ref="stsProperties" />
</bean>
<jaxws:endpoint id="transportSTS"
implementor="#transportSTSProvider"
address="/SecurityTokenService/Transport"
wsdlLocation="wsdl/ws-trust-1.4-service.wsdl"
xmlns:wst="http://docs.oasis-open.org/ws-sx/ws-trust/200512/"
serviceName="wst:SecurityTokenService"
endpointName="wst:Transport_Port">
</jaxws:endpoint>
<property name="signatureUsername" value="sts" />
<property name="signatureCrypto" ref="xkmsCryptoProvider" />
<property name="callbackHandler" ref="pwdCallbackHandler" />
<property name="encryptionUsername" value="useReqSigCert" />
<property name="encryptionCrypto" ref="xkmsCryptoProvider" />
<property name="issuer" value="STS Issuer" />
</bean>
<bean id="transportSTSProvider" class="org.sopera.csg.tesbext.sts.factory.ProviderFactory" factory-method="create">
<argument ref="stsProperties" />
</bean>
<jaxws:endpoint id="transportSTS"
implementor="#transportSTSProvider"
address="/SecurityTokenService/Transport"
wsdlLocation="wsdl/ws-trust-1.4-service.wsdl"
xmlns:wst="http://docs.oasis-open.org/ws-sx/ws-trust/200512/"
serviceName="wst:SecurityTokenService"
endpointName="wst:Transport_Port">
</jaxws:endpoint>
jaxws:client is configuration of XKMS client, it is used as constructor argument of xkmsCryptoProviderFactory. Argument "/stsKeystore.properties" of xkmsCryptoProvider is necessary to create default keystore based crypto provider. It is responsible to get STS private key from the local keystore. Lookup and validation of the public keys (X509) will be delegated to XKMS crypto provider and XKMS service.
Alternatively XKMS Crypto Provider can be set directly into STS jaxws:endpoint:
...
<jaxws:endpoint id="transportSTS"
implementor="#transportSTSProvider"
address="/SecurityTokenService/Transport"
wsdlLocation="wsdl/ws-trust-1.4-service.wsdl"
xmlns:wst="http://docs.oasis-open.org/ws-sx/ws-trust/200512/"
serviceName="wst:SecurityTokenService"
endpointName="wst:Transport_Port">
<jaxws:properties>
implementor="#transportSTSProvider"
address="/SecurityTokenService/Transport"
wsdlLocation="wsdl/ws-trust-1.4-service.wsdl"
xmlns:wst="http://docs.oasis-open.org/ws-sx/ws-trust/200512/"
serviceName="wst:SecurityTokenService"
endpointName="wst:Transport_Port">
<jaxws:properties>
...
<entry key="ws-security.signature.crypto" value-ref="xkmsCryptoProvider"/>
<entry key="ws-security.encryption.crypto" value-ref="xkmsCryptoProvider"/>
</jaxws:properties>
</jaxws:properties>
</jaxws:endpoint>
Using XKMS for STS scenario with Symmetric Key
This feature can be especially useful for STS scenario with
SymmetricKey. With this scenario, the STS and the WS consumer negotiate a
symmetric key.
STS scenario with SymmetricKey (used from SAP resource) |
- The WS-Client authenticates himself to STS and contributes material to the creation of symmetric key.
- The STS verifies WS-Client authentication and generates symmetric key using material received from WS-Client
- The STS encrypts symmetric key using WS-Service public key and inserts the encrypted key together with security token into SAML assertion. The STS signs SAML assertion and sends it together with key material for generation symmetric key to the WS-Client.
- The WS-Client generates short-lived symmetric key from own material and the key material from the STS.
- The WS-Client inserts the SAML token, into the message header. It encrypts the message texts or/and signs the message with the generated symmetric key. It then sends the user's message to the WS-Service.
- The WS-Service checks the signature in the SAML token and uses its private key to decrypt the symmetric key contained in the SAML token.
- The WS-Service verifies the signature of the WS-Client (Holder-of-Key) with the decrypted symmetric key. In this way, the STS confirms that the Holder-of-Key is the subject (the user) in the assertion. The WS-Service uses the symmetric key to decrypt the message text.
On the step (3) STS needs the public key (certificate) of target WS-Service. Normally STS servers not only one, but multiple services (restricted by url patterns in TokenServiceProvider). This can be a serious drawback to manage public certificates of all services into STS local keystore.
XKMS Crypto provider provides elegant solution of this using following configuration:
- encryptionUsername (in StaticSTSProperties or jaxws:endpoint properties) should be set into special value: useEndpointAsCertAlias (STSConstants.USE_ENDPOINT_AS_CERT_ALIAS)
- encryptionCrypto should be set to XKMS Crypto implementation
- Service certificates should be saved into XKMS under service endpoint (use Application "urn:apache:cxf:service:endpoint" and service endpoint as identifier)
<!-- XKMS configuration -->
<bean id="xkmsExtensions"
class="org.apache.cxf.xkms.model.extensions.AdditionalClassesFactory" />
<jaxws:client xmlns:serviceNamespace="http://www.w3.org/2002/03/xkms#wsdl"
id="xkmsClient" serviceClass="org.w3._2002._03.xkms_wsdl.XKMSPortType"
serviceName="serviceNamespace:XKMSService" endpointName="serviceNamespace:XKMSPort"
address="http://localhost:8080/xkms/XKMS/">
<jaxws:properties>
<entry key="jaxb.additionalContextClasses">
<bean class="java.lang.Object" factory-bean="xkmsExtensions"
factory-method="create" />
</entry>
</jaxws:properties>
</jaxws:client>
<bean id="xkmsCryptoProviderFactory" class="org.apache.cxf.xkms.crypto.impl.XkmsCryptoProviderFactory">
<constructor-arg ref="xkmsClient"/>
</bean>
<bean id="xkmsCryptoProvider" class="org.apache.cxf.xkms.crypto.impl.XkmsCryptoProvider"
factory-bean="xkmsCryptoProviderFactory"
factory-method="create">
<constructor-arg type="java.lang.String" value="/stsKeystore.properties" />
</bean>
<!-- end of XKMS configuration -->
<bean id="mySTSProviderBean"
class="org.apache.cxf.sts.provider.DefaultSecurityTokenServiceProvider">
<property name="stsProperties" ref="mySTSProperties" />
<property name="services" ref="myServiceList" />
</bean>
<bean id="myServiceList" class="org.apache.cxf.sts.service.StaticService">
<property name="endpoints" ref="wspAllowedEndpoints" />
</bean>
<util:list id="wspAllowedEndpoints">
<value>http://localhost:8080/doubleit/services/doubleit.*</value>
</util:list>
<bean id="mySTSProperties" class="org.apache.cxf.sts.StaticSTSProperties">
<property name="signatureUsername" value="mystskey" />
<property name="callbackHandlerClass" value="sts.PasswordCallbackHandler" />
<property name="issuer" value="DoubleItSTSIssuer" />
<property name="encryptionUsername" value="useEndpointAsCertAlias"/>
<property name="encryptionCrypto" ref="xkmsCryptoProvider" />
<property name="signatureCrypto" ref="xkmsCryptoProvider" />
</bean>
<jaxws:endpoint id="CXFSTS" implementor="#mySTSProviderBean"
address="/STS" wsdlLocation="/WEB-INF/wsdl/DoubleItSTSService.wsdl"
xmlns:ns1="http://docs.oasis-open.org/ws-sx/ws-trust/200512/"
serviceName="ns1:SecurityTokenService" endpointName="ns1:STS_Port">
<jaxws:properties>
<entry key="ws-security.callback-handler" value="sts.PasswordCallbackHandler" />
<entry key="ws-security.signature.properties" value="stsKeystore.properties" />
<entry key="ws-security.signature.username" value="mystskey" />
<!-- Below unused/unneeded if using UT auth between WSC and STS -->
<entry key="ws-security.encryption.username" value="useReqSigCert" />
</jaxws:properties>
</jaxws:endpoint>
STS can server multiple WS-Services and doesn't care about services certificates locally - they are stored and managed in central XKMS repository.