Project

General

Profile

Feature #3075

Design support for SignatureHmacWithSha256

Added by Jeff Thompson almost 5 years ago. Updated 9 days ago.

Status:
In Progress
Priority:
Normal
Assignee:
Category:
Security
Target version:
-
Start date:
Due date:
% Done:

70%

Estimated time:

Description

The signature format SignatureHmacWithSha256 has been approved.

Need to design support HMAC signatures where the shared key is accessed with a keyName.

Ideas:

A simple option is to support HMAC keys in SecTpm::signInTpm.

Then, if KeyChain::prepareSignatureInfo supports HMAC signatures, then the existing KeyChain::signImpl will work.


Related issues

Related to ndn-cxx - Task #5121: Reserve and enforce restriction for /localhost/identity namespaceCode reviewAlex Afanasyev

Actions
Blocked by ndn-cxx - Task #2926: Refactor KeyChainClosedYingdi Yu

Actions
#1

Updated by Junxiao Shi almost 5 years ago

  • Description updated (diff)
  • Category set to Security
  • Start date deleted (07/30/2015)

With an HMAC key, there's no division of public key (lives in PIB) and private key (lives in TPM).

If we place HMAC key bits into PIB, it would be exposed, especially when PIB service backend is being used.

If we don't place HMAC key bits into PIB, the Validator doesn't have access to TPM and won't be able to validate an HMAC signature.


Since HMAC keys are short-lived, and the mapping from KeyLocatorName to key bits is managed by application, do they even need to go into PIB/TPM?

How about: let the application locate the correct key and supply it to KeyChain?

keyChain.sign(pkt, signingWithHmac(hmacKey));
// hmacKey is a object that contains KeyLocatorName and key bits; it's not a Name or iterator in PIB/TPM

I'm unfamiliar with the trust schema API so I don't have a suggestion on the Validator syntax.

#2

Updated by Jeff Thompson almost 5 years ago

If the TPM supports signing the HMAC, then it is possible to provide the TPM object to the validator. The process to verify an HMAC signature is really the same as to sign: you create a new signature with the same key and compare the signature. Therefore, the validate method can get the keyName from the data packet, call the TPM signInTpm(keyName) and compare. If the library doesn't have a way to look up the HMAC key by keyName like with signInTpm(keyName), then it will be hard for validate(data) to work with any data packet.

#3

Updated by Alex Afanasyev over 4 years ago

  • Related to Task #3457: Collect and notify apps about routable prefixes added
#4

Updated by Jeff Thompson over 4 years ago

#5

Updated by Jeff Thompson over 4 years ago

  • Assignee set to Yingdi Yu

After finishing issue #2926, Yingdi plans to spec out static KeyChain methods for symmetric sign and verify, where the application supplies the shared secret. (It won't use the TPM or Validator classes.)

#6

