Automated Generation of Shared Secret - dogtagpki/pki GitHub Wiki

Introduction

This is a design for the automated generation and retrieval of the shared secret (symmetric key shared between the TKS and TPS) as part of the installation process. Currently, the approach is to use tkstool to generate the shared secret on the TKS, and then use tkstool to reconstruct the symmetric key using the shares on the TPS. As tkstool involves typing lots of random characters, it is error prone and just generically a pain to use.

The idea instead will be to:

  1. Generate the symmetric key as part of the TKS configuration servlet, storing that key as "sharedSecret" or whatever the installer chooses for the name.

  2. Provide a new REST servlet to provide that shared secret. This servlet will be authenticated using client authentication and will be limited to TKS agents. The shared secret will be wrapped using the agent’s client cert.

  3. In the TPS configuration servlet, call the REST servlet and obtain the wrapped shared secret. This secret will then be unwrapped in the token and stored.

Note that currently the TKS only talks to one TPS using one shared secret. In the details below, we will discuss design that will allow multiple shared secrets.

Details

Step 1: Generate the symmetric key

By default, the TKS configuration will generate one shared secret. The following parameters will be added to pkispawn (with defaults):

tps.connections=1  (number of tps connections)
tps.0.sharedSecretName=sharedSecret %(pki_hostname)s:%(pki_https_port)
tps.0.hostport=%(pki_hostname)s:%(pki_https_port)

In future, users will be able to add tps.1.XXX, tps.2.XXXX, etc. There will be a requirement enforced by the parser that all the sharedSecret nicknames be unique.

The shared secret names will be stored in CS.cfg as:

conn.tps0.sharedSecretName=<foo>
conn.tps0.hostport=<host>:<port>

For now, until we implement using multiple keys, the default shared secret will be stored in the legacy parameter:

tks.tksSharedSymKeyName=sharedSecret

The TKS configuration servlet will create a symetric key using JSS code and store it in the software token. Currently, there is an interface KeyManager.generateUniqueNamedKey() which seems to fit the bill. If it works, then the code to generate a symmetric key simply looks like this:

public static void createSharedSecret(String nickname) throws NotInitializedException, TokenException {
    CryptoManager cm = CryptoManager.getInstance();
    CryptoToken token = cm.getInternalKeyStorageToken();
    KeyManager km = new KeyManager(token);
    km.generateUniqueNamedKey(nickname);
}

If it doesn’t work, then we need to make the JSS changes described by Jack below.

Possible JSS changes needed

  1. We already have a way to make something temporary or permanent:

Interface: KeyGenerator

method:
 /**
     * Tells the generator to generate temporary or permanent keys.
     * Temporary keys are not written permanently to the token.  They
     * are destroyed by the garbage collector.  If this method is not
     * called, the default is temporary keys.
     */
    public void temporaryKeys(boolean temp);

sample:
        alg = ... ;
        SymmetricKey key = null;
        KeyGenerator kg = token.getKeyGenerator(alg);
        kg.temporaryKeys(true);
        key = kg.generate();

Proposed enhancement to give us the ability to name a key:

  • Add method to KeyGenerator interface, which is implemented by real class: security/jss/org/mozilla/jss/pkcs11/PK11KeyGenerator.java

method:

public void setAlias(String nickName) {
    this.alias = nickName;
}

Then this class has a private method here:

/**
     * A native method to generate a non-PBE key.
     * @param token The token where the key generation happens
     * @param algorithm The algorithm to use
     * @param strength The key size in bits, should be 0 for fixed-length
     *      key algorithms.
     * @param opFlags The crypto operations the key will support
     * @param temporary Whether the key will be temporary or permanent
     */
    private static native SymmetricKey
    generateNormal(PK11Token token, KeyGenAlgorithm algorithm, int strength,
        int opFlags, boolean temporary, int sensitive)
        throws TokenException;

Here we add a parameter to generateNormal called nickName. I’m not sure yet what type, just whatever the JNI layer takes as a string. The higher level code would pass this.alias as the name to set. I believe the method generate calls generateNormal.

