Skip to content
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
6 changes: 3 additions & 3 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ services:
- 7723:7723

keria:
image: ${KERIA_IMAGE:-weboftrust/keria}:${KERIA_IMAGE_TAG:-0.2.0-dev6}
image: cardanofoundation/cf-idw-keria:c6498308
environment:
KERI_AGENT_CORS: 1
<<: *python-env
volumes:
- ./config/keria.json:/keria/config/keri/cf/keria.json
command: start --config-dir /keria/config --config-file keria.json --name agent # Adjusted command line
entrypoint: keria start --config-dir /keria/config --config-file keria.json --name agent # Adjusted command line
healthcheck:
test: wget --spider http://keria:3902/spec.yaml
<<: *healthcheck
Expand All @@ -48,4 +48,4 @@ services:
ports:
- 5642:5642
- 5643:5643
- 5644:5644
- 5644:5644
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.cardanofoundation.signify.app.credentialing.credentials;

import org.cardanofoundation.signify.cesr.Serder;

public class CredentialVerifyOptions {
private final Serder acdc;
private final Serder iss;
private final String acdcAtc;
private final String issAtc;

private CredentialVerifyOptions(Builder builder) {
this.acdc = builder.acdc;
this.iss = builder.iss;
this.acdcAtc = builder.acdcAtc;
this.issAtc = builder.issAtc;
}

public Serder getAcdc() { return acdc; }
public Serder getIss() { return iss; }
public String getAcdcAtc() { return acdcAtc; }
public String getIssAtc() { return issAtc; }

public static Builder builder() { return new Builder(); }

public static class Builder {
private Serder acdc;
private Serder iss;
private String acdcAtc;
private String issAtc;

public Builder acdc(Serder acdc) {
this.acdc = acdc;
return this;
}
public Builder iss(Serder iss) {
this.iss = iss;
return this;
}
public Builder acdcAtc(String acdcAtc) {
this.acdcAtc = acdcAtc;
return this;
}
public Builder issAtc(String issAtc) {
this.issAtc = issAtc;
return this;
}
public CredentialVerifyOptions build() {
return new CredentialVerifyOptions(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,34 +47,57 @@ public Object list(CredentialFilter kargs) throws IOException, InterruptedExcept
return Utils.fromJson(response.body(), Object.class);
}

public Optional<Object> get(String said) throws IOException, InterruptedException, LibsodiumException {
return this.get(said, false);
/**
* Get a credential as raw CESR string.
*/
public Optional<String> get(String said) throws IOException, InterruptedException, LibsodiumException {
return this.getCESR(said);
}

/**
* Get a credential
*
* @param said - SAID of the credential
* @param includeCESR - Optional flag export the credential in CESR format
* @return Optional containing the credential if found, or empty if not found
* Get a credential as parsed JSON (Object) or raw CESR string depending on includeCESR.
*/
public Optional<Object> get(String said, boolean includeCESR) throws IOException, InterruptedException, LibsodiumException {
if (includeCESR) {
// For backward compatibility, but prefer getCESR for type safety
Optional<String> cesr = getCESR(said);
return cesr.map(s -> (Object) s);
} else {
return getJson(said);
}
}

/**
* Get a credential as raw CESR string.
*/
public Optional<String> getCESR(String said) throws IOException, InterruptedException, LibsodiumException {
final String path = "/credentials/" + said;
final String method = "GET";

Map<String, String> extraHeaders = new LinkedHashMap<>();
if (includeCESR) {
extraHeaders.put("Accept", "application/json+cesr");
} else {
extraHeaders.put("Accept", "application/json");
extraHeaders.put("Accept", "application/json+cesr");

HttpResponse<String> response = this.client.fetch(path, method, null, extraHeaders);

if (response.statusCode() == java.net.HttpURLConnection.HTTP_NOT_FOUND) {
return Optional.empty();
}
return Optional.of(response.body());
}

/**
* Get a credential as parsed JSON (Object).
*/
private Optional<Object> getJson(String said) throws IOException, InterruptedException, LibsodiumException {
final String path = "/credentials/" + said;
final String method = "GET";
Map<String, String> extraHeaders = new LinkedHashMap<>();
extraHeaders.put("Accept", "application/json");

HttpResponse<String> response = this.client.fetch(path, method, null, extraHeaders);

if (response.statusCode() == java.net.HttpURLConnection.HTTP_NOT_FOUND) {
return Optional.empty();
}

return Optional.of(Utils.fromJson(response.body(), Object.class));
}

Expand Down Expand Up @@ -196,7 +219,7 @@ public RevokeCredentialResult revoke(String name, String said, String datetime)
final String vs = CoreUtil.versify(CoreUtil.Ident.KERI, null, CoreUtil.Serials.JSON, 0);
final String dt = datetime != null ? datetime : Utils.currentDateTimeString();

Map<String, Object> cred = Utils.toMap(this.get(said)
Map<String, Object> cred = Utils.toMap(this.get(said, false)
.orElseThrow(() -> new IllegalArgumentException("Credential not found: " + said)));

// Create rev
Expand Down Expand Up @@ -256,4 +279,30 @@ public RevokeCredentialResult revoke(String name, String said, String datetime)

return new RevokeCredentialResult(new Serder(ixn), new Serder(rev), op);
}

/**
* Verify a credential and issuing event
*
* @param options CredentialVerifyOptions containing all verification parameters
* @return Operation containing the verification result
*/
public Operation<?> verify(CredentialVerifyOptions options) throws IOException, InterruptedException, LibsodiumException {
final String path = "/credentials/verify";
final String method = "POST";

Map<String, Object> body = new LinkedHashMap<>();
body.put("acdc", options.getAcdc().getKed());
body.put("iss", options.getIss().getKed());

if (options.getAcdcAtc() != null && !options.getAcdcAtc().isEmpty()) {
body.put("acdcAtc", options.getAcdcAtc());
}

if (options.getIssAtc() != null && !options.getIssAtc().isEmpty()) {
body.put("issAtc", options.getIssAtc());
}

HttpResponse<String> response = this.client.fetch(path, method, body);
return Operation.fromObject(Utils.fromJson(response.body(), Map.class));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.cardanofoundation.signify.app.clienting.SignifyClient;
import org.cardanofoundation.signify.app.coring.Coring;
import org.cardanofoundation.signify.app.coring.Operation;
import org.cardanofoundation.signify.app.habery.TraitCodex;
import org.cardanofoundation.signify.cesr.Keeping;
import org.cardanofoundation.signify.cesr.Serder;
Expand Down Expand Up @@ -165,4 +166,27 @@ public Object rename(String name, String registryName, String newName) throws IO
HttpResponse<String> response = this.client.fetch(path, method, data);
return Utils.fromJson(response.body(), Object.class);
}

/**
* Verify a registry with optional attachment
*
* @param options RegistryVerifyOptions containing all verification parameters
* @return Operation containing the verification result
* @throws IOException if an I/O error occurs
* @throws InterruptedException if the operation is interrupted
* @throws LibsodiumException if a sodium exception occurs
*/
public Operation<?> verify(RegistryVerifyOptions options) throws IOException, InterruptedException, LibsodiumException {
final String path = "/registries/verify";
final String method = "POST";

Map<String, Object> body = new LinkedHashMap<>();
body.put("vcp", options.getVcp().getKed());
if (options.getAtc() != null && !options.getAtc().isEmpty()) {
body.put("atc", options.getAtc());
}

HttpResponse<String> response = this.client.fetch(path, method, body);
return Operation.fromObject(Utils.fromJson(response.body(), Map.class));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.cardanofoundation.signify.app.credentialing.registries;

import org.cardanofoundation.signify.cesr.Serder;

public class RegistryVerifyOptions {
private final Serder vcp;
private final String atc;

private RegistryVerifyOptions(Builder builder) {
this.vcp = builder.vcp;
this.atc = builder.atc;
}

public Serder getVcp() { return vcp; }
public String getAtc() { return atc; }

public static Builder builder() { return new Builder(); }

public static class Builder {
private Serder vcp;
private String atc;

public Builder vcp(Serder vcp) {
this.vcp = vcp;
return this;
}
public Builder atc(String atc) {
this.atc = atc;
return this;
}
public RegistryVerifyOptions build() {
return new RegistryVerifyOptions(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package org.cardanofoundation.signify.cesr.util;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
* Utility class for parsing and reconstructing CESR format streams.
*/
public class CESRStreamUtil {

/**
* Parses CESR format string into an array of events with their attachments.
* CESR format: {json_event}{attachment}{json_event}{attachment}...
*
* @param cesrData The CESR format string
* @return List of maps containing "event" and "atc" keys
*/
@SuppressWarnings("unchecked")
public static List<Map<String, Object>> parseCESRData(String cesrData) {
List<Map<String, Object>> result = new ArrayList<>();

int index = 0;
while (index < cesrData.length()) {
// Find the start of JSON event (look for opening brace)
if (cesrData.charAt(index) == '{') {
// Find the end of JSON event by counting braces
int braceCount = 0;
int jsonStart = index;
int jsonEnd = index;

for (int i = index; i < cesrData.length(); i++) {
char ch = cesrData.charAt(i);
if (ch == '{') {
braceCount++;
} else if (ch == '}') {
braceCount--;
if (braceCount == 0) {
jsonEnd = i + 1;
break;
}
}
}

// Extract JSON event
String jsonEvent = cesrData.substring(jsonStart, jsonEnd);

// Find attachment data (everything until next '{' or end of string)
int attachmentStart = jsonEnd;
int attachmentEnd = cesrData.length();

for (int i = attachmentStart; i < cesrData.length(); i++) {
if (cesrData.charAt(i) == '{') {
attachmentEnd = i;
break;
}
}

String attachment = "";
if (attachmentStart < attachmentEnd) {
attachment = cesrData.substring(attachmentStart, attachmentEnd);
}

// Parse JSON event to Object
try {
Map<String, Object> eventObj = Utils.fromJson(jsonEvent, Map.class);

Map<String, Object> eventMap = new LinkedHashMap<>();
eventMap.put("event", eventObj);
eventMap.put("atc", attachment);
result.add(eventMap);
} catch (Exception e) {
System.err.println("Failed to parse JSON event: " + jsonEvent);
e.printStackTrace();
}

index = attachmentEnd;
} else {
index++;
}
}

return result;
}

/**
* Reconstructs a CESR format stream from parsed events and their attachments.
* @param parsedData List of maps containing "event" and "atc" keys
* @return A CESR format string with events and attachments concatenated
*/
@SuppressWarnings("unchecked")
public static String makeCESRStream(List<Map<String, Object>> parsedData) {
StringBuilder cesrStream = new StringBuilder();

for (Map<String, Object> eventData : parsedData) {
Map<String, Object> event = (Map<String, Object>) eventData.get("event");
String attachment = (String) eventData.get("atc");

if (event != null) {
String jsonEvent = Utils.jsonStringify(event);
cesrStream.append(jsonEvent);
}

if (attachment != null && !attachment.isEmpty()) {
cesrStream.append(attachment);
}
}

return cesrStream.toString();
}

/**
* Reconstructs a CESR format stream from separate lists of events and attachments.
* @param events List of event maps (VCP, ISS, ACDC events)
* @param attachments List of attachment strings corresponding to each event (can be null for events without attachments)
* @return A CESR format string with events and attachments concatenated
* @throws IllegalArgumentException if events and attachments lists have different sizes
*/
public static String makeCESRStream(List<Map<String, Object>> events, List<String> attachments) {
if (events.size() != attachments.size()) {
throw new IllegalArgumentException(
"Events and attachments lists must have the same size. " +
"Events: " + events.size() + ", Attachments: " + attachments.size()
);
}

List<Map<String, Object>> parsedData = new ArrayList<>();
for (int i = 0; i < events.size(); i++) {
Map<String, Object> eventMap = new LinkedHashMap<>();
eventMap.put("event", events.get(i));
eventMap.put("atc", attachments.get(i) != null ? attachments.get(i) : "");
parsedData.add(eventMap);
}

return makeCESRStream(parsedData);
}
}
Loading
Loading