package won.cryptography.rdfsign;
import org.apache.jena.query.Dataset;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.StmtIterator;
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 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.util.RdfUtils;
import won.protocol.util.WonRdfUtils;
import won.protocol.vocabulary.WONMSG;
import java.io.StringWriter;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Signature;
import java.util.Base64;
import java.util.Map;
import static won.cryptography.rdfsign.WonSigner.ENV_HASH_ALGORITHM;
import static won.cryptography.rdfsign.WonSigner.SIGNING_ALGORITHM_PROVIDER;
/**
* User: ypanchenko
* Date: 15.07.2014
*/
public class WonVerifier
{
private final Logger logger = LoggerFactory.getLogger(getClass());
private Dataset dataset;
private SignatureVerificationState verificationState = new SignatureVerificationState();
public WonVerifier(Dataset dataset) {
Provider provider = new BouncyCastleProvider();
this.dataset = dataset;
prepareForVerifying();
}
/**
* find corresponding signature graphs for all non-signature graphs
*/
private void prepareForVerifying() {
for (String graphURI : RdfUtils.getModelNames(dataset)) {
Model model = dataset.getNamedModel(graphURI);
if (WonRdfUtils.SignatureUtils.isSignatureGraph(graphURI, model)) {
addSignatureToResult(graphURI, model);
} else {
verificationState.addSignedGraphName(graphURI);
addSignatureReferenceToResult(graphURI, model);
}
}
}
public SignatureVerificationState getVerificationResult() {
return verificationState;
}
//TODO exceptions
public boolean verify(Map<String,PublicKey> publicKeys) throws Exception {
// check if there are any signatures at all
if (verificationState.getSignatures().size() == 0) {
verificationState.verificationFailed("No signatures found");
return verificationState.isVerificationPassed();
}
// check that the default graph is empty
if (dataset.getDefaultModel().listStatements().hasNext()) {
verificationState.verificationFailed("unsigned data found in default graph");
return verificationState.isVerificationPassed();
}
// Get algorithms to use from signature data
SignatureAlgorithmInterface canonicAlgorithm = new SignatureAlgorithmFisteus2010();
SignatureAlgorithmInterface hashingAlgorithm = canonicAlgorithm;
MessageDigest messageDigest = MessageDigest.getInstance(ENV_HASH_ALGORITHM, SIGNING_ALGORITHM_PROVIDER);
// verify each signature's graph
for (WonSignatureData wonSignatureData: verificationState.getSignatures()) {
// extract signature graph, signature data and corresponding signed graph
// make sure the signed graph specified in signature exists in the message
if (!dataset.containsNamedModel(wonSignatureData.getSignedGraphUri())) {
logger.debug("cannot verify signature {} as it is not part of this message ", wonSignatureData.getSignatureUri());
continue;
//TODO: fetch the external reference and check it here
//verificationState.setVerificationFailed(wonSignatureData.getSignatureUri(), "No signed graph found for " +
// "signature " + wonSignatureData.getSignatureUri());
//return verificationState.isVerificationPassed();
}
//is the signature there?
String sigString = wonSignatureData.getSignatureValue();
if (sigString == null) {
verificationState
.setVerificationFailed(wonSignatureData.getSignatureUri(), "Failed to compute a signature value " +
wonSignatureData.getSignatureUri());
return verificationState.isVerificationPassed();
}
if (sigString.length() == 0) {
verificationState.setVerificationFailed(wonSignatureData.getSignatureUri(), "Computed an empty signature value " +
wonSignatureData.getSignatureUri());
return verificationState.isVerificationPassed();
}
//do we have the public key?
PublicKey publicKey = publicKeys.get(wonSignatureData.getVerificationCertificateUri());
if (publicKey == null) {
verificationState
.setVerificationFailed(wonSignatureData.getSignatureUri(), "No public key found for " + wonSignatureData.getSignatureUri());
return verificationState.isVerificationPassed();
}
//check if its fingerprint matches the fingerprint in the signature
String fingerprint = Base64.getEncoder().encodeToString(messageDigest.digest(publicKey.getEncoded()));
if (!wonSignatureData.getPublicKeyFingerprint().equals(fingerprint)){
verificationState.setVerificationFailed(wonSignatureData.getSignatureUri(), "Fingerprint computed for the " +
"specified public key " + wonSignatureData.getVerificationCertificateUri() + " is " + fingerprint + ", " +
"which differs from the value found in signature " + wonSignatureData.getSignatureUri());
return verificationState.isVerificationPassed();
}
// normalize, hash and post-hash signed graph data
GraphCollection inputGraph = ModelConverter.modelToGraphCollection(wonSignatureData.getSignedGraphUri(), dataset);
canonicAlgorithm.canonicalize(inputGraph);
canonicAlgorithm.postCanonicalize(inputGraph);
hashingAlgorithm.hash(inputGraph, ENV_HASH_ALGORITHM);
hashingAlgorithm.postHash(inputGraph);
//check the hash of the data. It must be identical to the hash in the signature
BigInteger hashValue = inputGraph.getSignature().getHash();
String hashString = Base64.getEncoder().encodeToString(hashValue.toByteArray());
if (!wonSignatureData.getHash().equals(hashString)){
verificationState.setVerificationFailed(wonSignatureData.getSignatureUri(), "Computed hash value " +
hashString + " differs from value " + wonSignatureData.getHash() +
" found in signature " + wonSignatureData.getSignatureUri());
if (logger.isDebugEnabled()) {
StringWriter sw = new StringWriter();
RDFDataMgr.write(sw, dataset.getNamedModel(wonSignatureData.getSignedGraphUri()), Lang.TRIG);
logger.debug("wrong signature hash for graph {} with content: {}",
wonSignatureData.getSignedGraphUri(), sw.toString());
}
return verificationState.isVerificationPassed();
}
//verify the signature
Signature sig = Signature.getInstance(WonSigner.SIGNING_ALGORITHM_NAME,
SIGNING_ALGORITHM_PROVIDER);
sig.initVerify(publicKey);
sig.update(hashValue.toByteArray());
// Verify
byte[] sigBytes = Base64.getDecoder().decode(sigString);
if (!sig.verify(sigBytes)) {
verificationState.setVerificationFailed(wonSignatureData.getSignatureUri(), "Failed to verify " + wonSignatureData
.getSignatureUri() + " with public key " + wonSignatureData.getVerificationCertificateUri());
// interrupt verification process if one of the graph's verification fails
return verificationState.isVerificationPassed();
}
}
return verificationState.isVerificationPassed();
}
private void addSignatureToResult(final String graphUri, final Model model) {
WonSignatureData wonSignatureData = WonRdfUtils.SignatureUtils.extractWonSignatureData(graphUri, model);
if (wonSignatureData != null && wonSignatureData.getSignatureValue() != null) {
verificationState.addSignatureData(wonSignatureData);
}
}
private void addSignatureReferenceToResult(final String graphURI, final Model model) {
RDFNode tempNode = null;
StmtIterator si = model.listStatements(null, WONMSG.CONTAINS_SIGNATURE_PROPERTY, tempNode);
while (si.hasNext()) {
WonSignatureData sigRef = WonRdfUtils.SignatureUtils.extractWonSignatureData(si.nextStatement().getObject()
.asResource());
verificationState.addSignatureData(sigRef);
}
}
}