Modify the method in the JNI C file here:

/***********************************************************************
 *
 * PK11KeyGenerator.generateNormal
 *
 * Generates a non-PBE symmetric key on a token.
 *
 */
JNIEXPORT jobject JNICALL
Java_org_mozilla_jss_pkcs11_PK11KeyGenerator_generateNormal
    (JNIEnv *env, jclass clazz, jobject token, jobject alg, jint strength,
    jint opFlags, jboolean temporary, jint sensitive)
{

Add and retrieve the nickName param. Within this function make use of the NSS call: SECStatus PK11_SetSymKeyNickname(PK11SymKey *symKey, const char *nickname)) to actually set the name within this function and we should be good.

Notes:

  • We can make this nickName param optional everywhere so we don’t kill current clients.

  • I’d have to do some more checking to see if there are other implementers of KeyGenerator that would have to possibly be modified as well.

  • Another thing we could do is add a new method generateNormalWithName at the java level and the clients would not be disturbed this way as well.

Step 2: Expose the shared secret in a TKS REST servlet

A REST servlet with the following interface definition will be added to the TKS:

@Path("admin/shared-secret")
@ACLMapping("admin.sharedsecret")
@AuthMethodMapping("admin")
public interface SharedSecretResource {

    @GET
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public KeyData getSharedSecret(@QueryParam("tpsHost") String tpsHost, @QueryParam("tpsPort") String tpsPort);

    @POST
    public void createSharedSecret(@QueryParam("tpsHost") String tpsHost, @QueryParam("tpsPort") String tpsPort);

    @DEL
    public void deleteSharedSecret(@QueryParam("tpsHost") String tpsHost, @QueryParam("tpsPort") String tpsPort);
}

As noted from the @AuthMethodMapping above, the methods will be restricted to TKS Admins, and will require client cert authentication.

More specifically, during TPS installation, a user is created on the TKS with the dn: uid=TPS-<tpsHost>-<tpsPort> with the TPS subsystem certificate as its certificate. The getSharedSecret() method will:

  • Confirm that the certificate provided for client authentication matches the certificate for the user uid=TPS-<tpsHost>-<tpsPort>.

  • Get the shared secret corresponding to the relevant TPS, and wrap it (in the token) with the certificate stored for uid=TPS-<tpsHost>-<tpsPort>.

Obviously, the servlet cannot be called by the TPS installation before the user has been created. It will need to be one of the last steps of the configuration - if not a step to be executed on first startup post-configuration.

Sample code to extract the shared key and wrap it is shown below. This code does not handle multiple shared secrets.

    public static KeyData exportSharedSecret(java.security.cert.X509Certificate wrappingCert) throws EPropertyNotFound,
            EBaseException, NotInitializedException, TokenException, InvalidKeyException, IllegalStateException,
            NoSuchAlgorithmException, InvalidAlgorithmParameterException {
        IConfigStore cs = CMS.getConfigStore();
        String nickname = cs.getString("tks.tksSharedSymKeyName", "sharedSecret");

        CryptoManager cm = CryptoManager.getInstance();
        CryptoToken token = cm.getInternalKeyStorageToken();
        KeyManager km = new KeyManager(token);
        if (!km.uniqueNamedKeyExists(nickname)) {
            throw new EBaseException("Shared secret " + nickname + " does not exist");
        }
        SecretKey skey = km.lookupUniqueNamedKey(EncryptionAlgorithm.DES3_ECB, nickname);

        KeyWrapper keyWrap = token.getKeyWrapper(KeyWrapAlgorithm.RSA);
        PublicKey pub = wrappingCert.getPublicKey();
        keyWrap.initWrap(pub, null);
        byte[] wrappedKey = keyWrap.wrap(((SecretKeyFacade) skey).key);
        KeyData data = new KeyData();
        data.setWrappedPrivateData(Utils.base64encode(wrappedKey));
        return data;
    }

