Migration from Java API v3 to v5.0

Introduction

In this guide, you will learn how to migrate an existing project that uses the Cryptshare Java API version 3 to the latest version of the Cryptshare Java API. This guide is a technical guide that lists the changes for people who are familiar with using the previous Java API version. For general information about the changes introduced in version 5, see the update notes.

Preparation

Installing the new Java API Version

For information on installing the new Java API version into your development system, click here for more information.

Migrating existing projects

Replace dependency in the dependency management system

The GroupID and the ArtifactID of the Java API have changed. First, please replace the dependency entry in your dependency management, e.g. in the pom.xml as follows:

Old
<dependency>
 <groupId>com.befinesolutions.cryptshare</groupId>
 <artifactId>java-api-ng</artifactId>
 <version>3.X.X</version>
</dependency>
New
<dependency>
   <groupId>com.cryptshare.api</groupId>
   <artifactId>cryptshare-api</artifactId>
   <version>5.0.1.190</version>
</dependency>

Incompatibility with existing client.store

The structure of the client.store file has been fundamentally revised. Thus, it is unfortunately not possible to continue using the existing client.store together with its verifications. Before you test your project with the new version of the Java API, delete the existing client.store file first.

com.cryptshare.api.ClientException: Can't read from the client store!
   at com.cryptshare.api.Client.openStore(Client.java:810)
   at com.cryptshare.api.Client.<init>(Client.java:126)
   at com.cryptshare.api.Client.<init>(Client.java:86)
   at com.befinesolutions.examples.api.ExampleMain.connectWithStore(ExampleMain.java:351)
   at com.befinesolutions.examples.api.ExampleMain.main(ExampleMain.java:48)
Caused by: com.cryptshare.api.ClientException: Unknown error
   at com.cryptshare.api.DpApiJNAWrapper.unprotect(DpApiJNAWrapper.java:57)
   at com.cryptshare.api.DpApiProtectionService.unprotect(DpApiProtectionService.java:74)
   at com.cryptshare.api.ProtectedFileStore.load(ProtectedFileStore.java:130)
   at com.cryptshare.api.ProtectedFileStore.open(ProtectedFileStore.java:108)
   at com.cryptshare.api.ProtectedFileStore.open(ProtectedFileStore.java:83)
   at com.cryptshare.api.Client.openStore(Client.java:808)
   ... 4 more
Caused by: com.sun.jna.platform.win32.Win32Exception: Wrong parameter.
   at com.sun.jna.platform.win32.Crypt32Util.cryptUnprotectData(Crypt32Util.java:144)
   at com.cryptshare.api.DpApiJNAWrapper.unprotect(DpApiJNAWrapper.java:55)
   ... 9 more

Changed package name

The package name of the used classes has changed. Next, in the import statements of your classes, please replace the package name:

Old
com.befinesolutions.cryptshare.aping
New
com.cryptshare.api

Changes in exception handling

To better identify the source of possible problems when using the API, CryptshareServerExceptions are now thrown instead of ClientExceptions in some cases. A ClientException is usually thrown in cases when an operation failed during preparation. CryptshareServerExceptions occur when the operation itself has failed on the Cryptshare server, when incorrect information has been detected, or processing or other errors have occurred.

Changes in the verification process

Initializing a Client

When creating a Client object, a string containing the path to the client.store file was previously required. Instead, a Path object containing the path to the client.store is now required, e.g. by Paths.get(stringPath).

Using a byte array as client store

In case a byte array was used instead of a client.store file, a custom implementation of the new interface IStore must now be used. You can find more information in the guide Provide your own Client Store.

Example implementation

Java API 3
// Get client store from your custom store. byte++[]++ clientStoreBytes
= // TODO: Load bytes from your custom store


CryptshareConnection connection = new CryptshareConnection(new WebServiceUri(SERVER_URL))
Client client = new Client("sender@domain.com", connection, clientStoreBytes);

// do something like verifications, transfers, ...

