All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.named_data.jndn.encrypt.Consumer Maven / Gradle / Ivy

Go to download

jNDN is a new implementation of a Named Data Networking client library written in Java. It is wire format compatible with the new NDN-TLV encoding, with NDNx and PARC's CCNx.

There is a newer version: 0.25
Show newest version
/**
 * Copyright (C) 2015-2017 Regents of the University of California.
 * @author: Jeff Thompson 
 * @author: From ndn-group-encrypt src/consumer https://github.com/named-data/ndn-group-encrypt
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 * A copy of the GNU Lesser General Public License is in the file COPYING.
 */

package net.named_data.jndn.encrypt;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.named_data.jndn.Data;
import net.named_data.jndn.Face;
import net.named_data.jndn.Interest;
import net.named_data.jndn.Link;
import net.named_data.jndn.Name;
import net.named_data.jndn.NetworkNack;
import net.named_data.jndn.OnData;
import net.named_data.jndn.OnNetworkNack;
import net.named_data.jndn.OnTimeout;
import net.named_data.jndn.encoding.EncodingException;
import net.named_data.jndn.encrypt.EncryptError.ErrorCode;
import net.named_data.jndn.encrypt.EncryptError.OnError;
import net.named_data.jndn.encrypt.algo.AesAlgorithm;
import net.named_data.jndn.encrypt.algo.EncryptAlgorithmType;
import net.named_data.jndn.encrypt.algo.EncryptParams;
import net.named_data.jndn.encrypt.algo.Encryptor;
import net.named_data.jndn.encrypt.algo.RsaAlgorithm;
import net.named_data.jndn.security.KeyChain;
import net.named_data.jndn.security.OnVerified;
import net.named_data.jndn.security.OnDataValidationFailed;
import net.named_data.jndn.security.SecurityException;
import net.named_data.jndn.util.Blob;

/**
 * A Consumer manages fetched group keys used to decrypt a data packet in the
 * group-based encryption protocol.
 * @note This class is an experimental feature. The API may change.
 */
public class Consumer {
  /**
   * Create a Consumer to use the given ConsumerDb, Face and other values.
   * @param face The face used for data packet and key fetching.
   * @param keyChain The keyChain used to verify data packets.
   * @param groupName The reading group name that the consumer belongs to.
   * This makes a copy of the Name.
   * @param consumerName The identity of the consumer. This makes a copy of the
   * Name.
   * @param database The ConsumerDb database for storing decryption keys.
   * @param cKeyLink The Link object to use in Interests for C-KEY retrieval.
   * This makes a copy of the Link object. If the Link object's
   * getDelegations().size() is zero, don't use it.
   * @param dKeyLink The Link object to use in Interests for D-KEY retrieval.
   * This makes a copy of the Link object. If the Link object's
   * getDelegations().size() is zero, don't use it.
   */
  public Consumer
    (Face face, KeyChain keyChain, Name groupName, Name consumerName,
     ConsumerDb database, Link cKeyLink, Link dKeyLink)
  {
    database_ = database;
    keyChain_ = keyChain;
    face_ = face;
    groupName_ = new Name(groupName);
    consumerName_ = new Name(consumerName);
    // Copy the Link object.
    cKeyLink_ = new Link(cKeyLink);
    dKeyLink_ = new Link(dKeyLink);
  }

  /**
   * Create a Consumer to use the given ConsumerDb, Face and other values.
   * @param face The face used for data packet and key fetching.
   * @param keyChain The keyChain used to verify data packets.
   * @param groupName The reading group name that the consumer belongs to.
   * This makes a copy of the Name.
   * @param consumerName The identity of the consumer. This makes a copy of the
   * Name.
   * @param database The ConsumerDb database for storing decryption keys.
   */
  public Consumer
    (Face face, KeyChain keyChain, Name groupName, Name consumerName,
     ConsumerDb database)
  {
    database_ = database;
    keyChain_ = keyChain;
    face_ = face;
    groupName_ = new Name(groupName);
    consumerName_ = new Name(consumerName);
    cKeyLink_ = NO_LINK;
    dKeyLink_ = NO_LINK;
  }

  public interface OnConsumeComplete {
    void onConsumeComplete(Data data, Blob result);
  }

  /**
   * Express an Interest to fetch the content packet with contentName, and
   * decrypt it, fetching keys as needed.
   * @param contentName The name of the content packet.
   * @param onConsumeComplete When the content packet is fetched and decrypted,
   * this calls onConsumeComplete.onConsumeComplete(contentData, result) where
   * contentData is the fetched Data packet and result is the decrypted plain
   * text Blob.
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param onError This calls onError.onError(errorCode, message) for an error.
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param link The Link object to use in Interests for data retrieval. This
   * makes a copy of the Link object. If the Link object's
   * getDelegations().size() is zero, don't use it.
   */
  public final void
  consume
    (Name contentName, final OnConsumeComplete onConsumeComplete,
     final OnError onError, Link link)
  {
    Interest interest = new Interest(contentName);
    // Copy the Link object since the passed link may become invalid.
    sendInterest
      (interest, 1, new Link(link),
       new OnVerified() {
         public void onVerified(final Data validData) {
           // Decrypt the content.
           decryptContent
             (validData,
              new OnPlainText() {
                public void onPlainText(Blob plainText) {
                  try {
                    onConsumeComplete.onConsumeComplete(validData, plainText);
                  } catch (Exception ex) {
                    logger_.log(Level.SEVERE, "Error in onConsumeComplete", ex);
                  }
                }
              },
              onError);
         }
       },
       onError);
  }

  /**
   * Express an Interest to fetch the content packet with contentName, and
   * decrypt it, fetching keys as needed.
   * @param contentName The name of the content packet.
   * @param onConsumeComplete When the content packet is fetched and decrypted,
   * this calls onConsumeComplete.onConsumeComplete(contentData, result) where
   * contentData is the fetched Data packet and result is the decrypted plain
   * text Blob.
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param onError This calls onError.onError(errorCode, message) for an error.
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   */
  public final void
  consume
    (Name contentName, final OnConsumeComplete onConsumeComplete,
     final OnError onError)
  {
    consume(contentName, onConsumeComplete, onError, NO_LINK);
  }

  /**
   * Set the group name.
   * @param groupName The reading group name that the consumer belongs to.
   * This makes a copy of the Name.
   */
  public final void
  setGroup(Name groupName) { groupName_ = new Name(groupName); }

  /**
   * Add a new decryption key with keyName and keyBlob to the database.
   * @param keyName The key name.
   * @param keyBlob The encoded key.
   * @throws ConsumerDb.Error if a key with the same keyName already exists in
   * the database, or other database error.
   * @throws Error if the consumer name is not a prefix of the key name.
   */
  public final void
  addDecryptionKey(Name keyName, Blob keyBlob) throws ConsumerDb.Error
  {
    if (!(consumerName_.match(keyName)))
      throw new Error
        ("addDecryptionKey: The consumer name must be a prefix of the key name");

    database_.addKey(keyName, keyBlob);
  }

  public interface OnPlainText {
    void onPlainText(Blob plainText);
  }

  /**
   * Decode encryptedBlob as an EncryptedContent and decrypt using keyBits.
   * @param encryptedBlob The encoded EncryptedContent to decrypt.
   * @param keyBits The key value.
   * @param onPlainText When encryptedBlob is decrypted, this calls
   * onPlainText.onPlainText(decryptedBlob) with the decrypted blob.
   * @param onError This calls onError.onError(errorCode, message) for an error.
   */
  private static void
  decrypt
    (Blob encryptedBlob, Blob keyBits, OnPlainText onPlainText, OnError onError)
  {
    EncryptedContent encryptedContent = new EncryptedContent();
    try {
      encryptedContent.wireDecode(encryptedBlob);
    } catch (EncodingException ex) {
      try {
        onError.onError(ErrorCode.InvalidEncryptedFormat, ex.getMessage());
      } catch (Exception exception) {
        logger_.log(Level.SEVERE, "Error in onError", exception);
      }
      return;
    }

    decrypt(encryptedContent, keyBits, onPlainText, onError);
  }

  /**
   * Decrypt encryptedContent using keyBits.
   * @param encryptedContent The EncryptedContent to decrypt.
   * @param keyBits The key value.
   * @param onPlainText When encryptedBlob is decrypted, this calls
   * onPlainText.onPlainText(decryptedBlob) with the decrypted blob.
   * @param onError This calls onError.onError(errorCode, message) for an error.
   */
  private static void
  decrypt
    (EncryptedContent encryptedContent, Blob keyBits, OnPlainText onPlainText,
     OnError onError)
  {
    Blob payload = encryptedContent.getPayload();

    if (encryptedContent.getAlgorithmType() == EncryptAlgorithmType.AesCbc) {
      // Prepare the parameters.
      EncryptParams decryptParams = new EncryptParams(EncryptAlgorithmType.AesCbc);
      decryptParams.setInitialVector(encryptedContent.getInitialVector());

      // Decrypt the content.
      Blob content;
      try {
        content = AesAlgorithm.decrypt(keyBits, payload, decryptParams);
      } catch (Exception ex) {
        try {
          onError.onError(ErrorCode.InvalidEncryptedFormat, ex.getMessage());
        } catch (Exception exception) {
          logger_.log(Level.SEVERE, "Error in onError", exception);
        }
        return;
      }
      try {
        onPlainText.onPlainText(content);
      } catch (Exception ex) {
        logger_.log(Level.SEVERE, "Error in onPlainText", ex);
      }
    }
    else if (encryptedContent.getAlgorithmType() == EncryptAlgorithmType.RsaOaep) {
      // Prepare the parameters.
      EncryptParams decryptParams = new EncryptParams(EncryptAlgorithmType.RsaOaep);

      // Decrypt the content.
      Blob content;
      try {
        content = RsaAlgorithm.decrypt(keyBits, payload, decryptParams);
      } catch (Exception ex) {
        try {
          onError.onError(ErrorCode.InvalidEncryptedFormat, ex.getMessage());
        } catch (Exception exception) {
          logger_.log(Level.SEVERE, "Error in onError", exception);
        }
        return;
      }
      try {
        onPlainText.onPlainText(content);
      } catch (Exception ex) {
        logger_.log(Level.SEVERE, "Error in onPlainText", ex);
      }
    }
    else {
      try {
        onError.onError
          (ErrorCode.UnsupportedEncryptionScheme,
           "" + encryptedContent.getAlgorithmType());
      } catch (Exception ex) {
        logger_.log(Level.SEVERE, "Error in onError", ex);
      }
    }
  }

  /**
   * Decrypt the data packet.
   * @param data The data packet. This does not verify the packet.
   * @param onPlainText When the data packet is decrypted, this calls
   * onPlainText.onPlainText(decryptedBlob) with the decrypted blob.
   * @param onError This calls onError.onError(errorCode, message) for an error.
   */
  private void
  decryptContent(Data data, final OnPlainText onPlainText, final OnError onError)
  {
    // Get the encrypted content.
    final EncryptedContent dataEncryptedContent = new EncryptedContent();
    try {
      dataEncryptedContent.wireDecode(data.getContent());
    } catch (EncodingException ex) {
      try {
        onError.onError(ErrorCode.InvalidEncryptedFormat, ex.getMessage());
      } catch (Exception exception) {
        logger_.log(Level.SEVERE, "Error in onError", exception);
      }
      return;
    }
    final Name cKeyName = dataEncryptedContent.getKeyLocator().getKeyName();

    // Check if the content key is already in the store.
    Blob cKey = (Blob)cKeyMap_.get(cKeyName);
    if (cKey != null)
      decrypt(dataEncryptedContent, cKey, onPlainText, onError);
    else {
      // Retrieve the C-KEY Data from the network.
      Name interestName = new Name(cKeyName);
      interestName.append(Encryptor.NAME_COMPONENT_FOR).append(groupName_);
      Interest interest = new Interest(interestName);
      sendInterest
        (interest, 1, cKeyLink_,
         new OnVerified() {
           public void onVerified(Data validCKeyData) {
             decryptCKey
               (validCKeyData,
                new OnPlainText() {
                  public void onPlainText(Blob cKeyBits) {
                    // cKeyName is already a copy inside the local dataEncryptedContent.
                    cKeyMap_.put(cKeyName, cKeyBits);
                    decrypt
                      (dataEncryptedContent, cKeyBits, onPlainText, onError);
                  }
                },
                onError);
           }
         },
         onError);
    }
  }

  /**
   * Decrypt cKeyData.
   * @param cKeyData The C-KEY data packet.
   * @param onPlainText When the data packet is decrypted, this calls
   * onPlainText.onPlainText(decryptedBlob) with the decrypted blob.
   * @param onError This calls onError.onError(errorCode, message) for an error.
   */
  private void
  decryptCKey(Data cKeyData, final OnPlainText onPlainText, final OnError onError)
  {
    // Get the encrypted content.
    Blob cKeyContent = cKeyData.getContent();
    final EncryptedContent cKeyEncryptedContent = new EncryptedContent();
    try {
      cKeyEncryptedContent.wireDecode(cKeyContent);
    } catch (EncodingException ex) {
      try {
        onError.onError(ErrorCode.InvalidEncryptedFormat, ex.getMessage());
      } catch (Exception exception) {
        logger_.log(Level.SEVERE, "Error in onError", exception);
      }
      return;
    }
    Name eKeyName = cKeyEncryptedContent.getKeyLocator().getKeyName();
    final Name dKeyName = eKeyName.getPrefix(-3);
    dKeyName.append(Encryptor.NAME_COMPONENT_D_KEY).append(eKeyName.getSubName(-2));

    // Check if the decryption key is already in the store.
    Blob dKey = (Blob)dKeyMap_.get(dKeyName);
    if (dKey != null)
      decrypt(cKeyEncryptedContent, dKey, onPlainText, onError);
    else {
      // Get the D-Key Data.
      Name interestName = new Name(dKeyName);
      interestName.append(Encryptor.NAME_COMPONENT_FOR).append(consumerName_);
      Interest interest = new Interest(interestName);
      sendInterest
        (interest, 1, dKeyLink_,
         new OnVerified() {
           public void onVerified(Data validDKeyData) {
             decryptDKey
               (validDKeyData,
                new OnPlainText() {
                  public void onPlainText(Blob dKeyBits) {
                    // dKeyName is already a local copy.
                    dKeyMap_.put(dKeyName, dKeyBits);
                    decrypt
                      (cKeyEncryptedContent, dKeyBits, onPlainText, onError);
                  }
                },
                onError);
           }
         },
         onError);
    }
  }

