
net.named_data.jndn.encrypt.Consumer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jndn-android-with-async-io Show documentation
Show all versions of jndn-android-with-async-io Show documentation
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.
/**
* 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