The createSharedSecret() and deleteSharedSecret() methods will be used to create/delete additional shared keys. These are intended for use post-TKS-configuration, in case new TPS connections are required. Authorization is restricted as above to TKS admins.

Step 3: Retrieve shared secret from the TKS by the TPS configuration servlet

At the end of its configuration - or possibly as a first post-configuration step - the TPS will contact the TKS using the servlet described above in step 2, and providing its subsystem certificate for client authentication. An admin user should already have been created on the TKS for this TPS. This will return the shared secret wrapped using the subsystem cert.

This blob will be unwrapped in the token and stored in either the hardware or software token. The JSS calls to do this do not currently exist. They will need to be implemented as Jack described below.

JSS changes required

Problem #1 Issue with unwrapping something permanent.

Looks like the default creates a temp key, here is how:

The following JSS JNI function at the lowest level:

/***********************************************************************
 *
 * PK11KeyWrapper.nativeUnwrapSymWithSym
 */
JNIEXPORT jobject JNICALL
Java_org_mozilla_jss_pkcs11_PK11KeyWrapper_nativeUnwrapSymWithSym
    (JNIEnv *env, jclass clazz, jobject tokenObj, jobject unwrapperObj,
        jbyteArray wrappedBA, jobject wrapAlgObj, jobject typeAlgObj,
        jint keyLen, jbyteArray ivBA, jint usageEnum)
{

Makes use of the NSS call PK11_UnwrapSymKeyWithFlags which is not what we want. What we want is the NSS call PK11_UnwrapSymKeyWithFlagsPerm:

PK11SymKey *
PK11_UnwrapSymKeyWithFlagsPerm(PK11SymKey *wrappingKey,
                   CK_MECHANISM_TYPE wrapType,
                   SECItem *param, SECItem *wrappedKey,
                   CK_MECHANISM_TYPE target, CK_ATTRIBUTE_TYPE operation,
                   int keySize, CK_FLAGS flags, PRBool isPerm)

To give us this, I suggest the following:

  • In KeyWrapper.java we create this method which is overloaded just like in the current code:

/**
     * Unwraps a key and allows it to be used for all operations.
     * @param keyLength The expected length of the key in bytes.  This is
     *   only used for variable-length keys (RC4) and non-padding
     *   algorithms. Otherwise, it can be set to anything(like 0).
     */
    public SymmetricKey unwrapPermanentSymmetric(byte[] wrapped, SymmetricKey.Type type,
        int keyLength)
        throws TokenException, IllegalStateException,
            InvalidAlgorithmParameterException;

/**
     * Unwraps a key and allows it to be used for all operations.
     * @param keyLength The expected length of the key in bytes.  This is
     *   only used for variable-length keys (RC4) and non-padding
     *   algorithms. Otherwise, it can be set to anything(like 0).
     */
    public SymmetricKey unwrapPermanentSymmetric(byte[] wrapped, SymmetricKey.Type type,
        int keyLength)
        throws TokenException, IllegalStateException,
            InvalidAlgorithmParameterException;
  • In pkcs11/PK11KeyWrapper.java we create the same two methods:

 public SymmetricKey
    unwrapPermanentSymmetric(byte[] wrapped, SymmetricKey.Type type,
        SymmetricKey.Usage usage, int keyLen)
        throws TokenException, IllegalStateException,
            InvalidAlgorithmParameterException
    {
        return unwrapPermanentSymmetric(wrapped, type, usage.getVal(), keyLen);
    }

    public SymmetricKey
    unwrappermanentSymmetric(byte[] wrapped, SymmetricKey.Type type, int keyLen)
        throws TokenException, IllegalStateException,
            InvalidAlgorithmParameterException
    {
        return unwrapPermanentSymmetric(wrapped, type, -1, keyLen);
    }

This calls to analogous private methods of this same class as above.

  • The methods will call the new nativeMethod: /pkcs11/PK11KeyWrapper.c

  • The second version of unwrapPermanentSymmetric will all the logic contained will have a call to a new native method:

nativeUnwrapSymWithSymPerm(token, symKey, wrapped, algorithm,
                        algFromType(type), keyLen, IV, usageEnum, isPerm);

That method’s sig will be:

/***********************************************************************
 *
 * PK11KeyWrapper.nativeUnwrapSymWithSymPerm
 */
JNIEXPORT jobject JNICALL
Java_org_mozilla_jss_pkcs11_PK11KeyWrapper_nativeUnwrapSymWithSymPerm
    (JNIEnv *env, jclass clazz, jobject tokenObj, jobject unwrapperObj,
        jbyteArray wrappedBA, jobject wrapAlgObj, jobject typeAlgObj,
        jint keyLen, jbyteArray ivBA, jint usageEnum, jboolean isPerm)

This new native method will use the following NSS call:

 PRBool isPerm = true;

 symKey = PK11_UnwrapSymKeyWithFlagsPerm(wrappingKey, wrappingMech, param,
        wrappedKey, keyTypeMech, operation, keyLen, flags, isPerm)

Problem #2: Naming symmetric keys obtained by unwrapping

Right now there is no way to name a symm key that is created by unwrapping a key onto a token. The following simple fix the the SymmetricKey class should do the trick:

  • Create the key using the chosen unwrap functionality in JSS, I assume we will use the new mechanism to unwrap something to a permanent state.

  • Once we have the SymmetricKey object in java, we can immediately call a new method of the class to set the name:

String name = "sharedSecret";
symmKey.setaAlias(name);

This is accomplished by:

  • Add two methods of cryto/SymmetricKey.java

void setNickname(String name)
void getNickname()
  • Modify the implementation file pkcs11/PK11SymKey.cpp and .c. Will have a native method declarations:

 protected native String getNickname();

 protected native void setNickname(String nickname)
 public native String getNickname();
  • In the .c file the methods would look like this:

JNIEXPORT jstring JNICALL
Java_org_mozilla_jss_pkcs11_setNickname
    (JNIEnv *env, jobject this, jstring nickname)
{

    jboolean isCopy = JNI_FALSE;

    PK11SymKey *key = NULL;


    PR_ASSERT(env!=NULL && this!=NULL);

    /* Get the C key structure */
    if( JSS_PK11_getSymKeyPtr(env, this, &key) != PR_SUCCESS) {
        PR_ASSERT( (*env)->ExceptionOccurred(env) != NULL);
        goto finish;
    }

    const char *c_nickname  = (*env)->GetStringUTFChars(env, nickname, &isCopy);

    PK11_SetSymKeyNickname(key, c_nickname);

    if (isCopy == JNI_TRUE) {
      /* release memory */
      (*env)->ReleaseStringUTFChars(env, nickname, c_nickname);
    }
}

JNIEXPORT jstring JNICALL
Java_org_mozilla_jss_pkcs11_getNickname
    (JNIEnv *env, jobject this)
{

    PK11SymKey *key = NULL;
    PR_ASSERT(env!=NULL && this!=NULL);

    /* Get the C key structure */
    if( JSS_PK11_getSymKeyPtr(env, this, &key) != PR_SUCCESS) {
        PR_ASSERT( (*env)->ExceptionOccurred(env) != NULL);
        goto finish;
    }


    char *nickname = PK11_GetSymKeyNickname(key);

    if( nickname ) {
        return (*env)->NewStringUTF(env, nickname);
    } else {
        return NULL;
    }
}

Jack Notes

Just a note on something I may have left out with respect to Symmetric Key suport in JSS.

The current TPS code, which is in C, does some magic with the sharedSecret when processing messages from the TKS.

Thus there may be a need to add a method to JSS where we can locate a key by name, in case that Ade’s method does not work, which uses the KeyManager.

Also, the current TPS code does some manipulation with symm keys using some NSS functions that create new keys from other keys. Stuff like adding two keys together and such. Either we can look into putting that capability into JSS or re-work the code to not need that functionality. I would have to do further research on that. Another approache would be to support limited JNI functionality in TPS to perform low level NSS tasks such as that.

⚠️ **GitHub.com Fallback** ⚠️