Automated Generation of Shared Secret v2 - 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.

This is a second version of this design, prompted by some observations and discussions with Endi. The key point is that the shared secret is a property of the TPS-TKS connection, and should be generated when that connection is established, rather than as part of the TKS configuration. Implicit in all of this is an attempt to allow the configuration of multiple shared secrets so that a TKS might be connected to multiple TPS.

The flow will look something like this then:

  1. TKS is configured with no default shared secret. The TKSKnownSessionKey self test currently attempts to generate a session key with know parameters using a shared key called sharedSecret to wrap the session key. Thus, it fails if the session key does not exist. The test will be modified to only attempt a test if the shared secret is configured. Alternatively, also, we could modify SymKey to compute a session key without wrapping in case no shared secret is configured.

  2. TPS is configured. The TPS registers a user (agent) on the TKS, and provides the TPS subsystem certificate for that agent. This operation occurs already today, and is protected by tokenAuth.

  3. TPS requests a new shared secret by calling in to a new REST interface. This key is generated, stored with the name requested by the TPS, and returned to the TPS wrapped with the certificate of the TPS agent.

  4. The TPS unwraps the shared secret in its token, gives it a nickname and stores it permanently.

Note that it will still be possible to use tkstool to generate and transfer a shared secret. Some people like error prone typing of random strings.

Exposing the Shared Secret in a TKS REST Servlet

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

@Path("/tps-clients")
@AuthMethodMapping("admin")
public interface TPSClientsResource {
    @GET
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public TPSClientCollection listClients();

    @GET
    @Path({id})
    public TPSClient getClient(@QureyParam("id") String id);

    @POST
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public TPSClient createClient(@QueryParam("host") String tpsHost, @QueryParam("port") String tpsPort
                                  @QueryParam("userid") String userid);

    @DEL({id})
    public void deleteClient(@QueryParam("id") String id);

    @POST
    @Path({id}/shared-secret)
    @ACLMapping("admin.sharedsecret")
    public KeyData createSharedSecret();

    @PUT
    @Path({id}/shared-secret)
    @ACLMapping("admin.sharedsecret")
    public KeyData replaceSharedSecret();

    @DEL
    @Path({id}/shared-secret)
    @ACLMapping("admin.sharedsecret")
    public void deleteSharedSecret();

    @GET
    @Path({id}/shared-secret)
    @ACLMapping("admin.sharedsecret")
    public KeyData getSharedSecret();
}
  • 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.

  • listClients() returns a collection of TPSClient objects, which contain information about the different TPS connections to the TKS. These will include for now : connection identifier (tps0, tps1, etc.), hostname, port, userid. The userid is the id of the system user created in the previous step. It is expected that the userid corresponds to the system user created by the TPS on the TKS. As such, there is only one key per userid.

  • createClient() creates a TPSClient entry in CS.cfg. The call is protected by tokenAuth. This entry will look something like:

tps.0.host=foo
tps.0.port=bar
tps.0.userid=userid
  • createSharedSecret() creates a new TPSClient entry in CS.cfg. This call is protected by client auth. We expect the caller to use the certificate associated with the user in the TPSClient entry. The shared secret will be wrapped with this user’s certificate. An entry in CS.cfg will be generated as :

tps.0.nickname=sharedSecret-{userid}
  • replaceSharedSecret() and deleteSharedSecret() are similarly protected by client auth.

  • deleteClient() is protected by client authentication, and will delete both the TPSClient entry and the shared secret.

  • At some point, the TPS code will be modified in the new tomcat TPS to pass either the userid or the shared secret nickname along with its request. This will allow the TPS to select the right shared key nickname to pass into its calls. To allow existing TPS to continue to work with the new TKS, the parameter tks.tksSharedSymKeyName=sharedSecret will be kept, and will be used if no parameter is passed in.

Generating the Symmetric Key

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.

Wrapping the Shared Secret

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;
    }

Unwrapping and Storing the Shared Secret in the TPS

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, 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** ⚠️