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

Counter KDF: NIST SP 800-108r1-upd1 #399

Merged
merged 2 commits into from
Sep 4, 2024
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 2.5.0

### Minor
* [PR 397:](https://github.com/corretto/amazon-corretto-crypto-provider/pull/397) Support for Concatenation KDFs
* [PR 399:](https://github.com/corretto/amazon-corretto-crypto-provider/pull/399) Support for Counter KDFs

## 2.4.1

### Patch
Expand Down
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,8 @@ if(FIPS)
else()
set(TEST_FIPS_PROPERTY "-DFIPS=false")
set(C_SRC ${C_SRC}
csrc/concatenation_kdf.cpp)
csrc/concatenation_kdf.cpp
csrc/counter_kdf.cpp)
endif()

add_library(amazonCorrettoCryptoProvider SHARED ${C_SRC})
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ SecretKeyFactory:
* ConcatenationKdfWithSHA512 (not available in FIPS builds)
* ConcatenationKdfWithHmacSHA256 (not available in FIPS builds)
* ConcatenationKdfWithHmacSHA512 (not available in FIPS builds)
* CounterKdfWithHmacSHA256 (not available in FIPS builds)
* CounterKdfWithHmacSHA384 (not available in FIPS builds)
* CounterKdfWithHmacSHA512 (not available in FIPS builds)
WillChilds-Klein marked this conversation as resolved.
Show resolved Hide resolved

SecureRandom:
* ACCP's SecureRandom uses [AWS-LC's DRBG implementation](https://github.com/aws/aws-lc/blob/main/crypto/fipsmodule/rand/rand.c).
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ plugins {
}

group = 'software.amazon.cryptools'
version = '2.4.1'
version = '2.5.0'
ext.isFips = Boolean.getBoolean('FIPS')
if (ext.isFips) {
ext.awsLcGitVersionId = 'AWS-LC-FIPS-2.0.13'
Expand Down
32 changes: 32 additions & 0 deletions csrc/counter_kdf.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#include "buffer.h"
#include "env.h"
#include "generated-headers.h"
#include <openssl/evp.h>
#include <openssl/kdf.h>

using namespace AmazonCorrettoCryptoProvider;

extern "C" JNIEXPORT void Java_com_amazon_corretto_crypto_provider_CounterKdfSpi_nKdf(JNIEnv* env,
jclass,
jint digestCode,
jbyteArray jSecret,
jint secretLen,
jbyteArray jInfo,
jint infoLen,
jbyteArray jOutput,
jint outputLen)
{
try {
EVP_MD const* digest = digest_code_to_EVP_MD(digestCode);
WillChilds-Klein marked this conversation as resolved.
Show resolved Hide resolved
JBinaryBlob secret(env, nullptr, jSecret);
JBinaryBlob info(env, nullptr, jInfo);
JBinaryBlob output(env, nullptr, jOutput);
if (KBKDF_ctr_hmac(output.get(), outputLen, digest, secret.get(), secretLen, info.get(), infoLen) != 1) {
throw_openssl(EX_RUNTIME_CRYPTO, "KBKDF_ctr_hmac failed.");
}
} catch (java_ex& ex) {
ex.throw_to_java(env);
}
}
2 changes: 1 addition & 1 deletion examples/gradle-kt-dsl/lib/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
val accpVersion = "2.4.1"
val accpVersion = "2.5.0"
val accpLocalJar: String by project
val fips: Boolean by project
val PLATFORMS_WITHOUT_FIPS_SUPPORT = setOf("osx-x86_64", "osx-aarch_64")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import static com.amazon.corretto.crypto.provider.ConcatenationKdfSpi.CKDF_WITH_SHA256;
import static com.amazon.corretto.crypto.provider.ConcatenationKdfSpi.CKDF_WITH_SHA384;
import static com.amazon.corretto.crypto.provider.ConcatenationKdfSpi.CKDF_WITH_SHA512;
import static com.amazon.corretto.crypto.provider.CounterKdfSpi.CTR_KDF_WITH_HMAC_SHA256;
import static com.amazon.corretto.crypto.provider.CounterKdfSpi.CTR_KDF_WITH_HMAC_SHA384;
import static com.amazon.corretto.crypto.provider.CounterKdfSpi.CTR_KDF_WITH_HMAC_SHA512;
import static com.amazon.corretto.crypto.provider.HkdfSecretKeyFactorySpi.HKDF_WITH_SHA1;
import static com.amazon.corretto.crypto.provider.HkdfSecretKeyFactorySpi.HKDF_WITH_SHA256;
import static com.amazon.corretto.crypto.provider.HkdfSecretKeyFactorySpi.HKDF_WITH_SHA384;
Expand Down Expand Up @@ -102,12 +105,17 @@ private void buildServiceMap() {

// Once these KDFs are added to a FIPS branch of AWS-LC, we can remove this check.
if (!Loader.FIPS_BUILD) {
final String concatenationKdfSpi = "ConcatenationKdf";
final String concatenationKdfSpi = "ConcatenationKdfSpi";
addService("SecretKeyFactory", CKDF_WITH_SHA256, concatenationKdfSpi, false);
addService("SecretKeyFactory", CKDF_WITH_SHA384, concatenationKdfSpi, false);
addService("SecretKeyFactory", CKDF_WITH_SHA512, concatenationKdfSpi, false);
addService("SecretKeyFactory", CKDF_WITH_HMAC_SHA256, concatenationKdfSpi, false);
addService("SecretKeyFactory", CKDF_WITH_HMAC_SHA512, concatenationKdfSpi, false);

final String counterKdfSpi = "CounterKdfSpi";
addService("SecretKeyFactory", CTR_KDF_WITH_HMAC_SHA256, counterKdfSpi, false);
addService("SecretKeyFactory", CTR_KDF_WITH_HMAC_SHA384, counterKdfSpi, false);
addService("SecretKeyFactory", CTR_KDF_WITH_HMAC_SHA512, counterKdfSpi, false);
}

addService("KeyPairGenerator", "RSA", "RsaGen");
Expand Down Expand Up @@ -342,6 +350,12 @@ public Object newInstance(final Object constructorParameter) throws NoSuchAlgori
if (ckdfSpi != null) {
return ckdfSpi;
}

final CounterKdfSpi cntrKdfSpi =
CounterKdfSpi.INSTANCES.get(CounterKdfSpi.getSpiFactoryForAlgName(algo));
if (cntrKdfSpi != null) {
return cntrKdfSpi;
}
}

if ("KeyGenerator".equalsIgnoreCase(type) && "AES".equalsIgnoreCase(algo)) {
Expand Down
54 changes: 54 additions & 0 deletions src/com/amazon/corretto/crypto/provider/CounterKdfSpec.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package com.amazon.corretto.crypto.provider;

import java.security.spec.KeySpec;
import java.util.Objects;

/**
* Represents the inputs to CounterKdfSpec algorithms.
*
* <p>If info is not provided, an empty byte array is used.
*
* <p>The algorithmName is the name of algorithm used to create SecretKeySpec.
*/
public class CounterKdfSpec implements KeySpec {
private final byte[] secret;
private final byte[] info;
private final int outputLen;
private final String algorithName;

public CounterKdfSpec(
final byte[] secret, final byte[] info, final int outputLen, final String algorithName) {
this.secret = Objects.requireNonNull(secret);
if (this.secret.length == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

From a cryptographic perspective, should we really be letting people have 1-byte KDK's? This seems like an unnecessary footgun. FWIW, I did look at the AWS-LC source and they also only check that this is non-zero. So if we follow the principal of not adding any new semantics in ACCP this should stay (but I feel gross about it).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From the library perspective, I think the right approach is to implement the standard faithfully.

throw new IllegalArgumentException("Secret must be byte array with non-zero length.");
}
this.info = Objects.requireNonNull(info);
if (outputLen <= 0) {
throw new IllegalArgumentException("Output size must be greater than zero.");
}
this.outputLen = outputLen;
this.algorithName = Objects.requireNonNull(algorithName);
}

public CounterKdfSpec(final byte[] secret, final int outputLen, final String algorithName) {
this(secret, Utils.EMPTY_ARRAY, outputLen, algorithName);
}

public byte[] getSecret() {
return secret;
}

public byte[] getInfo() {
return info;
}

public int getOutputLen() {
return outputLen;
}

public String getAlgorithName() {
return algorithName;
}
}
67 changes: 67 additions & 0 deletions src/com/amazon/corretto/crypto/provider/CounterKdfSpi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package com.amazon.corretto.crypto.provider;

import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

class CounterKdfSpi extends KdfSpi {
private final int digestCode;
WillChilds-Klein marked this conversation as resolved.
Show resolved Hide resolved

CounterKdfSpi(final int digestCode) {
this.digestCode = digestCode;
}

@Override
protected SecretKey engineGenerateSecret(final KeySpec keySpec) throws InvalidKeySpecException {
if (!(keySpec instanceof CounterKdfSpec)) {
throw new InvalidKeySpecException("Expected a key spec of type CounterKdfSpec");
}
final CounterKdfSpec spec = (CounterKdfSpec) keySpec;

final byte[] secret = spec.getSecret();

final byte[] info = spec.getInfo();

final byte[] output = new byte[spec.getOutputLen()];

nKdf(digestCode, secret, secret.length, info, info.length, output, output.length);

return new SecretKeySpec(output, spec.getAlgorithName());
}

private static native void nKdf(
int digestCode,
byte[] secret,
int secretLen,
byte[] info,
int infoLen,
byte[] output,
int outputLen);

static final Map<String, CounterKdfSpi> INSTANCES = getInstances();

static final String CTR_KDF_WITH_HMAC_SHA256 = "CounterKdfWithHmacSHA256";
static final String CTR_KDF_WITH_HMAC_SHA384 = "CounterKdfWithHmacSHA384";
static final String CTR_KDF_WITH_HMAC_SHA512 = "CounterKdfWithHmacSHA512";

private static Map<String, CounterKdfSpi> getInstances() {
final Map<String, CounterKdfSpi> kdfs = new HashMap<>();
kdfs.put(
getSpiFactoryForAlgName(CTR_KDF_WITH_HMAC_SHA256), new CounterKdfSpi(Utils.SHA256_CODE));
kdfs.put(
getSpiFactoryForAlgName(CTR_KDF_WITH_HMAC_SHA384), new CounterKdfSpi(Utils.SHA384_CODE));
kdfs.put(
getSpiFactoryForAlgName(CTR_KDF_WITH_HMAC_SHA512), new CounterKdfSpi(Utils.SHA512_CODE));
return Collections.unmodifiableMap(kdfs);
}

static String getSpiFactoryForAlgName(final String alg) {
return alg.toUpperCase();
}
}
Loading
Loading