// Write encrypted modified client store back into the custom store.
byte[] modifiedClientStoreBytes = client.getStore().save();
// TODO: Save modifiedClientStoreBytes to your custom store

The possibility to specify a byte array when creating a client object has been removed. Instead, an implementation of the IStore interface is required. In the following example a minimal implementation has been implemented. Please note the places marked with TODO. These places indicate places where you would need to check if your operating system used is the same as the example or where you need to implement your own load and store methods that you have already used so far in your program using the Cryptshare Java API v3.

Java API 5 CustomStore
import com.cryptshare.api.++*++; import
org.apache.commons.codec.DecoderException; import
org.apache.commons.codec.binary.Hex;


import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * Implementation of {@link IStore} that persists to a custom source, and encrypts the store.
 */
public class CustomProtectedStore implements IStore {

   private final Map<String, String> storeValues;
   private final IProtectionService protectionService;

   private CustomProtectedStore(IProtectionService protectionService) {
       this.protectionService = protectionService;
       this.storeValues = new HashMap<>();
   }

   /**
    * A default {@link IProtectionService} is used to protect the store:
    * AES encryption, using {@link AesProtectionService} with {@link LinuxKeySourceProvider}.
    *
    * @return An instance of a {@link CustomProtectedStore}, with all values loaded.
    * @throws ClientException If opening fails.
    */
   public static CustomProtectedStore open() throws ClientException {
       // TODO: When your program is run under Windows instead, use the DpApiProtectionService instead of the {@link AesProtectionService}.
       IProtectionService protectionService = new AesProtectionService(new LinuxKeySourceProvider());
       CustomProtectedStore protectedFileStore = new CustomProtectedStore(protectionService);
       protectedFileStore.load();

       return protectedFileStore;
   }

   /**
    * Read from client store
    */
   private void load() throws ClientException {
       try {
           byte[] protectedContent = loadStore();
           if (protectedContent.length == 0) {
               return;
           }

           byte[] unprotectedContent = protectionService.unprotect(protectedContent);
           String storeContent = new String(unprotectedContent, StandardCharsets.UTF_8);

           String[] entries = storeContent.split(";");

           for (String entry : entries) {
               String key = entry.substring(0, entry.indexOf('='));
               String value = entry.substring(entry.indexOf('=') + 1);
               storeValues.put(new String(Hex.decodeHex(key)), value);
           }

       } catch (DecoderException e) {
           throw new ClientException(ClientException.CODE_STORE_ACCESS_FAILED, "Could not read from the client store!", e);
       }
   }

   /**
    * Gets a stored value from the store.
    *
    * @param key Unique key to identify the value. If null, the method returns null.
    * @return The value that corresponds to the given key, or null, if the key was null or not found.
    */
   @Override
   public synchronized String getValue(String key) {
       if (key == null) {
           return null;
       }
       return storeValues.get(key);
   }

   /**
    * Sets a value in the store.
    *
    * @param key Unique key for the value. If null, the method call has no effect.
    * @param value Value to be stored. If null, the method call has no effect.
    */
   @Override
   public synchronized void setValue(String key, String value) {
       if (key == null {{!}}{{!}} value == null) {
           return;
       }
       storeValues.put(key, value);
   }

   /**
    * Removes a specific value from the store.
    *
    * @param key Unique key to identify the value. If null, the method call has no effect.
    */
   @Override
   public synchronized void removeValue(String key) {
       if (key == null) {
           return;
       }
       storeValues.remove(key);
   }

   /**
    * Saves this store object to the custom store.
    */
   @Override
   public synchronized void persist() throws ClientException {
       StringBuilder content = new StringBuilder();
       storeValues.forEach((k, v) -> {
           content.append(Hex.encodeHexString(k.getBytes()));
           content.append("=");
           content.append(v);
           content.append(";");
       });

       byte[] unprotectedContent = content.toString().getBytes(StandardCharsets.UTF_8);
       byte[] protectedContent = protectionService.protect(unprotectedContent);

       saveStore(protectedContent);
   }

