blob: e1086c4c4d237ffc307839481d5b99666a4cb89a [file] [log] [blame]
/*
* Copyright 2001-2008 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
using org.apache.juddi.jaxb;
using org.apache.juddi.v3.client.config;
using org.apache.juddi.v3.client.log;
using org.uddi.apiv3;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Text;
using System.Xml;
namespace org.apache.juddi.v3.client.cryptor
{
/// <summary>
/// A utility class for signing and verifying JAXB Objects, such as UDDI entities.
/// Notes: This class only supports elements that are signed once.
/// Multiple signature are not currently supported.
///
/// Digital signatures can be generated using a standalone PFX file or via the Windows Certificate store
/// </summary>
/// <author><a href="mailto:alexoree@apache.org">Alex O'Ree</a></author>
public class DigSigUtil
{
/// <summary>
/// creates an uninitialized DigSigUtil, use put to configure
/// </summary>
public DigSigUtil()
{
map = new Properties();
}
/// <summary>
/// Constructor that will accept a properties set from the juddi config file, or whatever you want
///
/// </summary>
/// <param name="c"></param>
public DigSigUtil(Properties c)
{
map = c;
}
private Log logger = LogFactory.getLog(typeof(DigSigUtil));
/// <summary>
/// added a new key/value to the running config
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public void put(String key, String value)
{
map.put(key, value);
}
/**
* clears the configuration for reuse
*/
public void clear()
{
}
private Properties map = new Properties();
/**
* This is the location of the keystore
*
* If referencing a Windows certificate store, use WINDOWS-MY as a value
* with a null password
*/
public readonly static String SIGNATURE_KEYSTORE_FILE = "keyStorePath";
/**
* The type of file, such as JKS for most Java applications, or WINDOWS-MY
* to use the Windows certificate store of the current user or KeychainStore
* for MacOS
*/
public readonly static String SIGNATURE_KEYSTORE_FILETYPE = "keyStoreType";
public readonly static string SIGNATURE_KEYSTORE_FILETYPE_VALUE_PFX = "PFX";
public readonly static String SIGNATURE_KEYSTORE_FILE_PASSWORD = "filePassword";
public readonly static String SIGNATURE_KEYSTORE_KEY_PASSWORD = "keyPassword";
public readonly static String SIGNATURE_KEYSTORE_KEY_ALIAS = "keyAlias";
public readonly static String TRUSTSTORE_FILE = "trustStorePath";
public readonly static String TRUSTSTORE_FILETYPE = "trustStoreType";
public readonly static String TRUSTSTORE_FILE_PASSWORD = "trustStorePassword";
/**
* default is CanonicalizationMethod.EXCLUSIVE
*
* @see CanonicalizationMethod
*/
public readonly static String CANONICALIZATIONMETHOD = "CanonicalizationMethod";
/**
* default is http://www.w3.org/2000/09/xmldsig#rsa-sha1
*
* @see SignatureMethod
*/
public readonly static String SIGNATURE_METHOD = "SignatureMethod";
/**
* Defines whether or not a certificate is included with the signature.
* Values - Include whole X509 Public Key in the signature (recommended)
* (default) * Example
* <example>
* <code>
* Map map = new HashMap();
* map.put(DigSigUtil.SIGNATURE_OPTION_CERT_INCLUSION_BASE64, "true");
* </code>
* </example>
*/
public readonly static String SIGNATURE_OPTION_CERT_INCLUSION_BASE64 = "BASE64";
/// <summary>
/// Include the signer's serial of the public key and the issuer's subject name
///
/// Clients will not be able to validate the signature unless they have a copy of the signer's public key
/// in a trust store or the full certificate is included
/// out of band
///
/// Example
/// <pre>
/// Map map = new HashMap();
/// map.put(DigSigUtil.SIGNATURE_OPTION_CERT_INCLUSION_SERIAL, "true");</pre>
/// any value can be used.
/// @see SIGNATURE_OPTION_CERT_INCLUSION_BASE64
/// </summary>
public readonly static String SIGNATURE_OPTION_CERT_INCLUSION_SERIAL = "SERIAL";
/*
* Include the signer's Subject DN of the public key.
*
* Clients will not be able to validate the signature unless they have a copy of the signer's public key
* in a trust store or the full certificate is included
* out of band
*
* Example
* <pre>
* Map map = new HashMap();
* map.put(DigSigUtil.SIGNATURE_OPTION_CERT_INCLUSION_SUBJECTDN, "true");</pre>
* any value can be used.
*@see SIGNATURE_OPTION_CERT_INCLUSION_BASE64
*/
public readonly static String SIGNATURE_OPTION_CERT_INCLUSION_SUBJECTDN = "SUBJECTDN";
/*
* Include the signer's X500 Prinicple of the public key.
*
* Clients will not be able to validate the signature unless they have a copy of the signer's public key
* in a trust store or the full certificate is included
* out of band
* <example>
* Example
* <code>
* Map map = new HashMap();
* map.put(DigSigUtil.SIGNATURE_OPTION_CERT_INCLUSION_X500_PRINICPAL, "true");
* </code></example>
*@see SIGNATURE_OPTION_CERT_INCLUSION_BASE64
*/
//public readonly static String SIGNATURE_OPTION_CERT_INCLUSION_X500_PRINICPAL = "X500";
public readonly static String XML_DIGSIG_NS = "http://www.w3.org/2000/09/xmldsig#";
/**
* Default value DigestMethod.SHA1 =
* "http://www.w3.org/2000/09/xmldsig#sha1"
*
* @see javax.xml.crypto.dsig.DigestMethod
*/
public readonly static String SIGNATURE_OPTION_DIGEST_METHOD = "digestMethod";
/**
* When validating a signature, include this field will validate that the
* signature is still valid with regards to timestamps NotBefore and
* OnOrAfter
*
* Example
* <pre>
* Map map = new HashMap();
* map.put(DigSigUtil.CHECK_TIMESTAMPS, true);</pre> any value can be used.
*/
public readonly static String CHECK_TIMESTAMPS = "checkTimestamps";
public readonly static String CHECK_REVOCATION_STATUS_OCSP = "checkRevocationOCSP";
public readonly static String CHECK_REVOCATION_STATUS_CRL = "checkRevocationCRL";
public readonly static String CHECK_TRUST_CHAIN = "checkTrust";
/// <summary>
///
/// Verifies the signature on an enveloped digital signature on a UDDI
/// entity, such as a business, service, tmodel or binding template.
/// It is expected that either the public key of the signing certificate is
/// included within the signature keyinfo section OR that sufficient
/// information is provided in the signature to reference a public key
/// located within the Trust Store provided. Optionally, this function
/// also validate the signing certificate using the options provided to the
/// configuration map.
/// </summary>
/// <param name="obj"></param>
/// <param name="OutErrorMessage"></param>
/// <returns></returns>
public bool verifySignedUddiEntity(Object obj, out String OutErrorMessage)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
string msg = "";
//serialize to string
XmlDocument doc = null;
if (obj.GetType().Equals(typeof(bindingTemplate)))
{
PrintUDDI<bindingTemplate> p = new PrintUDDI<bindingTemplate>();
String s = p.print(obj);
doc = StringToXmlDocument(s);
}
if (obj.GetType().Equals(typeof(businessService)))
{
PrintUDDI<businessService> p = new PrintUDDI<businessService>();
String s = p.print(obj);
doc = StringToXmlDocument(s);
}
if (obj.GetType().Equals(typeof(businessEntity)))
{
PrintUDDI<businessEntity> p = new PrintUDDI<businessEntity>();
String s = p.print(obj);
doc = StringToXmlDocument(s);
}
if (obj.GetType().Equals(typeof(tModel)))
{
PrintUDDI<tModel> p = new PrintUDDI<tModel>();
String s = p.print(obj);
doc = StringToXmlDocument(s);
}
//get signing certificate
X509Certificate2 signingCert = getSigningCertificatePublicKey(doc);
//check timestamps
if (map.containsKey(DigSigUtil.CHECK_TIMESTAMPS) && map.getProperty(DigSigUtil.CHECK_TIMESTAMPS).Equals("true", StringComparison.CurrentCultureIgnoreCase))
{
if (DateTime.Now < signingCert.NotBefore)
{
msg += "Signing certificate is not yet valid";
}
if (DateTime.Now > signingCert.NotAfter)
{
msg += "Signing certificate is not yet valid";
}
}
if (map.containsKey(DigSigUtil.CHECK_TRUST_CHAIN) && map.getProperty(DigSigUtil.CHECK_TRUST_CHAIN).Equals("true", StringComparison.CurrentCultureIgnoreCase))
{
//check trust
X509Chain chain = new X509Chain();
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
bool r = chain.Build(signingCert);
if (!r)
{
foreach (X509ChainElement element in chain.ChainElements)
{
msg += ("Element issuer name: " + element.Certificate.Issuer + " is " + element.Certificate.Verify());
}
}
}
//check ocsp
//check crl
if ((map.containsKey(DigSigUtil.CHECK_REVOCATION_STATUS_OCSP) && map.getProperty(DigSigUtil.CHECK_REVOCATION_STATUS_OCSP).Equals("true", StringComparison.CurrentCultureIgnoreCase)) ||
(map.containsKey(DigSigUtil.CHECK_REVOCATION_STATUS_CRL) && map.getProperty(DigSigUtil.CHECK_REVOCATION_STATUS_CRL).Equals("true", StringComparison.CurrentCultureIgnoreCase)))
{
//check trust
X509Chain chain = new X509Chain();
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
bool r = chain.Build(signingCert);
if (!r)
{
foreach (X509ChainElement element in chain.ChainElements)
{
msg += ("Element issuer name: " + element.Certificate.Issuer + " is " + element.Certificate.Verify());
}
}
}
//verify crypto (math)
String verifytext = "";
bool valid = verifySignature(doc, signingCert, out verifytext);
OutErrorMessage = verifytext + msg;
return valid;
}
private bool verifySignature(XmlDocument Doc, X509Certificate2 cert, out string OutErrorMessage)
{
string msg = "";
// Create a new SignedXml object and pass it
// the XML document class.
SignedXml signedXml = new SignedXml(Doc);
// Find the "Signature" node and create a new
// XmlNodeList object.
XmlNodeList nodeList = Doc.GetElementsByTagName("Signature");
// Throw an exception if no signature was found.
if (nodeList.Count <= 0)
{
msg += ("Verification failed: No Signature was found in the document.");
}
// This example only supports one signature for
// the entire XML document. Throw an exception
// if more than one signature was found.
if (nodeList.Count >= 2)
{
msg += ("Verification failed: More that one signature was found for the document.");
}
// Load the first <signature> node.
signedXml.LoadXml((XmlElement)nodeList[0]);
// Check the signature and return the result.
OutErrorMessage = msg;
return signedXml.CheckSignature(cert, true);
}
private X509Certificate2 getSigningCertificatePublicKey(XmlDocument doc)
{
if (doc == null)
throw new ArgumentNullException("doc");
XmlNode node = doc.ChildNodes[1]; //this should be the uddi entry
X509Certificate2 cert = null;
IEnumerator it = node.ChildNodes.GetEnumerator();
while (it.MoveNext())
{
XmlNode x = (XmlNode)it.Current;
if (x.NamespaceURI.Equals(DigSigUtil.XML_DIGSIG_NS, StringComparison.CurrentCultureIgnoreCase) &&
x.LocalName.Equals("Signature", StringComparison.CurrentCultureIgnoreCase))
{
IEnumerator it2 = x.ChildNodes.GetEnumerator();
while (it2.MoveNext())
{
XmlNode x2 = (XmlNode)it2.Current;
if (x2.LocalName.Equals("KeyInfo", StringComparison.CurrentCultureIgnoreCase))
{
IEnumerator it3 = x2.ChildNodes.GetEnumerator();
while (it3.MoveNext())
{
XmlNode x3 = (XmlNode)it3.Current;
if (x3.LocalName.Equals("X509Data", StringComparison.CurrentCultureIgnoreCase))
{
//X509Certificate
IEnumerator it4 = x3.ChildNodes.GetEnumerator();
while (it4.MoveNext())
{
XmlNode x4 = (XmlNode)it4.Current;
if (x4.LocalName.Equals("X509Certificate", StringComparison.CurrentCultureIgnoreCase))
{
//X509Certificate
String c =
//"-----BEGIN CERTIFICATE-----\n"
x4.InnerText;
//+ "\n-----END CERTIFICATE-----";
cert = new X509Certificate2(Convert.FromBase64String(c));
logger.info("embedded certificate found, X509 public key " + cert.Subject);
return cert;
}
}
}
}
}
}
}
}
return null;
}
/// <summary>
/// Digitally signs a UDDI entity, such as a business, service, tmodel or
/// binding template using the map to provide certificate key stores and
/// credentials. The UDDI entity MUST support XML Digital Signatures
/// (tModel, Business, Service, Binding Template)
/// </summary>
/// <param name="bt"></param>
/// <returns></returns>
public object signUddiEntity(object bt)
{
XmlDocument doc = null;
if (bt.GetType().Equals(typeof(bindingTemplate)))
{
PrintUDDI<bindingTemplate> p = new PrintUDDI<bindingTemplate>();
String s = p.print(bt);
doc = StringToXmlDocument(s);
}
if (bt.GetType().Equals(typeof(businessService)))
{
PrintUDDI<businessService> p = new PrintUDDI<businessService>();
String s = p.print(bt);
doc = StringToXmlDocument(s);
}
if (bt.GetType().Equals(typeof(businessEntity)))
{
PrintUDDI<businessEntity> p = new PrintUDDI<businessEntity>();
String s = p.print(bt);
doc = StringToXmlDocument(s);
}
if (bt.GetType().Equals(typeof(tModel)))
{
PrintUDDI<tModel> p = new PrintUDDI<tModel>();
String s = p.print(bt);
doc = StringToXmlDocument(s);
}
X509Certificate2 key = GetKey();
XmlElement sig = SignXml(doc, key);
//append the signature to the document
doc.ChildNodes[1].AppendChild(sig);
String signedXml = doc.OuterXml;
if (bt.GetType().Equals(typeof(bindingTemplate)))
{
PrintUDDI<bindingTemplate> p = new PrintUDDI<bindingTemplate>();
return p.createObject(signedXml);
}
if (bt.GetType().Equals(typeof(businessService)))
{
PrintUDDI<businessService> p = new PrintUDDI<businessService>();
return p.createObject(signedXml);
}
if (bt.GetType().Equals(typeof(businessEntity)))
{
PrintUDDI<businessEntity> p = new PrintUDDI<businessEntity>();
return p.createObject(signedXml);
}
if (bt.GetType().Equals(typeof(tModel)))
{
PrintUDDI<tModel> p = new PrintUDDI<tModel>();
return p.createObject(signedXml);
}
return bt;
}
private X509Certificate2 GetKey()
{
String storelocation = map.getProperty(DigSigUtil.SIGNATURE_KEYSTORE_FILETYPE);
if (storelocation.Equals( DigSigUtil.SIGNATURE_KEYSTORE_FILETYPE_VALUE_PFX, StringComparison.CurrentCultureIgnoreCase))
{
logger.info("Attempting to load certificate from " + map.getProperty(DigSigUtil.SIGNATURE_KEYSTORE_FILE));
X509Certificate2 cert = new X509Certificate2(map.getProperty(DigSigUtil.SIGNATURE_KEYSTORE_FILE),
//this should be decrypted already
map.getProperty(DigSigUtil.SIGNATURE_KEYSTORE_FILE_PASSWORD));
return cert;
}
String storename = map.getProperty(DigSigUtil.SIGNATURE_KEYSTORE_FILE);
String keyserial = map.getProperty(DigSigUtil.SIGNATURE_KEYSTORE_KEY_ALIAS);
X509Store store = new X509Store(
(StoreName)Enum.Parse(typeof(StoreName), storename),
(StoreLocation)Enum.Parse(typeof(StoreLocation), storelocation));
store.Open(OpenFlags.ReadOnly);
X509Certificate2Enumerator it = store.Certificates.GetEnumerator();
while (it.MoveNext())
{
X509Certificate2 cert = it.Current;
if (cert.HasPrivateKey)
{
//do some comparisions
if (cert.SerialNumber.Equals(keyserial, StringComparison.CurrentCultureIgnoreCase) ||
cert.FriendlyName.Equals(keyserial, StringComparison.CurrentCultureIgnoreCase) ||
cert.Subject.Equals(keyserial, StringComparison.CurrentCultureIgnoreCase) ||
cert.Thumbprint.Equals(keyserial, StringComparison.CurrentCultureIgnoreCase))
{
store.Close();
return cert;
}
}
}
store.Close();
return null;
}
private XmlDocument StringToXmlDocument(String s)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.PreserveWhitespace = false;
xmlDoc.LoadXml(s);
return xmlDoc;
}
//source http://objectmix.com/dotnet/794749-digitally-sign-xml-doc-x509certificate-solution.html
//which came from the msdn tutorial
//Certificate get Signature method
private XmlElement SignXml(XmlDocument xmlDoc, X509Certificate2 cert)
{
//preserve ws - difference here I noticed - mine was set to true
xmlDoc.PreserveWhitespace = false;
// Create a SignedXml object.
SignedXml signedXml = new SignedXml(xmlDoc);
// Load the certificate into a KeyInfoX509Data object
// and add it to the KeyInfo object.
//// Add an RSAKeyValue KeyInfo (optional; helps recipient find key to validate).
KeyInfo keyInfo = new KeyInfo();
if (map.getProperty(DigSigUtil.SIGNATURE_OPTION_CERT_INCLUSION_BASE64).Equals("true", StringComparison.CurrentCultureIgnoreCase))
keyInfo.AddClause(new KeyInfoX509Data(cert));
if (map.getProperty(DigSigUtil.SIGNATURE_OPTION_CERT_INCLUSION_SUBJECTDN).Equals("true", StringComparison.CurrentCultureIgnoreCase))
{
KeyInfoX509Data data = new KeyInfoX509Data();
data.AddSubjectName(cert.SubjectName.Name);
keyInfo.AddClause(data);
}
if (map.getProperty(DigSigUtil.SIGNATURE_OPTION_CERT_INCLUSION_SERIAL).Equals("true", StringComparison.CurrentCultureIgnoreCase))
{
KeyInfoX509Data data = new KeyInfoX509Data();
data.AddIssuerSerial(cert.IssuerName.Name, cert.SerialNumber);
keyInfo.AddClause(data);
}
signedXml.KeyInfo = keyInfo;
//CANON method
signedXml.SignedInfo.CanonicalizationMethod = map.getProperty(DigSigUtil.CANONICALIZATIONMETHOD);
if (String.IsNullOrEmpty(signedXml.SignedInfo.CanonicalizationMethod))
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NWithCommentsTransformUrl;
signedXml.SignedInfo.SignatureMethod = map.getProperty(DigSigUtil.SIGNATURE_METHOD);
if (String.IsNullOrEmpty(signedXml.SignedInfo.SignatureMethod))
signedXml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA1Url;
// Set the rsaKey to the certificate's private key
RSACryptoServiceProvider rsaKey = (RSACryptoServiceProvider)cert.PrivateKey;
// Add the key to the SignedXml document.
signedXml.SigningKey = rsaKey;
// Create a reference to be signed.
Reference reference = new Reference();
reference.Uri = "";
// Add an enveloped transformation to the reference.
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(env);
// Add the reference to the SignedXml object.
signedXml.AddReference(reference);
// Now we can compute the signature.
signedXml.ComputeSignature();
return signedXml.GetXml();
// return signedXml;
}
//gets payload data and returns xmn XMLDocument
private XmlDocument GetPayLoadData(string xmlstring)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xmlstring);
return xmlDoc;
}
}
}