id.unum.service.IssuerService Maven / Gradle / Ivy
The newest version!
package id.unum.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.protobuf.Timestamp;
import id.unum.crossPlatformInterfaces.Encoding;
import id.unum.domain.CredentialOptions;
import id.unum.dto.RegisteredIssuer;
import id.unum.dto.Success;
import id.unum.dto.Unum;
import id.unum.enums.CredentialStatusOptions;
import id.unum.error.UnumError;
import id.unum.facade.rest.Client;
import id.unum.facade.rest.UnumAPIService;
import id.unum.facade.rest.request.RegisterIssuerRequest;
import id.unum.facade.rest.request.UpdateCredentialStatusRequest;
import id.unum.protos.credential.v1.*;
import id.unum.protos.crypto.v1.EncryptedData;
import id.unum.protos.crypto.v1.KeyPairSet;
import id.unum.protos.crypto.v1.PublicKeyInfo;
import id.unum.protos.didDocument.v1.DidDocument;
import id.unum.protos.issuer.v1.Issuer;
import id.unum.protos.proof.v1.Proof;
import id.unum.types.CredentialSubject;
import id.unum.utils.CryptoUtils;
import lombok.extern.log4j.Log4j2;
import retrofit2.Call;
import retrofit2.Response;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
import static com.google.protobuf.util.Timestamps.fromMillis;
import static id.unum.converter.ProtoToDto.*;
import static id.unum.utils.CryptoUtils.createProof;
import static id.unum.utils.CryptoUtils.generateKeyPairSet;
import static id.unum.utils.Utils.*;
@Log4j2
public final class IssuerService implements IssuerServiceInterface {
private final UnumAPIService _unumService;
private final DidDocService _didDocService;
public IssuerService(String url) {
Client client = new Client(url);
_unumService = client.getRetrofit().create(UnumAPIService.class);
_didDocService = new DidDocService(url);
}
@Override
public Unum registerIssuer(String customerUuid, String apiKey) throws UnumError {
log.info("calling register issuer");
KeyPairSet keyPairSet = generateKeyPairSet(Encoding.BASE58);
RegisterIssuerRequest request = new RegisterIssuerRequest();
request.setCustomerUuid(customerUuid);
request.setPublicKeyInfo(extractPublicKeyInfo(keyPairSet, Encoding.BASE58));
// Call call = _unumService.registerIssuer("Bearer " + apiKey, request);
Call call = _unumService.registerIssuer("Bearer " + apiKey, request);
try {
// Response response = call.execute();
Response response = call.execute();
if (!response.isSuccessful()) {
String message = response.errorBody() != null
? response.errorBody().string() : "Unknown error";
log.error("Saas error calling register issuer " + message);
throw new UnumError(response.errorBody().hashCode(), message);
}
Issuer issuer = response.body();
String newAuthToken = response.headers().get("X-Auth-Token");
Unum result = new Unum();
RegisteredIssuer body = convertIssuer(issuer);
result.setBody(body);
result.setAuthToken(handleAuthToken(newAuthToken));
return result;
} catch (IOException e) {
log.error("IOException calling register issuer: " + e);
e.printStackTrace();
}
throw new UnumError(500, "Unknown error.");
}
@Override
public Unum issueCredential(String authToken, List types, String issuer,
ObjectNode credentialSubject, String signingPrivateKey,
Date expirationDate) throws UnumError {
requireAuth(authToken);
// todo?
validateIssueCredentialInput(types, issuer, credentialSubject, signingPrivateKey, expirationDate);
CredentialOptions options = constructCredentialOptions(types, issuer, credentialSubject, expirationDate);
/**
* Need to loop through all versions except most recent so that can issued credentials could be backwards compatible with older holder versions.
* However, only care to return the most recent Credential type for customers to use.
* Note: only supporting the most recent version in this sdk for now, 3.0.0
*/
List versions = getVersionList();
for (int i = 0; i < versions.size() - 1; i++) {
// note this should never be reached until the version list has more than one entry
}
// Grabbing the latest version as defined in the version list, 3.0.0
String latestVersion = versions.get(versions.size() - 1);
// Create latest version of the UnsignedCredential object
UnsignedCredential unsignedCredential = null;
try {
unsignedCredential = constructUnsignedCredential(options);
} catch (JsonProcessingException e) {
e.printStackTrace();
log.error(e.getMessage());
throw new UnumError(500, e.getMessage());
}
// Create the signed Credential object from the unsignedCredential object
Credential credential = constructSignedCredential(unsignedCredential, signingPrivateKey);
// Create the attributes for an encrypted credential. The authorization string is used to get the DID Document containing the subject's public key for encryption.
try {
List encryptedCredentials = constructEncryptedCredentials(credential, authToken);
IssueCredentialRequest request = IssueCredentialRequest.newBuilder()
.setIssuer(issuer)
.setCredentialId(credential.getId())
.setSubject(credential.getCredentialSubject())
.setType(getCredentialType(credential))
.addAllEncryptedCredentials(encryptedCredentials)
.build();
Call call = _unumService.issueCredential(authToken, request);
Response response = call.execute();
if (!response.isSuccessful()) {
String message = response.errorBody() != null
? response.errorBody().string() : "Unknown error";
log.error("Saas error creating credential" + message);
throw new UnumError(response.errorBody().hashCode(), message);
}
String newAuthToken = response.headers().get("X-Auth-Token");
Unum result = new Unum();
id.unum.types.dto.Credential body = convertCredential(credential);
result.setBody(body);
result.setAuthToken(handleAuthToken(newAuthToken));
return result;
} catch (IOException e) {
log.error("IOException issuing credentials: " + e.getMessage());
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
log.error(e.getLocalizedMessage(), e);
e.printStackTrace();
}
throw new UnumError(500, "Unknown error issuing credentials.");
}
@Override
public Unum updateCredentialStatus(String authToken, String credentialId, CredentialStatusOptions status) throws UnumError {
requireAuth(authToken);
// todo?
validateUpdateCredentialStatusInput(credentialId, status);
UpdateCredentialStatusRequest request = new UpdateCredentialStatusRequest();
request.setStatus(status.getValue());
Call call = _unumService.updateCredentialStatus(authToken, credentialId, request);
try {
Response response = call.execute();
if (!response.isSuccessful()) {
String message = response.errorBody() != null
? response.errorBody().string() : "Unknown error";
log.error("Saas error updating credential status" + message);
throw new UnumError(response.errorBody().hashCode(), message);
}
String newAuthToken = response.headers().get("X-Auth-Token");
Unum result = new Unum();
result.setBody(response.body());
result.setAuthToken(handleAuthToken(newAuthToken));
return result;
} catch (IOException e) {
log.error("IOException updating credential status: " + e);
e.printStackTrace();
}
throw new UnumError(500, "Unknown error updating credential status.");
}
private void validateUpdateCredentialStatusInput(String credentialId, CredentialStatusOptions status) {
}
private List constructEncryptedCredentials(Credential credential, String authToken) throws NoSuchAlgorithmException {
log.info("constructing encrypted credentials");
CredentialSubject credentialSubject = convertCredentialSubject(credential.getCredentialSubject());
String subjectDid = credentialSubject.getId();
// resolve the subject's DID
Unum didDocument = _didDocService.getDIDDoc(authToken, subjectDid);
// TODO need to pass back the new authToken...
List pubKeys = _didDocService.getKeysFromDIDDoc(didDocument.getBody(), "RSA");
if (pubKeys.isEmpty())
throw new UnumError(404, "Public key not found for the DID");
List result = new ArrayList<>();
for (PublicKeyInfo key : pubKeys) {
String subjectDidWithKeyFragment = subjectDid + "#" + key.getId();
// TODO for now mocked.
final EncryptedData encryptedData = CryptoUtils.encrypt(subjectDid, key, credential.toByteArray());
final EncryptedCredential encryptedCredential = EncryptedCredential.newBuilder()
.setCredentialId(credential.getId())
.setSubject(subjectDidWithKeyFragment)
.setData(encryptedData)
.setIssuer(credential.getIssuer())
.setType(getCredentialType(credential))
.build();
result.add(encryptedCredential);
}
return result;
}
private Credential constructSignedCredential(UnsignedCredential unsignedCredential, String privateKey) {
// convert protobuf to byte array
byte[] bytes = unsignedCredential.toByteArray();
// TODO. for now mocked
Proof proof = createProof(bytes, privateKey, unsignedCredential.getIssuer(), "SHA256withDSA");
Credential credential = Credential.newBuilder()
.addAllContext(unsignedCredential.getContextList())
.setCredentialStatus(unsignedCredential.getCredentialStatus())
.setCredentialSubject(unsignedCredential.getCredentialSubject())
.setIssuer(unsignedCredential.getIssuer())
.addAllType(unsignedCredential.getTypeList())
.setId(unsignedCredential.getId())
.setIssuanceDate(unsignedCredential.getIssuanceDate())
.setExpirationDate(unsignedCredential.getExpirationDate())
.setProof(proof)
.build();
return credential;
}
private UnsignedCredential constructUnsignedCredential(CredentialOptions options) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String credentialSubjectJson = mapper.writeValueAsString(options.getCredentialSubject());
String credentialId = UUID.randomUUID().toString();
options.getType().add(0, "VerifiableCredential");
CredentialStatus status = CredentialStatus.newBuilder()
.setType("CredentialStatus")
.setId(credentialId)
.build();
Instant nowPlusTenYears = Instant.now().plus(365 * 10, ChronoUnit.DAYS);
Timestamp tenYearsFromNow = Timestamp.newBuilder().setSeconds(nowPlusTenYears.getEpochSecond())
.setNanos(0).build(); // Note: to avoid JSON precision rounding inconsistencies
Timestamp expirationDate = options.getExpirationDate() == null ? tenYearsFromNow : fromMillis(options.getExpirationDate().getTime());
UnsignedCredential result = UnsignedCredential.newBuilder()
.addContext("https://www.w3.org/2018/credentials/v1")
.setCredentialStatus(status)
.setCredentialSubject(credentialSubjectJson)
.setIssuer(options.getIssuer())
.addAllType(options.getType())
.setId(credentialId)
.setIssuanceDate(Timestamp.newBuilder().setSeconds(Instant.now().getEpochSecond()).build())
.setExpirationDate(expirationDate)
.build();
return result;
}
private CredentialOptions constructCredentialOptions(List types, String issuer, ObjectNode credentialSubject, Date expirationDate) {
List filteredTypes = types.stream().filter(type ->
!Objects.equals(type, "VerifiableCredential")
).collect(Collectors.toList());
CredentialOptions options = CredentialOptions.builder()
.credentialSubject(credentialSubject)
.issuer(issuer)
.type(filteredTypes)
.expirationDate(expirationDate)
.build();
return options;
}
private void validateIssueCredentialInput(List types, String issuer, ObjectNode credentialSubject,
String signingPrivateKey, Date expirationDate) throws UnumError {
}
}