// grab the signing key
Element signingElement = feed.getFirstChild(new QName(Common.NS_URI,
Common.SIGN));
if (signingElement == null) {
throw new XMLSignatureException(
"Could not find signing key for feed: " + feed.getId());
}
// verify that the key matches the id
PublicKey publicKey = Common.toPublicKeyFromX509(signingElement
.getText());
if (Common.fromFeedUrn(feed.getId()) == null
|| !Common.fromFeedUrn(feed.getId()).equals(
Common.toFeedId(publicKey))) {
throw new XMLSignatureException(
"Signing key does not match feed id: "
+ Common.fromFeedUrn(feed.getId()) + " : "
+ Common.toFeedId(publicKey));
}
// prep the verifier
AbderaSecurity security = new AbderaSecurity(Abdera.getInstance());
Signature signature = security.getSignature();
SignatureOptions options = signature.getDefaultSignatureOptions();
options.setSigningAlgorithm("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1");
options.setSignLinks(false);
options.setPublicKey(publicKey);
// validate, persist, and remove each entry
List<Entry> entries = new LinkedList<Entry>();
entries.addAll(feed.getEntries()); // make a copy
String existingEntryXml;
for (Entry entry : feed.getEntries()) {
String feedId = Common.toFeedIdString(feed.getId());
long entryId = Common.toEntryId(entry.getId());
try {
try {
existingEntryXml = persistence.readEntry(feedId, entryId);
} catch (FileNotFoundException fnfe) {
existingEntryXml = null;
}
if (existingEntryXml != null) {
Entry parsed = (Entry) Abdera.getInstance().getParser()
.parse(new StringReader(existingEntryXml))
.getRoot();
if (entry.getUpdated().after(parsed.getUpdated())) {
// discard what we have in cache
existingEntryXml = null;
}
}
} catch (Exception e) {
existingEntryXml = null;
log.warn(
"Unexpected error parsing existing entry before validation: "
+ entry.getId(), e);
}
if (existingEntryXml != null) {
log.trace("Skipping validation for existing entry: "
+ entry.getId());
} else {
if (!signature.verify(entry, options)) {
// failed validation
Element activity = entry.getExtension(new QName(
"http://activitystrea.ms/spec/1.0/", "verb",
"activity"));
// if not a 'deleted' entry
if (activity == null
|| !"deleted".equals(activity.getText())) {
// TODO: should validate that the 'delete' entry that
// this entry mentions is mentioning this entry
log.warn("Could not verify signature for entry with id: "
+ feed.getId());
// fail ingest
throw new XMLSignatureException(
"Could not verify signature for entry with id: "
+ entry.getId() + " : " + feed.getId());
} else {
log.warn("Skipping signature verification for deleted entry: "
+ feed.getId());
}
}
try {
// yield a bit while validating entries
Thread.sleep(100);
} catch (InterruptedException e) {
log.error("Should never happen: ", e);
}
}
// remove from feed parent
entry.discard();
try {
// see if this file already exists
storage.readEntry(Common.toFeedIdString(feed.getId()),
Common.toEntryId(entry.getId()));
// this file exists; remove from processing
entries.remove(entry);
} catch (FileNotFoundException e) {
// file does not already exist: resume
}
}
// setEditDetail(request, entry, key);
// String edit = entry.getEditLinkResolvedHref().toString();
// remove all navigation links before signing
for (Link link : feed.getLinks()) {
if (Link.REL_FIRST.equals(link.getRel())
|| Link.REL_LAST.equals(link.getRel())
|| Link.REL_CURRENT.equals(link.getRel())
|| Link.REL_NEXT.equals(link.getRel())
|| Link.REL_PREVIOUS.equals(link.getRel())) {
link.discard();
}
}
// remove all opensearch elements before verifying
for (Element e : feed
.getExtensions("http://a9.com/-/spec/opensearch/1.1/")) {
e.discard();
}
// now validate feed signature sans entries
if (!signature.verify(feed, options)) {
log.warn("Could not verify signature for feed with id: "
+ feed.getId());
throw new XMLSignatureException(
"Could not verify signature for feed with id: "
+ feed.getId());
}
// persist feed