   private byte[] loadStore() {
       // TODO: Load bytes from your custom store, e.g. from a file or database.
   }

   private void saveStore(byte[] bytes) {
       // TODO: Save bytes to your custom store, e.g. to a file or database.
   }
}
Java API 5
// Get client store from your custom store. CryptshareConnection
connection = new CryptshareConnection(new WebServiceUri(SERVER++_++URL))
Client client = new Client("sender@domain.com", Locale.ENGLISH,
connection, CustomProtectedStore.open());

// do something like verifications, transfers, ...

// Write encrypted modified client store back into the custom store.
// Info: This step is not necessary anymore.

Verification

The check for the current verification status has changed. The Client#checkVerification method now returns a CheckVerificationResult object. Whether the client is verified is now checked with CheckVerificationResult#isUserVerified instead of VerificationStatus#isVerified. The verification mode is obtained with CheckVerificationResult#getVerificationMode instead of VerificationStatus#isSenderVerification. Below you can see the verification process in detail for both verification modes:

Client Verification
private final String SENDER = "john.doe@example.com";
private final String SERVER_URL = "`https://cryptshare.mydomain.com[`https://cryptshare.mydomain.com`]`";
private final String CLIENT_STORE_PATH = "client.store";

CryptshareConnection connection = new CryptshareConnection(new WebServiceUri(SERVER_URL));
Client client = new Client(SENDER, connection, Paths.get(CLIENT_STORE_PATH));
System.out.println("Checking whether the client is verified...");
CheckVerificationResult result = client.checkVerification();
if (result.isUserVerified()) {
   System.out.println("The client is verified; nothing to do.");
} else {
   if (result.getVerificationMode() == VerificationMode.SENDER) {
       System.out.println("Verification mode is set to 'Sender', please set it to 'Client' and add the following Client ID: " + client.getClientId());
   return;
   }
   try {
       client.RequestClientVerification();
       System.out.println("The client verification has been requested. Please rerun the program and check whether your client is verified.");
   } catch (CryptshareServerException e) {
       System.out.println("The verification mode is set to 'Client', but your Client ID is not whitelisted. " +
    "Please add your Client ID '" + client.getClientId() + "' to the list of allowed clients.");
       return;
   }
}

The method Client#checkVerification now returns another type of object that also has to be used differently for checking for an existing verification. Instead of calling Client#requestVerification a client verification has to be requested explicitely by Client#requestClientVerification. In case the client is not verified yet Client#requestClientVerification throws an exception that has to be handled.

Sender Verification
private final String SENDER = "john.doe@example.com";
private final String SERVER_URL = "`https://cryptshare.mydomain.com[`https://cryptshare.mydomain.com`]`";
private final String CLIENT_STORE_PATH = "client.store";

CryptshareConnection connection = new CryptshareConnection(new WebServiceUri(SERVER_URL));
Client client = new Client(SENDER, connection, Paths.get(CLIENT_STORE_PATH));
System.out.println("Checking whether the client is verified...");
CheckVerificationResult result = client.checkVerification();
if (result.isUserVerified()) {
   System.out.println("The client is verified; nothing to do.");
} else {
   if (result.getVerificationMode() == VerificationMode.CLIENT) {
       System.out.println("Verification mode is set to 'Client', please set it to 'Sender'.");
   return;
   }
   client.RequestSenderVerification();
   System.out.println("Please type in the verification code:");
   BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
   String vericode = reader.readLine();
   reader.close();

   // now tell the client to store the verification code in the verification store
   client.confirmSenderVerification(vericode.trim());
}

The method Client#checkVerification now returns another type of object that also has to be used differently for checking for an existing verification. Instead of calling Client#requestVerification a sender verification has to be requested explicitely by Client#requestSenderVerification. Saving the verification code does not work via Client#storeVerification, but with the method Client#confirmSenderVerification.

Changed transfer process

Preparing a transfer

Methods for preparing a transfer were renamed or require new data types as parameters.:

Description Old Method New Method Change

Should the server send a download notification to the sender of the transfer?

