From 07ebf86ab279811c365e8174807dbf36fc2f4ca4 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Tue, 10 Nov 2020 16:36:02 +0300 Subject: ActivityPub: Digest header is mandatory now for POST requests --- src/main/java/com/juick/ActivityPubManager.java | 11 ++++--- src/main/java/com/juick/SignatureManager.java | 42 +++++++++++++++++++------ 2 files changed, 38 insertions(+), 15 deletions(-) (limited to 'src/main/java/com/juick') diff --git a/src/main/java/com/juick/ActivityPubManager.java b/src/main/java/com/juick/ActivityPubManager.java index e3b1ac8e..22fe826f 100644 --- a/src/main/java/com/juick/ActivityPubManager.java +++ b/src/main/java/com/juick/ActivityPubManager.java @@ -54,6 +54,7 @@ import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.net.URI; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -104,7 +105,7 @@ public class ActivityPubManager implements ActivityListener, NotificationListene signatureManager.post(me, follower, accept); socialService.addFollower(followedUser, follower.getId()); logger.info("Follower added for {}", followedUser.getName()); - } catch (IOException e) { + } catch (IOException | NoSuchAlgorithmException e) { logger.info("activitypub exception", e); } } @@ -145,7 +146,7 @@ public class ActivityPubManager implements ActivityListener, NotificationListene try { logger.info("Deletion to follower {}", follower.getId()); signatureManager.post(me, follower, delete); - } catch (IOException e) { + } catch (IOException | NoSuchAlgorithmException e) { logger.warn("activitypub exception", e); } }); @@ -194,7 +195,7 @@ public class ActivityPubManager implements ActivityListener, NotificationListene try { logger.info("Update to follower {}", follower.getId()); signatureManager.post(me, follower, update); - } catch (IOException e) { + } catch (IOException | NoSuchAlgorithmException e) { logger.warn("activitypub exception", e); } }); @@ -239,7 +240,7 @@ public class ActivityPubManager implements ActivityListener, NotificationListene create.setObject(note); try { signatureManager.post(me, follower, create); - } catch (IOException e) { + } catch (IOException | NoSuchAlgorithmException e) { logger.warn("activitypub exception", e); } } @@ -386,7 +387,7 @@ public class ActivityPubManager implements ActivityListener, NotificationListene try { logger.info("Announcing top: {}", message.getMid()); signatureManager.post(me, (Person)person, announce); - } catch (IOException e) { + } catch (IOException | NoSuchAlgorithmException e) { logger.warn("activitypub exception", e); } }, () -> logger.warn("Follower not found: {}", acct)); diff --git a/src/main/java/com/juick/SignatureManager.java b/src/main/java/com/juick/SignatureManager.java index fc92f39a..fed6c368 100644 --- a/src/main/java/com/juick/SignatureManager.java +++ b/src/main/java/com/juick/SignatureManager.java @@ -26,6 +26,7 @@ import com.juick.www.api.activity.model.Context; import com.juick.www.api.activity.model.objects.Person; import com.juick.www.api.webfinger.model.Account; import com.juick.www.api.webfinger.model.Link; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpEntity; @@ -34,6 +35,8 @@ import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; +import org.tomitribe.auth.signatures.Base64; +import org.tomitribe.auth.signatures.MissingRequiredHeaderException; import org.tomitribe.auth.signatures.Signature; import org.tomitribe.auth.signatures.Signer; import org.tomitribe.auth.signatures.Verifier; @@ -43,10 +46,13 @@ import javax.inject.Inject; import java.io.IOException; import java.net.URI; import java.security.Key; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; import java.time.Instant; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -63,35 +69,51 @@ public class SignatureManager { @Inject private RestTemplate apClient; - public void post(Person from, Person to, Context data) throws IOException { + public void post(Person from, Person to, Context data) throws IOException, NoSuchAlgorithmException { UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(to.getInbox()); URI inbox = uriComponentsBuilder.build().toUri(); Instant now = Instant.now(); String requestDate = DateFormattersHolder.getHttpDateFormatter().format(now); String host = inbox.getPort() > 0 ? String.format("%s:%d", inbox.getHost(), inbox.getPort()) : inbox.getHost(); - String signatureString = addSignature(from, host, "POST", inbox.getPath(), requestDate); + var finalContext = Context.build(data); + var payload = jsonMapper.writeValueAsString(finalContext); + final byte[] digest = MessageDigest.getInstance("SHA-256").digest(payload.getBytes()); // (1) + final String digestHeader = "SHA-256=" + new String(Base64.encodeBase64(digest)); + String signatureString = addSignature(from, host, "POST", inbox.getPath(), requestDate, digestHeader); HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.add("Content-Type", Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE); requestHeaders.add("Date", requestDate); requestHeaders.add("Host", host); + requestHeaders.add("Digest", digestHeader); requestHeaders.add("Signature", signatureString); - HttpEntity request = new HttpEntity<>(Context.build(data), requestHeaders); - logger.info("Sending context to {}: {}", to.getId(), jsonMapper.writeValueAsString(data)); + HttpEntity request = new HttpEntity<>(finalContext, requestHeaders); + logger.info("Sending context to {}: {}", to.getId(), payload); ResponseEntity response = apClient.postForEntity(inbox, request, Void.class); logger.info("Remote response: {}", response.getStatusCodeValue()); } - public String addSignature(Person from, String host, String method, String path, String dateString) throws IOException { - return addSignature(from, host, method, path, dateString, keystoreManager); + public String addSignature(Person from, String host, String method, + String path, String dateString, String digestHeader) + throws IOException { + return addSignature(from, host, method, path, dateString, + digestHeader, keystoreManager); } - public String addSignature(Person from, String host, String method, String path, String dateString, KeystoreManager keystoreManager) throws IOException { - Signature templateSignature = new Signature(from.getPublicKey().getId(), "rsa-sha256", null, - "(request-target)", "host", "date"); + public String addSignature(Person from, String host, String method, + String path, String dateString, String digestHeader, + KeystoreManager keystoreManager) throws IOException { + List requiredHeaders = StringUtils.isEmpty(digestHeader) ? + Arrays.asList("(request-target)", "host", "date") + : Arrays.asList("(request-target)", "host", "date", "digest"); + Signature templateSignature = new Signature(from.getPublicKey().getId(), + "rsa-sha256", null, requiredHeaders); Map headers = new HashMap<>(); headers.put("host", host); headers.put("date", dateString); + if (StringUtils.isNotEmpty(digestHeader)) { + headers.put("digest", digestHeader); + } Signer signer = new Signer(keystoreManager.getPrivateKey(), templateSignature); Signature signature = signer.sign(method, path, headers); // remove "Signature: " from result @@ -122,7 +144,7 @@ public class SignatureManager { } else { return AnonymousUser.INSTANCE; } - } catch (NoSuchAlgorithmException | SignatureException | IOException e) { + } catch (NoSuchAlgorithmException | SignatureException | MissingRequiredHeaderException | IOException e) { logger.warn("Invalid signature {}", signatureString); } } else { -- cgit v1.2.3