Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CMAC to JSS #259

Merged
merged 2 commits into from
Sep 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ find_package(JNI REQUIRED)
# Since we found Java, include UseJava to provide the find_jar function.
include(UseJava)

# This include is required for the macro check_symbol_exists in jss_config()
include(CheckSymbolExists)

# Load JSSConfig module; this defines the jss_config() macro which defines
# JSS-specific configuration values.
include(JSSConfig)
Expand Down
14 changes: 14 additions & 0 deletions cmake/JSSConfig.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ macro(jss_config)

# Template auto-generated files
jss_config_template()

# Check symbols to see what tests we run
jss_config_symbols()
endmacro()

macro(jss_config_version MAJOR MINOR PATCH BETA)
Expand Down Expand Up @@ -335,3 +338,14 @@ macro(jss_config_template)
"${CMAKE_BINARY_DIR}/run_test.sh"
)
endmacro()

macro(jss_config_symbols)
list(APPEND CMAKE_REQUIRED_INCLUDES ${NSPR_INCLUDE_DIRS})
list(APPEND CMAKE_REQUIRED_INCLUDES ${NSS_INCLUDE_DIRS})
list(JOIN JSS_C_FLAGS " " CMAKE_REQUIRED_FLAGS)

check_symbol_exists("CKM_AES_CMAC" "nspr.h;nss.h;pkcs11t.h" HAVE_NSS_CMAC)
if(NOT HAVE_NSS_CMAC)
message(WARNING "Your NSS version doesn't support CMAC; some features of JSS won't work.")
endif()
endmacro()
7 changes: 7 additions & 0 deletions cmake/JSSTests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,13 @@ macro(jss_tests)
COMMAND "org.mozilla.jss.tests.HmacTest" "${RESULTS_NSSDB_OUTPUT_DIR}" "${PASSWORD_FILE}"
DEPENDS "Setup_DBs"
)
if(HAVE_NSS_CMAC)
jss_test_java(
NAME "CMAC_Test"
COMMAND "org.mozilla.jss.tests.TestCmac" "${RESULTS_NSSDB_OUTPUT_DIR}" "${PASSWORD_FILE}"
DEPENDS "Setup_DBs"
)
endif()
jss_test_java(
NAME "Mozilla_JSS_Secret_Key_Generation"
COMMAND "org.mozilla.jss.tests.JCASymKeyGen" "${RESULTS_NSSDB_OUTPUT_DIR}"
Expand Down
9 changes: 9 additions & 0 deletions org/mozilla/jss/JSSProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,20 @@ public JSSProvider() {
put("Alg.Alias.Mac.Hmac-SHA384", "HmacSHA384");
put("Mac.HmacSHA512",
"org.mozilla.jss.provider.javax.crypto.JSSMacSpi$HmacSHA512");
put("Mac.CmacAES", "org.mozilla.jss.provider.javax.crypto.JSSMacSpi$CmacAES");
put("Alg.Alias.Mac.Hmac-SHA512", "HmacSHA512");
put("Alg.Alias.Mac.SHA-1-HMAC", "HmacSHA1");
put("Alg.Alias.Mac.SHA-256-HMAC", "HmacSHA256");
put("Alg.Alias.Mac.SHA-384-HMAC", "HmacSHA384");
put("Alg.Alias.Mac.SHA-512-HMAC", "HmacSHA512");
put("Alg.Alias.Mac.AES-128-CMAC", "CmacAES");
put("Alg.Alias.Mac.AES-192-CMAC", "CmacAES");
put("Alg.Alias.Mac.AES-256-CMAC", "CmacAES");
put("Alg.Alias.Mac.CmacAES128", "CmacAES");
put("Alg.Alias.Mac.CmacAES192", "CmacAES");
put("Alg.Alias.Mac.CmacAES256", "CmacAES");
put("Alg.Alias.Mac.AES_CMAC", "CmacAES");
put("Alg.Alias.Mac.CMAC_AES", "CmacAES");


/////////////////////////////////////////////////////////////
Expand Down
10 changes: 9 additions & 1 deletion org/mozilla/jss/crypto/Algorithm.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
static PRStatus
getAlgInfo(JNIEnv *env, jobject alg, JSS_AlgInfo *info);

/* Helpers to handle differences in NSS versions. */
#ifndef CKM_AES_CMAC
#define CKM_AES_CMAC CKM_INVALID_MECHANISM
#endif

/***********************************************************************
**
** Algorithm indices. This must be kept in sync with the algorithm
Expand Down Expand Up @@ -97,7 +102,10 @@ JSS_AlgInfo JSS_AlgTable[NUM_ALGS] = {
/* 66 */ {CKM_AES_KEY_WRAP_PAD, PK11_MECH},
/* 67 */ {CKM_SHA256_HMAC, PK11_MECH},
/* 68 */ {CKM_SHA384_HMAC, PK11_MECH},
/* 69 */ {CKM_SHA512_HMAC, PK11_MECH}
/* 69 */ {CKM_SHA512_HMAC, PK11_MECH},

/* CKM_AES_CMAC is new to NSS; some implementations might not yet have it. */
/* 70 */ {CKM_AES_CMAC, PK11_MECH}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another question:
I assume by adding this mechanism , which nss now implements, is enough to make existing jni code in jss do this job for us? Just curious to take a look at what's going on down there. Is PK11MessageDigest.c where the magic is happening?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! That's some lovely magic.

So my understanding of PKCS#11 is a bit... rough, and Bob is definitely the one to ask these questions to. :)

But there's a few basic operations in PKCS#11: Sign, Verify, Encrypt, Decrypt, etc. Each of these correspond to a top-level function call in NSS: PK11_Sign, PK11_Verify, etc. So, once you've set up the context, you essentially get away with calling one of those methods, and then you'll have the result. But anyways... let's start at the top.

You create a new javax.crypto.Mac instance of type CmacAES (or one of its aliases). This gets backed by a JSSMacSpi instance of subclass CmacAES. All this does is initialize the JSSMacSpi instance with the CMACAlgorithm.AES algorithm. This is an instance of org.mozilla.jss.crypto.CMACAlgorithm with the correct PKCS#11 constant (er, proxies). [*]

Anyhow, so when you call Mac.init, it calls into PK11MessageDigest.initHMAC(...), which creates the PKCS#11 mechanism, makes sure the key is enabled for signing, and then creates the PKCS#11 context.

Each call to Mac.update then calls PK11_DigestOp, and in the end PK11_DigestFinish is called. And to finish; PK11_DigestOp/PK11_DigestFinal is merely a nice wrapper around all of those different PKCS#11 operations.

[*]: This is where I disagree with the construction of org.mozilla.jss.crypto.Algorithm: rather than handling PKCS#11-backed and SecOID-backed algorithms separately, they merge it into one big table of tuples (value, type). Then, they define pretty values like Algorithm.CKM_AES_CMAC = 70, which is an index and not the actual value. They could've instead made all PKCS#11-backed algorithms use, well, the underlying PKCS11Constants values and not made a redundant mapping (that class was added by me since its counterpart in the JDK was removed in JDK9, but that's well before our time)... But that's neither here nor there, and we have org.mozilla.jss.crypto.PKCS11Algorithm to map between them for us.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent info. I will approve since it looked good to me, just wanted a couple of clarifications. I just wanted to make sure I missed some other code that calls down into your new nss cmac code. Thanks!

/* REMEMBER TO UPDATE NUM_ALGS!!! (in Algorithm.h) */
};

Expand Down
2 changes: 1 addition & 1 deletion org/mozilla/jss/crypto/Algorithm.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ typedef struct JSS_AlgInfoStr {
JSS_AlgType type;
} JSS_AlgInfo;

#define NUM_ALGS 70
#define NUM_ALGS 71

extern JSS_AlgInfo JSS_AlgTable[];
extern CK_ULONG JSS_symkeyUsage[];
Expand Down
3 changes: 3 additions & 0 deletions org/mozilla/jss/crypto/Algorithm.java
Original file line number Diff line number Diff line change
Expand Up @@ -241,4 +241,7 @@ public PKCS11Algorithm getEnum() {
protected static final int CKM_SHA256_HMAC=67;
protected static final int CKM_SHA384_HMAC=68;
protected static final int CKM_SHA512_HMAC=69;

// PKCS#11 AES-CMAC
protected static final int CKM_AES_CMAC=70;
}
69 changes: 69 additions & 0 deletions org/mozilla/jss/crypto/CMACAlgorithm.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.jss.crypto;

import java.security.NoSuchAlgorithmException;
import java.util.Hashtable;

import org.mozilla.jss.asn1.OBJECT_IDENTIFIER;

/**
* Algorithms for performing CMACs. These can be used to create
* MessageDigests.
*/
public class CMACAlgorithm extends DigestAlgorithm {

protected CMACAlgorithm(int oidIndex, String name, OBJECT_IDENTIFIER oid,
int outputSize) {
super(oidIndex, name, oid, outputSize);

if (oid != null && oidMap.get(oid) == null) {
oidMap.put(oid, this);
}
}

///////////////////////////////////////////////////////////////////////
// OID mapping
///////////////////////////////////////////////////////////////////////
private static Hashtable<OBJECT_IDENTIFIER, CMACAlgorithm> oidMap = new Hashtable<>();

/**
* Looks up the CMAC algorithm with the given OID.
*
* @param oid OID.
* @return CMAC algorithm.
* @exception NoSuchAlgorithmException If no registered CMAC algorithm
* has the given OID.
*/
public static CMACAlgorithm fromOID(OBJECT_IDENTIFIER oid)
throws NoSuchAlgorithmException
{
CMACAlgorithm alg = oidMap.get(oid);
if (alg == null) {
throw new NoSuchAlgorithmException("No such algorithm for OID: " + oid);
}

return alg;
}

/**
* CMAC AES-X. This is a Message Authentication Code that uses a
* symmetric key together with the AES cipher to create a form of
* signature.
*
* Note that we pass null for the OID here: neither NIST nor any other
* standards body has defined an OID for use with CMAC. Since we use
* a PKCS#11 backend and NSS doesn't otherwise define CMAC based on a
* SEC OID, we don't strictly need one.
*
* We've left the fromOID code (and oid parameter in the constructor) as
* other projects use them for HMACAlgorith. At such time as an OID is
* defined, it can be added here.
*/
public static final CMACAlgorithm AES = new CMACAlgorithm(CKM_AES_CMAC, "AES-CMAC", null, 16);
public static final CMACAlgorithm AES128 = AES;
public static final CMACAlgorithm AES192 = AES;
public static final CMACAlgorithm AES256 = AES;
}
3 changes: 2 additions & 1 deletion org/mozilla/jss/crypto/PKCS11Algorithm.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public enum PKCS11Algorithm {
CKM_SHA_1_HMAC (Algorithm.CKM_SHA_1_HMAC, PKCS11Constants.CKM_SHA_1_HMAC),
CKM_SHA_256_HMAC (Algorithm.CKM_SHA256_HMAC, PKCS11Constants.CKM_SHA256_HMAC),
CKM_SHA_384_HMAC (Algorithm.CKM_SHA384_HMAC, PKCS11Constants.CKM_SHA384_HMAC),
CKM_SHA_512_HMAC (Algorithm.CKM_SHA512_HMAC, PKCS11Constants.CKM_SHA512_HMAC);
CKM_SHA_512_HMAC (Algorithm.CKM_SHA512_HMAC, PKCS11Constants.CKM_SHA512_HMAC),
CKM_AES_CMAC (Algorithm.CKM_AES_CMAC, PKCS11Constants.CKM_AES_CMAC);

// Value from Algorithm's constant -- this is an index into Algorithm's
// table.
Expand Down
6 changes: 3 additions & 3 deletions org/mozilla/jss/pkcs11/PK11MessageDigest.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ public void initHMAC(SymmetricKey key)
throws DigestException, InvalidKeyException
{

if( ! (alg instanceof HMACAlgorithm) ) {
throw new DigestException("Digest is not an HMAC digest");
if( ! (alg instanceof HMACAlgorithm || alg instanceof CMACAlgorithm) ) {
throw new DigestException("Digest is not an HMAC or CMAC digest");
}

reset();
Expand Down Expand Up @@ -90,7 +90,7 @@ public int digest(byte[] outbuf, int offset, int len)
}

public void reset() throws DigestException {
if( ! (alg instanceof HMACAlgorithm) ) {
if( ! (alg instanceof HMACAlgorithm || alg instanceof CMACAlgorithm) ) {
// This is a regular digest, so we have enough information
// to initialize the context
this.digestProxy = initDigest(alg);
Expand Down
11 changes: 9 additions & 2 deletions org/mozilla/jss/provider/javax/crypto/JSSMacSpi.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;

import org.mozilla.jss.crypto.CMACAlgorithm;
import org.mozilla.jss.crypto.CryptoToken;
import org.mozilla.jss.crypto.DigestAlgorithm;
import org.mozilla.jss.crypto.HMACAlgorithm;
import org.mozilla.jss.crypto.JSSMessageDigest;
import org.mozilla.jss.crypto.SecretKeyFacade;
Expand All @@ -21,9 +23,9 @@
class JSSMacSpi extends javax.crypto.MacSpi {

private JSSMessageDigest digest=null;
private HMACAlgorithm alg;
private DigestAlgorithm alg;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@edewata This isn't quite right I now realize (I thought it wasn't, then though it was, now realize it isn't).

DigestAlgorithm defines things like SHA-1, SHA-2, &c.

HMACAlgorithm is a subclass which defines things like HMAC/SHA-1, HMAC-SHA-2-256, &c. These all require keys though, unlike digests (though, some digests are keyed, but none we define are).

CMAC (the algorithm) isn't actually based off of a digest (hash algorithm), but is instead based off a (keyed) cipher.

So technically, we should have a class Structure like:

Algorithm
  DigestAlgorithm
  MACAlgorithm
    CMACAlgorithm
    HMACAlgorithm

But currently we have the following class structure:

Algorithm
  DigestAlgorithm
    CMACAlgorithm
    HMACAlgorithm

Lastly, these are protected so end-users won't directly be constructing them. I see the following actions we could take:

  1. Be happy how it is.
  2. Add an explicit check for HMAC or CMAC subclass.
  3. (A breaking change for anyone using HMACAlgorithm) Fix the class structure.
  4. Make CMAC inherit from Algorithm or a new MACAlgorithm, not DigestAlgorithm, checking either for HMACAlgorithm or MACAlgorithm.

Note that any checking that we would do here is limited to developers adding new HMAC/CMAC/*MAC implementations and making their lives easier.

Thoughts?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(UMAC is the only other MAC implementation I could see someone wanting to add, but NSS doesn't yet support it).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm OK with #3 for a new JSS minor version (4.7?). If we want to keep the current minor version we can use any other options to provide the new functionality without introducing incompatible changes, but consider that as a temporary solution until we fix the structure. As long as we document the changes clearly it should be OK to require people to change their code when upgrading JSS.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we add that as a second patchset after this merges, since it involves changing both HMAC and CMAC, and we can do additional simplifications internally?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, thanks!


protected JSSMacSpi(HMACAlgorithm alg) {
protected JSSMacSpi(DigestAlgorithm alg) {
try {
this.alg = alg;
CryptoToken token =
Expand Down Expand Up @@ -116,4 +118,9 @@ public HmacSHA512() {
}
}

public static class CmacAES extends JSSMacSpi {
public CmacAES() {
super(CMACAlgorithm.AES);
}
}
}
89 changes: 89 additions & 0 deletions org/mozilla/jss/tests/TestCmac.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.mozilla.jss.tests;

import java.util.Arrays;
import java.util.Base64;
import java.security.Key;

import javax.crypto.*;
import javax.crypto.spec.*;

import org.mozilla.jss.*;
import org.mozilla.jss.crypto.*;
import org.mozilla.jss.util.*;

public class TestCmac {
private static final byte[] NIST_128 = Base64.getDecoder().decode("K34VFiiu0qar9xWICc9PPA==");
private static final byte[] NIST_192 = Base64.getDecoder().decode("jnOw99oOZFLIEPMrgJB55WL46tJSLGt7");
private static final byte[] NIST_256 = Base64.getDecoder().decode("YD3rEBXKcb4rc67whX13gR81LAc7YQjXLZgQowkU3/Q=");

public static void main(String[] args) throws Exception {
CryptoManager.initialize(args[0]);
CryptoManager cm = CryptoManager.getInstance();
CryptoToken tok = cm.getInternalKeyStorageToken();
PasswordCallback cb = new FilePasswordCallback(args[1]);
tok.login(cb);

testNISTExamples();
}

/*
* The following test vectors come from NIST's Examples with Intermediate
* Values page:
* https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines/example-values
*
* These are the same vectors utilized by NSS in:
* gtests/freebl_gtest/cmac_unittests.cc
*
* These same vectors are also found in FRC 4493, Section 4.
*/
public static void testNISTExamples() throws Exception {
byte[] all_input = Base64.getDecoder().decode("a8G+4i5An5bpPX4Rc5MXKq4tilceA6ycnrdvrEWvjlEwyBxGo1zkEeX7wRkaClLv9p8kRd9PmxetK0F75mw3EA==");
int[] input_lengths = new int[] { 0, 16, 20, 64};

byte[][] all_expected = new byte[][] {
Base64.getDecoder().decode("ux1pKelZNyh/o30Sm3VnRg=="),
Base64.getDecoder().decode("BwoWtGtNQUT3m92d0EoofA=="),
Base64.getDecoder().decode("fYVEnqbqGcgjp794g3363g=="),
Base64.getDecoder().decode("UfC+v347nZL8SXQXeTY8/g=="),
Base64.getDecoder().decode("0X3fRq2qzeUxysSD3nqTZw=="),
Base64.getDecoder().decode("npmnvzHnEJAGYvZeYXxRhA=="),
Base64.getDecoder().decode("PXXBlO2WBwREqfp+x0Ds+A=="),
Base64.getDecoder().decode("odXfDu15D3lNd1iWWfOaEQ=="),
Base64.getDecoder().decode("Aoli9ht7+J78a1UfRmfZgw=="),
Base64.getDecoder().decode("KKcCP0Uuj4K9S/KNjDfDXA=="),
Base64.getDecoder().decode("FWcn3Ah4lEoCPB/gO61tkw=="),
Base64.getDecoder().decode("4ZkhkFSfbtVpaiwFbDFUEA==")
};

for (int i = 0; i < all_expected.length; i++) {
byte[] key = getKey(i);
byte[] input = Arrays.copyOf(all_input, input_lengths[i % input_lengths.length]);
byte[] expected = all_expected[i];

testCMAC(key, input, expected);
}
}

public static byte[] getKey(int index) {
if (index < 4) {
return NIST_128;
} else if (index < 8) {
return NIST_192;
} else if (index < 12) {
return NIST_256;
}

return null;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question:

In TPS/TKS we start with a symmetric key such as the master key and then send that down into the routine that calculates the CMAC. The key will be on the hsm possibly as a SymmetricKey object variable. How do we use this from JSS from that starting point? The test examples start from byte array keys, which is what you expect in the HMAC stuff.

Would it be similar to what I found in JSS here:

// perform the digesting
JSSMessageDigest digest = token.getDigestContext(HMACAlgorithm.SHA1);
digest.initHMAC(key);
byte[] digestBytes = digest.digest(toBeMACed);
Or is there a way to use the nice provider support we have here starting with a sym key object?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So there's two interfaces and a class in question here... and a pointer to the PKI code might be nice. But I'll assume the following is correct. :)

Classes/Interfaces in question:

Everything that JSS uses has to be an instance of SecretKeyFacade. Internally, the key member (of interface SymmetricKey) is usually of type org.mozilla.jss.pkcs11.PK11SymKey. The latter includes additional information like algorithm, and a NSS PK11SymKey pointer. So when you say you have a SymmetricKey instance, you usually actually have a PK11SymKey instance. But anyways...

To go from SymmetricKey to something usable by the JSS Provider (and, not just the underlying crypto), your easiest option is to do something like:

javax.crypto.SecretKey key = new org.mozilla.jss.crypto.SecretKeyFacade(mySymmetricKey);

That's really what JSSSecretKeyFactorySpi does under the covers.

Note that JSSSecretKeyFactorySpi implements the javax.crypto.SecretKeyFactorySpi SPI class, so it can eventually be called from the Java Provider javax.crypto.SecretKeyFactory member (when the provider is Mozilla-JSS).

That brings us full-circle: if you have bytes, you can use the SecretKeyFactory with a correct spec class to get a SecretKey instance to use, otherwise, if you already have a PKCS#11-backed key from JSS, you can just use SecretKeyFacade to wrap it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #271. I think we can support just passing SymmetricKey directly here and be better off.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is the info I wanted! Just double checking I can do what we need :)

public static void testCMAC(byte[] key_bytes, byte[] input, byte[] expected) throws Exception {
Mac mac = Mac.getInstance("AES_CMAC", "Mozilla-JSS");
SecretKeyFactory factory = SecretKeyFactory.getInstance("AES", "Mozilla-JSS");
Key key = factory.generateSecret(new SecretKeySpec(key_bytes, "AES"));
mac.init(key);

byte[] actual = mac.doFinal(input);

assert(Arrays.equals(actual, expected));
}
}