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

Added capability attestation code #214

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
21 changes: 21 additions & 0 deletions data-modules/output/asn/SignedCapabilityAttestation.asn
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
SignedCapabilityAttestation

DEFINITIONS ::=
BEGIN

SignedCapabilityAttestation ::= SEQUENCE {
capabilityAttestation CapabilityAttestation,
signatureValue BIT STRING
}


CapabilityAttestation ::= SEQUENCE {
uniqueId INTEGER,
sourceDomain UTF8String, -- MUST be an URI of the domain of the issuer --
targetDomain UTF8String, -- MUST be an URI of the target domain --
notBefore INTEGER, -- UNIX time, milliseconds since epoch --
notAfter INTEGER, -- UNIX time, milliseconds since epoch --
capabilities BIT STRING -- Encoding of each of the capabilities issued --
}

END
25 changes: 25 additions & 0 deletions data-modules/src/SignedCapabilityAttestation.asd
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<asnx:module name="SignedCapabilityAttestation" xmlns:asnx="urn:ietf:params:xml:ns:asnx">
<namedType name="SignedCapabilityAttestation">
<type>
<sequence>
<element name="capabilityAttestation" type="CapabilityAttestation">
<annotation>The actual, unsigned, capability attestation</annotation>
</element>
<element name="signatureValue" type="asnx:BIT-STRING"/>
</sequence>
</type>
</namedType>
<namedType name="CapabilityAttestation">
<type>
<sequence>
<element name="uniqueId" type="asnx:INTEGER"/>
<element name="sourceDomain" type="asnx:UTF8String"/>
<element name="targetDomain" type="asnx:UTF8String"/>
<element name="notBefore" type="asnx:INTEGER"/>
<element name="notAfter" type="asnx:INTEGER"/>
<element name="capabilities" type="asnx:BIT-STRING"/>
</sequence>
</type>
</namedType>
</asnx:module>
241 changes: 241 additions & 0 deletions src/main/java/org/tokenscript/attestation/CapabilityAttestation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package org.tokenscript.attestation;

import java.io.IOException;
import java.io.InvalidObjectException;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Instant;
import java.util.BitSet;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.tokenscript.attestation.core.Attestable;
import org.tokenscript.attestation.core.ExceptionUtil;
import org.tokenscript.attestation.core.SignatureUtility;

