id.unum.service.VerifierService Maven / Gradle / Ivy
The newest version!
package id.unum.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.gson.JsonObject;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Timestamp;
import com.google.protobuf.util.JsonFormat;
import id.unum.crossPlatformInterfaces.Encoding;
import id.unum.dto.*;
import id.unum.enums.CredentialStatusOptions;
import id.unum.enums.PresentationReplyTypes;
import id.unum.enums.PresentationTypes;
import id.unum.error.UnumError;
import id.unum.facade.rest.Client;
import id.unum.facade.rest.UnumAPIService;
import id.unum.facade.rest.request.RegisterVerifierRequest;
import id.unum.facade.rest.request.SendMessageRequest;
import id.unum.protos.credential.v1.Credential;
import id.unum.protos.credential.v1.UnsignedCredential;
import id.unum.protos.crypto.v1.EncryptedData;
import id.unum.protos.crypto.v1.KeyPair;
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.externalMessage.v1.ExternalMessage;
import id.unum.protos.presentation.v1.Presentation;
import id.unum.protos.presentation.v1.UnsignedPresentation;
import id.unum.protos.presentationRequest.v1.PresentationRequest;
import id.unum.protos.presentationRequest.v1.UnsignedPresentationRequest;
import id.unum.protos.presentationRequestEnriched.v1.PresentationRequestEnriched;
import id.unum.protos.presentationRequestEnriched.v1.PresentationRequestRepoDto;
import id.unum.protos.proof.v1.Proof;
import id.unum.protos.receipt.v1.Receipt;
import id.unum.protos.verifier.v1.Verifier;
import id.unum.types.CredentialSubject;
import id.unum.types.VerifiedStatus;
import id.unum.types.dto.CredentialRequest;
import id.unum.types.dto.VersionInfo;
import lombok.extern.log4j.Log4j2;
import retrofit2.Call;
import retrofit2.Response;
import java.io.IOException;
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.DtoToProto.*;
import static id.unum.converter.ProtoToDto.*;
import static id.unum.utils.CryptoUtils.*;
import static id.unum.utils.Utils.*;
@Log4j2
public class VerifierService implements VerifierServiceInterface{
private UnumAPIService _unumService;
private DidDocServiceInterface _didDocService;
private ReceiptServiceInterface _receiptService;
public VerifierService(String url) {
Client client = new Client(url);
_unumService = client.getRetrofit().create(UnumAPIService.class);
_didDocService = new DidDocService(url);
_receiptService = new ReceiptService(url);
}
@Override
public Unum registerVerifier(String customerUuid, String url, String apiKey,
List versionInfoList) throws UnumError {
log.info("calling register verifier");
KeyPairSet keyPairSet = generateKeyPairSet(Encoding.PEM);
RegisterVerifierRequest request = new RegisterVerifierRequest();
request.setCustomerUuid(customerUuid);
request.setPublicKeyInfo(extractPublicKeyInfo(keyPairSet, Encoding.PEM));
request.setUrl(url);
request.setVersionInfo(versionInfoList);
Call call = _unumService.registerVerifier("Bearer " + apiKey, request);
try {
Response response = call.execute();
if (!response.isSuccessful()) {
String message = response.errorBody() != null
? response.errorBody().string() : "Unknown error";
log.error("Saas error calling register verifier " + message);
throw new UnumError(response.errorBody().hashCode(), message);
}
Verifier verifier = response.body();
String newAuthToken = response.headers().get("X-Auth-Token");
RegisteredVerifier body = convertVerifier(verifier);
Unum result = new Unum<>();
result.setBody(body);
result.setAuthToken(handleAuthToken(newAuthToken));
return result;
} catch (IOException e) {
log.error("IOException calling register verifier: " + e.toString());
e.printStackTrace();
}
throw new UnumError(500, "Unknown error registering verifier.");
}
@Override
public Unum sendRequest(String authToken, String verifier,
List credentialRequests,
String signingPrivateKey, String holderAppUuid,
Date expiresAt, JsonObject metadata) throws UnumError{
requireAuth(authToken);
// todo?
validateSendRequestInput(verifier, credentialRequests, signingPrivateKey, holderAppUuid);
String id = UUID.randomUUID().toString(); // an identifier to span request versions, however currently only supporting 3.0.0
/**
* 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);
try {
// Create the unsigned presentation request object from the unsignedPresentation object
UnsignedPresentationRequest unsignedPresentationRequest = constructUnsignedPresentationRequest(verifier,
credentialRequests, holderAppUuid, expiresAt, metadata, id, latestVersion);
// Create the signed presentation request object from the unsignedPresentation object
PresentationRequest signedPresentationRequest =
constructSignedPresentationRequest(unsignedPresentationRequest, signingPrivateKey);
// PresentationRequestCreateRequest request =
// convertPresentationRequestToCreateRequest(signedPresentationRequest);
//
// PresentationRequestCreateRequest request = new PresentationRequestCreateRequest();
// request.setCredentialRequests();
// request.setVerifier(signedPresentationRequest.getVerifier());
Call call = _unumService.sendRequest(authToken, signedPresentationRequest);
// try {
Response response = call.execute();
if (!response.isSuccessful()) {
String message = response.errorBody() != null
? response.errorBody().string() : "Unknown error";
log.error("Saas error creating presentation request" + message);
throw new UnumError(response.errorBody().hashCode(), message);
}
String newAuthToken = response.headers().get("X-Auth-Token");
Unum result = new Unum<>();
result.setBody(convertPresentationRequestEnriched(response.body()));
result.setAuthToken(handleAuthToken(newAuthToken));
return result;
// }
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
} catch (JsonProcessingException e) {
e.printStackTrace();
}
catch (IOException e) {
log.error("IOException creating presentation request: " + e.toString());
e.printStackTrace();
}
throw new UnumError(500, "Unknown error creating presentation request.");
}
private PresentationRequest constructSignedPresentationRequest(UnsignedPresentationRequest unsignedPresentationRequest, String signingPrivateKey) {
// convert protobuf to byte array
byte[] bytes = unsignedPresentationRequest.toByteArray();
Proof proof = createProof(bytes, signingPrivateKey, unsignedPresentationRequest.getVerifier(), "SHA256withECDSA");
PresentationRequest result = PresentationRequest.newBuilder()
.setCreatedAt(unsignedPresentationRequest.getCreatedAt())
.setUpdatedAt(unsignedPresentationRequest.getUpdatedAt())
.setExpiresAt(unsignedPresentationRequest.getExpiresAt())
.addAllCredentialRequests(unsignedPresentationRequest.getCredentialRequestsList())
.setId(unsignedPresentationRequest.getId())
.setUuid(unsignedPresentationRequest.getUuid())
.setVerifier(unsignedPresentationRequest.getVerifier())
.setHolderAppUuid(unsignedPresentationRequest.getHolderAppUuid())
.setMetadata(unsignedPresentationRequest.getMetadata())
.setVersion(unsignedPresentationRequest.getVersion())
.setProof(proof)
.build();
return result;
}
private UnsignedPresentationRequest constructUnsignedPresentationRequest(String verifier,
List credentialRequests, String holderAppUuid, Date expiresAt, JsonObject metadata, String id, String version) throws InvalidProtocolBufferException, JsonProcessingException {
String uuid = UUID.randomUUID().toString();
Timestamp now = Timestamp.newBuilder().build();
// Instant time = Instant.now();
// Timestamp tenMinutesFromNow = Timestamp.newBuilder().setSeconds(time.getEpochSecond() + 60 * 10)
// .setNanos(0).build(); // Note: to avoid JSON precision rounding inconsistencies
Instant nowPlusTenMinutes = Instant.now().plus(10, ChronoUnit.MINUTES);
Timestamp tenMinutesFromNow = Timestamp.newBuilder().setSeconds(nowPlusTenMinutes.getEpochSecond())
.setNanos(0).build(); // Note: to avoid JSON precision rounding inconsistencies
Timestamp end = expiresAt == null ? tenMinutesFromNow : fromMillis(expiresAt.getTime());
UnsignedPresentationRequest result = UnsignedPresentationRequest.newBuilder()
.setCreatedAt(now)
.setUpdatedAt(now)
.setExpiresAt(end)
.setVerifier(verifier)
.setUuid(uuid)
.setId(id)
.setVersion(version)
.addAllCredentialRequests(convertCredentialsToProtos(credentialRequests))
.setHolderAppUuid(holderAppUuid)
.setMetadata(metadata != null ? metadata.toString() : "{}")
.build();
return result;
}
@Override
public Unum verifyPresentation(String authToken,
final id.unum.types.EncryptedData encryptedPresentationDto,
final String verifierDid, final String encryptionPrivateKey,
final id.unum.types.dto.PresentationRequestEnriched presentationRequestEnriched) throws UnumError {
requireAuth(authToken);
try {
EncryptedData encryptedPresentation = convertEncryptedDataToProto(encryptedPresentationDto);
// creating a KeyPair to appease the cryptolib
KeyPair keyPair = KeyPair.newBuilder().setPrivateKey(encryptionPrivateKey).build();
validateVerifyPresentationInput(encryptedPresentation, verifierDid, keyPair, presentationRequestEnriched);
log.info("Decrypting presentation");
// decrypt the presentation
byte[] presentationBytes = decrypt(keyPair, encryptedPresentation);
Presentation presentation = Presentation.parseFrom(presentationBytes);
// todo check env var, if debug print the decrypted presentation
validatePresentation(presentation);
// verify the presentation request uuid match
log.info("Validating presentation request");
if (presentationRequestEnriched != null && presentationRequestEnriched.getPresentationRequest().getId() != presentation.getPresentationRequestId()) {
throw new UnumError(400,
"Presentation request id provided, " + presentationRequestEnriched.getPresentationRequest().getId() + ", does not match the presentationRequestId that the presentation was in response to. " + presentation.getPresentationRequestId() + ".");
}
// verify the presentation request signature if present
// PresentationRequest presentationRequest = PresentationRequest.newBuilder().build();
PresentationRequest presentationRequest = null;
if (presentationRequestEnriched != null){
// validate the provided presentation request
presentationRequest =
validatePresentationRequest(presentationRequestEnriched.getPresentationRequest());
} else if (!presentation.getPresentationRequestId().isEmpty()) {
// grab the request from the saas
log.info("Getting presentation request from saas as one wasn't provided: " + presentation.getPresentationRequestId());
Unum presentationRequestRepoDto = getPresentationRequest(authToken, presentation.getPresentationRequestId());
authToken = presentationRequestRepoDto.getAuthToken();
presentationRequest = extractPresentationRequest(presentationRequestRepoDto.getBody());
}
if (presentationRequest != null) {
log.info("Verifying presentation request");
Unum requestVerificationResult = verifyPresentationRequest(authToken, presentationRequest);
authToken = requestVerificationResult.getAuthToken();
// if invalid then can stop here but still send back the decrypted presentation with the verification results
if (!requestVerificationResult.getBody().isVerified()) {
log.info("Presentation request deemed invalid: " + requestVerificationResult.getBody().getMessage());
PresentationTypes type = isDeclinedPresentation(presentation) ? PresentationTypes.DeclinedPresentation :
PresentationTypes.VerifiablePresentation;
DecryptedPresentation body = new DecryptedPresentation();
body.setPresentation(convertPresentation(presentation));
body.setType(type);
body.setVerified(false);
body.setMessage(requestVerificationResult.getBody().getMessage());
Unum result = new Unum<>();
result.setAuthToken(handleAuthToken(authToken));
result.setBody(body);
return result;
}
}
if (isDeclinedPresentation(presentation)) {
log.info("Handling declined presentation");
Unum verificationResult = verifyDeclinedPresentationHelper(authToken, presentation, verifierDid, presentationRequest.getUuid());
log.info("Declined presentation deemed valid " + verificationResult.getBody().isVerified() + " " + verificationResult.getBody().getMessage());
DecryptedPresentation body = new DecryptedPresentation();
body.setPresentation(convertPresentation(presentation));
body.setType(PresentationTypes.DeclinedPresentation);
body.setVerified(verificationResult.getBody().isVerified()); // if here ought to always be true
body.setMessage(verificationResult.getBody().getMessage());
Unum result = new Unum<>();
result.setAuthToken(handleAuthToken(verificationResult.getAuthToken()));
result.setBody(body);
return result;
}
List credentialRequests = presentationRequest.getCredentialRequestsList();
Unum verificationResult = verifyPresentationHelper(authToken, presentation, verifierDid, credentialRequests, presentationRequest.getUuid());
log.info("Approved presentation deemed valid " + verificationResult.getBody().isVerified() + (verificationResult.getBody().isVerified() ? "" : " " + verificationResult.getBody().getMessage()));
DecryptedPresentation body = new DecryptedPresentation();
body.setPresentation(convertPresentation(presentation));
body.setType(PresentationTypes.VerifiablePresentation);
body.setVerified(verificationResult.getBody().isVerified());
body.setMessage(verificationResult.getBody().getMessage());
Unum result = new Unum<>();
result.setAuthToken(handleAuthToken(verificationResult.getAuthToken()));
result.setBody(body);
return result;
} catch (InvalidProtocolBufferException e) {
log.error("Protobuff Error decoding byte array to Presentation object: " + e.getMessage());
e.printStackTrace();
throw new UnumError(500, "Error decoding byte array to Presentation object: " + e.getMessage());
} catch (JsonProcessingException e) {
e.printStackTrace();
log.error("Json Error decoding byte array to Presentation object: " + e.getMessage());
throw new UnumError(500, "Error decoding byte array to Presentation object: " + e.getMessage());
}
}
/**
* Helper to extract the presentationRequest from the PresentationRequestRepo's response, which is a map keyed on version.
* @param presentationRequestRepoDto
* @return
*/
private PresentationRequest extractPresentationRequest(PresentationRequestRepoDto presentationRequestRepoDto) {
try {
PresentationRequestEnriched presentationRequestEnriched = presentationRequestRepoDto.getPresentationRequestsMap().get("3.0.0");
return presentationRequestEnriched.getPresentationRequest();
} catch (Exception e) {
throw new UnumError(500, "Error handling presentation request from Saas: Error " + e);
}
}
private Unum verifyPresentationHelper(String authToken, Presentation presentation, String verifierDid, List credentialRequests, String requestUuid) throws InvalidProtocolBufferException {
log.info("Verifying approved presentation");
requireAuth(authToken);
validateVerifiablePresentation(presentation);
// validate that the presentation as verifiable credentials
if (presentation.getVerifiableCredentialCount() == 0) {
log.error("The presentation has undefined verifiableCredential attribute, this should have already be checked.");
throw new UnumError(500, "presentation as an undefined verifiableCredentials");
}
// if specific credential requests, then need to confirm the presentation provided meets the requirements
if (!credentialRequests.isEmpty()) {
validatePresentationMeetsRequestedCredentials(presentation, credentialRequests);
}
// remove the 'VerifiableCredential' type string in each array
List credentialTypesFiltered = new ArrayList<>();
for (Credential credential : presentation.getVerifiableCredentialList()) {
List credentialTypes =
credential.getTypeList().stream().filter(type -> !type.equals("VerifiedCredential")).collect(Collectors.toList());
credentialTypesFiltered.addAll(credentialTypes);
}
// get all credential issuers
List issuers =
presentation.getVerifiableCredentialList().stream().map(Credential::getIssuer).collect(Collectors.toList());
Unum presentationResult = verifyPresentationProof(authToken, presentation, verifierDid);
if (!presentationResult.getBody().isVerified()) {
String message = presentationResult.getBody().getMessage();
try {
Unum receipt = _receiptService.sendPresentationVerifiedReceipt(authToken, verifierDid, presentation.getProof().getVerificationMethod(), PresentationReplyTypes.approved.toString(), false, presentation.getPresentationRequestId(), requestUuid, message, issuers, credentialTypesFiltered);
authToken = receipt.getAuthToken();
} catch (Exception e) {
log.error("Error creating presentation verified receipt: " + e.getMessage());
}
return presentationResult;
}
// Need to verify the credentials in the presentation are valid
boolean areCredentialsValid = true;
String credentialInvalidMessage = "";
for (Credential credential : presentation.getVerifiableCredentialList()) {
boolean isExpired = isCredentialExpired(credential);
if (isExpired) {
areCredentialsValid = false;
credentialInvalidMessage = "Credential " + credential.getType(1) + " " + credential.getId() + " is expired.";
break;
}
Unum credentialStatusInfo = checkCredentialStatus(authToken, credential.getId());
authToken = credentialStatusInfo.getAuthToken();
boolean isStatusValid = credentialStatusInfo.getBody().getStatus() == CredentialStatusOptions.valid;
if (!isStatusValid) {
areCredentialsValid = false;
credentialInvalidMessage = "Credential " + credential.getType(1) + " " + credential.getId() + " status is invalid.";
break;
}
Unum isVerifiedResponse = verifyCredential(authToken, credential);
authToken = isVerifiedResponse.getAuthToken();
if (!isVerifiedResponse.getBody()) {
areCredentialsValid = false;
credentialInvalidMessage = "Credential " + credential.getType(1) + " " + credential.getId() + " signature can not be verified.";
break;
}
}
if (!areCredentialsValid) {
try {
Unum receipt = _receiptService.sendPresentationVerifiedReceipt(authToken, verifierDid, presentation.getProof().getVerificationMethod(), PresentationReplyTypes.approved.toString(), false, presentation.getPresentationRequestId(), requestUuid, credentialInvalidMessage, issuers, credentialTypesFiltered);
authToken = receipt.getAuthToken();
} catch (Exception e) {
log.error("Error creating presentation verified receipt: " + e.getMessage());
}
VerifiedStatus status = VerifiedStatus.builder()
.isVerified(false)
.message(credentialInvalidMessage)
.build();
Unum result = new Unum<>();
result.setAuthToken(handleAuthToken(authToken));
result.setBody(status);
return result;
}
try {
Unum receipt = _receiptService.sendPresentationVerifiedReceipt(authToken, verifierDid, presentation.getProof().getVerificationMethod(), PresentationReplyTypes.approved.toString(), true, presentation.getPresentationRequestId(), requestUuid, "", issuers, credentialTypesFiltered);
authToken = receipt.getAuthToken();
} catch (Exception e) {
log.error("Error creating presentation verified receipt: " + e.getMessage());
}
presentationResult.setAuthToken(handleAuthToken(authToken));
return presentationResult;
}
/**
* Validates the presentation object has the proper attributes.
* @param presentation
*/
private void validateVerifiablePresentation(Presentation presentation) {
if (presentation.getVerifiableCredentialCount() == 0) {
throw new UnumError(400, "Invalid Presentation: verifiableCredential must be a non-empty array.");
}
if (presentation.getContextCount() == 0) {
throw new UnumError(400, "Invalid Presentation: context must be a non-empty array.");
}
if (presentation.getTypeCount() == 0) {
throw new UnumError(400, "Invalid Presentation: type must be a non-empty array.");
}
if (!presentation.getType(0).equals("VerifiablePresentation")) {
throw new UnumError(400, "Invalid Presentation: type's first array element must be " +
"'VerifiablePresentation'");
}
validateCredentialInput(presentation.getVerifiableCredentialList());
}
/**
* Validates the attributes for a credential from UnumId's Saas
* @param verifiableCredentialList
*/
private void validateCredentialInput(List verifiableCredentialList) {
for (int i = 0; i < verifiableCredentialList.size(); i++) {
String credentialPositionString = "[" + i + "]";
Credential credential = verifiableCredentialList.get(i);
// Validate the existence of elements in Credential object
String invalidMessage = "Invalid verifiableCredential" + credentialPositionString + ":";
if (credential.getContextCount() == 0) {
throw new UnumError(400, invalidMessage + " context must be a non-empty array");
}
CredentialSubject credentialSubject = convertCredentialSubject(credential.getCredentialSubject());
if (credentialSubject.getId() == null) {
throw new UnumError(400, invalidMessage + " credentialSubject must contain id property.");
}
if (credential.getTypeCount() == 0) {
throw new UnumError(400, invalidMessage + " type must be a non-empty array.");
}
// validateProof(credential.getProof());
}
}
/**
* Used to verify the credential signature given the corresponding Did document's public key.
* @param authToken
* @param credential
* @return
*/
protected Unum verifyCredential(String authToken, final Credential credential) throws InvalidProtocolBufferException {
Proof proof = credential.getProof();
Unum didDocument = _didDocService.getDIDDoc(authToken, proof.getVerificationMethod());
authToken = didDocument.getAuthToken();
id.unum.protos.crypto.v1.PublicKeyInfo publicKey = _didDocService.getKeyFromDIDDoc(didDocument.getBody(), "secp256r1");
// Create an UnsignedCredential object
Credential credentialNoProof = credential.toBuilder().clearProof().build();
String json = JsonFormat.printer().omittingInsignificantWhitespace().print(credentialNoProof);
UnsignedCredential.Builder unsignedCredentialBuilder =
UnsignedCredential.newBuilder();
JsonFormat.parser().merge(json, unsignedCredentialBuilder);
UnsignedCredential unsignedCredential = unsignedCredentialBuilder.build();
// convert to bytes
byte[] bytes = unsignedCredential.toByteArray();
// verify the byte array
boolean isVerified = doVerify(proof.getSignatureValue(), bytes, publicKey, publicKey.getEncoding());
Unum result = new Unum<>();
result.setAuthToken(handleAuthToken(authToken));
result.setBody(isVerified);
return result;
}
/**
* Validates that:
* a. all requested credentials types are present
* b. credentials are only from list of required issuers, if the list is present
* @param presentation
* @param credentialRequests
*/
private void validatePresentationMeetsRequestedCredentials(Presentation presentation, List credentialRequests) {
if (presentation.getVerifiableCredentialCount() == 0) {
return; // just skip because this is a declined presentation
}
for (id.unum.protos.credential.v1.CredentialRequest requestedCred : credentialRequests) {
if (requestedCred.getRequired()) {
// check that the request credential is present in the presentation
List presentationCreds = presentation.getVerifiableCredentialList();
boolean found = false;
for (Credential presentationCred : presentationCreds) {
// checking required credential types are present
found = presentationCred.getTypeList().contains(requestedCred.getType());
if (found) {
// checking the required issuers are present
if (requestedCred.getIssuersCount() > 0 && !requestedCred.getIssuersList().contains(presentationCred.getIssuer())) {
String errorMessage = "Invalid Presentation: credentials provided did not meet the issuer" +
" requirements. Issuer requested: " + requestedCred.getIssuersList() + ". Issuer " +
"of the credential received " + presentationCred.getIssuer();
log.warn(errorMessage);
throw new UnumError(400, errorMessage);
}
// can break from inner loop because validation has been met.
break;
}
}
if (!found) {
String errorMessage = "Invalid Presentation: credentials provided did not meet type requirements." +
" Presented credentials: " + presentation.getVerifiableCredentialList().stream().map(pc -> pc.getTypeList().stream().filter(t -> !t.equals("VerifiableCredential"))).collect(Collectors.toList()) +
". Requested credentials: " + credentialRequests.stream().map(id.unum.protos.credential.v1.CredentialRequest::getType).collect(Collectors.toList()) + ".";
log.warn(errorMessage);
throw new UnumError(400, errorMessage);
}
}
}
}
/**
* Handler for when a user does not agree to share the information in the credential request.
* @param authToken
* @param presentation
* @param verifierDid
* @return
*/
private Unum verifyDeclinedPresentationHelper(String authToken, final Presentation presentation, final String verifierDid, final String requestUuid) throws InvalidProtocolBufferException {
log.info("Verifying a declined presentation");
requireAuth(authToken);
validateDeclinedPresentationInput(presentation);
String presentationVerifierDid = presentation.getVerifierDid();
Proof proof = presentation.getProof();
String verificationMethod = proof.getVerificationMethod();
Unum result = verifyPresentationProof(authToken, presentation, verifierDid);
try {
// Create receipt for Saas
Unum receipt = _receiptService.sendPresentationVerifiedReceipt(authToken, presentationVerifierDid, verificationMethod, PresentationReplyTypes.declined.toString(), result.getBody().isVerified(), presentation.getPresentationRequestId(), requestUuid, result.getBody().getMessage(), Collections.emptyList(), Collections.emptyList());
authToken = receipt.getAuthToken();
result.setAuthToken(authToken);
} catch (Exception e) {
log.error("Error creating presentation verified receipt: " + e.getMessage());
}
return result;
}
private Unum verifyPresentationProof(String authToken, final Presentation presentation,
final String verifierDid) throws InvalidProtocolBufferException {
String presentationVerifierDid = presentation.getVerifierDid();
Proof proof = presentation.getProof();
String verificationMethod = proof.getVerificationMethod();
String signatureValue = proof.getSignatureValue();
// validate that the verifier did provided matches the verifier did in the presentation
if (!presentationVerifierDid.equals(verifierDid)) {
VerifiedStatus status = VerifiedStatus.builder()
.isVerified(false)
.message("The presentation was meant for verifier, " + presentationVerifierDid + ", not the " +
"provided verifier, " + verifierDid + ".")
.build();
Unum result = new Unum<>();
result.setAuthToken(handleAuthToken(authToken));
result.setBody(status);
return result;
}
PublicKeyInfo keyInfo = null;
try {
log.info("Grabbing subject's DID doc to retrieve the public key from presentation proof verification: " + verificationMethod);
if (verificationMethod.length() == 82 && verificationMethod.contains("#")) {
// did doc has key id, i.e. did:unum:00e1691b-437e-4096-93d8-0ca523d7d2c9#5e6c4504-15ff-4ced-85a9-baae32d662f7
log.info("Did doc has key id");
Unum publicKeyInfoUnum = _didDocService.getDidDocPublicKeyInfoByKeyId(authToken, verificationMethod);
authToken = publicKeyInfoUnum.getAuthToken();
keyInfo = publicKeyInfoUnum.getBody();
} else {
Unum didDocument = _didDocService.getDIDDoc(authToken, verificationMethod);
authToken = didDocument.getAuthToken();
keyInfo = _didDocService.getKeyFromDIDDoc(didDocument.getBody(), "secp256r1");
}
} catch (UnumError error) {
Unum result = new Unum<>();
result.setAuthToken(handleAuthToken(authToken));
result.setBody(VerifiedStatus.builder()
.isVerified(false)
.message("Public key not found for the DID associated with the proof.verificationMethod")
.build());
return result;
}
String encoding = keyInfo.getEncoding();
// Create an UnsignedPresentation object
Presentation presentationNoProof = presentation.toBuilder().clearProof().build();
String json = JsonFormat.printer().omittingInsignificantWhitespace().print(presentationNoProof);
UnsignedPresentation.Builder unsignedPresentationBuilder =
UnsignedPresentation.newBuilder();
JsonFormat.parser().merge(json, unsignedPresentationBuilder);
UnsignedPresentation unsignedPresentation = unsignedPresentationBuilder.build();
// convert to bytes
byte[] bytes = unsignedPresentation.toByteArray();
log.info("Verifying approved presentation with signature " + signatureValue + " with publicKey " + keyInfo.getPublicKey());
// verify the byte array
boolean isVerified = doVerify(signatureValue, bytes, keyInfo, encoding);
if (!isVerified) {
VerifiedStatus status = VerifiedStatus.builder()
.isVerified(false)
.message("Presentation signature can not be verified.")
.build();
Unum result = new Unum<>();
result.setAuthToken(handleAuthToken(authToken));
result.setBody(status);
return result;
}
VerifiedStatus status = VerifiedStatus.builder()
.isVerified(true)
.build();
Unum result = new Unum<>();
result.setAuthToken(handleAuthToken(authToken));
result.setBody(status);
return result;
}
private void validateDeclinedPresentationInput(Presentation presentation) {
if (presentation.getTypeCount() == 0) {
throw new UnumError(400, "Invalid Presentation: type must be a non-empty array.");
}
if (presentation.getVerifiableCredentialCount() != 0) {
throw new UnumError(400, "Invalid Declined Presentation: verifiableCredential must be null or empty.");
}
// validateProof(presentation.getProof());
}
/**
* Verify the PresentationRequest signature as a way to side step verifier MITM attacks where an entity spoofs requests.
* @param authToken
* @param presentationRequest
* @return
* @throws InvalidProtocolBufferException
*/
private Unum verifyPresentationRequest(String authToken, PresentationRequest presentationRequest) throws InvalidProtocolBufferException {
if (presentationRequest == null) {
throw new UnumError(500, "PresentationRequest is null in verify call. This should never happen");
}
Proof proof = presentationRequest.getProof();
String verificationMethod = proof.getVerificationMethod();
String signatureValue = proof.getSignatureValue();
Unum didDocument = _didDocService.getDIDDoc(authToken, verificationMethod);
authToken = didDocument.getAuthToken();
PublicKeyInfo keyInfo = _didDocService.getKeyFromDIDDoc(didDocument.getBody(), "secp256r1");
String publicKey = keyInfo.getPublicKey();
String encoding = keyInfo.getEncoding();
// Create an UnsignedPresentationRequest object
PresentationRequest presentationRequestNoProof = presentationRequest.toBuilder().clearProof().build();
String json = JsonFormat.printer().omittingInsignificantWhitespace().print(presentationRequestNoProof);
UnsignedPresentationRequest.Builder unsignedPresentationRequestBuilder =
UnsignedPresentationRequest.newBuilder();
JsonFormat.parser().merge(json, unsignedPresentationRequestBuilder);
UnsignedPresentationRequest unsignedPresentationRequest = unsignedPresentationRequestBuilder.build();
// convert to bytes
byte[] bytes = unsignedPresentationRequest.toByteArray();
// verify the byte array
boolean isVerified = doVerify(signatureValue, bytes, keyInfo, encoding);
if (!isVerified) {
VerifiedStatus status = VerifiedStatus.builder()
.isVerified(false)
.message("PresentationRequest signature can not be verified.")
.build();
Unum result = new Unum<>();
result.setAuthToken(handleAuthToken(authToken));
result.setBody(status);
return result;
}
VerifiedStatus status = VerifiedStatus.builder()
.isVerified(true)
.build();
Unum result = new Unum<>();
result.setAuthToken(handleAuthToken(authToken));
result.setBody(status);
return result;
}
protected Unum getPresentationRequest(String authToken, String presentationRequestId) {
Call call = _unumService.getPresentationRequest(authToken, presentationRequestId);
try {
Response response = call.execute();
if (!response.isSuccessful()) {
String message = response.errorBody() != null
? response.errorBody().string() : "Unknown error";
log.error("Saas error calling get presentationRequestRepo " + message);
throw new UnumError(response.errorBody().hashCode(), message);
}
PresentationRequestRepoDto dto = response.body();
if (dto == null) {
throw new UnumError(404, "presentationRequestId " + presentationRequestId + " not found");
}
String newAuthToken = response.headers().get("X-Auth-Token");
Unum result = new Unum<>();
result.setAuthToken(handleAuthToken(newAuthToken));
result.setBody(dto);
return result;
} catch (IOException e) {
log.error("IOException calling get presentationRequestRepoDto: " + e.toString());
e.printStackTrace();
}
throw new UnumError(500, "Unknown error getting presentationRequests via SaaS repo.");
}
/**
* Validates the presentation request object has the proper attributes.
* @param presentationRequest
* @return
* @throws InvalidProtocolBufferException
* @throws JsonProcessingException
*/
private PresentationRequest validatePresentationRequest(id.unum.types.dto.PresentationRequest presentationRequest) throws InvalidProtocolBufferException, JsonProcessingException {
if (presentationRequest.getCredentialRequests() == null) {
throw new UnumError(400, "Invalid PresentationRequest: credentialRequests is required.");
}
if (presentationRequest.getHolderAppUuid() == null) {
throw new UnumError(400, "Invalid PresentationRequest: holderAppUuid is required.");
}
if (presentationRequest.getProof() == null) {
throw new UnumError(400, "Invalid PresentationRequest: proof is required.");
}
if (presentationRequest.getVerifier() == null) {
throw new UnumError(400, "Invalid PresentationRequest: verifier is required.");
}
validateCredentialRequests(presentationRequest.getCredentialRequests());
return convertPresentationRequestToProto(presentationRequest);
}
/**
* Validates the attributes for a credential request to UnumID's SaaS.
* @param credentialRequests
*/
private void validateCredentialRequests(List credentialRequests) {
if (credentialRequests.size() == 0) {
throw new UnumError(400, "Invalid PresentationRequest: credentialRequests must be a non-empty array..");
}
for (int i = 0; i < credentialRequests.size(); i++) {
String credentialPositionString = "[" + i + "]";
CredentialRequest request = credentialRequests.get(i);
if (request.getType() == null) {
throw new UnumError(400,
"Invalid PresentationRequest CredentialRequest " + credentialPositionString + ": type must be" +
" defined.");
}
if (request.getIssuers() == null) {
throw new UnumError(400,
"Invalid PresentationRequest CredentialRequest " + credentialPositionString + ": issuers must" +
" be defined.");
}
}
}
/**
* Validates the presentation object has the proper attributes.
* @param presentation
*/
private void validatePresentation(Presentation presentation) {
log.info("Validating presentation");
if (presentation.getContextCount() == 0) {
throw new UnumError(400, "Invalid Presentation: context must be a non-empty array.");
}
if (presentation.getTypeCount() == 0) {
throw new UnumError(400, "Invalid Presentation: type must be a non-empty array.");
}
}
private void validateVerifyPresentationInput(EncryptedData encryptedPresentation, String verifierDid,
KeyPair keyPair,
id.unum.types.dto.PresentationRequestEnriched presentationRequest) {
if (encryptedPresentation == null) {
throw new UnumError(400, "encryptedPresentation is required.");
}
if (verifierDid == null) {
throw new UnumError(400, "verifier is required.");
}
if (keyPair == null) {
throw new UnumError(400, "verifier encryptionPrivateKey is required.");
}
if (presentationRequest != null && !presentationRequest.getVerifier().getDid().equals(verifierDid)) {
throw new UnumError(400,
"verifier provided " + verifierDid + " does not match request verifier " + presentationRequest.getVerifier().getDid() + ".");
}
}
@Override
public Unum sendSms(String authToken, String to, String deeplink) throws UnumError {
log.info("calling send sms");
requireAuth(authToken);
// SendMessageRequest request = new SendMessageRequest(to, deeplink);
// SendMessageRequest request = new SendMessageRequest();
// request.setTo(to);
// request.setDeeplink(deeplink);
ExternalMessage request = ExternalMessage.newBuilder()
.setDeeplink(deeplink)
.setTo(to)
.build();
Call call = _unumService.sendSms(authToken, request);
try {
Response response = call.execute();
if (!response.isSuccessful()) {
String message = response.errorBody() != null
? response.errorBody().string() : "Unknown error";
log.error("Saas error sending sms" + 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 sending sms: " + e.toString());
e.printStackTrace();
}
throw new UnumError(500, "Unknown error sending sms.");
}
@Override
public Unum sendEmail(String authToken, String to, String deeplink) throws UnumError {
log.info("calling send email");
requireAuth(authToken);
// SendMessageRequest request = new SendMessageRequest(to, deeplink);
SendMessageRequest request = new SendMessageRequest();
request.setTo(to);
request.setDeeplink(deeplink);
Call call = _unumService.sendEmail(authToken, request);
try {
Response response = call.execute();
if (!response.isSuccessful()) {
String message = response.errorBody() != null
? response.errorBody().string() : "Unknown error";
log.error("Saas error sending email" + 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 sending email: " + e.toString());
e.printStackTrace();
}
throw new UnumError(500, "Unknown error sending sms.");
}
@Override
public Unum checkCredentialStatus(String authToken, String credentialId) throws UnumError {
log.info("calling check credential status");
requireAuth(authToken);
Call call = _unumService.checkCredentialStatus(authToken, credentialId);
try {
Response response = call.execute();
if (!response.isSuccessful()) {
String message = response.errorBody() != null
? response.errorBody().string() : "Unknown error";
log.error("Saas error calling check credential status " + message);
throw new UnumError(response.errorBody().hashCode(), message);
}
String newAuthToken = response.headers().get("X-Auth-Token");
Unum result = new Unum();
result.setBody(convertCredentialStatusInfo(response.body()));
result.setAuthToken(handleAuthToken(newAuthToken));
return result;
} catch (IOException e) {
log.error("IOException calling check credential status: " + e.toString());
e.printStackTrace();
}
throw new UnumError(500, "Unknown error checking credential status.");
}
private void validateSendRequestInput(String verifier, List credentialRequests,
String signingPrivateKey, String holderAppUuid) throws UnumError{
}
}