All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.devcon.ticket.CapabilityIssuer Maven / Gradle / Ivy

package org.devcon.ticket;

import org.tokenscript.attestation.core.AttestationCrypto;
import org.tokenscript.attestation.core.SignatureUtility;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator.Builder;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.util.Base64;
import java.util.Date;
import java.util.Set;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;

/**
 * Issues a long-term JWT to approve-list a specific domain to give it access to the DevCon ticket API.
 * Specifically the website who holds the token will be able to access a set of specific tasks (methods).
 *
 * This is specifically going to allow a third party site to open an iframe to ticket.devcon
 * in order to access the ticket secret stored in the local cache to construct a useDevconTicket request.
 */
public class CapabilityIssuer extends CapabilityCommon {
  private final KeyPair signingKeys;
  private final URL verifierDomain;

  public CapabilityIssuer(AsymmetricCipherKeyPair signingKeys, String verifierDomain) throws MalformedURLException {
    this.signingKeys = SignatureUtility.convertBouncyCastleKeysToJavaKey(signingKeys);
    this.verifierDomain = new URL(verifierDomain);
  }

  public String makeToken(String domain, Set tasks, int expirationTimeInDays) throws MalformedURLException {
    URL urlDomain = new URL(domain);
    String flattenedTasks = flattenSet(tasks);
    long currentTime = System.currentTimeMillis();
    long expirationInMs = currentTime + (long) expirationTimeInDays * 24l * 60l * 60l * 1000l;
    return buildSignedToken(urlDomain, flattenedTasks, expirationInMs, currentTime);
  }

  String buildSignedToken(URL domain, String flattenedTasks, long expirationTimeInMs, long creationTimeInMs) {
    Builder builder = JWT.create();
    // Only withAudience, withSubject, withIssuer, withExpiresAt, withNotBefore and withClaim(tasksClaimName) are required
    builder.withClaim(TasksClaimName, flattenedTasks);
    // Both the issuer and verifier is the same
    builder.withAudience(verifierDomain.toString());
    builder.withIssuer(verifierDomain.toString());
    builder.withSubject(domain.toString());
    builder.withNotBefore(new Date(creationTimeInMs));
    builder.withExpiresAt(new Date(expirationTimeInMs));
    // withIssuedAt and withJWTId are OPTIONAL
    builder.withIssuedAt(new Date(creationTimeInMs));
    builder.withJWTId(getJWTID(domain.toString(), flattenedTasks, expirationTimeInMs, creationTimeInMs));
    return builder.sign(getAlgorithm(signingKeys.getPublic(), signingKeys.getPrivate()));
  }

  String flattenSet(Set tasks) {
    if (tasks.size() == 0) {
      throw new IllegalArgumentException("At least one task must be assigned");
    }
    StringBuilder flattenedList = new StringBuilder();
    for (String task : tasks) {
      String normalizedTask = task.toLowerCase().trim();
      if (normalizedTask.contains(",")) {
        throw new IllegalArgumentException("A task contains a ',' which is not permitted");
      }
      flattenedList.append(normalizedTask).append(',');
    }
    String unprunedResult = flattenedList.toString();
    // Remove trailing ','
    return unprunedResult.substring(0, unprunedResult.length()-1);
  }

  private String getJWTID(String domain, String flattenedTasks, long expirationTime, long currentTime) {
    ByteBuffer toHash = ByteBuffer.allocate(domain.length() + TasksClaimName.length() +
        flattenedTasks.length() + 2 * (Long.SIZE / 8));
    toHash.put(domain.getBytes(StandardCharsets.UTF_8));
    toHash.put(TasksClaimName.getBytes(StandardCharsets.UTF_8));
    toHash.put(flattenedTasks.getBytes(StandardCharsets.UTF_8));
    toHash.putLong(expirationTime);
    toHash.putLong(currentTime);

    byte[] digest = AttestationCrypto.hashWithKeccak(toHash.array());
    return Base64.getEncoder().encodeToString(digest);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy