Skip to content

Commit

Permalink
Send nonces in C++ and Java Crypto (#4509)
Browse files Browse the repository at this point in the history
This PR makes C++ and Java Crypto send nonces in Proto messages.

This PR is part of a non-breaking change aimed to implement random nonces for Hybrid Encryption.
Ref #4507

For now we send deterministic nonces, but once we make sure that code uses nonces from the proto instead of computing them, we can swap nonces for random ones.
  • Loading branch information
ipetr0v authored Nov 28, 2023
1 parent 91d857c commit 28b91c9
Show file tree
Hide file tree
Showing 17 changed files with 197 additions and 57 deletions.
5 changes: 4 additions & 1 deletion cc/crypto/client_encryptor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,16 @@ absl::StatusOr<std::unique_ptr<ClientEncryptor>> ClientEncryptor::Create(
absl::StatusOr<EncryptedRequest> ClientEncryptor::Encrypt(absl::string_view plaintext,
absl::string_view associated_data) {
// Encrypt request.
absl::StatusOr<std::string> ciphertext = sender_context_->Seal(plaintext, associated_data);
const std::vector<uint8_t> nonce = sender_context_->GenerateNonce();
absl::StatusOr<std::string> ciphertext = sender_context_->Seal(nonce, plaintext, associated_data);
if (!ciphertext.ok()) {
return ciphertext.status();
}

// Create request message.
EncryptedRequest encrypted_request;
*encrypted_request.mutable_encrypted_message()->mutable_nonce() =
std::string(nonce.begin(), nonce.end());
*encrypted_request.mutable_encrypted_message()->mutable_ciphertext() = *ciphertext;
*encrypted_request.mutable_encrypted_message()->mutable_associated_data() = associated_data;

Expand Down
12 changes: 10 additions & 2 deletions cc/crypto/hpke/jni/com_google_oak_crypto_hpke_RecipientContext.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions cc/crypto/hpke/jni/com_google_oak_crypto_hpke_SenderContext.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 46 additions & 6 deletions cc/crypto/hpke/jni/context_jni.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,30 @@
#include "com_google_oak_crypto_hpke_SenderContext.h"
#include "jni_helper.h"

JNIEXPORT jbyteArray JNICALL
Java_com_google_oak_crypto_hpke_SenderContext_nativeGenerateNonce(JNIEnv* env, jobject obj) {
jclass sender_context_class = env->GetObjectClass(obj);
jfieldID fid = env->GetFieldID(sender_context_class, "nativePtr", "J");
oak::crypto::SenderContext* sender_context =
(oak::crypto::SenderContext*)(env->GetLongField(obj, fid));
if (sender_context == NULL) {
return {};
}

std::vector<uint8_t> nonce = sender_context->GenerateNonce();

jbyteArray ret = env->NewByteArray(nonce.size());
env->SetByteArrayRegion(ret, 0, nonce.size(), reinterpret_cast<const jbyte*>(&nonce.front()));
return ret;
}

JNIEXPORT jbyteArray JNICALL Java_com_google_oak_crypto_hpke_SenderContext_nativeSeal(
JNIEnv* env, jobject obj, jbyteArray plaintext, jbyteArray associated_data) {
if (plaintext == NULL || associated_data == NULL) {
JNIEnv* env, jobject obj, jbyteArray nonce, jbyteArray plaintext, jbyteArray associated_data) {
if (nonce == NULL || plaintext == NULL || associated_data == NULL) {
return {};
}

std::string nonce_str = convert_jbytearray_to_string(env, nonce);
std::string plaintext_str = convert_jbytearray_to_string(env, plaintext);
std::string associated_data_str = convert_jbytearray_to_string(env, associated_data);

Expand All @@ -38,7 +56,9 @@ JNIEXPORT jbyteArray JNICALL Java_com_google_oak_crypto_hpke_SenderContext_nativ
return {};
}

absl::StatusOr<std::string> result = sender_context->Seal(plaintext_str, associated_data_str);
const std::vector<uint8_t> nonce_bytes(nonce_str.begin(), nonce_str.end());
absl::StatusOr<std::string> result =
sender_context->Seal(nonce_bytes, plaintext_str, associated_data_str);
if (!result.ok()) {
return {};
}
Expand Down Expand Up @@ -77,6 +97,23 @@ JNIEXPORT jbyteArray JNICALL Java_com_google_oak_crypto_hpke_SenderContext_nativ
return ret;
}

JNIEXPORT jbyteArray JNICALL
Java_com_google_oak_crypto_hpke_RecipientContext_nativeGenerateNonce(JNIEnv* env, jobject obj) {
jclass recipient_context_class = env->GetObjectClass(obj);
jfieldID fid = env->GetFieldID(recipient_context_class, "nativePtr", "J");
oak::crypto::RecipientContext* recipient_context =
(oak::crypto::RecipientContext*)(env->GetLongField(obj, fid));
if (recipient_context == NULL) {
return {};
}

std::vector<uint8_t> nonce = recipient_context->GenerateNonce();

jbyteArray ret = env->NewByteArray(nonce.size());
env->SetByteArrayRegion(ret, 0, nonce.size(), reinterpret_cast<const jbyte*>(&nonce.front()));
return ret;
}

JNIEXPORT jbyteArray JNICALL Java_com_google_oak_crypto_hpke_RecipientContext_nativeOpen(
JNIEnv* env, jobject obj, jbyteArray ciphertext, jbyteArray associated_data) {
if (ciphertext == NULL || associated_data == NULL) {
Expand Down Expand Up @@ -106,11 +143,12 @@ JNIEXPORT jbyteArray JNICALL Java_com_google_oak_crypto_hpke_RecipientContext_na
}

JNIEXPORT jbyteArray JNICALL Java_com_google_oak_crypto_hpke_RecipientContext_nativeSeal(
JNIEnv* env, jobject obj, jbyteArray plaintext, jbyteArray associated_data) {
if (plaintext == NULL || associated_data == NULL) {
JNIEnv* env, jobject obj, jbyteArray nonce, jbyteArray plaintext, jbyteArray associated_data) {
if (nonce == NULL || plaintext == NULL || associated_data == NULL) {
return {};
}

std::string nonce_str = convert_jbytearray_to_string(env, nonce);
std::string plaintext_str = convert_jbytearray_to_string(env, plaintext);
std::string associated_data_str = convert_jbytearray_to_string(env, associated_data);

Expand All @@ -122,7 +160,9 @@ JNIEXPORT jbyteArray JNICALL Java_com_google_oak_crypto_hpke_RecipientContext_na
return {};
}

absl::StatusOr<std::string> result = recipient_context->Seal(plaintext_str, associated_data_str);
const std::vector<uint8_t> nonce_bytes(nonce_str.begin(), nonce_str.end());
absl::StatusOr<std::string> result =
recipient_context->Seal(nonce_bytes, plaintext_str, associated_data_str);
if (!result.ok()) {
return {};
}
Expand Down
23 changes: 22 additions & 1 deletion cc/crypto/hpke/jni/jni_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,34 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "cc/crypto/hpke/jni/jni_helper.h"

#include <memory>
#include <string>
#include <vector>

// std::unique_ptr<char[]> convert_jbytearray_to_array(JNIEnv* env, jbyteArray arr) {
// int len = env->GetArrayLength(arr);
// char* buf = new char[len];
// std::unique_ptr<char[]> buf_uptr(buf);
// return buf_uptr;
// }

std::string convert_jbytearray_to_string(JNIEnv* env, jbyteArray arr) {
int len = env->GetArrayLength(arr);
char* buf = new char[len];
std::unique_ptr<char[]> buf_uptr(buf);
env->GetByteArrayRegion(arr, 0, len, reinterpret_cast<jbyte*>(buf));
std::string result(buf, len);
return result;
}
}

// std::vector<uint8_t> convert_jbytearray_to_vector(JNIEnv* env, jbyteArray arr) {
// int len = env->GetArrayLength(arr);
// char* buf = new char[len];
// std::unique_ptr<char[]> buf_uptr(buf);
// env->GetByteArrayRegion(arr, 0, len, reinterpret_cast<jbyte*>(buf));
// std::vector<uint8_t> result{buf, len};
// return result;
// }
9 changes: 7 additions & 2 deletions cc/crypto/hpke/recipient_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ absl::StatusOr<std::unique_ptr<RecipientContext>> RecipientContext::Deserialize(
/* response_sequence_number= */ serialized_recipient_context.response_sequence_number());
}

std::vector<uint8_t> RecipientContext::GenerateNonce() {
std::vector<uint8_t> nonce = CalculateNonce(response_base_nonce_, response_sequence_number_);
return nonce;
}

absl::StatusOr<std::string> RecipientContext::Open(absl::string_view ciphertext,
absl::string_view associated_data) {
/// Maximum sequence number which can fit in kAeadNonceSizeBytes bytes.
Expand All @@ -132,14 +137,14 @@ absl::StatusOr<std::string> RecipientContext::Open(absl::string_view ciphertext,
return plaintext;
}

absl::StatusOr<std::string> RecipientContext::Seal(absl::string_view plaintext,
absl::StatusOr<std::string> RecipientContext::Seal(const std::vector<uint8_t>& nonce,
absl::string_view plaintext,
absl::string_view associated_data) {
/// Maximum sequence number which can fit in kAeadNonceSizeBytes bytes.
/// <https://www.rfc-editor.org/rfc/rfc9180.html#name-encryption-and-decryption>
if (response_sequence_number_ == UINT64_MAX) {
return absl::OutOfRangeError("Maximum sequence number reached");
}
std::vector<uint8_t> nonce = CalculateNonce(response_base_nonce_, response_sequence_number_);

absl::StatusOr<std::string> ciphertext =
AeadSeal(response_aead_context_.get(), nonce, plaintext, associated_data);
Expand Down
5 changes: 4 additions & 1 deletion cc/crypto/hpke/recipient_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,17 @@ class RecipientContext {
static absl::StatusOr<std::unique_ptr<RecipientContext>> Deserialize(
::oak::crypto::v1::CryptoContext serialized_recipient_context);

std::vector<uint8_t> GenerateNonce();

// Decrypts message and validates associated data using AEAD.
// <https://www.rfc-editor.org/rfc/rfc9180.html#name-encryption-and-decryption>
absl::StatusOr<std::string> Open(absl::string_view ciphertext, absl::string_view associated_data);

// Encrypts response message with associated data using AEAD as part of bidirectional
// communication.
// <https://www.rfc-editor.org/rfc/rfc9180.html#name-bidirectional-encryption>
absl::StatusOr<std::string> Seal(absl::string_view plaintext, absl::string_view associated_data);
absl::StatusOr<std::string> Seal(const std::vector<uint8_t>& nonce, absl::string_view plaintext,
absl::string_view associated_data);

~RecipientContext();

Expand Down
12 changes: 8 additions & 4 deletions cc/crypto/hpke/recipient_context_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ TEST_F(RecipientContextTest, RecipientContextOpenSuccess) {

std::string plaintext = "Hello World";

auto ciphertext = (*sender_context)->Seal(plaintext, associated_data_request_);
const std::vector<uint8_t> nonce = (*sender_context)->GenerateNonce();
auto ciphertext = (*sender_context)->Seal(nonce, plaintext, associated_data_request_);
ASSERT_TRUE(ciphertext.ok());

std::string encap_public_key = (*sender_context)->GetSerializedEncapsulatedPublicKey();
Expand All @@ -145,7 +146,8 @@ TEST_F(RecipientContextTest, RecipientRequestContextOpenFailure) {

std::string plaintext = "Hello World";

auto ciphertext = (*sender_context)->Seal(plaintext, associated_data_request_);
const std::vector<uint8_t> nonce = (*sender_context)->GenerateNonce();
auto ciphertext = (*sender_context)->Seal(nonce, plaintext, associated_data_request_);
ASSERT_TRUE(ciphertext.ok());

std::string edited_ciphertext = absl::StrCat(*ciphertext, "no!");
Expand All @@ -165,7 +167,8 @@ TEST_F(RecipientContextTest, RecipientResponseContextSealSuccess) {

std::string plaintext = "Hello World";

auto ciphertext = (*recipient_context)->Seal(plaintext, associated_data_response_);
const std::vector<uint8_t> nonce = (*recipient_context)->GenerateNonce();
auto ciphertext = (*recipient_context)->Seal(nonce, plaintext, associated_data_response_);
ASSERT_TRUE(ciphertext.ok());
EXPECT_THAT(plaintext, StrNe(*ciphertext));
}
Expand All @@ -176,7 +179,8 @@ TEST_F(RecipientContextTest, RecipientResponseContextSealFailure) {

std::string empty_plaintext = "";

auto ciphertext = (*recipient_context)->Seal(empty_plaintext, associated_data_response_);
const std::vector<uint8_t> nonce = (*recipient_context)->GenerateNonce();
auto ciphertext = (*recipient_context)->Seal(nonce, empty_plaintext, associated_data_response_);
EXPECT_FALSE(ciphertext.ok());
EXPECT_EQ(ciphertext.status().code(), absl::StatusCode::kInvalidArgument);
}
Expand Down
9 changes: 7 additions & 2 deletions cc/crypto/hpke/sender_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,19 @@ namespace oak::crypto {

const uint64_t kStartingSequenceNumber = 0;

absl::StatusOr<std::string> SenderContext::Seal(absl::string_view plaintext,
std::vector<uint8_t> SenderContext::GenerateNonce() {
std::vector<uint8_t> nonce = CalculateNonce(request_base_nonce_, request_sequence_number_);
return nonce;
}

absl::StatusOr<std::string> SenderContext::Seal(const std::vector<uint8_t>& nonce,
absl::string_view plaintext,
absl::string_view associated_data) {
/// Maximum sequence number which can fit in kAeadNonceSizeBytes bytes.
/// <https://www.rfc-editor.org/rfc/rfc9180.html#name-encryption-and-decryption>
if (request_sequence_number_ == UINT64_MAX) {
return absl::OutOfRangeError("Maximum sequence number reached");
}
std::vector<uint8_t> nonce = CalculateNonce(request_base_nonce_, request_sequence_number_);

absl::StatusOr<std::string> ciphertext =
AeadSeal(request_aead_context_.get(), nonce, plaintext, associated_data);
Expand Down
5 changes: 4 additions & 1 deletion cc/crypto/hpke/sender_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,12 @@ class SenderContext {
return serialized_encapsulated_public_key_;
}

std::vector<uint8_t> GenerateNonce();

// Encrypts message with associated data using AEAD.
// <https://www.rfc-editor.org/rfc/rfc9180.html#name-encryption-and-decryption>
absl::StatusOr<std::string> Seal(absl::string_view plaintext, absl::string_view associated_data);
absl::StatusOr<std::string> Seal(const std::vector<uint8_t>& nonce, absl::string_view plaintext,
absl::string_view associated_data);

// Decrypts response message and validates associated data using AEAD as part of
// bidirectional communication.
Expand Down
3 changes: 2 additions & 1 deletion cc/crypto/hpke/sender_context_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,9 @@ TEST_F(SenderContextTest, SenderSealsMessageSuccess) {

std::string plaintext = "Hello World";

const std::vector<uint8_t> nonce = (*sender_context)->GenerateNonce();
absl::StatusOr<std::string> encrypted_request =
(*sender_context)->Seal(plaintext, associated_data_request_);
(*sender_context)->Seal(nonce, plaintext, associated_data_request_);
EXPECT_TRUE(encrypted_request.ok());
EXPECT_THAT(*encrypted_request, StrNe(plaintext));
}
Expand Down
6 changes: 5 additions & 1 deletion cc/crypto/server_encryptor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,17 @@ absl::StatusOr<EncryptedResponse> ServerEncryptor::Encrypt(absl::string_view pla
}

// Encrypt response.
absl::StatusOr<std::string> ciphertext = recipient_context_->Seal(plaintext, associated_data);
const std::vector<uint8_t> nonce = recipient_context_->GenerateNonce();
absl::StatusOr<std::string> ciphertext =
recipient_context_->Seal(nonce, plaintext, associated_data);
if (!ciphertext.ok()) {
return ciphertext.status();
}

// Create response message.
EncryptedResponse encrypted_response;
*encrypted_response.mutable_encrypted_message()->mutable_nonce() =
std::string(nonce.begin(), nonce.end());
*encrypted_response.mutable_encrypted_message()->mutable_ciphertext() = *ciphertext;
*encrypted_response.mutable_encrypted_message()->mutable_associated_data() = associated_data;

Expand Down
34 changes: 18 additions & 16 deletions java/src/main/java/com/google/oak/crypto/ClientEncryptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,24 +82,26 @@ private ClientEncryptor(SenderContext senderContext) {
public final Result<EncryptedRequest, Exception> encrypt(
final byte[] plaintext, final byte[] associatedData) {
// Encrypt request.
return senderContext.seal(plaintext, associatedData).map(ciphertext -> {
// Create request message.
EncryptedRequest.Builder encryptedRequestBuilder =
EncryptedRequest.newBuilder().setEncryptedMessage(
AeadEncryptedMessage.newBuilder()
.setCiphertext(ByteString.copyFrom(ciphertext))
.setAssociatedData(ByteString.copyFrom(associatedData))
.build());
return senderContext.generateNonce().andThen(
nonce -> senderContext.seal(nonce, plaintext, associatedData).map(ciphertext -> {
// Create request message.
EncryptedRequest.Builder encryptedRequestBuilder =
EncryptedRequest.newBuilder().setEncryptedMessage(
AeadEncryptedMessage.newBuilder()
.setNonce(ByteString.copyFrom(nonce))
.setCiphertext(ByteString.copyFrom(ciphertext))
.setAssociatedData(ByteString.copyFrom(associatedData))
.build());

// Encapsulated public key is only sent in the initial request message of the session.
if (!serializedEncapsulatedPublicKeyHasBeenSent) {
encryptedRequestBuilder.setSerializedEncapsulatedPublicKey(
ByteString.copyFrom(senderContext.getSerializedEncapsulatedPublicKey()));
serializedEncapsulatedPublicKeyHasBeenSent = true;
}
// Encapsulated public key is only sent in the initial request message of the session.
if (!serializedEncapsulatedPublicKeyHasBeenSent) {
encryptedRequestBuilder.setSerializedEncapsulatedPublicKey(
ByteString.copyFrom(senderContext.getSerializedEncapsulatedPublicKey()));
serializedEncapsulatedPublicKeyHasBeenSent = true;
}

return encryptedRequestBuilder.build();
});
return encryptedRequestBuilder.build();
}));
}

/**
Expand Down
Loading

0 comments on commit 28b91c9

Please sign in to comment.