Transfer#setDownloadNotification

Transfer#setInformAboutDownload

New method name

Setting the language for sender notification emails

Transfer#setSenderLanguage

Client#setUserLanguage

New method name and the language is set for the client instead of the transfer.

Setting transfer recipients

Transfer#addRecipient Transfer#setRecipients Transfer#addRecipients

These methods now require Recipient objects instead of strings, e.g. transfer.addRecipient("john.doe@http://cryptshare.com[cryptshare.com]") will be replaced by transfer.addRecipient(new Recipient("john.doe@cryptshare,com"))

Setting the expiration date

Transfer#setExpirationDate(Date)

Transfer#setExpirationDate(LocalDate)

This method now requires a LocalDate object instead of a Date object, e.g. LocalDate.of(2020, 12, 24);

Setting the recipient language

Transfer#setRecipientLanguage(String)

Transfer#setRecipientLanguage(Locale)

This method now requires a Locale object instead of a string, e.g. Locale.ENGLISH.

Generate a random password

Client#requestPassword(int) Client#requestPassword(int, boolean)

Client#requestGeneratedPassword(int)

New method name. The local generation of passwords is not possible anymore.

Setting a generated password

Transfer#setGeneratedPassword

Transfer#setPassword

Automatically generated passwords have to set exactly as manual passwords.

Creating objects for transfer files

TransferFile(String)

TransferFile(Path)

The creation of TransferFile objects now require a Path object instead of a string, e.g. Paths.get(pathString).

Adding transfer files to a transfer

Transfer#setFiles

Transfer#setTransferFiles

New method name

Perform a transfer

The execution of asynchronous and synchronous transfers has been adjusted by not requiring only one class that takes care of all possible state changes of the transfer such as upload progress change, upload finished, upload interrupted and transfer aborted. Instead, a separate handler can now be defined for each of these state changes. These derive their names from the following interfaces: IUploadProgressChangedHandler, IUploadCompleteHandler, IUploadInterruptedHandler, IUploadCancelledHandler. These each require a method to be implemented. The method to be implemented directly provides all available information, so it is no longer necessary to extract it from an event object. If a handler is not needed, null can be specified for that handler when starting a transfer. Examples of handlers:

public class UploadProgressChangedHandler implements IUploadProgressChangedHandler {
   @Override
   public void progressChanged(String currentFileNo, String currentFileName, double bytesUploaded, double bytesTotal, long bytesPerSecond) {
       // do something
   }
}

public class UploadCompleteHandler implements IUploadCompleteHandler {
   @Override
   public void uploadComplete(Map<String, String> urlMappings, Map<String, String> smtpMappings, String serverGenPassword, TransferError transferError,
           String trackingId) {
       // do something
   }
}

public class UploadInterruptedHandler implements IUploadInterruptedHandler {
   @Override
   public void uploadInterrupted(CryptshareException cryptshareException) {
       // do something
   }
}

public class UploadCancelledHeader implements IUploadCancelledHandler {
   @Override
   public void cancelled() {
       // do something
   }
}

Usage of handler during starting a transfer:

// Asynchronous Transfer
client.beginTransfer(transfer, new UploadProgressChangedHandler(), new UploadCompleteHandler(), new UploadInterruptedHandler(), new UploadCancelledHeader(), pollingTimer);

// Synchronous Transfer
client.performTransfer(transfer, new UploadProgressChangedHandler(), new UploadCompleteHandler(), new UploadInterruptedHandler(), new UploadCancelledHeader(), pollingTimer);

The new integer parameter pollingTimer specifies in milliseconds how often the server should be polled for the current state of the running transfer, e.g. every 1000 milliseconds (= 1 second).

Changes in other methods

Requesting general server data

Client#requestTransferData was removed. Please use Client#requestPolicy instead to determine the valid settings for your sender-recipient combination.

List<String> recipients = new ArrayList<>();
recipients.add("user001@cryptshare.com");
recipients.add("user002@cryptshare.com");
Policy policy = client.requestPolicy(recipients);

