package won.cryptography.rdfsign;
import org.apache.jena.query.Dataset;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.vocabulary.RDF;
import de.uni_koblenz.aggrimm.icp.crypto.sign.algorithm.SignatureAlgorithmInterface;
import de.uni_koblenz.aggrimm.icp.crypto.sign.algorithm.algorithm.SignatureAlgorithmFisteus2010;
import de.uni_koblenz.aggrimm.icp.crypto.sign.graph.GraphCollection;
import de.uni_koblenz.aggrimm.icp.crypto.sign.graph.SignatureData;
import org.apache.jena.riot.Lang;
import org.apache.jena.riot.RDFDataMgr;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import won.protocol.message.WonSignatureData;
import won.protocol.vocabulary.SFSIG;
import java.io.StringWriter;
import java.security.*;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.List;
/**
* Created by ypanchenko on 12.06.2014.
*/
public class WonSigner
{
private final Logger logger = LoggerFactory.getLogger(getClass());
// TODO make it configurable which algorithm is used RSA or ECDSA
public static final String SIGNING_ALGORITHM_NAME = "NONEwithECDSA";
public static final String SIGNING_ALGORITHM_PROVIDER = "BC";
//TODO which hashing algorithm to use?
public static final String ENV_HASH_ALGORITHM = "sha-256";
private SignatureAlgorithmInterface algorithm;
private Dataset dataset;
public static final Model defaultGraphSigningMethod;
static{
//initialize a model with the triples indicating the default graph signing method, so we are not
//required to add it to every signature
defaultGraphSigningMethod = ModelFactory.createDefaultModel();
Resource bNode = defaultGraphSigningMethod.createResource();
bNode.addProperty(RDF.type, SFSIG.GRAPH_SIGNING_METHOD);
bNode.addProperty(SFSIG.HAS_DIGEST_METHOD, SFSIG.DIGEST_METHOD_SHA_256);
bNode.addProperty(SFSIG.HAS_GRAPH_CANONICALIZATION_METHOD, SFSIG.GRAPH_CANONICALIZATION_METHOD_Fisteus2010);
bNode.addProperty(SFSIG.HAS_GRAPH_DIGEST_METHOD, SFSIG.GRAPH_DIGEST_METHOD_Fisteus2010);
bNode.addProperty(SFSIG.HAS_GRAPH_SERIALIZATION_METHOD, SFSIG.GRAPH_SERIALIZATION_METHOD_TRIG);
bNode.addProperty(SFSIG.HAS_SIGNATURE_METHOD, SFSIG.SIGNATURE_METHOD_ECDSA);
}
public WonSigner(Dataset dataset) {
this.dataset = dataset;
//default algorithm: Fisteus2010
this.algorithm = new SignatureAlgorithmFisteus2010();
Provider provider = new BouncyCastleProvider();
}
/**
* Signs the graphs of the dataset with the provided private key and referencing
* the provided certificate/public key uri in signature, this uri will be used
* to extract key by the verification party.
*
* @param privateKey the private key
* @param cert the certificate reference (where the public key can be found for verification)
* @param graphsToSign the names of the graphs that have to be signed. If not provided -
* all the graphs that don't have signatures will be signed.
* @throws Exception
*/
//TODO chng exceptions to won exceptions?
public List<WonSignatureData> sign(PrivateKey privateKey, String cert, PublicKey publicKey, String...
graphsToSign) throws
Exception {
List<WonSignatureData> sigRefs = new ArrayList<>(graphsToSign.length);
MessageDigest md = MessageDigest.getInstance(ENV_HASH_ALGORITHM, SIGNING_ALGORITHM_PROVIDER);
String fingerprint = Base64.getEncoder().encodeToString(md.digest(publicKey.getEncoded()));
for (String signedGraphUri : graphsToSign) {
//TODO should be generated in a more proper way and not here - check of the name already exists etc.
if (logger.isDebugEnabled()){
StringWriter sw = new StringWriter();
RDFDataMgr.write(sw, dataset.getNamedModel(signedGraphUri), Lang.TRIG);
logger.debug("signing graph {} with content: {}", graphsToSign, sw.toString());
}
String signatureUri = signedGraphUri + "-sig";
// create GraphCollection with one NamedGraph that corresponds to this Model
GraphCollection inputGraph = ModelConverter.modelToGraphCollection(signedGraphUri, dataset);
// sign the NamedGraph inside that GraphCollection
SignatureData sigValue = signNamedGraph(inputGraph, privateKey, cert);
String hash = new String(Base64
.getEncoder().encodeToString(sigValue
.getHash().toByteArray()));
WonSignatureData sigRef = new WonSignatureData(signedGraphUri, signatureUri, sigValue.getSignature(), hash ,
fingerprint,
cert);
sigRefs.add(sigRef);
}
return sigRefs;
}
public List<WonSignatureData> sign(PrivateKey privateKey, String cert, PublicKey publicKey, Collection<String>
graphsToSign) throws
Exception {
String[] array = new String[graphsToSign.size()];
return sign(privateKey, cert, publicKey,graphsToSign.toArray(array));
}
private de.uni_koblenz.aggrimm.icp.crypto.sign.graph.SignatureData signNamedGraph(final GraphCollection inputWithOneNamedGraph, final PrivateKey privateKey, String cert)
throws Exception {
this.algorithm.canonicalize(inputWithOneNamedGraph);
this.algorithm.postCanonicalize(inputWithOneNamedGraph);
this.algorithm.hash(inputWithOneNamedGraph, ENV_HASH_ALGORITHM);
this.algorithm.postHash(inputWithOneNamedGraph);
return sign(inputWithOneNamedGraph, privateKey, cert);
}
private SignatureData sign(GraphCollection gc, PrivateKey privateKey, String verificationCertificate) throws Exception {
if (verificationCertificate == null) {
verificationCertificate = "\"cert\"";
} else {
verificationCertificate = "<" + verificationCertificate + ">";
}
// Signature Data existing?
if (!gc.hasSignature()) {
throw new Exception("GraphCollection has no signature data. Call 'canonicalize' and 'hash' methods first.");
}
// Get Signature Data
SignatureData sigData = gc.getSignature();
// Sign
Signature sig = Signature.getInstance(SIGNING_ALGORITHM_NAME, SIGNING_ALGORITHM_PROVIDER);
sig.initSign(privateKey);
sig.update(sigData.getHash().toByteArray());
byte[] signatureBytes = sig.sign();
//String signature = new BASE64Encoder().encode(signatureBytes);
String signature = Base64.getEncoder().encodeToString(signatureBytes);
//Update Signature Data
sigData.setSignature(signature);
sigData.setSignatureMethod(privateKey.getAlgorithm().toLowerCase());
sigData.setVerificationCertificate(verificationCertificate);
return sigData;
}
}