GitHub Webhook Signature mit Java validieren

GitHub bietet Webhooks an, die es einem ermöglichen, sich über Repository-Änderungen informieren zu lassen. Je nach Auswahl sendet GitHub dann einen JSON-Payload an die hinterlegte URL. Damit man sich auf der Serverseite sicher sein kann, dass die Daten auch wirklich von GitHub kommen und korrekt sind, sendet GitHub eine x-hub-signature mit. Die von GitHub versendete Signatur kann dann validiert werden. Wie das geht, zeigt folgendes Beispiel in Java.

Code-Beispiel

WebHookServlet.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
@WebServlet(name = "WebHookServlet", urlPatterns = {"/webhook/*"})
public class WebHookServlet extends HttpServlet {
 
  private static final Logger LOG = Logger.getLogger(WebHookServlet.class.getName());
 
  protected void processRequest(HttpServletRequest request, HttpServletResponse response)
          throws ServletException, IOException {
    response.setContentType("text/html;charset=UTF-8");
 
    try (PrintWriter out = response.getWriter()) {
      out.println("<!DOCTYPE html>");
      out.println("<html>");
      out.println("<head>");
      out.println("<title>WebHookServlet</title>");
      out.println("</head>");
      out.println("<body>");
      out.println("<p>OK</p>");
      out.println("</body>");
      out.println("</html>");
    }
  }
 
  @Override
  protected void doPost(HttpServletRequest servletRequest, HttpServletResponse response)
          throws ServletException, IOException {
    String userAgent = servletRequest.getHeader("user-agent");
    String event = servletRequest.getHeader("x-github-event");
 
    if (userAgent.contains("GitHub-Hookshot") && event.equals("push")) {
      String signature = servletRequest.getHeader("x-hub-signature");
      String secret = "abc123";
 
      // Read request body
      StringBuilder sb = new StringBuilder();
      String line;
 
      try {
        BufferedReader reader = servletRequest.getReader();
        while ((line = reader.readLine()) != null) {
          sb.append(line);
        }
      } catch (IOException ex) {
        LOG.log(Level.WARNING, ex.getLocalizedMessage());
      }
 
      String payload = sb.toString();
      boolean isValid = GitHubUtility.verifySignature(payload, signature, secret);
 
      LOG.log(Level.INFO, "Valid GitHub Webhook Payload: {0}", isValid);
    }
 
    processRequest(servletRequest, response);
  }
 
}

GitHubUtility.java

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
 
public class GitHubUtility {
 
  private static final Logger LOG = Logger.getLogger(GitHubUtility.class.getName());
  private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
  private static final char[] HEX = {
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
  };
 
  public static boolean verifySignature(String payload, String signature, String secret) {
    boolean isValid = false;
 
    try {
 
      Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
      SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(), HMAC_SHA1_ALGORITHM);
      mac.init(signingKey);
      byte[] rawHmac = mac.doFinal(payload.getBytes());
 
      String expected = signature.substring(5);
      String actual = new String(encode(rawHmac));
 
      isValid = expected.equals(actual);
 
    } catch (NoSuchAlgorithmException | InvalidKeyException | IllegalStateException ex) {
 
      LOG.log(Level.WARNING, ex.getLocalizedMessage());
 
    }
 
    return isValid;
  }
 
  private static char[] encode(byte[] bytes) {
    final int amount = bytes.length;
    char[] result = new char[2 * amount];
 
    int j = 0;
    for (int i = 0; i < amount; i++) {
      result[j++] = HEX[(0xF0 & bytes[i]) >>> 4];
      result[j++] = HEX[(0x0F & bytes[i])];
    }
 
    return result;
  }
 
}

Anwendung eines Webhooks

github-webhook

netbeans-webhook

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.