  /**
   * Decrypt dKeyData.
   * @param dKeyData The D-KEY data packet.
   * @param onPlainText When the data packet is decrypted, this calls
   * onPlainText.onPlainText(decryptedBlob) with the decrypted blob.
   * @param onError This calls onError.onError(errorCode, message) for an error.
   */
  private void
  decryptDKey(Data dKeyData, OnPlainText onPlainText, final OnError onError)
  {
    // Get the encrypted content.
    Blob dataContent = dKeyData.getContent();

    // Process the nonce.
    // dataContent is a sequence of the two EncryptedContent.
    EncryptedContent encryptedNonce = new EncryptedContent();
    try {
      encryptedNonce.wireDecode(dataContent);
    } catch (EncodingException ex) {
      try {
        onError.onError(ErrorCode.InvalidEncryptedFormat, ex.getMessage());
      } catch (Exception exception) {
        logger_.log(Level.SEVERE, "Error in onError", exception);
      }
      return;
    }
    Name consumerKeyName = encryptedNonce.getKeyLocator().getKeyName();

    // Get consumer decryption key.
    Blob consumerKeyBlob;
    try {
      consumerKeyBlob = getDecryptionKey(consumerKeyName);
    } catch (ConsumerDb.Error ex) {
      try {
        onError.onError(ErrorCode.NoDecryptKey, "Database error: " + ex.getMessage());
      } catch (Exception exception) {
        logger_.log(Level.SEVERE, "Error in onError", exception);
      }
      return;
    }
    if (consumerKeyBlob.size() == 0) {
      try {
        onError.onError(ErrorCode.NoDecryptKey,
          "The desired consumer decryption key in not in the database");
      } catch (Exception exception) {
        logger_.log(Level.SEVERE, "Error in onError", exception);
      }
      return;
    }

    // Process the D-KEY.
    // Use the size of encryptedNonce to find the start of encryptedPayload.
    ByteBuffer encryptedPayloadBuffer = dataContent.buf().duplicate();
    encryptedPayloadBuffer.position(encryptedNonce.wireEncode().size());
    final Blob encryptedPayloadBlob = new Blob(encryptedPayloadBuffer, false);
    if (encryptedPayloadBlob.size() == 0) {
      try {
        onError.onError(ErrorCode.InvalidEncryptedFormat,
          "The data packet does not satisfy the D-KEY packet format");
      } catch (Exception ex) {
        logger_.log(Level.SEVERE, "Error in onError", ex);
      }
      return;
    }

    // Decrypt the D-KEY.
    final OnPlainText callerOnPlainText = onPlainText;
    decrypt
      (encryptedNonce, consumerKeyBlob,
       new Consumer.OnPlainText() {
         public void onPlainText(Blob nonceKeyBits) {
           decrypt(encryptedPayloadBlob, nonceKeyBits, callerOnPlainText, onError);
         }
       },
       onError);
  }

  /**
   * Express the interest, call verifyData for the fetched Data packet and call
   * onVerified if verify succeeds. If verify fails, call
   * onError.onError(ErrorCode.Validation, "verifyData failed"). If the interest
   * times out, re-express nRetrials times. If the interest times out nRetrials
   * times, or for a network Nack, call
   * onError.onError(ErrorCode.DataRetrievalFailure, interest.getName().toUri()).
   * @param interest The Interest to express.
   * @param nRetrials The number of retrials left after a timeout.
   * @param link The Link object to use in the Interest. This does not make a
   * copy of the Link object. If the Link object's getDelegations().size() is
   * zero, don't use it.
   * @param onVerified When the fetched Data packet validation succeeds, this
   * calls onVerified.onVerified(data).
   * @param onError This calls onError.onError(errorCode, message) for an error.
   */
  private void
  sendInterest
    (Interest interest, final int nRetrials, final Link link,
     final OnVerified onVerified, final OnError onError)
  {
    // Prepare the callback functions.
    OnData onData = new OnData() {
      public void onData(Interest contentInterest, final Data contentData) {
        // The Interest has no selectors, so assume the library correctly
        // matched with the Data name before calling onData.

        try {
          keyChain_.verifyData
            (contentData, onVerified,
             new OnDataValidationFailed() {
               public void onDataValidationFailed(Data d, String reason) {
                 try {
                   onError.onError
                     (ErrorCode.Validation, "verifyData failed. Reason: " +
                      reason);
                 } catch (Exception ex) {
                   logger_.log(Level.SEVERE, "Error in onError", ex);
                 }
               }
             });
        } catch (SecurityException ex) {
          try {
            onError.onError
             (ErrorCode.SecurityException, "verifyData error: " + ex.getMessage());
          } catch (Exception exception) {
            logger_.log(Level.SEVERE, "Error in onError", exception);
          }
        }
      }
    };

    final OnNetworkNack onNetworkNack = new OnNetworkNack() {
      public void onNetworkNack(Interest interest, NetworkNack networkNack) {
        // We have run out of options. Report a retrieval failure.
        try {
          onError.onError
            (ErrorCode.DataRetrievalFailure, interest.getName().toUri());
        } catch (Exception exception) {
          logger_.log(Level.SEVERE, "Error in onError", exception);
        }
      }
    };

    OnTimeout onTimeout = new OnTimeout() {
      public void onTimeout(final Interest interest) {
        if (nRetrials > 0)
          sendInterest(interest, nRetrials - 1, link, onVerified, onError);
        else {
          // We have run out of options. Report a retrieval failure.
          try {
            onError.onError
              (ErrorCode.DataRetrievalFailure, interest.getName().toUri());
          } catch (Exception exception) {
            logger_.log(Level.SEVERE, "Error in onError", exception);
          }
        }
      }
    };

    Interest request;
    if (link.getDelegations().size() == 0)
      // We can use the supplied interest without copying.
      request = interest;
    else {
      // Copy the supplied interest and add the Link.
      request = new Interest(interest);
      // This will use a cached encoding if available.
      request.setLinkWireEncoding(link.wireEncode());
    }

    try {
      face_.expressInterest(request, onData, onTimeout, onNetworkNack);
    } catch (IOException ex) {
      try {
        onError.onError
         (ErrorCode.IOException, "expressInterest error: " + ex.getMessage());
      } catch (Exception exception) {
        logger_.log(Level.SEVERE, "Error in onError", exception);
      }
    }
  }

