This directory contains the Java source code and pom.xml file required to compile a simple Java callout for Apigee, that encrypts or decrypts the Body of a SOAP message per the WS-Security standard, using an RSA Key and an x509v3 certificate.
This example is not an official Google product, nor is it part of an official Google product.
This material is Copyright 2018-2021, Google LLC. and is licensed under the Apache 2.0 license. See the LICENSE file.
This code is open source but you don't need to compile it in order to use it.
Use maven to build and package the jar. You need maven v3.5 at a minimum.
mvn clean package
The 'package' goal will copy the jar to the resources/java directory for the example proxy bundle. If you want to use this in your own API Proxy, you need to drop this JAR into the appropriate API Proxy bundle. Or include the jar as an environment-wide or organization-wide jar via the Apigee administrative API.
There is a single jar, apigee-wssec-xmlenc-20210409.jar . Within that jar, there are two callout classes,
- com.google.apigee.callouts.wsseccrypto.Encrypt - encrypts a SOAP document.
- com.google.apigee.callouts.wsseccrypto.Decrypt - decrypts the encrypted SOAP document
The Encrypt callout has these constraints and features:
- supports RSA key encryption algorithms - PKCS1.5 or OAEP
- supports AES content encryption algorithms - AES 128, 192, 256, in CBC or GCM
- supports soap1.1 and soap1.2
- encrypts the SOAP Body
- has options for embedding the Key and the certificate in the encrypted document
The Decrypt callout has these constraints and features:
- supports RSA algorithms - PKCS1.5 and OAEP
- supports AES content encryption algorithms - AES 128, 192, 256, in CBC or GCM
- supports soap1.1. (Not tested with soap 1.2; might work!)
Make sure these JARs are available as resources in the proxy or in the environment or organization.
-
xmlsec-2.1.5.jar and its dependencies
-
Bouncy Castle
The BouncyCastle jar is available as part of the Apigee runtime, although it is not a documented part of the Apigee platform and is therefore not guaranteed to remain available. In the highly unlikely future scenario in which Apigee removes the BC jar from the Apigee runtime, you could simply upload the BouncyCastle jar as a resource, either with the apiproxy or with the organization or environment, to resolve the dependency.
A simple policy that accepts an inbound SOAP document and encrypts the body looks like this:
<JavaCallout name='Java-WSSEC-Encrypt-1'>
<Properties>
<Property name='source'>message.content</Property>
<Property name='output-variable'>output</Property>
<Property name='certificate'>{my_certificate}</Property>
</Properties>
<ClassName>com.google.apigee.callouts.wsseccrypto.Encrypt</ClassName>
<ResourceURL>java://apigee-wssec-xmlenc-20210409.jar</ResourceURL>
</JavaCallout>
What this says:
- accept a SOAP document from message.content
- use the certificate specified (in PEM format) to extract an RSA Public Key for the encryption
- use the defaults for RSA algorithm, and key location, and certificate embedding
- place the output into the context variable
output
For a source document like this:
<soapenv:Envelope
xmlns:ns1='http://ws.example.com/'
xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'>
<soapenv:Body>
<ns1:sumResponse>
<ns1:return>9</ns1:return>
</ns1:sumResponse>
</soapenv:Body>
</soapenv:Envelope>
The result is a document like this:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wssec="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"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
xmlns:ns1="http://ws.example.com/"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<soapenv:Header>
<wssec:Security soapenv:mustUnderstand="1">
<wssec: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="SecurityToken-98d649fe-4941-468c-a24d-c1c57ef19d34">MIIDpDCCAowCCQDsXkZg2rbAwTANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xETAPBgNVBAcMCEtpcmtsYW5kMQ8wDQYDVQQKDAZHb29nbGUxDzANBgNVBAsMBkFwaWdlZTEaMBgGA1UEAwwRYXBpZ2VlLmdvb2dsZS5jb20xHjAcBgkqhkiG9w0BCQEWD2Rpbm9AYXBpZ2VlLmNvbTAeFw0xOTEwMDgxMTExMjBaFw0yOTEwMDUxMTExMjBaMIGTMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjERMA8GA1UEBwwIS2lya2xhbmQxDzANBgNVBAoMBkdvb2dsZTEPMA0GA1UECwwGQXBpZ2VlMRowGAYDVQQDDBFhcGlnZWUuZ29vZ2xlLmNvbTEeMBwGCSqGSIb3DQEJARYPZGlub0BhcGlnZWUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqJA2NwXeqXaVaHO0mkKDpTdhPCh/80gH2Oun4DJOYjYgvdMRJ83xB6Hwm809T593rkX9PwOjUDQ7kJvH0aLaqxb+FrrTDTFcXZXJZ65dca9lYARxgEAwasPIkvBdr0nP2W2VQgPKtkwStZinMiJh/JSlXCz7ULDGVqW8FyklGaVIkxrXhHsjH+hhJ8Kp+zjFsfdsTkGbaqXj/qexeHUBcF6GbHe7xhaLoj/P24D7mFHB3uXx4vN3ohP+ZiT1y5X8fCLVu5SSC+vDFHR2Z5I26yTlcNRwKt24lNypGsEzM5KZILJlEr3BnAA1qkcSX7wZQDHp3XOHJHaR6lxarvYlmQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBD+S4bF8b07955E7HshWFez5Q4/7cXhGPjFaiNw9lH9Ny1iIcblRl36iiVda4Lemy1Kxa5xGJ+I5NZ8k1MyxZ1x5K9bPX5LiI8ThLGRxBNUNLgaoQ+7FZLklpZARoIuQ3Gg90V0qUqkm2eipgZxzdtEGj+lqoX10A2B+wimO6nzUv6vYoJARMBtqsYmQKz5GRBoajGdMn60UF9Ry5B32k31JVpat4qm7+Ig1YMwv2nfY6bgHzsI4WjETOLvFCYgBDJzIEy+0jA1FUe5Ec5Fs5nmiG8F7FRJ/9aYb1e+cbQVRZyc1wKlmIReK/LgG8FjdDjeqFZTg0AjInG8/oOz5ib</wssec:BinarySecurityToken>
<xenc:EncryptedKey>
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
<ds:KeyInfo>
<wssec:SecurityTokenReference>
<wssec:Reference URI="#SecurityToken-98d649fe-4941-468c-a24d-c1c57ef19d34" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>
</wssec:SecurityTokenReference>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>K1U0yu1I7/EY2SSzKphZAQ+JX4Du9eDK0fPmNorkOtQfV+hpGpiHyq10ykN88Y9uneFvvwkM8mAx
9FT7f/AvWRE3SLCgVPu8UNK12byWZPGVm3oHjOopo9nD3jjFrT33qgyy4dwAeXekdfhrRO5edfl5
jkIC1iFAfmjmKHKEKpjPsM3Ba/sB89mS0MUQxuyZzo42TxcFcw0nvnc8rR6iy4AOMDkBz0wzHH7U
0M3FIfVK9JOkGUxZGK+ztATM0HwAmhUw362rq1or9Z4OqHE3e1p9B+AHh4tV+D0DbAG9sF0x3SBK
XIa63vOkN5co8sQ0s9b2tTpS6k0foNkZvIJ88Q==</xenc:CipherValue>
</xenc:CipherData>
<xenc:ReferenceList>
<xenc:DataReference URI="#Enc-1"/>
</xenc:ReferenceList>
</xenc:EncryptedKey>
</wssec:Security>
</soapenv:Header>
<soapenv:Body wsu:Id="Body-a5a5ab07-26fe-404c-a8e3-f353f6cb2831">
<xenc:EncryptedData Id="Enc-1" Type="http://www.w3.org/2001/04/xmlenc#Element">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes192-cbc"/>
<xenc:CipherData>
<xenc:CipherValue>HJ6LzvAYym73A5A3llk3UPQft8Dk5IYH1bVJngj0xbJexo3MfWX0/f2ee4Kdxz42+6YysDCgpXwl
7irG4WdM4yqpVwqBeeWg9G9sVmfnfnOTvHPQRBPb4CFVpSuQk0y9</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</soapenv:Body>
</soapenv:Envelope>
You can see that:
- the
soap:Body
element is now replaced withxenc:EncryptedData
- There is an
EncryptedKey
element placed in thesoap:Header
. This is the symmetric content-encryption key - The x509 certificate used is embedded in a
wsse:BinarySecurityToken
in thesoap:Header
. - The content-encryption algorithm is aes192-cbc (the default)
- The key-encryption algorithm is RSA PKCS1.5 (the default)
By changing the properties applied to the policy, there are numerous other variations possible.
The available properties are:
name | description |
---|---|
source | source of the SOAP document to sign. Usually message.content. |
soap-version | soap1.1 or soap1.2. Defaults to soap1.1 |
certificate | required. the X509v3 certificate to use for the public key for encryption. This cert will then be referenced in the encrypted document in some way. |
rsa-algorithm | optional. PKCS1_5 or OAEP. Defaults to PKCS1_5 |
key-identifier-type | optional. how to embed a reference to the key in the output document. |
encrypted-key-location | optional. where to embed the encrypted key in the output document. This is either in-security-header or under-encrypted-data , Defaults to in header. |
issuer-name-style | optional. One of {SHORT , SUBJECT_DN }. See below for details. |
content-encryption-cipher | AES-128-CBC, AES-192-CBC, AES-256-CBC, AES-128-GCM, AES-192-GCM, AES-256-GCM or TRIPLEDES. Defaults to AES-128-CBC. ) |
Regarding key-identifier-type
, these are the options:
-
bst_direct_reference
. This is the default; this is what you get if you omit this property. With this setting, the Sign callout embeds the certificate into the signed document using a BinarySecurityToken and a SecurityTokenReference that points to it.The KeyInfo element looks like this:
<KeyInfo> <wssec:SecurityTokenReference> <wssec:Reference URI="#SecurityToken-e828bfab-bb52-4429" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/> </wssec:SecurityTokenReference> </KeyInfo>
And there will be a child element of the wssec:Security element that looks like this:
<wssec: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="SecurityToken-e828bfab-bb52-4429-b6a4-755b26abc387">MIIC0...</wssec:BinarySecurityToken>
-
thumbprint
gives you this:<KeyInfo> <wsse:SecurityTokenReference> <wsse:KeyIdentifier ValueType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security1.1#ThumbprintSHA1">9JscCwWHk5IvR/6JLTSayTY7M=</wsse:KeyIdentifier> </wsse:SecurityTokenReference> </KeyInfo>
-
issuer_serial
(common with WCF) results in this:<KeyInfo> <wsse:SecurityTokenReference wsu:Id="STR-2795B41DA34FD80A771574109162615125"> <X509Data> <X509IssuerSerial> <X509IssuerName>CN=common.name.on.cert</X509IssuerName> <X509SerialNumber>837113432321</X509SerialNumber> </X509IssuerSerial> </X509Data> </wsse:SecurityTokenReference> </KeyInfo>
For this case, you can specify another property,
issuer-name-style
, as eithershort
orsubject_dn
. The former is the default. The latter results in something like this:<X509IssuerSerial> <X509IssuerName>C=US,ST=Washington,L=Kirkland,O=Google,OU=Apigee,CN=apigee.google.com,E=dino@apigee.com</X509IssuerName> <X509SerialNumber>837113432321</X509SerialNumber> </X509IssuerSerial>
-
x509_cert_direct
gives you this:<KeyInfo> <X509Data> <X509Certificate>MIICAjCCAWu....7BQnulQ=</X509Certificate> </X509Data> </KeyInfo>
-
rsa_key_value
gives you this:<KeyInfo> <KeyValue> <RSAKeyValue> <Modulus>B6PenDyT58LjZlG6LYD27IFCh1yO+4...yCP9YNDtsLZftMLoQ==</Modulus> <Exponent>AQAB</Exponent> </RSAKeyValue> </KeyValue> </KeyInfo>
<JavaCallout name='Java-WSSEC-Decrypt-1'>
<Properties>
<Property name='source'>message.content</Property>
<Property name='require-expiry'>false</Property>
<Property name='private-key'>{my_private_key}</Property>
</Properties>
<ClassName>com.google.apigee.callouts.wsseccrypto.Decrypt</ClassName>
<ResourceURL>java://apigee-wssec-xmlenc-20210409.jar</ResourceURL>
</JavaCallout>
The properties are:
name | description |
---|---|
source | optional. the variable name in which to obtain the source signed document to validate. Defaults to message.content |
private-key | required. The PEM file containing the RSA private key to decrypt. |
rsa-algorithm | optional. Specify PKCS1_5 or OAEP to require either of these. |
content-encryption-algorithm | optional. Specify one of the AES-* or TRIPLEDES to require tone of those. |
See the example API proxy included here for a working example of these policy configurations.
Deploy the API Proxy to an organization and environment using a tool like importAndDeploy.js
There are some sample SOAP request documents included in this repo that you can use for demonstrations.
This request encrypts using key encryption RSA PKCS1.5, content encryption AES-128-CBC, using BinarySecurityToken to embed the certificate:
ORG=myorgname
ENV=myenv
curl -i https://${ORG}-${ENV}.apigee.net/wssec-enc/encrypt1 -H content-type:application/xml \
--data-binary @./sample-data/request1.xml
There are other combinations; see the API Proxy bundle for the variations of encryption options.
This request decrypts:
curl -i https://${ORG}-${ENV}.apigee.net/wssec-enc/decrypt1 -H content-type:application/xml \
--data-binary @./sample-data/encrypted-request.xml
The output of the above should indicate that the signature on the document is valid.
There is a private RSA key and a corresponding certificate embedded in the API Proxy. You should not use those for your own purposes. Create your own. Self-signed is fine for testing purposes. You can do it with openssl. Creating a privatekey, a certificate signing request, and a certificate, is as easy as 1, 2, 3:
openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:2048 -out privatekey.pem
openssl req -key privatekey.pem -new -out domain.csr
openssl x509 -req -days 3650 -in domain.csr -signkey privatekey.pem -out domain.cert
none?