package com.fsenablers.saml;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.StringReader;
import java.io.PrintWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.*;
import javax.xml.XMLConstants;
import javax.xml.crypto.*;
import javax.xml.crypto.dsig.*;
import javax.xml.crypto.dom.*;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.*;
import javax.xml.crypto.dsig.spec.*;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.xpath.*;
import org.xml.sax.InputSource;
import org.w3c.dom.*;
/**
* Sign XML file.
*/
public class XMLSigner {
private static final String STYLESHEET =
""+
""+
""+
""+
""+
""+
""+
""+
""+
""+
""+
""+
""+
""+
""+
""+
"";
private static final String KEY_STORE_TYPE = "JKS";
private static final String KEY_STORE_NAME = "/devtools/data/keystore.jks";
private static final String KEY_STORE_PASS = "password";
private static final String PRIVATE_KEY_PASS = "password";
private static final String KEY_ALIAS = "mykey";
private static final String PATH = "/PatientRecord/Account";
private static final String ID = "acct";
private static enum SignatureType {
SIGN_BY_ID,
SIGN_BY_PATH,
SIGN_WHOLE_DOCUMENT
};
public static String sign(
String keyStoreName,
String keyStorePass,
String keyAlias,
String privateKeyPass,
String unsignedXMLSrc,
String signBy,
String signByTarget ) throws Exception {
SignatureType sigType = SignatureType.SIGN_WHOLE_DOCUMENT;
if ("id".equals(signBy)) {
sigType = SignatureType.SIGN_BY_ID;
}
else if ("path".equals(signBy)) {
sigType = SignatureType.SIGN_BY_PATH;
}
// Instantiate the document to be signed
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
dbFactory.setNamespaceAware(true);
Document doc = dbFactory
.newDocumentBuilder()
.parse( new InputSource( new StringReader( unsignedXMLSrc )));
// prepare signature factory
String providerName = System.getProperty(
"jsr105Provider",
"org.jcp.xml.dsig.internal.dom.XMLDSigRI"
);
final XMLSignatureFactory sigFactory = XMLSignatureFactory.getInstance(
"DOM",
(Provider) Class.forName(providerName).newInstance()
);
Node nodeToSign = null;
Node sigParent = null;
String referenceURI = null;
String xPathStr = null;
XPathExpression expr = null;
NodeList nodes;
// List transforms = null;
List transforms = new ArrayList();
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
switch (sigType) {
case SIGN_BY_ID:
nodes = null;
String[] attrNames = { "ID", "id", "Id" };
boolean bFound = false;
for( int x = 0; x < attrNames.length; x++ ) {
xPathStr = String.format("//*[@%s='%s']", attrNames[x], signByTarget );
expr = xpath.compile( xPathStr );
nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
if( nodes.getLength() > 0 ) {
bFound = true;
break;
}
}
/*
xPathStr = String.format("//*[@ID='%s']", signByTarget );
expr = xpath.compile( xPathStr );
nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
if (nodes.getLength() == 0) {
System.out.println("Can't find node with id: " + signByTarget);
return null;
}
*/
if( ! bFound ) {
System.out.println("Can't find node with id: " + signByTarget);
return null;
}
nodeToSign = nodes.item(0);
sigParent = nodeToSign; // sigParent = nodeToSign.getParentNode();
referenceURI = "#" + signByTarget;
Transform envTransform = sigFactory.newTransform( Transform.ENVELOPED, (TransformParameterSpec) null );
transforms.add( envTransform );
Transform c14nTransform = sigFactory.newTransform( CanonicalizationMethod.EXCLUSIVE, (TransformParameterSpec) null );
transforms.add( c14nTransform );
break;
case SIGN_BY_PATH:
// Find the node to be signed by PATH
expr = xpath.compile(PATH);
nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
if (nodes.getLength() < 1) {
System.out.println("Invalid document, can't find node by PATH: " + PATH);
return null;
}
nodeToSign = nodes.item(0);
sigParent = nodeToSign.getParentNode();
referenceURI = ""; // Empty string means whole document
transforms = new ArrayList() {{
add(sigFactory.newTransform(
Transform.XPATH,
new XPathFilterParameterSpec(PATH)
)
);
add(sigFactory.newTransform(
Transform.ENVELOPED,
(TransformParameterSpec) null
)
);
}};
break;
default:
sigParent = doc.getDocumentElement();
referenceURI = ""; // Empty string means whole document
transforms = Collections.singletonList(
sigFactory.newTransform(
Transform.ENVELOPED,
(TransformParameterSpec) null
)
);
break;
}
// Retrieve signing key
KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE);
keyStore.load(
new FileInputStream(keyStoreName),
keyStorePass.toCharArray()
);
PrivateKey privateKey = (PrivateKey) keyStore.getKey(
keyAlias,
privateKeyPass.toCharArray()
);
X509Certificate cert = (X509Certificate) keyStore.getCertificate(keyAlias);
System.out.println( "CERT - START" );
System.out.println( cert );
System.out.println( "CERT - END" );
// System.out.println( cert.getSubjectDN() );
PublicKey publicKey = cert.getPublicKey();
System.out.println( "PUBLIC KEY - START" );
System.out.println( publicKey );
System.out.println( "PUBLIC KEY - END" );
// Create a Reference to the enveloped document
Reference ref = sigFactory.newReference(
referenceURI,
sigFactory.newDigestMethod(DigestMethod.SHA1, null),
transforms,
null,
null
);
// Create the SignedInfo
SignedInfo signedInfo = sigFactory.newSignedInfo(
sigFactory.newCanonicalizationMethod(
CanonicalizationMethod.EXCLUSIVE,
(C14NMethodParameterSpec) null
),
sigFactory.newSignatureMethod(
SignatureMethod.RSA_SHA1,
null
),
Collections.singletonList(ref)
);
// Create a KeyValue containing the RSA PublicKey
KeyInfoFactory keyInfoFactory = sigFactory.getKeyInfoFactory();
// KeyValue keyValue = keyInfoFactory.newKeyValue(publicKey);
X509Data x509Data = keyInfoFactory.newX509Data( Collections.singletonList(cert));
// Create a KeyInfo and add the KeyValue to it
KeyInfo keyInfo = keyInfoFactory.newKeyInfo(Collections.singletonList(x509Data)); // Collections.singletonList(keyValue)
// Create a DOMSignContext and specify the RSA PrivateKey and
// location of the resulting XMLSignature's parent element
DOMSignContext dsc = new DOMSignContext(
privateKey,
sigParent
);
// dsc.setBaseURI( "http://www.fsenablers.com/dsig#" );
// dsc.putNamespacePrefix( "fse", "http://www.w3.org/2000/09/xmldsig#" );
// dsc.putNamespacePrefix( "ds", null );
dsc.setDefaultNamespacePrefix( "ds" );
// Create the XMLSignature (but don't sign it yet)
XMLSignature signature = sigFactory.newXMLSignature(signedInfo, keyInfo);
System.out.println( signature.getClass() );
// Marshal, generate (and sign) the enveloped signature
signature.sign(dsc);
// output the resulting document
StringWriter signedXMLSrcWriter = new StringWriter();
TransformerFactory transformerFactory = TransformerFactory.newInstance();
// transformerFactory.setFeature( XMLConstants.FEATURE_SECURE_PROCESSING, true );
// transformerFactory.setAttribute( "http://xml.apache.org/xalan/features/optimize", Boolean.FALSE );
System.out.println( transformerFactory.getClass() );
// DocumentBuilderFactory xsf = DocumentBuilderFactory.newInstance();
// xsf.setNamespaceAware(true);
// DocumentBuilder xsBuilder = xsf.newDocumentBuilder();
// InputSource stylesheet = new InputSource( new StreamSource ( new StringReader( STYLESHEET )));
// FileInputStream fis = new FileInputStream( "/devtools/stylesheet.xsl" );
// StringReader reader = new StringReader( STYLESHEET );
// Transformer trans = transformerFactory.newTransformer( new StreamSource( reader ));
Transformer trans = transformerFactory.newTransformer();
System.out.println( trans.getClass() );
trans.setOutputProperty( OutputKeys.METHOD, "xml" );
trans.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "yes" );
trans.transform(new DOMSource(doc), new StreamResult(signedXMLSrcWriter));
// System.out.println( trans.getClass().toString() );
return signedXMLSrcWriter.toString();
}
public static void main(String[] args) throws Exception {
if (args.length < 2) {
usage();
return;
}
String inputFile = args[0];
String outputFile = args[1];
String signByTarget = null;
String signBy = "whole";
if (args.length >= 3) {
signBy = args[2];
}
if( "id".equals( signBy ) && args.length >= 4 ) {
signByTarget = args[3];
}
StringWriter unsignedXMLSrcWriter = new StringWriter();
PrintWriter writer = new PrintWriter( unsignedXMLSrcWriter );
BufferedReader inputFileReader = new BufferedReader( new FileReader( inputFile ));
String lineOfText = inputFileReader.readLine();
while( lineOfText != null ) {
writer.println( lineOfText );
lineOfText = inputFileReader.readLine();
}
String unsignedXMLSrc = unsignedXMLSrcWriter.toString();
String signedXMLSrc = sign( KEY_STORE_NAME, KEY_STORE_PASS, KEY_ALIAS, PRIVATE_KEY_PASS, unsignedXMLSrc, signBy, signByTarget );
FileWriter fileWriter = new FileWriter( outputFile );
fileWriter.write( signedXMLSrc );
fileWriter.close();
}
private static void usage() {
System.out.println("Usage: java XMLSigner [id|path|whole]");
}
}