This repo contains the Java source code and pom.xml file required to compile a simple Java callout for Apigee, that creates or validates a signed SOAP document that complies with the SOAP Message Security standard, sometimes referred to as WS-Security. This repo also contains the packaged jar.
There's a great deal of flexibility in the WS-Security standard, in terms of how signatures are generated and embedded into a document, and how keys are referenced. This callout in particular supports:
-
RSA key pairs
-
Signing or validating with RSA-SHA1 (http://www.w3.org/2000/09/xmldsig#rsa-sha1 ) or RSA-SHA256 (http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 ). The latter is highly recommended.
-
using a digest method of sha1 (http://www.w3.org/2000/09/xmldsig#sha1) or sha256 (http://www.w3.org/2001/04/xmlenc#sha256) . The latter is highly recommended.
-
When signing
- signing either or both of the soap:Body, and the wssec:Security/wsu:Timestamp element, and optionally signing SignatureConfirmation elements. Signing both Body and Timestamp is highly recommended.
- injecting a Timestamp element if one does not exist, with an expiry, optionally
- injecting one or more SignatureConfirmation elements as necessary
-
When validating
- checking that the WS-Security Security header is a child of the SOAP Header
- validating all signatures
- checking that either the Body, or the Timestamp, or both, have been signed.
- checking the Timestamp expiry, and optionally the maximum lifetime of the signed document
- checking signatures via public keys embedded in X509v3 certificates.
- Obtaining the certificates to use to Validate in one of two ways: either in the signed document itself, or as configuration to the Validate step.
This replaces the previous version of the callout, which can still be found at this link . The previous version of the callout was not parameterizable, and also depended upon wss4j. The wss4j dependency prevented the use of the callout in Apigee Edge or Apigee X (cloud hosted versions of Apigee). This callout does not have a dependency on wss4j, and can run in the cloud-hosted versions of Apigee. Also, this callout is much more flexible and parameterizable.
This example is not an official Google product, nor is it part of an official Google product.
This material is Copyright 2018-2024, 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.
You do not need to build this callout in order to use it. You can build it if you wish. To do so, use Apache Maven. To build, you need:
- JDK 8 or JDK 11
- maven v3.9 at a minimum
To build on JDK 11, make sure you have a JDK11 bin on your path, and:
mvn clean package
To build on JDK 8, make sure you have a JDK8 bin on your path, and:
mvn -f pom-java8.xml 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 copy 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-wssecdsig-20241129.jar . Within that jar, there are two callout classes,
- com.google.apigee.callouts.wssecdsig.Sign - signs the input SOAP document.
- com.google.apigee.callouts.wssecdsig.Validate - validates the signed SOAP document
The Sign callout has these constraints and features:
- supports RSA algorithms - rsa-sha1 (default) or rsa-sha256
- supports soap1.1 and soap1.2
- Will automatically add a Timestamp to the WS-Security header
- Can optionally add an explicit Expiry to that timestamp (recommended)
- signs the SOAP Body, or the Timestamp, or both (default)
- uses a canonicalization method of "http://www.w3.org/2001/10/xml-exc-c14n#"
- uses a digest mode of sha1 (default) or sha256
- has various options for embedding the KeyInfo for the certificate in the signed document: directly embedding the certificate, embedding a thumprint, a serial number, or embedding a public RSA key.
The Validate callout has these constraints and features:
- supports RSA algorithms - rsa-sha1 (default) or rsa-sha256 (recommended)
- supports soap1.1. (Not tested with soap 1.2; might work!)
- Enforces the location of the WS-Sec Security element as a child of the SOAP header (by default, though this is optional.
- If a Timestamp is present in the WS-Security header, validates expiry.
- Optionally require that a Timestamp is present in the WS-Security header, with an Expires element.
- Optionally enforce a maximum lifetime of the signature. This is the difference between Created and Expires within the Timestamp. You may wish to limit this to 5 minutes, for example.
- verify that a specific digest method - sha-1 or sha-256 - is used when signing.
- verify that the certificate that provides the verification key, is not expired
- verify the thumbprint on the certificate that provides the verification key
- optionally verify the Common Name on the certificate matches a particular value
Make sure these JARs are available as resources in the proxy or in the environment or organization.
- Bouncy Castle: bcprov-jdk15on-1.66.jar, bcpkix-jdk15on-1.66.jar
This Callout does not depend on WSS4J. The WSS4J is prohibited from use within Apigee SaaS, due to Java permissions settings. This callout is intended to be usable in Apigee SaaS (Edge or X), OPDK, or hybrid.
Configure the policy this way:
<JavaCallout name='Java-WSSEC-Sign'>
<Properties>
<Property name='source'>message.content</Property>
<Property name='output-variable'>output</Property>
<Property name='expiry'>180s</Property>
<Property name='signing-method'>rsa-sha256</Property>
<Property name='digest-method'>sha256</Property>
<Property name='private-key'>{my_private_key}</Property>
<Property name='certificate'>{my_certificate}</Property>
</Properties>
<ClassName>com.google.apigee.callouts.wssecdsig.Sign</ClassName>
<ResourceURL>java://apigee-wssecdsig-20241129.jar</ResourceURL>
</JavaCallout>There are a number of available properties for configuring the Sign callout, to affect the shape of the signed document. This affects things like what elements to sign, which signature method to use, the desired format of the Key Information, and much more. These properties are described in detail here:
| name | description |
|---|---|
source |
optional. the variable name in which to obtain the source document to sign. Defaults to message.content |
soap-version |
optional. Either soap1.1 or soap1.2. Defaults to soap1.1 . |
output-variable |
optional. the variable name in which to write the signed XML. Defaults to message.content |
private-key |
required. the PEM-encoded RSA private key. You can use a variable reference here as shown above. Probably you want to read this from a secure store - maybe the encrypted KVM. |
private-key-password |
optional. The password for the key, if it is encrypted. |
key-identifier-type |
optional. One of {BST_DIRECT_REFERENCE, THUMBPRINT, ISSUER_SERIAL, X509_CERT_DIRECT, or RSA_KEY_VALUE}. Defaults to BST_DIRECT_REFERENCE. See below for details on these options. |
issuer-name-style |
optional. One of {CN, DN}. This is relevant only if key-identifier-type has the value ISSUER_SERIAL. See below for details. |
certificate |
required. The certificate matching the private key. In PEM form. |
signing-method |
optional. Takes value rsa-sha1 or rsa-sha256. Defaults to rsa-sha1. Despite this, rsa-sha256 is highly recommended. |
digest-method |
optional. Takes value sha1 or sha256. Defaults to sha1. If you have the flexibility to do so, it's preferred that you use sha256. |
elements-to-sign |
optional. Takes a comma-and-maybe-space-separated value of prefix:Tag forms. For example "wsu:Timestamp, soap:Body, wsa:To, wsa:MessageID". Case is important. Default: the signer signs both the wsu:Timestamp and the soap:Body. |
expiry |
optional. Takes a string like 120s, 10m, 4d, etc to imply 120 seconds, 10 minutes, 4 days, and injects an Expires element into the Timestamp. Default: no expiry. |
c14-inclusive-elements |
optional. Takes a comma-separated value of namespace URIs (not prefixes). Used to add an InclusiveElements element to the CanonicalizationMethod element. |
transform-inclusive-elements |
optional. Takes a comma-separated value of namespace URIs (not prefixes). Used to add an InclusiveElements element to the Transform element. |
ds-prefix |
optional. A simple string, to be used as the prefix for the namespace "http://www.w3.org/2000/09/xmldsig#". Some users have expressed a desire to control this, and this callout makes it possible. This property affects the aesthetics of the document only, does not affect the XML InfoSet. In case you care, the default prefix is "ds". |
confirmations |
optional. Either: (a) a list of signature values in SignatureConfirmation elements, which will then be signed. If a SignatureConfirmation element with a given value is not present, one will be injected. or (b) the string \*all\* , to indicate that any existing SignatureConfirmation elements in the source document will be signed. or (c) an empty string, which tells the callout to inject an empty SignatureConfirmation element. These signatures are in addition to the elements specified in elements-to-sign. |
ignore-security-header-placement |
optional. true or false, defaults false. When true, tells the sign callout to not check the placement of any existing Security header in the unsigned payload. For compatibility with some legacy systems. This is not recommended because it can expose you to signature wrapping attacks. |
This policy will create the appropriate signature or signatures, and embed each Signature element as a child of the WS-Security header element.
The value you specify for the key-identifier-type property affects the shape of the output KeyInfo element. 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 aBinarySecurityTokenand aSecurityTokenReferencethat points to it.The resulting
KeyInfoelement 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:Securityelement 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>
-
thumbprintgives you aSecurityTokenReferencewith aKeyIdentifier, like 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>
Use this if you plan to share the certificate with the receiver, and the receiver will verify the certificate via the thumbprint. There is no way to embed a SHA256 thumbprint of the certificate, today.
-
issuer_serial(common with WCF) gives you aSecurityTokenReferencewith an identification of an X509 cert, like 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 optionally specify another property,
issuer-name-style, as eitherCNorDN. For the former, and an example for that is shown above; only the CN is included in theIssuerNameelement. The latter is the default, and provides the full distinguished name (DN), which 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_directgives you aKeyInfowith theX509Datadirectly embedding the certificate, like this:<KeyInfo> <X509Data> <X509Certificate>MIICAjCCAWu....7BQnulQ=</X509Certificate> </X509Data> </KeyInfo>
-
rsa_key_valuegives you aKeyInfowith aKeyValueelement, like this:<KeyInfo> <KeyValue> <RSAKeyValue> <Modulus>B6PenDyT58LjZlG6LYD27IFCh1yO+4...yCP9YNDtsLZftMLoQ==</Modulus> <Exponent>AQAB</Exponent> </RSAKeyValue> </KeyValue> </KeyInfo>
All of these are valid according to the WS-Security standard. In all cases, the sender and receiver of a signed document must agree on which configuration to use.
Here's an example policy configuration:
<JavaCallout name='Java-WSSEC-Validate'>
<Properties>
<Property name='source'>message.content</Property>
<Property name='max-lifetime'>10m</Property>
<Property name='accept-thumbprints'>ada3a946669ad4e6e2c9f81360c3249e49a57a7d</Property>
</Properties>
<ClassName>com.google.apigee.callouts.wssecdsig.Validate</ClassName>
<ResourceURL>java://apigee-wssecdsig-20241129.jar</ResourceURL>
</JavaCallout>This will:
-
verify a WS-Security signature on the specified document, by default requiring that both soap:Body and wsu:Timestamp are signed.
-
It will by default verify that both a Created and an Expires element exist in the Timestamp. It will require that the timespan between the Created and Expires times does not exceed 10 minutes.
-
It will validate only a signed document that includes an embedded certificate. It will check that the embedded cert is valid (not expired and not being used before its not-before date). It will also check that the base16-encoded (aka hex-encoded) SHA1 thumbprint on the embedded certificate matches that specified in the
accept-thumbprintsproperty.
To verify a signature, over both the soap:Body and the wsu:Timestamp elements, but NOT require a Timestamp/Expires element, use this:
<JavaCallout name='Java-WSSEC-Validate'>
<Properties>
<Property name='source'>message.content</Property>
<Property name='require-expiry'>false</Property>
<Property name='accept-thumbprints'>ada3a946669ad4e6e2c9f81360c3249e49a57a7d</Property>
</Properties>
<ClassName>com.google.apigee.callouts.wssecdsig.Validate</ClassName>
<ResourceURL>java://apigee-wssecdsig-20241129.jar</ResourceURL>
</JavaCallout>To verify a signature, over both the soap:Body and the wsu:Timestamp elements, but NOT require a Timestamp/Expires element, and also enforce a subject common name on the certificate, use this:
<JavaCallout name='Java-WSSEC-Validate'>
<Properties>
<Property name='source'>message.content</Property>
<Property name='require-expiry'>false</Property>
<Property name='accept-thumbprints'>ada3a946669ad4e6e2c9f81360c3249e49a57a7d</Property>
<Property name='accept-subject-cns'>host.example.com</Property>
</Properties>
<ClassName>com.google.apigee.callouts.wssecdsig.Validate</ClassName>
<ResourceURL>java://apigee-wssecdsig-20241129.jar</ResourceURL>
</JavaCallout>The properties available for the Validate callout are:
| name | description |
|---|---|
source |
optional. the variable name in which to obtain the source signed document to validate. Defaults to message.content |
signing-method |
optional. Takes value rsa-sha1 or rsa-sha256. Checks that the signing method on the document is as specified. If this property is not present, there is no check on the algorithm. |
digest-method |
optional. Takes value sha1 or sha256. Checks that the digest method for each reference is as specified. If this property is not present, there is no check on the algorithm. |
accept-thumbprints |
optional. a comma-separated list of SHA-1 thumbprints of the certs which are acceptable signers. If any signature is from a cert that has a thumbprint other than that specified, the verification fails. Either this property, or the similar accept-thumbprints-sha256 is required if the certificate property is not provided. You should specify only one of accept-thumbprints or accept-thumbprints-256. |
accept-thumbprints-sha256 |
optional. a comma-separated list of SHA-256 thumbprints of the certs which are acceptable signers. If any signature is from a cert that has a thumbprint other than that specified, the verification fails. Either this property, or the similar accept-thumbprints is required if the certificate property is not provided. You should specify only one of accept-thumbprints or accept-thumbprints-256. |
accept-subject-cns |
optional. a comma-separated list of common names (CNs) for the subject which are acceptable signers. If any signature is from a CN other than that specified, the verification fails. |
require-expiry |
optional. true or false, defaults true. Whether to require an expiry in the timestamp. It is highly recommended that you use 'true' here, or just omit this property and accept the default. |
required-signed-elements |
optional. a comma-and-maybe-space-separated list of prefix:Tag forms indicating the elements that must be signed. Defaults to soap:Body, wsu:Timestamp . To require only a signature on the wsu:Timestamp and not the soap:Body when validating, set this to wsu:Timestamp. (You probably don't want to do this.) To require only a signature on the Body and not the Timestamp when validating, set this to soap:Body. (You probably don't want to do this, either.) Probably you want to just leave this element out of your configuration and accept the default. Case is significant for the prefix and the tag. The predefined prefixes are listed below. |
ignore-expiry |
optional. true or false. defaults false. When true, tells the validator to ignore the Timestamp/Expires field when evaluating validity of the soap message. |
ignore-certificate-expiry |
optional. true or false. defaults false. When true, tells the validator to ignore any validity dates on the provided certificate. Useful mostly for testing. |
ignore-security-header-placement |
optional. true or false, defaults false. When true, tells the validator to not check the placement of the Security header in the signed payload. For compatibility with some legacy systems. This is not recommended because it can expose you to signature wrapping attacks. |
max-lifetime |
optional. Takes a string like 120s, 10m, 4d, etc to imply 120 seconds, 10 minutes, 4 days. Use this to limit the acceptable lifetime of the signed document. This requires the Timestamp to include a Created as well as an Expires element. Default: no maximum lifetime. |
throw-fault-on-invalid |
optional. true or false, defaults to false. Whether to throw a fault when the signature is invalid, or when validation fails for another reason (wrong elements signed, lifetime exceeds max, etc). |
certificate |
optional. The certificate that provides the public key to verify the signature. This is required (and used) only if the KeyInfo in the signed document does not explicitly provide the Certificate. |
issuer-name-style |
optional. One of {CN, DN}. Used only if the signed document includes a KeyInfo that wraps X509IssuerSerial. See below for further details. |
issuer-name-dn-comparison |
optional. One of {string, normal, reverse, unordered}, default is string. Applies only if the signed document includes a KeyInfo that wraps X509IssuerSerial and the issuer-name-style is DN (which is the default). See below for further details. |
issuer-name-dn-comparison-exclude-numeric-oids |
optional. true/false. Applies only if the signed document includes a KeyInfo that wraps X509IssuerSerial and the issuer-name-style is DN, and issuer-name-dn-comparison is normal, reverse or unordered. See below for further details. |
The result of the Validate callout is to set a single variable: wssec_valid.
It takes a true value if the signature was valid; false otherwise. You can use a
Condition in your Proxy flow to examine that result. If the document is
invalid, then the policy will also throw a fault if the throw-fault-on-invalid
property is true.
Further comments:
-
The Validate callout checks for the presence of a Security header which uses the XML namespace
http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd. This is normally a child of the SOAP Header. This callout by default checks that placement, to protect against XM Signature Wrapping attacks, but you can disable the check using the propertyignore-security-header-placement. See the table above. -
The Validate callout verifies signatures using x509v3 certificates that contain RSA public keys. The callout is not able to validate a signature using an embedded RSA key found in the signed document. (This is a reasonable feature enhancement; but it hasn't been requested yet.)
-
Every certificate has a "thumbprint", which is just a SHA-1 or SHA-256 hash of the encoded certificate data. This thumbprint is unique among certificates. If the certificate is embedded within the signed document, then the Validate callout checks for certificate trust via these thumbprints. In that case, one of
accept-thumbprintsoraccept-thumbprints-sha256is required; You must configure one of those properties when using the Validate callout on a signed document that embeds the certificate.When validating a signed document that does not embed the certificate, you must explicitly provide the certificate in the callout configuration via the
certificateproperty. In that caseaccept-thumbprintsandaccept-thumbprints-sha256are ignored, because the assumption is that if you specify the certificate, you trust it. -
The
issuer-name-styleproperty is meaningful only if the incoming signed document includes aKeyInfoelement, which wraps anX509IssuerSerialelement. Signers can use a brief form, specifying only the CN of the issuer (e.g.CN=xxx), or a full DN style, of a structure similar toCN=xxx,O=xxx,L=xxx,ST=xxx,C=US. By default the callout will infer the appropriate name style. Specify eitherCNorDNhere to force the callout to use a particular style. If you useDNhere, or leave it blank, there is an additional propertyissuer-name-dn-comparison, which accepts one of {string,normal,reverse,unordered}.-
string: does a straight string comparison of the Issuer DN in the document, against the Issuer DN on the certificate. -
normal,reverse,unordered: compares each RDN in the Issuer DN in the document, against the corresponding RDN from the Issuer DN on the certificate. In thenormalcase, the order is normal. In thereversecase, the callout reverses the order of the RDNs before comparison; some signers do this. In theunorderedcase, the callout just checks that each RDN in the Issuer DN from the doc is present in the Issuer DN on the cert, without considering order. In all three of these options, there is another property,issuer-name-dn-comparison-exclude-numeric-oids, which tells the callout to exclude RDNs that begin with numbers. Sometimes, RDNs are encoded into strings using an LDAP OID, rather than a string, for the attribute type. For example, the OID1.2.840.113549.1.9.1refers to anemailAddressattribute. If a signed document uses numeric OIDs for some RDNs, the straight "string comparison" will fail. This property can work around that interoperability issue.
-
-
With the
max-lifetimeproperty, you can configure the policy to reject a signature that has a lifetime greater, say, 5 minutes. The maximum lifetime of a signed documented is computed from the asserted (and, ideally signed) Timestamp, by computing the difference between the Created and the Expires times. It does not make sense to specifymax-lifetimeif you also specifyrequired-signed-elementsto not include Timestamp, for an obvious reason: If the signature does not sign the Timestamp, it means any party can change the Timestamp, and therefore the computed lifetime of the document would be untrustworthy. -
it is possible to configure the policy with
require-expiry= true andignore-expiry= true. While this seems nonsensical, it can be useful in testing scenarios. It tells the policy to check that an Expires element is present in the Timestamp, but do not evaluate the value of the element. This will be wanted rarely if ever, in a production situation. -
There is a
wssec_errorvariable that gets set when the validation check fails. It will give you some additional information about the validation failure. -
For specifying which elements must be checked for signature in the
required-signed-elementsproperty, there is no way to define a prefix in the policy configuration. Instead you must select a prefix from the set of available "conventional" prefixes known by this callout. These are:prefix namespace wsahttp://www.w3.org/2005/08/addressing wsuhttp://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd soapeither http://schemas.xmlsoap.org/soap/envelope/ for soap1.1 or http://www.w3.org/2003/05/soap-envelope for soap1.2 These are the only prefixes available to check.
As an example, if your document uses
soapenvas the prefix for the soap1.1 namespace, then you can use a string likesoap:Bodyin therequired-signed-elementsproperty to require that the callout validate that the Body element has been signed.
See the example API proxy included here for a working example of these policy configurations.
There is a well-described technique for attacking XML signatures, called "signature wrapping". It involves modifying the signed document in such a way that the verifier still verifies a signature, but the signature is on the wrong element in the document. The actual body gets replaced with malicious content.
This callout is not vulnerable to the signature wrapping attack, because it verifies that the Body and Timestamp are signed, and that they appear in the expected places in the XML document, and that there is exactly one WS-Security Security element, and that it is a direct child of the SOAP Header element.
Regarding the check on placement, if you have a signed SOAP document that looks like this:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:oas="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:ser="http://webservices.cashedge.com/services"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<soapenv:Header>
<ser:AuthHeader>
<ser:HomeID>redacted</ser:HomeID>
<!-- In this document, the Security element is not a child of the soap Header. -->
<oas:Security>
<oas:UsernameToken>
<oas:Username>redacted</oas:Username>
<oas:Password>redacted</oas:Password>
</oas:UsernameToken>
</oas:Security>
</ser:AuthHeader>
...the callout will, by default, reject that because the Security element is not a direct child of the soap Header.
You can disable the check of the placement of the Security header, by setting
the property ignore-security-header-placement to the value true. In that
case, the Validate callout would treat a signed document structured as above, to
be valid.
Deploy the API Proxy to an organization and environment using a tool like importAndDeploy.js or apigeecli.
There are some sample SOAP request documents included in this repo that you can use for demonstrations.
-
Signing with Timestamp but no expiry, using BinarySecurityToken
# Apigee Edge endpoint=https://${ORG}-${ENV}.apigee.net # Apigee X or hybrid endpoint=https://my-api-endpoint.net curl -i $endpoint/wssec/sign1 -H content-type:application/xml \ --data-binary @./sample-data/request1.xml -
Signing with Timestamp that includes an expiry, with BinarySecurityToken
curl -i $endpoint/wssec/sign2 -H content-type:application/xml \ --data-binary @./sample-data/request1.xml -
Signing with Timestamp and expiry, emitting KeyInfo containing X509IssuerSerial
curl -i $endpoint/wssec/sign3 -H content-type:application/xml \ --data-binary @./sample-data/request1.xml -
Signing with Timestamp and expiry, emitting KeyInfo containing X509Data (raw certificate)
curl -i $endpoint/wssec/sign4 -H content-type:application/xml \ --data-binary @./sample-data/request1.xml -
Signing with Timestamp and expiry, emitting KeyInfo containing Thumbprint
curl -i $endpoint/wssec/sign5 -H content-type:application/xml \ --data-binary @./sample-data/request1.xml -
Signing, emitting KeyInfo with raw RSA Key
curl -i $endpoint/wssec/sign6 -H content-type:application/xml \ --data-binary @./sample-data/request1.xml -
Signing with SHA256 and RSA-SHA256 digest and signature methods
curl -i $endpoint/wssec/sign7 -H content-type:application/xml \ --data-binary @./sample-data/request1.xml -
Validating with hardcoded Common Name
curl -i $endpoint/wssec/validate1 -H content-type:application/xml \ --data-binary @./sample-data/signed-request.xmlThe output of the above should indicate that the signature on the document is valid.
-
Validating with hardcoded Common Name, and a message with an expiry
curl -i $endpoint/wssec/validate1 -H content-type:application/xml \ --data-binary @./sample-data/signed-expiring-request.xmlThe output of the above should indicate that the message is expired.
-
Validating with parameterized Thumbprint
curl -i $endpoint/wssec/validate2?thumbprint=xxxyyyyzzz \ -H content-type:application/xml \ --data-binary @./sample-data/signed-request.xmlThe output of the above should indicate that the signature on the document is not valid, because the thumbprint provided does not match the thumbprint on the cert used to sign the document.
curl -i $endpoint/wssec/validate2?thumbprint=ada3a946669ad4e6e2c9f81360c3249e49a57a7d \ -H content-type:application/xml \ --data-binary @./sample-data/signed-request.xmlThe output of the above should indicate that the signature on the document is valid, because the thumbprint provided matches the thumbprint on the cert that was used to sign the document.
-
Validating with specified digest and signing method
curl -i $endpoint/wssec/validate3 -H content-type:application/xml \ --data-binary @./sample-data/signed-request-nonexpiring-sha256.xmlThe output of the above should indicate that the message is valid.
Supposing the input XML looks 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>Then, given the default settings for digest-method, signing-method, and key-identifier-type,
the signed payload looks like this:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://ws.example.com/"
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">
<soapenv:Header>
<wssec:Security soapenv:mustUnderstand="1">
<wsu:Timestamp wsu:Id="Timestamp-57cd5229-1827-4fb7-a3fd-e9fd98dcd243">
<wsu:Created>2019-10-08T10:25:57Z</wsu:Created>
</wsu:Timestamp>
<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-24acaa0b-6643-40ef-be10-b5b65195bc12">MIIDpDCCAowCCQCVwuB4ec2igTANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xETAPBgNVBAcMCEtpcmtsYW5kMQ8wDQYDVQQKDAZHb29nbGUxDzANBgNVBAsMBkFwaWdlZTEaMBgGA1UEAwwRYXBpZ2VlLmdvb2dsZS5jb20xHjAcBgkqhkiG9w0BCQEWD2Rpbm9AYXBpZ2VlLmNvbTAeFw0xOTEwMDgxMDE0MTlaFw0yOTEwMDUxMDE0MTlaMIGTMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjERMA8GA1UEBwwIS2lya2xhbmQxDzANBgNVBAoMBkdvb2dsZTEPMA0GA1UECwwGQXBpZ2VlMRowGAYDVQQDDBFhcGlnZWUuZ29vZ2xlLmNvbTEeMBwGCSqGSIb3DQEJARYPZGlub0BhcGlnZWUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApTEZRbMzhzl29R9SQ6mpo4Bz5DlvxbupLzelu6xPvi1K8JAd5GdKlvImUobDYznNUlvSxSQgJb8FNYFQ9Ty6jDle2+nOo8jIWf/FRByzRz+q7dGVNk2ngYteAfnjM62pFzb+asrxMNexP6atJukdcq3RpBac4FTTreHr68rvYlXs0/GpHj6sDXiguf+921aMb7ox0BGiuh4ydzPMofXXL4IF8HJQoUkXvJ7FGEGqK5R78/FcOvOzMim2TOKuO2TraUFtezFvUG0waTOGexhUfFI4AKD8lHuR0SlAThniVYs9P+X+ySmv/G/aYJPeYq4Lh3Ox1fUkE8EcSPvqqfzD1wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQAjsv6qkiUjoOOKMVMxkUtfNzbRKbpv4wDL4PR4mavPxRfJC9X9b5hozSkyOaxqkJ4XUwqXS9PwI3/D47P5kuLS5Q7sWHbphKFgJf5r8RAX5c3LjImodwPebrRXfouvQXn55LUDBFMEVp8fZOL10FRP0RIT22C7tAhU9eL8khSW0mPv+CNC410mDlxDat9N7RPC/EOxfroFk8Wv29rTRSR5boSdSFaPQkm8LjNW8VimdMu1qEg4sRlcEJlfQFE2ZojdhJGfftSXCOm+rin8MSzG6SE2fDrq44evnamzC321CebW16KoTcrFf4W/jCXdZx5iWLlvgK5XOhz9BmNo8Fal</wssec:BinarySecurityToken>
<Signature
xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="#Body-97d94bd5-96d8-46e6-ad55-a3f1e12a413b">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>l3Wd6rPSvwISidh/HI6YH8iXwdw=</DigestValue>
</Reference>
<Reference URI="#Timestamp-57cd5229-1827-4fb7-a3fd-e9fd98dcd243">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>TM7h1jcO4sRjxufRJ2cToXFvdnQ=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>CWaL/zScIG5Yb70mpCreSmEQQihemDJbmkQlGQ5m+xlMUW53oY1ReUg8iCQg2YEsa5QwKqHEj0yJ
3X3FF1uJIjlQoAT8n+f0lLcDDRYOp239fwIzY6fFhLdwzsD/hKHzzDnV7Q/fEviywGsR4Gknxtrt
tIoMiXIeMLWEWeiyteaefhhJcyNrE8nxbtPDcJFHm+gE8buFYAf7U2290lt7vfu8UKHTYBDrvGfb
CIIyZUJeEX99e3o+fC4CUtiA4UEnHtDI3Z4ifPhkhJ+DYdTWQfejMKj8R5HiW9Pq5JZyUVYCK3bc
Na9z4UZsLsVglRjzUIBzciuQ09Yw6f9yg3dBlA==</SignatureValue>
<KeyInfo>
<wssec:SecurityTokenReference>
<wssec:Reference URI="#SecurityToken-24acaa0b-6643-40ef-be10-b5b65195bc12" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>
</wssec:SecurityTokenReference>
</KeyInfo>
</Signature>
</wssec:Security>
</soapenv:Header>
<soapenv:Body wsu:Id="Body-97d94bd5-96d8-46e6-ad55-a3f1e12a413b">
<ns1:sumResponse>
<ns1:return>9</ns1:return>
</ns1:sumResponse>
</soapenv:Body>
</soapenv:Envelope>This example has been prettified. The signed document will not be pretty-printed like that; applying an XML Digital Signature will collapse whitespace. If you do "pretty print" the signed document, you may render the signature invalid. Whitespace matters.
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 keypair and certificate. Self-signed certificates are 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
This callout is open-source software, and is not a supported part of Apigee. If you need assistance, you can try inquiring on the Google Cloud Community forum dedicated to Apigee There is no service-level guarantee for responses to inquiries posted to that forum; we do the best we can!
- Limitation: The Sign callout always uses XML Canonicalization, never uses Transform.ENVELOPED.