Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: signature-based UN and possibility of adding context to UN #284

Merged
merged 14 commits into from
Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 0 additions & 92 deletions src/main/java/org/devcon/ticket/UnpredictableNumberTool.java

This file was deleted.

28 changes: 14 additions & 14 deletions src/main/java/org/devcon/ticket/UseTicketBundle.java
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
package org.devcon.ticket;

import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Date;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.tokenscript.attestation.AttestedObject;
import org.tokenscript.attestation.core.ExceptionUtil;
import org.tokenscript.attestation.core.SignatureUtility;
import org.tokenscript.attestation.core.Verifiable;
import org.tokenscript.attestation.Timestamp;
import org.tokenscript.attestation.core.*;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Date;

public class UseTicketBundle implements Verifiable {
private static final Logger logger = LogManager.getLogger(UseTicketBundle.class);

private final AttestedObject useTicket;
private final AttestedObject<Ticket> useTicket;
private final UnpredictableNumberBundle un;
private final byte[] signature;
private final byte[] messageToSign;

private final ObjectMapper jsonMapper;

public UseTicketBundle(AttestedObject useTicket, UnpredictableNumberBundle un, AsymmetricKeyParameter signingKey) {
public UseTicketBundle(AttestedObject<Ticket> useTicket, UnpredictableNumberBundle un, AsymmetricKeyParameter signingKey) {
this.jsonMapper = new ObjectMapper();

this.useTicket = useTicket;
Expand All @@ -34,7 +34,7 @@ public UseTicketBundle(AttestedObject useTicket, UnpredictableNumberBundle un, A
constructorCheck();
}

public UseTicketBundle(AttestedObject useTicket, UnpredictableNumberBundle un, byte[] signature) {
public UseTicketBundle(AttestedObject<Ticket> useTicket, UnpredictableNumberBundle un, byte[] signature) {
this.jsonMapper = new ObjectMapper();
this.useTicket = useTicket;
this.un = un;
Expand All @@ -46,7 +46,7 @@ public UseTicketBundle(AttestedObject useTicket, UnpredictableNumberBundle un, b
public UseTicketBundle(String jsonBundle, AsymmetricKeyParameter ticketIssuerPublicKey, AsymmetricKeyParameter attestorPublicKey) throws Exception {
this.jsonMapper = new ObjectMapper();
JsonUseTicketBundle decodedBundle = jsonMapper.readValue(jsonBundle, JsonUseTicketBundle.class);
this.useTicket = new AttestedObject(decodedBundle.getUseTicketDer(), new DevconTicketDecoder(ticketIssuerPublicKey), attestorPublicKey);
this.useTicket = new AttestedObject<>(decodedBundle.getUseTicketDer(), new DevconTicketDecoder(ticketIssuerPublicKey), attestorPublicKey);
this.un = decodedBundle.getUn();
this.messageToSign = computeMessage(un);
this.signature = decodedBundle.getSignature();
Expand All @@ -68,7 +68,7 @@ private byte[] computeMessage(UnpredictableNumberBundle currentUn) {
return messageToSignString.getBytes(StandardCharsets.UTF_8);
}

public AttestedObject getUseTicket() {
public AttestedObject<Ticket> getUseTicket() {
return useTicket;
}

Expand All @@ -84,17 +84,17 @@ public byte[] getMessageToSign() {
return messageToSign;
}

public String getJsonBundle() throws Exception {
public String getJsonBundle() throws JsonProcessingException {
return jsonMapper.writeValueAsString(new JsonUseTicketBundle(useTicket.getDerEncoding(), un,
signature));
signature));
}

public boolean validateAndVerify(UnpredictableNumberTool unt) {
if (!useTicket.checkValidity()) {
logger.error("Use ticket is not valid");
return false;
}
if (!unt.validateUnpredictableNumber(un.getNumber(), un.getRandomness(), un.getExpiration())) {
if (!unt.validateUnpredictableNumber(un.getNumber(), un.getRandomness(), un.getExpiration(), un.getContext())) {
logger.error("Unpredictable number is not valid ");
return false;
}
Expand Down
103 changes: 103 additions & 0 deletions src/main/java/org/tokenscript/attestation/core/UNMac.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package org.tokenscript.attestation.core;

import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Clock;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.crypto.digests.SHA3Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.tokenscript.eip712.Eip712Common;

public class UNMac implements UnpredictableNumberTool {

public static final int BYTES_IN_UN = 16; // 128 bits
private static final Logger logger = LogManager.getLogger(UNMac.class);

private final SecureRandom random;
private final String domain;
private final long validityInMs;
private final HMac hmac = new HMac(new SHA3Digest(BYTES_IN_SEED * 8));

public UNMac(byte[] key, String domain) {
this(new SecureRandom(), key, domain);
}

public UNMac(SecureRandom random, byte[] key, String domain) {
this(random, key, domain, DEFAULT_VALIDITY_IN_MS);
}

public UNMac(SecureRandom random, byte[] key, String domain, long validityInMs) {
this.random = random;
this.domain = domain;
this.validityInMs = validityInMs;
hmac.init(new KeyParameter(key));
// todo should be moved to url utility
if (!Eip712Common.isDomainValid(domain)) {
throw ExceptionUtil.throwException(logger, new IllegalArgumentException("Domain is not a valid domain"));
}
}

@Override
public String getDomain() {
return domain;
}

@Override
public UnpredictableNumberBundle getUnpredictableNumberBundle() {
return getUnpredictableNumberBundle(null);
}


@Override
public UnpredictableNumberBundle getUnpredictableNumberBundle(byte[] context) {
long expiration = Clock.systemUTC().millis() + validityInMs;
byte[] randomness = new byte[BYTES_IN_SEED];
random.nextBytes(randomness);
// Construct UN of BYTES_IN_UN bytes
return new UnpredictableNumberBundle(
getUnpredictableNumber(randomness, expiration, context, BYTES_IN_UN)
, randomness, domain, expiration, context);
}

private String getUnpredictableNumber(byte[] randomness, long expirationInMs, byte[] context,
int unSize) {
// compute HMAC on the expiration, randomness and the hash digest of the context
hmac.reset();
hmac.update(UnpredictableNumberTool.longToBytes(expirationInMs), 0, Long.BYTES);
hmac.update(randomness, 0, BYTES_IN_SEED);
if (context != null) {
hmac.update(UnpredictableNumberTool.hashContext(context), 0, BYTES_IN_SEED);
}
hmac.update(domain.getBytes(StandardCharsets.UTF_8), 0,
domain.getBytes(StandardCharsets.UTF_8).length);
byte[] digest = new byte[BYTES_IN_SEED];
hmac.doFinal(digest, 0);
byte[] result = new byte[unSize];
System.arraycopy(digest, 0, result, 0, unSize);
return URLUtility.encodeData(result);
}

@Override
public boolean validateUnpredictableNumber(String un, byte[] randomness, long expirationInMs) {
return validateUnpredictableNumber(un, randomness, expirationInMs, null);
}

@Override
public boolean validateUnpredictableNumber(String un, byte[] randomness, long expirationInMs, byte[] context) {
if (Clock.systemUTC().millis() > expirationInMs) {
logger.error("Unpredictable number has expired");
return false;
}
int unByteLength = URLUtility.decodeData(un).length;
String expectedNumber = getUnpredictableNumber(randomness, expirationInMs, context,
unByteLength);
if (!expectedNumber.equals(un)) {
logger.error(
"The unpredictable number is computed incorrectly. Either wrong key or wrong domain");
return false;
}
return true;
}
}
Loading