Skip to content

Commit

Permalink
Added wrapper to ensure compatibility with attestation.id main branch
Browse files Browse the repository at this point in the history
  • Loading branch information
jot2re committed Mar 25, 2022
1 parent 1682084 commit 25cebb6
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 133 deletions.
130 changes: 130 additions & 0 deletions src/main/java/org/devcon/ticket/DevconTicketDecoder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package org.devcon.ticket;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.asn1.ASN1BitString;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1UTF8String;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory;
import org.tokenscript.attestation.ObjectDecoder;
import org.tokenscript.attestation.core.ExceptionUtil;
import org.tokenscript.attestation.core.SignatureUtility;

public class DevconTicketDecoder implements ObjectDecoder<Ticket> {
private static final Logger logger = LogManager.getLogger(DevconTicketDecoder.class);
private static final String DEFAULT = "default";

private Map<String, AsymmetricKeyParameter> idsToKeys;

public DevconTicketDecoder(Map<String, AsymmetricKeyParameter> idsToKeys) {
this.idsToKeys = idsToKeys;
}

public DevconTicketDecoder(AsymmetricKeyParameter publicKey) {
this();
idsToKeys.put(DEFAULT, publicKey);
}

public DevconTicketDecoder() {
idsToKeys = new HashMap<>();
}

@Override
public Ticket decode(byte[] encoding) throws IOException {
ASN1InputStream input = null;
try {
input = new ASN1InputStream(encoding);
ASN1Sequence asn1 = ASN1Sequence.getInstance(input.readObject());
input.close();
ASN1Sequence ticket = ASN1Sequence.getInstance(asn1.getObjectAt(0));
String devconId = (ASN1UTF8String.getInstance(ticket.getObjectAt(0))).getString();
ASN1Primitive ticketIdObj = ticket.getObjectAt(1).toASN1Primitive();
String ticketId;
if (ticketIdObj instanceof ASN1Integer) {
ticketId = (ASN1Integer.getInstance(ticket.getObjectAt(1))).getValue().toString();
} else { // ASN1UTF8String
ticketId = (ASN1UTF8String.getInstance(ticket.getObjectAt(1))).getString();
}
int ticketClassInt = ASN1Integer.getInstance(ticket.getObjectAt(2)).getValue().intValueExact();
byte[] commitment = (ASN1OctetString.getInstance(ticket.getObjectAt(3))).getOctets();
/* refactored 2021-01-05 : we don't care about the ticket class set on our level
TicketClass ticketClass = null;
for (TicketClass current : TicketClass.values()) {
if (current.getValue() == ticketClassInt) {
ticketClass = current;
}
}
if (ticketClass == null) {
throw new IOException("Not valid ticket class");
}
*/
byte[] signature = parsePKandSignature(asn1, devconId, 1);
return new Ticket(devconId, ticketId, ticketClassInt, commitment, signature, getPk(devconId));
} finally {
input.close();
}
}

/**
* Returns the signature and ensures that the optional public key is properly restored
* @param input The encoded Ticket
* @return
*/
byte[] parsePKandSignature(ASN1Sequence input, String devconId, int asnBaseIdx) throws IOException, IllegalArgumentException{
byte[] signature;
ASN1Encodable object = input.getObjectAt(asnBaseIdx);
if (object instanceof ASN1Sequence) {
// The optional PublicKeyInfo is included
parseEncodingOfPKInfo((ASN1Sequence) object, devconId);
signature = ASN1BitString.getInstance(input.getObjectAt(asnBaseIdx+1)).getBytes();
} else if (object instanceof DERBitString) {
// Only the signature is included
signature = ASN1BitString.getInstance(input.getObjectAt(asnBaseIdx)).getBytes();
} else {
throw ExceptionUtil.throwException(logger,
new IllegalArgumentException("Invalid ticket encoding"));
}
return signature;
}

void parseEncodingOfPKInfo(ASN1Sequence publicKeyInfo, String devconId) throws IOException, IllegalArgumentException {
AlgorithmIdentifier algorithm = AlgorithmIdentifier.getInstance(publicKeyInfo.getObjectAt(0));
byte[] publicKeyBytes = ASN1BitString.getInstance(publicKeyInfo.getObjectAt(1)).getEncoded();
AsymmetricKeyParameter decodedPublicKey = SignatureUtility.restoreDefaultKey(algorithm, publicKeyBytes);
SubjectPublicKeyInfo decodedSpki = SubjectPublicKeyInfoFactory
.createSubjectPublicKeyInfo(decodedPublicKey);
// Ensure that the right type of public key is given
if (getPk(devconId) != null) {
SubjectPublicKeyInfo referenceSpki = SubjectPublicKeyInfoFactory
.createSubjectPublicKeyInfo(getPk(devconId));
if (!Arrays.equals(referenceSpki.getEncoded(), decodedSpki.getEncoded())) {
throw ExceptionUtil.throwException(logger, new IllegalArgumentException(
"The public key is not of the same as supplied as argument"));
}
}
idsToKeys.put(devconId, decodedPublicKey);
}

AsymmetricKeyParameter getPk(String devconId) {
AsymmetricKeyParameter pk;
if (idsToKeys.get(devconId) != null) {
pk = idsToKeys.get(devconId);
} else {
pk = idsToKeys.get(DEFAULT);
}
return pk;
}
}
2 changes: 1 addition & 1 deletion src/main/java/org/devcon/ticket/LisconTicketDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* It is significantly less secure than the regular Ticket format and should only be used in legacy settings!
*/
@Deprecated
public class LisconTicketDecoder extends TicketDecoder {
public class LisconTicketDecoder extends DevconTicketDecoder {
public LisconTicketDecoder(AsymmetricKeyParameter publicKey) {
super(publicKey);
}
Expand Down
125 changes: 19 additions & 106 deletions src/main/java/org/devcon/ticket/TicketDecoder.java
Original file line number Diff line number Diff line change
@@ -1,130 +1,43 @@
package org.devcon.ticket;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.asn1.ASN1BitString;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1UTF8String;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory;
import org.tokenscript.attestation.ObjectDecoder;
import org.tokenscript.attestation.core.ExceptionUtil;
import org.tokenscript.attestation.core.SignatureUtility;

public class TicketDecoder implements ObjectDecoder<Ticket> {
/**
* Wrapper class that allows decoding of either Devcon or Liscon tickets.
* This class only exists for backward compatibility reasons
*/
@Deprecated
public class TicketDecoder implements ObjectDecoder<Ticket> {
private static final Logger logger = LogManager.getLogger(TicketDecoder.class);
private static final String DEFAULT = "default";

private Map<String, AsymmetricKeyParameter> idsToKeys;
private final List<ObjectDecoder<Ticket>> decoders = new ArrayList<>(2);

public TicketDecoder(Map<String, AsymmetricKeyParameter> idsToKeys) {
this.idsToKeys = idsToKeys;
decoders.add(new DevconTicketDecoder(idsToKeys));
}

public TicketDecoder(AsymmetricKeyParameter publicKey) {
this();
idsToKeys.put(DEFAULT, publicKey);
}

public TicketDecoder() {
idsToKeys = new HashMap<>();
decoders.add(new DevconTicketDecoder(publicKey));
decoders.add(new LisconTicketDecoder(publicKey));
}

@Override
public Ticket decode(byte[] encoding) throws IOException {
ASN1InputStream input = null;
try {
input = new ASN1InputStream(encoding);
ASN1Sequence asn1 = ASN1Sequence.getInstance(input.readObject());
input.close();
ASN1Sequence ticket = ASN1Sequence.getInstance(asn1.getObjectAt(0));
String devconId = (ASN1UTF8String.getInstance(ticket.getObjectAt(0))).getString();
ASN1Primitive ticketIdObj = ticket.getObjectAt(1).toASN1Primitive();
String ticketId;
if (ticketIdObj instanceof ASN1Integer) {
ticketId = (ASN1Integer.getInstance(ticket.getObjectAt(1))).getValue().toString();
} else { // ASN1UTF8String
ticketId = (ASN1UTF8String.getInstance(ticket.getObjectAt(1))).getString();
}
int ticketClassInt = ASN1Integer.getInstance(ticket.getObjectAt(2)).getValue().intValueExact();
byte[] commitment = (ASN1OctetString.getInstance(ticket.getObjectAt(3))).getOctets();
/* refactored 2021-01-05 : we don't care about the ticket class set on our level
TicketClass ticketClass = null;
for (TicketClass current : TicketClass.values()) {
if (current.getValue() == ticketClassInt) {
ticketClass = current;
}
for (ObjectDecoder<Ticket> currentDecoder : decoders) {
try {
return currentDecoder.decode(encoding);
} catch (Exception e) {
continue;
}
if (ticketClass == null) {
throw new IOException("Not valid ticket class");
}
*/
byte[] signature = parsePKandSignature(asn1, devconId, 1);
return new Ticket(devconId, ticketId, ticketClassInt, commitment, signature, getPk(devconId));
} finally {
input.close();
}
}

/**
* Returns the signature and ensures that the optional public key is properly restored
* @param input The encoded Ticket
* @return
*/
byte[] parsePKandSignature(ASN1Sequence input, String devconId, int asnBaseIdx) throws IOException, IllegalArgumentException{
byte[] signature;
ASN1Encodable object = input.getObjectAt(asnBaseIdx);
if (object instanceof ASN1Sequence) {
// The optional PublicKeyInfo is included
parseEncodingOfPKInfo((ASN1Sequence) object, devconId);
signature = ASN1BitString.getInstance(input.getObjectAt(asnBaseIdx+1)).getBytes();
} else if (object instanceof DERBitString) {
// Only the signature is included
signature = ASN1BitString.getInstance(input.getObjectAt(asnBaseIdx)).getBytes();
} else {
throw ExceptionUtil.throwException(logger,
new IllegalArgumentException("Invalid ticket encoding"));
}
return signature;
}

void parseEncodingOfPKInfo(ASN1Sequence publicKeyInfo, String devconId) throws IOException, IllegalArgumentException {
AlgorithmIdentifier algorithm = AlgorithmIdentifier.getInstance(publicKeyInfo.getObjectAt(0));
byte[] publicKeyBytes = ASN1BitString.getInstance(publicKeyInfo.getObjectAt(1)).getEncoded();
AsymmetricKeyParameter decodedPublicKey = SignatureUtility.restoreDefaultKey(algorithm, publicKeyBytes);
SubjectPublicKeyInfo decodedSpki = SubjectPublicKeyInfoFactory
.createSubjectPublicKeyInfo(decodedPublicKey);
// Ensure that the right type of public key is given
if (getPk(devconId) != null) {
SubjectPublicKeyInfo referenceSpki = SubjectPublicKeyInfoFactory
.createSubjectPublicKeyInfo(getPk(devconId));
if (!Arrays.equals(referenceSpki.getEncoded(), decodedSpki.getEncoded())) {
throw ExceptionUtil.throwException(logger, new IllegalArgumentException(
"The public key is not of the same as supplied as argument"));
}
}
idsToKeys.put(devconId, decodedPublicKey);
}

AsymmetricKeyParameter getPk(String devconId) {
AsymmetricKeyParameter pk;
if (idsToKeys.get(devconId) != null) {
pk = idsToKeys.get(devconId);
} else {
pk = idsToKeys.get(DEFAULT);
}
return pk;
ExceptionUtil.throwException(logger, new RuntimeException("Could not decode ticket"));
return null;
}
}
2 changes: 1 addition & 1 deletion src/main/java/org/devcon/ticket/UseTicketBundle.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 TicketDecoder(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 Down
4 changes: 2 additions & 2 deletions src/main/java/org/devcon/ticket/Validator.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ public static void main(String... args) {
static void validateTicket(String ticketInUrl, String pokInUrl, String mail, Path keyFile) throws IOException {
byte[] dataCER = DERUtility.restoreBytes(Files.readAllLines(keyFile));
AsymmetricKeyParameter issuerPubKey = DERUtility.restoreRFCRFC5915Key(dataCER);
TicketDecoder ticketDecoder = new TicketDecoder(issuerPubKey);
Ticket ticket = ticketDecoder.decode(URLUtility.decodeData(ticketInUrl));
DevconTicketDecoder devconTicketDecoder = new DevconTicketDecoder(issuerPubKey);
Ticket ticket = devconTicketDecoder.decode(URLUtility.decodeData(ticketInUrl));
if (!ticket.checkValidity()) {
throw new RuntimeException(
"Something went wrong and the constructed ticket could not be validated");
Expand Down
Loading

0 comments on commit 25cebb6

Please sign in to comment.