Updated by Alex Afanasyev over 4 years ago

  • Related to deleted (Task #3457: Collect and notify apps about routable prefixes)
#7

Updated by Jeff Thompson almost 4 years ago

This task is still assigned to Yingdi. Is someone else going to look at SignatureHmacWithSha256 support in ndn-cxx? If not, I can propose an API to use in the Common Client Libraries.

#8

Updated by Alex Afanasyev almost 4 years ago

Yes, you can try to get an API and have implementation.

#10

Updated by Alex Afanasyev over 3 years ago

  • Assignee deleted (Yingdi Yu)
#11

Updated by Junxiao Shi over 1 year ago

  • Assignee set to Laqin Fan

Laqin wanted to work on this issue to support his (yet another) secure bootstrapping protocol.

#12

Updated by Davide Pesavento over 1 year ago

  • Tracker changed from Task to Feature
  • Subject changed from Design support SignatureHmacWithSha256 to Design support for SignatureHmacWithSha256
#13

Updated by Junxiao Shi over 1 year ago

Change 5248 patchset1 implements HMAC verification as a free function in ndn-cxx/security/verification-helpers.cpp.
While this API is correct and necessary, two additional APIs are also needed:

  • HMAC should be convertible to PublicKey so that it can be used in ndn::security::transform::VerifierFilter.
  • HMAC should be usable in ValidationPolicyConfig.
#14

Updated by Davide Pesavento over 1 year ago

Junxiao Shi wrote:

  • HMAC should be convertible to PublicKey so that it can be used in ndn::security::transform::VerifierFilter.

I suppose you mean an HMAC *key* here, not the actual HMAC.

Two comments though:

  • VerifierFilter does not support HMAC verification currently. It uses the EVP_DigestVerify* family of functions which are for public-key-based signature schemes (sign and verify are asymmetric). For HMAC the two operations are identical, you just recalculate the signature and compare it. We already have an HmacFilter that can do this (minus the comparison).
  • Not sure if it's a good idea to support HMAC keys in PublicKey, since... well, they're not "public".
#15

Updated by Laqin Fan over 1 year ago

  • Status changed from New to Code review
#16

Updated by Junxiao Shi over 1 year ago

Not sure if it's a good idea to support HMAC keys in PublicKey, since... well, they're not "public".

It could be a dedicated HmacKey type. Passing pointer around is not sustainable: (1) caller must ensure pointer is valid (2) you can’t reference a key stored in Hardware Security Module (3) who’s going to cleanse the memory?

VerifierFilter does not support HMAC verification currently. It uses the EVP_DigestVerify* family of functions which are for public-key-based signature schemes (sign and verify are asymmetric).

From caller point of view it is more natural to support HmacKey type in SignerFilter and VerifierFilter. Internally they can invoke different OpenSSL functions.

HmacFilter should be deprecated because (1) it passes pointer around so is not sustainable (2) it’s ambiguous on whether it does signing or verification.

#17

Updated by Laqin Fan over 1 year ago

Junxiao Shi wrote:

Not sure if it's a good idea to support HMAC keys in PublicKey, since... well, they're not "public".

It could be a dedicated HmacKey type. Passing pointer around is not sustainable: (1) caller must ensure pointer is valid (2) you can’t reference a key stored in Hardware Security Module (3) who’s going to cleanse the memory?

So add EVP_PKEY_HMAC key type in PublicKey? Just as Davide mentions, they are not public, probably the PublicKey needs to be renamed.

#18

Updated by Junxiao Shi over 1 year ago

So add EVP_PKEY_HMAC key type in PublicKey? Just as Davide mentions, they are not public, probably the PublicKey needs to be renamed.

No. HmacKey is a separate type. It’s not a subclass of PublicKey or PrivateKey. Both SignerFilter and VerifierFilter should accept HmacKey in addition to PrivateKey or PublicKey.

#19

Updated by Laqin Fan over 1 year ago

No. HmacKey is a separate type. It’s not a subclass of PublicKey or PrivateKey. Both SignerFilter and VerifierFilter should accept HmacKey in addition to PrivateKey or PublicKey.

We can create an HMAC value with EVP_DigestSignInit, EVP_DigestSignUpdate and EVP_DigestSignFinal. And, to verify an HMAC value over the message using the same EVP_DigestSign functions. HmacFilter already does this.

And, VerifierFilter is using EVP_DigestVerify to verify a signature.

#20

Updated by Junxiao Shi over 1 year ago

ndn-cxx using OpenSSL is an implementation detail. What OpenSSL does is irrelevant to what ndn-cxx should expose to its callers.

#21

Updated by Junxiao Shi over 1 year ago

  • Status changed from Code review to Feedback

does the application provides the key to the SigningInfo?
Otherwise how do we retrieve the key during signing? Others do it via tpm.

TPM is one way to do it, but I'm unsure about whether storing HMAC keys should be TPM responsibility.
Neither macOS Keychain nor GNOME Keyring stores HMAC keys, so there's no precedent.
Both NDN-Lite and esp8266ndn let application manage HMAC keys.

Thus, I would suggest non-TPM APIs like this:

// ---- HmacKey construction ----

// default constructed key is invalid
ndn::security::HmacKey key;
// Attempting to use an invalid key raises an exception.

// make a key from memory (caller is responsible for cleansing the buffer)
ndn::security::HmacKey key("/key-name",
  {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
   0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
   0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
   0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
   0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
   0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
  48);

// generate a random key
uint8_t keyBits[32]; // generated key is written into this array
auto key = ndn::security::HmacKey::generate("/key-name", keyBits, sizeof(keyBits));
// There's no public method on the HmacKey type to export the key bits.
// If HmacKey internally stores plain key bits, its destructor must cleanse memory.


// ---- transforms ----
{
  namespace t = ndn::security::transform;

  uint8_t input[] = {0xCC, 0xCC, 0xCC, 0xCC};
  std::stringstream sigBuf;
  t::bufferSource(input, sizeof(input)) >>
    t::signerFilter(ndn::DigestAlgorithm::SHA256, key) >>
    t::streamSink(sigBuf);

  std::string sig = sigBuf.str();
  bool ok;
  t::bufferSource(input, sizeof(input)) >>
    t::verifierFilter(ndn::DigestAlgorithm::SHA256, key, reinterpret_cast<const uint8_t*>(sig.data()), sig.size()) >>
    t::boolSink(ok);

  // t::hmacFilter is deprecated in favor of t::signerFilter
}


// ---- signing via KeyChain ----

// construct SigningInfo
ndn::security::SigningInfo si(key);
// create SigningInfo via helper
auto si = ndn::security::signingByHmac(key);

ndn::security::KeyChain keyChain;
ndn::Data data("/data-name");
keyChain.sign(data, si);


// ---- verification helper ----
bool ok = ndn::security::verifyHmac(data, key);


// ---- ValidatorConfig ----
ndn::security::ValidatorConfig validator;
validator.load(R"CONF(
  rule {
    id 0
    for data
    checker {
      type customized
      sig-type hmac-sha256
      key-locator {
        type name
        name /key-name
        relation equal
      }
    }
  }
  hmac-key {
    name /key-name
    type base64
    base64-string "DDDD" ; base64 encoded HMAC key
  }
)CONF";)
#22

Updated by Laqin Fan over 1 year ago

// ---- signing via KeyChain ----

// construct SigningInfo
ndn::security::SigningInfo si(key);
// create SigningInfo via helper
auto si = ndn::security::signingByHmac(key);

ndn::security::KeyChain keyChain;
ndn::Data data("/data-name");
keyChain.sign(data, si);

In KeyChain, prepareSignatureInfo() returns keyname and SignatureInfo, then how to retrieve hmackey using the keyname in later sign(encoder.buf(), encoder.size(), keyName, params.getDigestAlgorithm()).

#23

Updated by Junxiao Shi over 1 year ago

In KeyChain, prepareSignatureInfo() returns keyname and SignatureInfo, then how to retrieve hmackey using the keyname in later sign(encoder.buf(), encoder.size(), keyName, params.getDigestAlgorithm()).

This means you have to refactor KeyChain type to retain the necessary information.

#24

Updated by Junxiao Shi over 1 year ago

One word on the benefit of having an HmacKey type: key protection.
Suppose a private HMAC key is stored on the filesystem in a file owned and only readable by root. A program can read this file as root, construct HmacKey instance, cleanse memory, and drop root privilege.
Pairing this with keeping only an EVP_PKEY* within HmacKey type instead of saving the key buffer, it reduces the risk of leaking the key.
This is also the reason existing HmacFilter is bad API design and should be deprecated.

#25

Updated by Laqin Fan over 1 year ago

Junxiao Shi wrote:
One word on the benefit of having an HmacKey type: key protection.
Suppose a private HMAC key is stored on the filesystem in a file owned and only readable by root. A program can read this file as root, construct HmacKey instance, cleanse memory, and drop root privilege.
Pairing this with keeping only an EVP_PKEY* within HmacKey type instead of saving the key buffer, it reduces the risk of leaking the key.
This is also the reason existing HmacFilter is bad API design and should be deprecated.

I think HmacFilter can still be used for hmac calculation. But the inputs can only be digest algorithm and HmacKey, similar with SignerFilter.

#26

Updated by Junxiao Shi over 1 year ago

I think HmacFilter can still be used for hmac calculation. But the inputs can only be digest algorithm and HmacKey, similar with SignerFilter.

No. The name “HmacFilter” is ambiguous on whether it performs signing or verification. Having SignerFilter and VerifierFilter accept HmacKey avoids this ambiguity.

#27

Updated by Junxiao Shi over 1 year ago

key name could be provided as parameter of SigningInfo, SigningInfo(*hmacKey, keyName), then we could get signerName to set keyLocator.

This looks good on signing side, but does not work well in ValidatorConfig, because HMAC keys do not go into the PIB.
Every RSA/EC key has a name that goes with it, not with the SigningInfo. However, they get the name at PIB layer, not transform layer.
note-21 design injects the name at transform layer. If we don't want a name in transform::HmacKey, we'll need another class that wraps an HMAC key and a name, for use in both SigningInfo and ValidatorConfig.

#28

Updated by Davide Pesavento over 1 year ago

Junxiao Shi wrote:

note-21 design injects the name at transform layer.

I don't love this approach, although not a strong opinion...

If we don't want a name in transform::HmacKey, we'll need another class that wraps an HMAC key and a name, for use in both SigningInfo and ValidatorConfig.

I can't tell if this is better or worse than having the name in transform::HmacKey

#29

Updated by Junxiao Shi over 1 year ago

20190320 NFD call decides to store HMAC keys as "passwords" in TPM, in order to effectively support Validator API. Given this complication, I suggest splitting into multiple commits:

  1. security: HMAC transforms
    • HmacKey as in note-21, except that it does not have a name
    • signerFilter and verifierFilter supports HmacKey
    • deprecated ill-designed hmacFilter
  2. security: verifyHmac helper
    • ndn::security::verifyHmac as in note-21
  3. security: store HMAC key in TPM
  4. security: reference HMAC key in PIB
  5. security: HMAC signing in KeyChain
  6. security: HMAC validation

The first two can already start. The rest needs to wait for Alex's design.

#30

Updated by Laqin Fan about 1 year ago

Junxiao Shi wrote:
20190320 NFD call decides to store HMAC keys as "passwords" in TPM, in order to effectively support Validator API. Given this complication, I suggest splitting into multiple commits:

  1. security: HMAC transforms
    • HmacKey as in note-21, except that it does not have a name
    • signerFilter and verifierFilter supports HmacKey
    • deprecated ill-designed hmacFilter
  2. security: verifyHmac helper
    • ndn::security::verifyHmac as in note-21
  3. security: store HMAC key in TPM
  4. security: reference HMAC key in PIB
  5. security: HMAC signing in KeyChain
  6. security: HMAC validation

The first two can already start. The rest needs to wait for Alex's design.

Based on the first two, here is a draft for hmac tpm design:

  1. security: store HMAC key in TPM
    • Create 'hmac key' mapping with an identity name. After generating the hmacHey, add the keyName into Identity (that is to add hmac Keyname into Pib)
  2. security: reference HMAC key in PIB
    • HmacKeyname is in Pib, we can use the keyName to find Hmackey in TPM for validation

Details:

//'''In Keychain class''':

// Create hmac identity
Identity
KeyChain::createHmacIdentity(identityName, params)
{ Identity id = m_pib->addIdentity(identityName);
  hmacIdentity = createHmacKey(id, params);
}

Identity
KeyChain::createHmacKey(identity,  params){
  keyName = m_tpm->createKey(identity.getName(), params);
  Identity id = m_pib->addIdentity(keyName);
}

// '''In Tpm class:'''
Name
Tpm::crerateKey(identityName, keyParam)
{ // based on KeyType (RSA, EC, or Hmac)
  Keyhandle = m_backend.createKey(identityName, keyparam);
  Name = keyhandle.getKeyName();
}

// In BackEnd class:(backend implementation for key handle)
KeyHandle
BackEnd::createKey( identityName, keyparams){
  // based on KeyIdType (user_specified, sha256, random)
  //generate Keyhandle, and setKeyName
  transform::generateHmacKey(keyparams);
  setKeyName(*keyHandle, identityName, params);
}
#31

Updated by Alex Afanasyev about 1 year ago

There is no need to make changes in KeyChain class. Currently, we have some support for Aes keys, but one cannot really be created.

So, my suggestion:

  • Create HmacKeyParams or directly reuse AesKeyParams for hmac purposes (the former is more appropriate for me; if so, AesKeyParams may need to be removed as it is unusable).

  • Add support to generate AES key in Tpm::createKey (right now it throws exception)

  • Add support for AES/hmac in transform::PrivateKey. Given there is no public key, all corresponding "derivation" methods should throw error for Aes/hmac

  • Add support for Hmac in signedFilter (this derives from PrivateKey support). No changes in verifyFillter, as verification of hmac is done through computing signature again and comparing them.

We will need to figure out naming of the hmac identities, but I think these we can figure out as we go.

#32

Updated by Junxiao Shi about 1 year ago

generate AES key in Tpm::createKey (right now it throws exception)

We are working on HMAC, not AES. HMAC key is similar to AES key, but not exactly the same: it needs to match hash function block length.

Hmac in signedFilter (this derives from PrivateKey support)

Yes, this is same as note-21.

No changes in verifyFilter, as verification of hmac is done through computing signature again and comparing them.

No, verifyFilter changes are necessary, so that programmer does not need to deal with constant-time memcmp directly.
Internally it would perform a signature computation and a constant-time memcmp, and write the outcome to boolSink.

#33

Updated by Davide Pesavento about 1 year ago

  • Status changed from Feedback to In Progress
  • % Done changed from 0 to 20
#34

Updated by Laqin Fan about 1 year ago

There is no need to make changes in KeyChain class. Currently, we have some support for Aes keys, but one cannot really be created.

If we wanna add HMAC signature support in KeyChain, we may need to change KeyChain a little to support HMAC key creation and importing. The existing KeyChain::createKey() returns Key which may not be applied to HMAC.

#35

Updated by Davide Pesavento about 1 year ago

Should we support specifying an HMAC key inline in the SigningInfo string representation? i.e. something like "hmac:<base64-encoded-key>".
One use case is being able to use HMAC signing with ndnputchunks, which already has a command-line option (-S) for the signing string. Otherwise HMAC signing would be unusable without some modifications to the program, given that the current direction seems to be that HMAC keys are supported only by the in-memory TPM, thus the user cannot "preload" the key into the TPM via ndnsec and later reference it by name.

#36

Updated by Laqin Fan about 1 year ago

Regarding HMAC validation, I think we could keep using Junxiao's validator config design.

// ---- ValidatorConfig ----
ndn::security::ValidatorConfig validator;
validator.load(R"CONF(
  rule {
    id 0
    for data
    checker {
      type customized
      sig-type hmac-sha256
      key-locator {
        type name
        name /key-name
        relation equal
      }
    }
  }
  hmac-key {
    name /key-name
    type base64
    base64-string "DDDD" ; base64 encoded HMAC key
  }
)CONF";)
  1. The base64-encoded-key provided inline in the config file, we can load it into TPM alongside with its keyname. 2. To validate data/interest, we look for HMAC key in TPM using the keyname.
#37

Updated by Laqin Fan about 1 year ago

Regarding HMAC validation, I think we could keep using Junxiao's validator config design.

// ---- ValidatorConfig ----
ndn::security::ValidatorConfig validator;
validator.load(R"CONF(
  rule {
    id 0
    for data
    checker {
      type customized
      sig-type hmac-sha256
      key-locator {
        type name
        name /key-name
        relation equal
      }
    }
  }
  hmac-key {
    name /key-name
    type base64
    base64-string "DDDD" ; base64 encoded HMAC key
  }
)CONF";)
  1. The base64-encoded-key provided inline in the config file, we can load it into TPM alongside with its keyname. 2. To validate data/interest, we look for HMAC key in TPM using the keyname.

However, Validator cannot get Tpm (tpm is only maintained by KeyChain), so there is no way to load hmac key into tpm in the beginning and later to look for key in TPM to validate data/interest.

How about we create one more container to store hmac key for validation(similar to CertificateStorage)? If so, in verification-helper, we cannot use keyhandle/tpm as the parameter in verifySignature() overloads, but use hmac key instead.

But hmac key should be private, not sure this key container works or not.

#38

Updated by Junxiao Shi 12 months ago

Should we support specifying an HMAC key inline in the SigningInfo string representation? i.e. something like "hmac:<base64-encoded-key>".

A question triggered by this is, whether SigningInfo's stream insertion operator should print the HMAC key like its input format?
My opinion is no, because allowing exporting a private key would be a security vulnerability.
I'd suggest printing hmac:<redacted>.

Just modify "createIdentity" method, so it would create HMAC "identity" (and key). for HMAC identity, you can throw if one wants to add another key, but I think it should be sufficient.

We can use a single identity /localhost/identity/hmac-sha256 as a container of all HMAC keys. The key name has an additional component that is the SHA256 digest of the key. Certificates are prohibited under this identity.

There's no concern on clashing key names because /localhost/identity is a reserved namespace, since the era when /localhost/identity/digest-sha256 was introduced.

#39

Updated by Davide Pesavento 12 months ago

Junxiao Shi wrote:

A question triggered by this is, whether SigningInfo's stream insertion operator should print the HMAC key like its input format?
My opinion is no, because allowing exporting a private key would be a security vulnerability.
I'd suggest printing hmac:<redacted>.

This has already been discussed on gerrit. Currently it prints hmac:/localhost/HMAC/<key-digest>, which is inconsistent with the syntax accepted by the constructor, but I don't really care, and we don't make any promise that operator<< follows the same format anyway.

There's no concern on clashing key names because /localhost/identity is a reserved namespace, since the era when /localhost/identity/digest-sha256 was introduced.

Is this documented anywhere? Does the code enforce this restriction?

#40

Updated by Davide Pesavento 9 months ago

  • % Done changed from 20 to 50
#41

Updated by Davide Pesavento 8 months ago

  • % Done changed from 50 to 70
#42

Updated by Davide Pesavento about 1 month ago

  • Tags set to security
#43

Updated by Alex Afanasyev 9 days ago

  • Related to Task #5121: Reserve and enforce restriction for /localhost/identity namespace added
#44

Updated by Alex Afanasyev 9 days ago

There's no concern on clashing key names because /localhost/identity is a reserved namespace, since the era when /localhost/identity/digest-sha256 was introduced.

Is this documented anywhere? Does the code enforce this restriction?

I don't think it is documented yet and we don't have enforcement too :( (just created #5121)

Also available in: Atom PDF