// TODO when PR 210 gets merged this should become Checkable https://github.com/TokenScript/attestation/pull/210
public class CapabilityAttestation implements Attestable {

// TODO should be deleted when merging PR 210
@Override
public byte[] getCommitment() {
return new byte[0];
}

public enum CapabilityType {
READ("read"), // 0
WRITE("write"), // 1
DELEGATE("delegate"); // 2

// private static final Map<Integer, CapabilityType> map = Map.of(
// 0, READ,
// 1, WRITE,
// 2, DELEGATE);
private final String type;

CapabilityType(String type) {
this.type = type;
}
public String toString() {
return type;
}

public static CapabilityType getType(String stringType) throws IllegalArgumentException {
CapabilityType type;
switch (stringType.toLowerCase()) {
case "read":
type = CapabilityType.READ;
break;
case "write":
type = CapabilityType.WRITE;
break;
case "delegate":
type = CapabilityType.DELEGATE;
break;
default:
System.err.println("Could not parse capability type, must be either \"read\", \"write\" or \"delegate\"");
throw new IllegalArgumentException("Wrong type of identifier");
}
return type;
}
public static CapabilityType getType(int index) throws IllegalArgumentException {
CapabilityType type;
switch (index) {
case 0:
type = CapabilityType.READ;
break;
case 1:
type = CapabilityType.WRITE;
break;
case 2:
type = CapabilityType.DELEGATE;
break;
default:
System.err.println("Could not parse capability type, must be between 0 and 2");
throw new IllegalArgumentException("Wrong type of identifier");
}
return type;
}
public static int getIndex(CapabilityType type) throws IllegalArgumentException {
int index;
switch (type) {
case READ:
index = 0;
break;
case WRITE:
index = 1;
break;
case DELEGATE:
index = 2;
break;
default:
System.err.println("Could not parse capability type, must be either \"read\", \"write\" or \"delegate\"");
throw new IllegalArgumentException("Wrong type of identifier");
}
return index;
}
}

private static final Logger logger = LogManager.getLogger(CapabilityAttestation.class);

private final BigInteger uniqueId;
private final URL sourceDomain;
private final URL targetDomain;
private final Instant notBefore;
private final Instant notAfter;
private final Set<CapabilityType> capabilities;
private final byte[] unsignedEncoding;
private final byte[] signedEncoding;
private final byte[] signature;
private final AsymmetricKeyParameter publicKey;

public CapabilityAttestation(BigInteger uniqueId, String sourceDomain, String targetDomain, Instant notBefore, Instant notAfter,
Set<CapabilityType> capabilities, AsymmetricCipherKeyPair signingKeys) throws MalformedURLException {
this.uniqueId = uniqueId;
this.targetDomain = new URL(targetDomain);
this.sourceDomain = new URL(sourceDomain);
this.notBefore = notBefore;
this.notAfter = notAfter;
this.capabilities = capabilities;
this.publicKey = signingKeys.getPublic();

try {
ASN1Sequence asn1CapAtt = makeCapabilityAtt();
this.unsignedEncoding = asn1CapAtt.getEncoded();
this.signature = SignatureUtility.signWithEthereum(unsignedEncoding, signingKeys.getPrivate());
this.signedEncoding = encodeSignedCapabilityAtt(asn1CapAtt);
} catch (IOException e) {
throw ExceptionUtil.makeRuntimeException(logger, "Could not construct encoding", e);
}
constructorCheck();
}

public CapabilityAttestation(BigInteger uniqueId, String sourceDomain, String targetDomain, Instant notBefore, Instant notAfter,
Set<CapabilityType> capabilities, byte[] signature, AsymmetricKeyParameter verificationKey) throws MalformedURLException {
this.uniqueId = uniqueId;
this.sourceDomain = new URL(sourceDomain);
this.targetDomain = new URL(targetDomain);
this.notBefore = notBefore;
this.notAfter = notAfter;
this.capabilities = capabilities;
this.signature = signature;
this.publicKey = verificationKey;

try {
ASN1Sequence asn1CapAtt = makeCapabilityAtt();
this.unsignedEncoding = asn1CapAtt.getEncoded();
this.signedEncoding = encodeSignedCapabilityAtt(asn1CapAtt);
} catch (IOException e) {
throw ExceptionUtil.makeRuntimeException(logger, "Could not construct encoding", e);
}
constructorCheck();
}

private void constructorCheck() {
if (!verify()) {
throw ExceptionUtil.throwException(logger,
new IllegalArgumentException("Could not verify"));
}
}

private ASN1Sequence makeCapabilityAtt() {
ASN1EncodableVector capabilityAttestation = new ASN1EncodableVector();
capabilityAttestation.add(new ASN1Integer(uniqueId));
capabilityAttestation.add(new DERUTF8String(sourceDomain.toString()));
capabilityAttestation.add(new DERUTF8String(targetDomain.toString()));
capabilityAttestation.add(new ASN1Integer(notBefore.getEpochSecond()*1000));
capabilityAttestation.add(new ASN1Integer(notAfter.getEpochSecond()*1000));
capabilityAttestation.add(new DERBitString(convertToBitString(capabilities)));
return new DERSequence(capabilityAttestation);
}

private byte[] encodeSignedCapabilityAtt(ASN1Sequence capabilityAtt) throws IOException {
ASN1EncodableVector signedCapAtt = new ASN1EncodableVector();
signedCapAtt.add(capabilityAtt);
signedCapAtt.add(new DERBitString(signature));
return new DERSequence(signedCapAtt).getEncoded();
}

static byte[] convertToBitString(Set<CapabilityType> capabilities) {
BitSet set = new BitSet();
for (CapabilityType current : capabilities) {
set.set(CapabilityType.getIndex(current), true);
}
return set.toByteArray();
}


public BigInteger getUniqueId() {
return uniqueId;
}

public String getSourceDomain() {
return sourceDomain.toString();
}

public String getTargetDomain() {
return targetDomain.toString();
}

public Set<CapabilityType> getCapabilities() {
return capabilities;
}

/**
* Return the capability attestation including signature
*/
@Override
public byte[] getDerEncoding() throws InvalidObjectException {
return signedEncoding;
}

@Override
public boolean checkValidity() {
Timestamp timestamp = new Timestamp(notBefore.getEpochSecond()*1000);
// It is valid the time difference between expiration and start validity
timestamp.setValidity(notAfter.getEpochSecond()*1000-notBefore.getEpochSecond()*1000);
if (!timestamp.validateAgainstExpiration(notAfter.getEpochSecond()*1000)) {
logger.error("Attestation not valid at this time");
return false;
}
return true;
}

@Override
public boolean verify() {
if (!SignatureUtility.verifyEthereumSignature(unsignedEncoding, signature, this.publicKey)) {
logger.error("Could not verify signature");
return false;
}
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.tokenscript.attestation;

import java.io.IOException;
import java.math.BigInteger;
import java.time.Instant;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.tokenscript.attestation.CapabilityAttestation.CapabilityType;

public class CapabilityAttestationDecoder implements AttestableObjectDecoder<CapabilityAttestation>{
private static final Logger logger = LogManager.getLogger(CapabilityAttestationDecoder.class);
private static final String DEFAULT = "default";

private Map<String, AsymmetricKeyParameter> idsToKeys = new HashMap<>();

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

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

@Override
public CapabilityAttestation decode(byte[] encoding) throws IOException {
ASN1InputStream input = new ASN1InputStream(encoding);
ASN1Sequence asn1 = ASN1Sequence.getInstance(input.readObject());
input.close();
ASN1Sequence capabilityAttestation = ASN1Sequence.getInstance(asn1.getObjectAt(0));
int innerCtr = 0;
BigInteger uniqueId = (ASN1Integer.getInstance(capabilityAttestation.getObjectAt(innerCtr++))).getValue();
String sourceDomain = DERUTF8String.getInstance(capabilityAttestation.getObjectAt(innerCtr++)).getString();
String targetDomain = DERUTF8String.getInstance(capabilityAttestation.getObjectAt(innerCtr++)).getString();
long notBeforeLong = (ASN1Integer.getInstance(capabilityAttestation.getObjectAt(innerCtr++))).getValue().longValueExact();
Instant notBefore = Instant.ofEpochSecond(notBeforeLong /1000);
long notAfterLong = (ASN1Integer.getInstance(capabilityAttestation.getObjectAt(innerCtr++))).getValue().longValueExact();
Instant notAfter = Instant.ofEpochSecond(notAfterLong /1000);
byte[] capabilityBytes = DERBitString.getInstance(capabilityAttestation.getObjectAt(innerCtr++)).getBytes();
Set<CapabilityType> capabilities = convertToSet(capabilityBytes);
byte[] signature = DERBitString.getInstance(asn1.getObjectAt(1)).getBytes();
return new CapabilityAttestation(uniqueId, sourceDomain, targetDomain, notBefore,
notAfter, capabilities, signature, getPk(sourceDomain));
}

static Set<CapabilityType> convertToSet(byte[] capabilityBytes) {
Set<CapabilityType> capabilitySet = new HashSet<>();
BitSet bitSet = BitSet.valueOf(capabilityBytes);
int lastBitIndex = 0;
while (bitSet.nextSetBit(lastBitIndex) != -1) {
int currentBitIndex = bitSet.nextSetBit(lastBitIndex);
CapabilityType currentType = CapabilityType.getType(currentBitIndex);
capabilitySet.add(currentType);
lastBitIndex = currentBitIndex+1;
}
return capabilitySet;
}

private AsymmetricKeyParameter getPk(String sourceDomain) {
AsymmetricKeyParameter pk;
if (idsToKeys.get(sourceDomain) != null) {
pk = idsToKeys.get(sourceDomain);
} else {
pk = idsToKeys.get(DEFAULT);
}
return pk;
}
}
Loading