All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 {

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy