CXF security uses asymmetric algorithms for different purposes: encryption of symmetric keys and payloads, signing security token and messages, SSL transport bindings.
If you look in samples and tutorials, the public keys (in form of X509 certificates) are normally stored in java keystores.
For example, if sender encrypts the message payload sending to the receiver, he should have access to receiver certificate saved in local keystore. The sender uses this certificate for message encryption and receiver decrypts request with corresponded own private key:
Seems to be OK? Imagine now that you have production environment with 100 different clients of this service and service certificate is expired. You should reissue and replace certificate in ALL client keystores! Even more, if keystores are packaged into war files or OSGi bundles – they should be unpackaged and updated. Not really acceptable for enterprise environments.
Therefore large service landscapes have concept of central certificates management. It means that X509 certificates are not stored locally in keystores, but are provided and administrated centrally.
Normally it is a responsibility of PublicKey Infrastructure (PKI) established in organization. PKI is responsible for create, manage, store, distribute, synchronize and revoke public certificates and certification authorities (CAs).
W3C specifies standard SOAP interface to access and administrate keys remotely: XML Key Management Specification (XKMS 2.0). XKMS contains two parts:
a) Key Information Service providing keys locating and validating functionality
b) Key Registration Service responsible for registration, revocation, recovery and reissuing of keys.
Note, that beginning from CXF 3.0.0, XKMS service and client implementation as well as XKMS Crypto Provider (see next chapter) will be available in CXF distribution.
WSS4J Crypto Providers
Fine, assume your organization has established simple PKI infrastructure and provides some kind of remote access to look up and validate the certificates (Rest or SOAP XKMS based).
The question now is the following: how to configure my CXF client and service to use public certificates not from local keystore, but from central PKI? Is there any easy way to do it?
Fortunately yes! CXF uses WSS4J for most of security aspects and WSS4J has own concept of crypto providers (don’t mix them with crypto providers of Java Cryptography Architecture).
WSS4J crypto providers are responsible to obtain X509 certificates and private keys, verify X509 trust chain and construct X509 certificates.
WSS4J crypto providers must implement Crypto interface. WSS4J also provides base abstract class with common functionality CryptoBase and keystore based implementation of crypto provider Merlin.
If you do not configure anything in CXF, WSS4J uses keystore based Merlin crypto provider by default. But, remember, we would like to get our certificates from PKI instead of keystore. Therefore it will be necessary to create own WSS4J crypto provider for this purpose. Interesting for us are two following methods:
We still obtain private keys from keystore, therefore corresponded methods will be delegated to standard Merlin provider. Skeleton implementation of PKI crypto provider can look like:
As far as crypto provider makes a lot of remote calls, it makes sense to care about certificate caching.
Using PKI Crypto provider our message encryption picture will change in following way:
CXF distributions (starting from version 3.0.0) will contain XKMS based WSS4J Crypto implementation, so you can just reuse it.
Configure custom WSS4J Crypto Providers
How CXF and WSS4J know that they should use our custom crypto provider instead standard Merlin? There are some ways to configure it.
Keystore properties
You can specify own crypto provider in keystore properties file.
clientKeystore.properties:
ws-security.encryption.properties says CXF that clientKeystore.properties will be used for the encryption. WSS4J will instantiate and call our PKICryptoProvider automatically. The same keystore properties can be specified for signature as well (ws-security.signature.properties).
Crypto provider object in Spring configuration
Sometimes more convenience way is to instantiate custom crypto provider explicitly. For example, if you want to inject proxy to communicate with PKI into PKICryptoProvider or use non-default constructor. For such cases CXF provides ws-security.encryption.crypto and ws-security.signature.crypto properties. You can pass pre-constructed PKICryptoProvider object using these properties.
Spring configuration in this case looks like:
Of course you can use the same approach to configure PKICryptoProvider for CXF service (jaxws:endpoint).
Specify Crypto provider object programmatically
Crypto provider object can be also passed programmatically. Client code will look like:
On the service side you can use this code:
And in interceptor:
In this blog we have discussed requirements and use cases to use central certificates management infrastructure, showed how to create custom PKI based crypto provider and configure it in CXF clients/services. PKI crypto provider can be used for communication with Security Token Service (STS) and inside STS to validate X509 certificates as well. But it is topic for the next blog.
If you look in samples and tutorials, the public keys (in form of X509 certificates) are normally stored in java keystores.
For example, if sender encrypts the message payload sending to the receiver, he should have access to receiver certificate saved in local keystore. The sender uses this certificate for message encryption and receiver decrypts request with corresponded own private key:
Seems to be OK? Imagine now that you have production environment with 100 different clients of this service and service certificate is expired. You should reissue and replace certificate in ALL client keystores! Even more, if keystores are packaged into war files or OSGi bundles – they should be unpackaged and updated. Not really acceptable for enterprise environments.
Therefore large service landscapes have concept of central certificates management. It means that X509 certificates are not stored locally in keystores, but are provided and administrated centrally.
Normally it is a responsibility of PublicKey Infrastructure (PKI) established in organization. PKI is responsible for create, manage, store, distribute, synchronize and revoke public certificates and certification authorities (CAs).
W3C specifies standard SOAP interface to access and administrate keys remotely: XML Key Management Specification (XKMS 2.0). XKMS contains two parts:
a) Key Information Service providing keys locating and validating functionality
b) Key Registration Service responsible for registration, revocation, recovery and reissuing of keys.
Note, that beginning from CXF 3.0.0, XKMS service and client implementation as well as XKMS Crypto Provider (see next chapter) will be available in CXF distribution.
WSS4J Crypto Providers
Fine, assume your organization has established simple PKI infrastructure and provides some kind of remote access to look up and validate the certificates (Rest or SOAP XKMS based).
The question now is the following: how to configure my CXF client and service to use public certificates not from local keystore, but from central PKI? Is there any easy way to do it?
Fortunately yes! CXF uses WSS4J for most of security aspects and WSS4J has own concept of crypto providers (don’t mix them with crypto providers of Java Cryptography Architecture).
WSS4J crypto providers are responsible to obtain X509 certificates and private keys, verify X509 trust chain and construct X509 certificates.
WSS4J crypto providers must implement Crypto interface. WSS4J also provides base abstract class with common functionality CryptoBase and keystore based implementation of crypto provider Merlin.
If you do not configure anything in CXF, WSS4J uses keystore based Merlin crypto provider by default. But, remember, we would like to get our certificates from PKI instead of keystore. Therefore it will be necessary to create own WSS4J crypto provider for this purpose. Interesting for us are two following methods:
- java.security.cert.X509Certificate[] getX509Certificates(CryptoType cryptoType)
- boolean verifyTrust(java.security.cert.X509Certificate[] certs, boolean enableRevocation)
We still obtain private keys from keystore, therefore corresponded methods will be delegated to standard Merlin provider. Skeleton implementation of PKI crypto provider can look like:
public class PKICryptoProvider extends CryptoBase {
private Crypto defaultCrypto;
private Properties keystoreProps;
public PKICryptoProvider() {
try {
// load keystore properties and alias for private keys
// …
defaultCrypto = CryptoFactory.getInstance(keystoreProps);
} catch (WSSecurityException e) {
throw new IllegalStateException(
"Cannot instantiate default crypto provider: "
+ e.getMessage(), e);
}
}
@Override
public X509Certificate[] getX509Certificates(CryptoType cryptoType)
throws WSSecurityException {
CryptoType.TYPE type = cryptoType.getType();
X509Certificate[] certs = new X509Certificate[0];
switch (type) {
case SUBJECT_DN: {
// get X509 certificate remotely from PKI on the base of subjectDN
break;
}
case ALIAS: {
// get X509 certificate remotely from PKI on the base of alias
break;
}
case ISSUER_SERIAL: {
// get X509 certificate remotely from PKI on the base of issuer DN
// and serial number
break;
}
default: {
throw new IllegalArgumentException("Not supported cryptoType: "
+ cryptoType);
}
}
return certs;
}
@Override
public String getX509Identifier(X509Certificate cert) {
return cert.getSubjectDN().getName();
}
@Override
public PrivateKey getPrivateKey(X509Certificate certificate,
CallbackHandler callbackHandler) throws WSSecurityException {
// …
return defaultCrypto.getPrivateKey(identifier, password);
}
@Override
public PrivateKey getPrivateKey(String identifier, String password)
throws WSSecurityException {
// …
return defaultCrypto.getPrivateKey(identifier, password);
}
@Override
public boolean verifyTrust(X509Certificate[] certs)
throws WSSecurityException {
return verifyTrust(certs, false);
}
@Override
public boolean verifyTrust(X509Certificate[] certs, boolean enableRevocation)
throws WSSecurityException {
// call PKI remote method to validate certificate trust chain
return false;
}
@Override
public boolean verifyTrust(PublicKey publicKey) throws WSSecurityException {
// call PKI remote method to validate certificate trust chain
return false;
}
}
As far as crypto provider makes a lot of remote calls, it makes sense to care about certificate caching.
Using PKI Crypto provider our message encryption picture will change in following way:
CXF distributions (starting from version 3.0.0) will contain XKMS based WSS4J Crypto implementation, so you can just reuse it.
Configure custom WSS4J Crypto Providers
How CXF and WSS4J know that they should use our custom crypto provider instead standard Merlin? There are some ways to configure it.
Keystore properties
You can specify own crypto provider in keystore properties file.
clientKeystore.properties:
org.apache.ws.security.crypto.provider=org.company.security.PKICryptoProvider
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=secret
org.apache.ws.security.crypto.merlin.keystore.private.password=secret
org.apache.ws.security.crypto.merlin.keystore.alias=myclient
org.apache.ws.security.crypto.merlin.keystore.file=./etc/keystores/clientstore.jks
After it you configure keystore properties for CXF client or service as usual. For example in Spring it will be looks like:
<jaxws:client id="MyClient" xmlns:serviceNamespace="http://services.company.org/MyService"
serviceClass="org.company.services.MyService" serviceName="serviceNamespace:MyServiceProvider"
endpointName="serviceNamespace:MyServicePort" address="http://localhost:8080/services/MyService">
<jaxws:properties>
<entry key="ws-security.encyption.properties" value="clientKeystore.properties" />
<entry key="ws-security.signature.properties" value="clientKeystore.properties" />
</jaxws:properties>
</jaxws:client>
ws-security.encryption.properties says CXF that clientKeystore.properties will be used for the encryption. WSS4J will instantiate and call our PKICryptoProvider automatically. The same keystore properties can be specified for signature as well (ws-security.signature.properties).
Crypto provider object in Spring configuration
Sometimes more convenience way is to instantiate custom crypto provider explicitly. For example, if you want to inject proxy to communicate with PKI into PKICryptoProvider or use non-default constructor. For such cases CXF provides ws-security.encryption.crypto and ws-security.signature.crypto properties. You can pass pre-constructed PKICryptoProvider object using these properties.
Spring configuration in this case looks like:
<bean id="pkiCryptoProvider" class="org.company.security.PKICryptoProvider">
<!—-inject proxies, fields here -->
</bean>
<jaxws:client id="MyClient" xmlns:serviceNamespace="http://services.company.org/MyService"
serviceClass="org.company.services.MyService" serviceName="serviceNamespace:MyServiceProvider"
endpointName="serviceNamespace:MyServicePort" address="http://localhost:8080/services/MyService">
<jaxws:properties>
…
<entry key="ws-security.encyption.crypto" value-ref=" pkiCryptoProvider " />
<entry key="ws-security.signature.crypto" value-ref=" pkiCryptoProvider " />
</jaxws:properties>
</jaxws:client>
Of course you can use the same approach to configure PKICryptoProvider for CXF service (jaxws:endpoint).
Specify Crypto provider object programmatically
Crypto provider object can be also passed programmatically. Client code will look like:
PKICryptoProvider pkiCryptoProvider = new PKICryptoProvider();
SOAPService ss = new SOAPService(wsdlURL, SERVICE_NAME);
Greeter port = ss.getSoapPort();
Map<String , Object> requestContext =
((javax.xml.ws.BindingProvider)port).getRequestContext();
requestContext.put(SecurityConstants.ENCRYPTION_CRYPTO,
pkiCryptoProvider);
requestContext.put(SecurityConstants.SIGNATURE_CRYPTO,
pkiCryptoProvider);
Remember that this code is not thread safe. To use thread safe request context it is necessary to add following property:
((BindingProvider)proxy).getRequestContext().put("thread.local.request.context", "true");
On the service side you can use this code:
…
@Resource
private WebServiceContext wsContext;
public ResponseData businessMethod(requestData) {
PKICryptoProvider pkiCryptoProvider = new PKICryptoProvider();
wsContext.getMessageContext().put(SecurityConstants.ENCRYPTION_CRYPTO, pkiCryptoProvider);
wsContext.getMessageContext().put(SecurityConstants.SIGNATURE_CRYPTO, pkiCryptoProvider);
}
And in interceptor:
public class CustomSecurityInterceptor extends AbstractPhaseInterceptor<Message> {
public CustomSecurityInterceptor () {
super(Phase.PRE_LOGICAL);
}
@Override
public void handleMessage(Message message) throws Fault {
PKICryptoProvider pkiCryptoProvider = new PKICryptoProvider();
message.put(SecurityConstants.ENCRYPTION_CRYPTO, pkiCryptoProvider);
message.put(SecurityConstants.SIGNATURE_CRYPTO, pkiCryptoProvider);
}
Conclusion In this blog we have discussed requirements and use cases to use central certificates management infrastructure, showed how to create custom PKI based crypto provider and configure it in CXF clients/services. PKI crypto provider can be used for communication with Security Token Service (STS) and inside STS to validate X509 certificates as well. But it is topic for the next blog.
Hi
ReplyDeleteJust wondering if you have published your next blog
"PKI crypto provider can be used for communication with Security Token Service (STS) and inside STS to validate X509 certificates"
Thanks
Sangram
Hi Sangram,
ReplyDeleteNot yet, but I will do it soon.
Basically integration in STS is done through WSS4J Crypto as well. I would show how STS configuration will look like in this case.
Regards,
Andrei.
Hello and thanks a lot for the article.
ReplyDeleteIs it normal to still have merlin properties in the properties file:
"
Keystore properties
You can specify own crypto provider in keystore properties file.
clientKeystore.properties:
org.apache.ws.security.crypto.provider=org.company.security.PKICryptoProvider
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=secret
org.apache.ws.security.crypto.merlin.keystore.private.password=secret
org.apache.ws.security.crypto.merlin.keystore.alias=myclient
org.apache.ws.security.crypto.merlin.keystore.file=./etc/keystores/clientstore.jks
"
Thanks a lot.
And one more question:
ReplyDeletethere is no configuration for the PKICryptoProvider?
Fo example how does it know the address of the PKI server.
Also, how do we pass the aliases we want? (either the alias for our certificate to sign with, or the one of some other web service to ecrypt when sending to that web service)
Thanks.