Policy rules

Names and data types of some policy information fields have changed:

Description Old Method New Method Change

Allowed standard password modes

Policy#getPasswordMode

Policy#getAllowedStandardPasswordModes

New method name and changed data type to Set instead of a List.

Recipients that are now allowed for the given sender

Policy#getFailedAddresses

Policy#getFailedEmailAddresses

New method name and changed data type to Set instead of a List.

Language Resources

Retrieving defined language pack versions is no longer possible. Instead, use those methods that do not specify the language package version, e.g. Client#requestLanguagePacks() and Client#requestLanguagePackFile(String, Locale).

Description Old Method New Method Change

Determine the language pack version

LanguagePack#getLanguagePackVersion

LanguagePack#getVersion

New method name.

Determine the last change for this language pack

LanguagePack#getLastUpdate

Method now returns a ZoneDateTime object instead of a Unix timestamp in the Long format.

Terms of Use

The return type of the terms of use using Client#requestTermsOfUse has changed. This method now returns a TermsOfUse object. You can get the terms of use specific to a language via termsOfUse#getTextByLanguage(Locale).

license Information

The data type of individual fields within the LicenseInfo object queried via Client#requestLicenseInfo have changed from String to LocalDate: LicenseInfo#getProductLicenseExpirationDate, LicenseInfo#getProductSubscriptionExpirationDate, LicenseInfo#getServerLicenseExpirationDate, and LicenseInfo#getServerSubscriptionExpirationDate.

Requesting email templates from the Cryptshare server

The method Client#requestMailTemplate(String, Map<Set<String>, Set<TemplateReplacement>, <language>, <mailFormat>>, Locale, String) now requires another new last parameter of type EmailFormat. Example:

Example 1. Old

Map<List<String>, String> mailTemplates = client.requestMailTemplate("recipient", replacements, new Locale("ja"), "html");

Example 2. New

Map<List<String>, String> mailTemplates = client.requestMailTemplate("recipient", replacements, new Locale("ja"), EmailFormat.HTML);

Additionally, the enumeration TemplatePlaceholder was removed. In order to replace the TemplatePlaceholders with their respective strings, consult the guide Email Notifications.

Example 3. Old

Set<TemplateReplacement> replacementSet = new HashSet<TemplateReplacement>(); replacementSet.add(new TemplateReplacement(TemplatePlaceholder.NAME, "John Adams")); replacementSet.add(new TemplateReplacement(TemplatePlaceholder.EMAIL, "john.adams@server.com"));

Example 4. New

Set<TemplateReplacement> replacementSet = new HashSet<>(); replacementSet.add(new TemplateReplacement("name", "John Adams")); replacementSet.add(new TemplateReplacement("email", "john.adams@server.com"));

Validating a password

Using the Client#checkPassword method, it was previously possible to have the server check the quality of a password. A PasswordPolicy object was returned as the return value. This has been replaced by a PasswordCheckResult object for this method. Fields not required by this method, which were previously always null, have been removed.

Manual setting of transfer errors

When sending email notification was not handled by the Cryptshare server but by the own application, it is possible to tell the Cryptshare server that the sending of emails failed. Using the method Client#updateTransferError(String trackingId, TransferError error) a TransferError can be defined for a specific tracking ID of a transfer. This TransferError has been modified accordingly so that values that can no longer be set cannot be set by setters. Likewise, to set those failed recipients, the recipient list of the TransferError must first be retrieved.

Old
TransferError error = new TransferError(); List<String> recipients = new
ArrayList<>(); recipients.add("recipient@cryptshare.com");
error.setFailedRecipients(recipients);
New
TransferError error = new TransferError(); List<String> recipients = new
ArrayList<>(); recipients.add("recipient@cryptshare.com");
error.getFailedRecipients().addAll(recipients);

Removed methods

  • Transfer#setSessionID

  • Transfer#setTransferSize

  • Client#manageTransfer

  • Client#handleAsynchFileTransferCompleted