  /**
   * Get the encoded blob of the decryption key with decryptionKeyName from the
   * database.
   * @param decryptionKeyName The key name.
   * @return A Blob with the encoded key, or an isNull Blob if cannot find the
   * key with decryptionKeyName.
   * @throws ConsumerDb.Error for a database error.
   */
  private Blob
  getDecryptionKey(Name decryptionKeyName) throws ConsumerDb.Error
  {
    return database_.getKey(decryptionKeyName);
  }

  /**
   * A class implements Friend if it has a method setConsumerFriendAccess
   * which setFriendAccess calls to set the FriendAccess object.
   */
  public interface Friend {
    void setConsumerFriendAccess(FriendAccess friendAccess);
  }

  /**
   * Call friend.setConsumerFriendAccess to pass an instance of
   * a FriendAccess class to allow a friend class to call private methods.
   * @param friend The friend class for calling setConsumerFriendAccess.
   * This uses friend.getClass() to make sure that it is a friend class.
   * Therefore, only a friend class gets an implementation of FriendAccess.
   */
  public static void setFriendAccess(Friend friend)
  {
    if (friend.getClass().getName().endsWith
          ("net.named_data.jndn.tests.integration_tests.TestGroupConsumer"))
    {
      friend.setConsumerFriendAccess(new FriendAccessImpl());
    }
  }

  /**
   * A friend class can call the methods of FriendAccess to access private
   * methods.  This abstract class is public, but setFriendAccess passes an
   * instance of a private class which implements the methods.
   */
  public abstract static class FriendAccess {
    public abstract void
    decrypt
      (Blob encryptedBlob, Blob keyBits, OnPlainText onPlainText,
       OnError onError);
  }

  /**
   * setFriendAccess passes an instance of this private class which implements
   * the FriendAccess methods.
   */
  private static class FriendAccessImpl extends FriendAccess {
    public void
    decrypt
      (Blob encryptedBlob, Blob keyBits, OnPlainText onPlainText,
       OnError onError)
    {
      Consumer.decrypt(encryptedBlob, keyBits, onPlainText, onError);
    }
  }

  private final ConsumerDb database_;
  private final KeyChain keyChain_;
  private final Face face_;
  private Name groupName_;
  private final Name consumerName_;
  private final Link cKeyLink_;
  // Use HashMap without generics so it works with older Java compilers.
  private final HashMap cKeyMap_ =
    new HashMap(); /**< The map key is the C-KEY name. The value is the encoded key Blob. */
  private final Link dKeyLink_;
  private final HashMap dKeyMap_ =
    new HashMap(); /**< The map key is the D-KEY name. The value is the encoded key Blob. */
  private static final Link NO_LINK = new Link();
  private static final Logger logger_ = Logger.getLogger(Consumer.class.getName());
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy