Clients: Java Cryptography Extension (JCE) Provider

1.0 Download

The Fortanix Data Security Manager (DSM) JCE Provider for all platforms can be downloaded here.

2.0 Installation

2.1 System-wide Install

  1. Move the downloaded bundled provider jar file sdkms-jce-provider-bundled-x.xx.xxxx.jar to ${JAVA_HOME}/jre/lib/ext directory.

  2. Apply Unlimited Strength Jurisdiction Policy Files by downloading the Policy file from the Java website. Extract the downloaded zip file, and copy the following files to the ${JAVA_HOME}/jre/lib/security directory.
    • local_policy.jar
    • US_export_policy.jar
  3. Add the provider to your code:
    import com.fortanix.sdkms.jce.provider.SdkmsJCE;
    SdkmsJCE provider = new SdkmsJCE();
  • Alternatively, the provider can be added in ${JAVA_HOME}/jre/lib/security/ file, as the last provider in the list. This will allow integration with non-program-based usage, for example, keytool, jarsigner, and so on.
    An example file would look like

2.2 Maven Based Install

This option is available starting from 3.19.1352 version.

Maven projects define dependencies within the pom.xml file in source under the <dependencies> tag. This file is located in the base directory of the maven project.

  1. Add the following dependency to JCE provider in the pom.xml file:
    For more information on the detailed project dependencies of the JCE provider, refer to the pom.xml files attached to the article.
  2. Add the following provider to your code:
    import com.fortanix.sdkms.jce.provider.SdkmsJCE;
    SdkmsJCE provider = new SdkmsJCE();

3.0 Configuration

For the JCE Provider to connect to Fortanix DSM, one needs to provide the server URL and an API key of an application that is used for authentication. These can be set as environment variables. For example:

export FORTANIX_API_ENDPOINT=https://<fortanix_dsm_url>
export FORTANIX_API_KEY=<your API key>

3.1 Certificated Based Authentication (mTLS)

Alternatively, the application can authenticate using a Client Certificate instead of an API key for Mutual TLS verified connection. For more information on certificate based authentication, refer to User's Guide: Authentication.

If the client's private key and certificate are in PEM format (provided by your CA or openssl), convert it into Java KeyStore (JKS) format.

openssl pkcs12 -export -in client-cert.pem -inkey client-key.pem -name "my-sdkms-app" -out client-sdkms.p12
This command asks for a password to be set for the local KeyStore. Enter any password of your choice, for example, 'PASSWORD'.

Provide the above generated KeyStore as a JVM argument to your Java program.


Or programmatically set as system properties:

System.setProperty("", "pkcs12");
System.setProperty("", <path to client-sdkms.p12 file>);
System.setProperty(", "PASSWORD");

Java tools like keytooland jarsigner take JVM arguments as follows:

jarsigner ...

3.2 Custom CA Configuration

For on-premise Fortanix DSM installations, one may need to add the Organization CA to TrustStore for successful TLS communication with API.

Create a Java TrustStore which saves the CA certificate (PEM format)

keytool -import -alias SDKMS_CA -file sdkms-root-ca.crt -keystore sdkms-truststore.jks -deststorepass PASSWORD

Provide the TrustStore as a JVM argument to your Java program

java -

Or programmatically set as system properties:

System.setProperty("", "jks");
System.setProperty("", <path to sdkms-truststore.jks file>);
System.setProperty(", "PASSWORD");

Java tools like keytooland jarsigner take JVM arguments as follows:

jarsigner ...

4.0 Supported Algorithms

Operation Algorithm Key Size (bits) Mode Padding Mode
Cipher AES 128, 192, 256 FF1, FPE NOPADDING
KeyGenerator AES 128, 192, 256    
KeyGenerator DES 56    
KeyGenerator DESede 168    
Keygenerator HmacSHA1      
Keygenerator HmacSHA256      
Keygenerator HmacSHA384      
Keygenerator HmacSHA512      
KeyPairGenerator RSA 1024, 2048, 4196, 8192    
KeyPairGenerator EC   SecP192K1, SecP224K1, SecP256K1, NistP-192, NistP-224, NistP-256, NistP-384, NistP-512, Ed25519  
Signature RSA with SHA   SHA1withRSA, SHA256withRSA, SHA384withRSA, SHA512withRSA  



EC with SHA   SHA1withECDSA, SHA256withECDSA, SHA384withECDSA, SHA512withECDSA  


DSA with SHA  









SHA1withRSAandPKCS1V15 SHA256withRSAandPKCS1V15 SHA384withRSAandPKCS1V15 SHA512withRSAandPKCS1V15


For a full list of supported algorithms refer to Algorithm Support.

5.0 Connection Pooling

Fortanix DSM version 3.21 and above supports a new feature called Connection Pooling.

Connection pooling allows restriction and reuse of connections with a maximum limit specified.
This allows setting some safe limits on each JCE application so that no single application can overwhelm the server.

5.1 With JCE Connection Pooling

The environment variable FORTANIX_CONN_MAX is set to the maximum number of connections from that instance of the JCE application.

5.2 Without JCE Connection Pooling

When the environment variable FORTANIX_CONN_MAX is not exported or is set to `0`, JCE will behave without any connection pooling/limit. This is similar to JCE behavior prior to version 3.21.

5.3 Scenarios

    • Existing behavior: number of sockets is equal to the number of concurrent threads.
  • FORTANIX_CONN_MAX = X, Concurrent threads less than X.
    • Behavior: Less than X sockets open at a time.
    • Observation: The sockets are also being reused.
  • FORTANIX_CONN_MAX = X, Concurrent threads greater than X.
    • Behavior: Maximum X sockets open with reuse.
    • Observation: higher latency, which is expected since threads are now waiting for connections to get free.

5.4 Connection Pooling Keep-Alive

The environment variable FORTANIX_CONN_KEEPALIVE is a configurable property that is used to keep the duration of a live connection open for reuse in case of subsequent requests.

The default value is 5000 milliseconds (5 seconds) in case it is not configured.

5.4.1 Configuration

To configure keep-alive, declare it as a system environment variable. For example,


6.0 Logging

With the 3.21 release, by default the logging option is disabled, and in order to enable it export the following environment variables:

  • To enable debug logs, set the environment variable:
    export FORTANIX_LOG_DEBUG=true
  • To enable only API logs, set the environment variable:
    export FORTANIX_LOG_API=true
  • To set a file location for local logs, set the environment variable:
    export FORTANIX_LOG_FOLDER="/path/to/logfile-folder"

This will create a log file /path/to/logfile-folder/sdkms-jce.log

7.0 Local Digest

Local Digest is now set as the default behavior in the Fortanix DSM JCE Provider from the 4.10 release onwards. By default, Fortanix DSM makes API calls to DSM for performing message digest operations, but from the 4.10 release onwards, it will not make the API calls and will perform the message digest operations locally without the user’s intervention through the SUN provider’s implementation. This reduces the overhead of the digest API calls to Fortanix DSM while performing sign and verify operations.
To enable or disable the local digest, use the environment variable FORTANIX_USE_LOCAL_DIGEST.
Set the environment variable to false to perform the digest operations through the DSM API calls.

8.0 Keytool and KeyStores

The keytool utility can now use the Fortanix DSM JCE provider for the management of key pairs and certificates which are backed by the Fortanix DSM service.

KeyStore is used to store the generated keys and certificates to Fortanix DSM.

Fortanix DSM JCE supports two types of KeyStores:

  • SDKMS-local

8.1 SDKMS-local

This KeyStore can be used by clients who expect more-or-less JKS (the default KeyStore) semantics. All metadata will be stored locally and imported to Fortanix DSM when is called.

To use keytool with the KeyStore SDKMS-local, provide the following :

  • storetype must be provided as SDKMS-local

  • providerName must be sdkms-jce

Different keytool operations that can performed using the local KeyStore are as follows (Refer to the Section Usage with keytool for setup):

8.1.1 Usage

keytool <operations> -storetype SDKMS-local -providerName sdkms-jce -storepass passwd -keypass passwd
  • Generate Asymmetric keys
    keytool -genkeypair -alias alias -keyalg RSA -keystore keystore_file -keysize 1024 -providerName sdkms-jce -storetype SDKMS-local -storepass passwd -keypass passwd -dname "CN=fortanix,OU=fortanix,O=fortanix,L=Mountain View,ST=California,C=US"
  • Import Certificate
    keytool -importcert -trustcacerts -alias alias -file certificate-path -keystore keystore_file -providerName sdkms-jce -storetype SDKMS-local -storepass passwd -noprompt -trustcacerts
  • Import Keystore
    keytool -importkeystore -srcstoretype SDKMS-local -deststoretype SDKMS-local -srcalias alias -srcProviderName sdkms-jce -destProviderName sdkms-jce -srckeystore source_keystore_file -destkeystore dest_keystore_file -srcstorepass passwd -deststorepass passwd -srckeypass passwd -destkeypass passwd
  • Generate AES Symmetric Keys
    keytool -genseckey -alias alias -keyalg AES -keysize 256 -storepass passwd -keypass passwd -keystore keystore_file -providerName sdkms-jce -storetype SDKMS-local
  • Import Password as Secret Keys
    keytool -importpassword -alias alias -storepass passwd -keystore keystore_file -providerName sdkms-jce -storetype SDKMS-local
  • List
    keytool -list -v -keystore keystore_file -providerName sdkms-jce -storetype SDKMS-local -storepass passwd
  • Delete
    keytool -delete -alias alias -keystore keystore_file -providerName sdkms-jce -storetype SDKMS-local -storepass passwd


This KeyStore can be used when a user needs to interact with Fortanix DSM directly. No metadata is stored locally. The Fortanix DSM groups are an abstraction for different KeyStores for a user, hence a groupId will be used while storing and retrieving the keys for this KeyStore type. 

To use keytool with keystore type SDKMS, provide the following :

  • storetype must be SDKMS.

  • providerName must be sdkms-jce.

  • key and store password must be provided as  groupId to store the corresponding key in that group.

Unsupported scenarios in KeyStore type SDKMS:

  • AES transient keys are supported to be imported in a different group than the group used to create the key.

  • RSA, EC, DES, and DES3 keys are persistent keys and cannot be updated to different groupId.

  • Importing a KeyStore is not supported as all the keys cannot be updated to a different groupId.

Different keytool operations that can be performed using the local KeyStore are as follows (Refer to the Section Usage with keytool for setup) :

8.2.1 Usage

keytool <operation> -storetype SDKMS -providerName sdkms-jce -keypass <groupId> -storepass <groupId>`
  • Import Certificate
    keytool -importcert -trustcacerts -alias alias -file certificate-path -keystore keystore_file -providerName sdkms-jce -storetype SDKMS -storepass 2ff36949-ee70-4145-bd57-7de1ada5c050 -noprompt -trustcacerts
  • Generate AES Symmetric Keys
    keytool -genseckey -alias alias -keyalg AES -keysize 256 -storepass 2ff36949-ee70-4145-bd57-7de1ada5c050 -keypass 2ff36949-ee70-4145-bd57-7de1ada5c050 -keystore keystore_file -providerName sdkms-jce -storetype SDKMS
  • Import Password as Secret Key
    keytool -importpassword -alias alias -storepass groupId -keystore keystore_file -providerName sdkms-jce -storetype SDKMS
  • List the above generated AES Key
    keytool -list -v -keystore keystore_file -providerName sdkms-jce -storetype SDKMS -storepass 2ff36949-ee70-4145-bd57-7de1ada5c050
  • Delete the above AES Key
    keytool -delete -alias alias -keystore keystore_file -providerName sdkms-jce -storetype SDKMS -storepass 2ff36949-ee70-4145-bd57-7de1ada5c050

9.0 Jarsigner

To generate an entity's signature for a file, the entity must first have a public/private key pair associated with it and one or more certificates that authenticate its public key. The jarsignercommand uses key and certificate information from a keystore to generate digital signatures for JAR files. For more details, refer to the Oracle jarsigner documentation.

9.1 Syntax

jarsigner -keystore <keystore_file> -storepass <storepassword> <filenameTosigned> <alias> 

9.2 Examples

jarsigner -keystore $KEYSTORE_PATH -storepass $JCE_SIGNING_PASSWD $JARFILE $ALIAS 
jarsigner -keystore $KEYSTORE -providerName $PROVIDER_NAME -sigalg SHA256withRSA -storetype $TYPE -storepass $PASSWD $RESOURCES_PATH"/JCETest-0.0.1.jar" jcetest 
jarsigner -verify $RESOURCES_PATH"/JCETest-0.0.1.jar" -providerName $PROVIDER_NAME -sigalg SHA256withRSA 

9.3 Environment Setup

Set the environment variables FORTANIX_API_ENDPOINT  and FORTANIX_API_KEY using the following commands:


Add the sdkms-jce-provider JAR inside the folder $JAVA_HOME/jre/lib/ext/ .

You can generate a private key and its public certificate using the keytool command as shown below. This command will generate the file  keystore1 locally and a private key named jcetest in Fortanix DSM.
You must provide the Fortanix DSM Group ID as the storepass option.

keytool -genkey -alias jcetest -keyalg RSA -keystore keystore1 -keysize 1024 -providername sdkms-jce -storetype SDKMS -storepass d7e89ac7-f0dc-49d1-9196-75aaf6afc47c 

Sign the JAR using the following command:

jarsigner -keystore keystore1 -providerName sdkms-jce -sigalg SHA256withRSA -storetype SDKMS -storepass d7e89ac7-f0dc-49d1-9196-75aaf6afc47c a.jar jcetest

9.4 Jarsigner Using Existing Keys

To sign the JAR using existing keys, import the existing private key along with its certificate chain into Fortanix DSM using the sdkms-cli tool, and then use the private key alias for signing the JAR.

10.0 Java Examples

10.1 Generate RSA Keys

//Generate RSA Keys:
SdkmsJCE provider = SdkmsJCE.getInstance();
String algorithm = AlgorithmParameters.RSA;
KeyPairGenerator kpg = KeyPairGenerator.getInstance(algorithm, provider);
SecurityObjectParameterSpec parameterSpec = new SecurityObjectParameterSpec(false);
kpg.initialize(parameterSpec, null);
KeyPair keyPair = kpg.genKeyPair();

10.2 RSA Sign and Verify

String algorithm = AlgorithmParameters.RSA;
KeyPair rsaKeyPair = keyGenerator.generateKeyPair();
Signature sig = Signature.getInstance(algorithm, provider);

byte[] data = "test".getBytes("UTF8");
Signature sig = Signature.getInstance(“SHA256withRSA”, provider);
byte[] signatureBytes = sig.sign();

assertEquals(true, sig.verify(signatureBytes));

10.3 Generate AES Keys

SdkmsJCE provider = SdkmsJCE.getInstance();
String algorithm = AlgorithmParameters.AES;
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance(algorithm, provider);
SecretKey aesKey = keyGenerator.generateKey();

10.4 AES Cipher Encryption and Decryption

String algorithm = AlgorithmParameters.AES;
String mode = CipherMode.ECB.toString();
String padding = ProviderConstants.PKCS5PADDING

SecretKey secretKey = keyGenerator.generateKey();

Cipher cipher = Cipher.getInstance(algorithm, provider);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);

String PLAIN = "testData";
// encryption
byte[] cipherBytes = cipher.doFinal(PLAIN.getBytes());

// decryption
cipher.init(Cipher.DECRYPT_MODE, key, params);
byte[] plainBytes = cipher.doFinal(cipherBytes);

// verify
assertEquals(new String(plainBytes), PLAIN); 

10.5 AES Cipher Encryption and Decryption Using Existing Keys in DSM

Instantiate an SobjectDescriptor with either key name or key ID.

    description = "This uniquely identifies a persisted or transient sobject. Exactly one of `kid`, `name`, and `transient_key` must be present. "
public class SobjectDescriptor {
    private String kid = null;
    private String name = null;
    private String transientKey = null;

Instantiate SdkmsAESKey object using the Sobjectdescriptor.

public SdkmsAESKey(SobjectDescriptor descriptor) {

Or, if you have the key ID, use the following constructor without instantiating the SobjectDescriptor.

public SdkmsAESKey(String keyId, Integer keySize, String transientKey) {
    this(new SobjectDescriptor().kid(keyId).transientKey(transientKey));
    this.keySize = keySize;

After the key object (let's say key ) has been instantiated, it can be supplied for encryption as shown below:

cipher.init(Cipher.ENCRYPT_MODE, key, params);

// Encrypted content 
byte[] cipherBytes = cipher.doFinal(PLAIN.getBytes());
// decrypt the same content 
cipher.init(Cipher.DECRYPT_MODE, key, params);
byte[] plainBytes = cipher.doFinal(cipherBytes);

10.6 AES Cipher Encryption and Decryption Multipart

private void testAesGcmCcmPkcs5(Integer keySize, CryptMode mode, boolean multiPart, byte[] blob) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException {
    String algorithm = String.format("AES_%d/%s/%s", keySize, mode.toString(), ProviderConstants.PKCS5PADDING);
    Cipher cipher = Cipher.getInstance(algorithm, provider);
    SecretKey secretKey = generateAESKey(keySize);
    AlgorithmParameters params = cipher.getParameters();

    int chunkSize = 1024; // 1KB

    ByteArrayOutputStream cipherStream = new ByteArrayOutputStream();
    cipher.init(Cipher.ENCRYPT_MODE, secretKey, params);
    if (!multiPart) {
    else {
        int offset = 0;
        int size  = chunkSize;
        int remaining = blob.length - size;
        cipherStream.write(cipher.update(blob, offset, size));
        while(remaining > 0) {
            offset = size;
            int nextChunk  = Math.min(remaining, chunkSize);
            cipherStream.write(cipher.update(blob, offset, nextChunk));
            size += nextChunk;
            remaining = blob.length - size;
    byte[] cipherBytes = cipherStream.toByteArray();

    ByteArrayOutputStream plainStream = new ByteArrayOutputStream();
    cipher.init(Cipher.DECRYPT_MODE, secretKey, params);
    if (!multiPart) {
    else {
        int offset = 0;
        int size  = chunkSize;
        int remaining = cipherBytes.length - size;
        plainStream.write(cipher.update(cipherBytes, offset, size));
        while(remaining > chunkSize) {
            offset = size;
            plainStream.write(cipher.update(cipherBytes, offset, chunkSize));
            size += chunkSize;
            remaining = cipherBytes.length - size;
        // while decrypting, the final chunk need to be passed to doFinal for extracting the tag.
        plainStream.write(cipher.doFinal(cipherBytes, size, remaining));

    // after going through encryption and decryption we should get same plain text back
    assertEquals(plainStream.toString(), new String(blob));

10.7 Storing RSA Key in KeyStore

KeyStore keyStore = KeyStore.getInstance("SDKMS", provider); // here can you either use SDKMS or sdkms-local as provider 

keyStore.load(null, null);
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA", provider);
KeyPair keyPair = gen.generateKeyPair();
keyStore.load(null, null);
keyStore.setKeyEntry(alias, keyPair.getPrivate(), SDKMS_GROUPID.toCharArray(), null);

10.8 Storing Secret Key in KeyStore

KeyGenerator gen = KeyGenerator.getInstance("AES", provider);
Key key = gen.generateKey();
keyStore.load(null, null);
keyStore.setKeyEntry(alias, key, SDKMS_GROUPID.toCharArray(), null);

10.9 Storing Certificate in KeyStore

 CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
 InputStream certificateInputStream = new FileInputStream("certificate.crt");
 Certificate certificate = certificateFactory.generateCertificate(certificateInputStream);
 keyStore.setCertificateEntry("certName", cert);

10.10 Listing Alias of KeyStore

Enumeration<String> keys = keyStore.aliases();
while (keys.hasMoreElements()) {

10.11 Deleting an Alias from KeyStore


10.12 Creating KeyStore for SSL/TLS

FileInputStream keyInputStream = new FileInputStream(PRIVATE_KEY_PATH);
byte[] keyBytes = new byte[keyInputStream.available()];;


String privateKey = new String(keyBytes, "UTF-8");
privateKey = privateKey.replaceAll("(-+BEGIN PRIVATE KEY-+\\r?\\n|-+END PRIVATE KEY-+\\r?\\n?)", "");

BASE64Decoder decoder = new BASE64Decoder();
keyBytes = decoder.decodeBuffer(privateKey);

PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA", provider);
PrivateKey pk = kf.generatePrivate(privKeySpec);

CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
FileInputStream certInputStream = new FileInputStream(CERTIFICATE_PATH);
Certificate cert = certFactory.generateCertificate(certInputStream);
Certificate[] chain = new Certificate[1];

KeyStore keyStore = KeyStore.getInstance("SDKMS-local", provider);
keyStore.setKeyEntry(TLS_CLIENT_KEY_NAME, pk, null, chain);

OutputStream stream = new FileOutputStream(TLS_KEYSTOR_PATH);, null);

10.13 Setting SSL/TLS context

KeyStore keyStore = KeyStore.getInstance("SDKMS-local", provider);
InputStream inputStream = new FileInputStream(TLS_KEYSTORE_PATH);
Enumeration<String> aliases = keyStore.aliases();
final String alias = aliases.nextElement();

// set ssl context
SSLContext sslContext = SSLContexts.custom()
    .loadKeyMaterial(keyStore, null, new PrivateKeyStrategy() {
        public String chooseAlias(Map<String, PrivateKeyDetails> aliases, Socket socket) {
            return alias;
    }).loadTrustMaterial(null, new TrustStrategy() {
        public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            return true;

SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
    new String[]{"TLSv1.2", "TLSv1.1"},

CloseableHttpClient client = HttpClients.custom()
HttpGet httpget = new HttpGet(TLS_ENDPOINT);
CloseableHttpResponse response = client.execute(httpget);
HttpEntity entity = response.getEntity();

10.14 Import DSA Keys

import com.fortanix.sdkms.jce.provider; 

10.15 Generate DSA Keys

int keySize = 2048; 
int subGroupSize = 224;
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA", provider);
DSAGenParameterSpec dsaGenParameterSpec = new DSAGenParameterSpec(keySize, subGroupSize);
SecurityObjectParameterSpec parameterSpec = new SecurityObjectParameterSpec(dsaGenParameterSpec, false);
kpg.initialize(parameterSpec, null);
KeyPair keyPair = kpg.genKeyPair();

10.16 DSA Sign

byte[] data = "test".getBytes("UTF8");
Signature sig = Signature.getInstance("SHA1withDSA", provider);
byte[] signatureBytes = sig.sign();

10.17 DSA Verify


assertEquals(true, sig.verify(signatureBytes));

10.18 Format Preserving Encryption (FPE)

The following is the sample code for generating tokenization objects.

FpeOptionsBasic basic = new FpeOptionsBasic();
int size = 256;
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES",provider);
FpeParameterSpec fpeParameterSpec = new FpeParameterSpec(basic);
SecurityObjectParameterSpec parameterSpec = new SecurityObjectParameterSpec(fpeParameterSpec,false);
SecretKey key = keyGenerator.generateKey();

The following is the sample code for encryption and decryption using tokenization. A tokenization object of a custom type is created.

int size = 256;
String mode= FPE;
String padding=NOPADDING;
String algorithm = String.format("AES_256/FPE/NOPADDING", keySize, mode, padding);
Cipher cipher = Cipher.getInstance(algorithm, provider);
SobjectDescriptor sobjDesc = new SobjectDescriptor().name("key_name");
KeyObject keyObj = SdkmsKeyService.getKeyObject(sobjDesc);
SdkmsAESKey key = new SdkmsAESKey(keyObj);
AlgorithmParameters params = cipher.getParameters();
cipher.init(Cipher.ENCRYPT_MODE, key, params);
String PLAIN = "123456789000";
// Encrypted content
byte[] cipherBytes = cipher.doFinal(PLAIN.getBytes());
// decrypt the same content
cipher.init(Cipher.DECRYPT_MODE, key, params);
byte[] plainBytes = cipher.doFinal(cipherBytes);;


Please sign in to leave a comment.

Was this article helpful?
0 out of 0 found this helpful