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

[Feature/Identity] Allow for Encryption/Decryption of Principals into PITs #4730

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
bd1c375
Backend for extension IDs & secrets, then hashing
stephen-crawford Oct 7, 2022
0651817
Hashing-based PIT Verification and Implementation
stephen-crawford Oct 10, 2022
511e568
Allow for encryption/decryption of Principals into PIT
stephen-crawford Oct 10, 2022
ceb395e
Merge branch 'opensearch-project:feature/identity' into feature/identity
stephen-crawford Oct 10, 2022
19ada6f
Version bump
stephen-crawford Oct 10, 2022
f10d2b7
Validation checks for existence and length
stephen-crawford Oct 11, 2022
dbe0923
Byte array encoding fix
stephen-crawford Oct 11, 2022
76e5af5
typo fix
stephen-crawford Oct 11, 2022
06ff2e4
Naming conventions
stephen-crawford Oct 11, 2022
46717dd
Correct non-reset push
stephen-crawford Oct 11, 2022
5ae24ac
Test principal name encryption/decryp
stephen-crawford Oct 11, 2022
e42d556
in progress changes
stephen-crawford Oct 11, 2022
554c8c0
Encryption works, trying to fix tag mismatch
stephen-crawford Oct 12, 2022
f971e62
Working encryption decryption
stephen-crawford Oct 12, 2022
363b261
Working encryption decryption
stephen-crawford Oct 12, 2022
2483640
Working encryption decryption
stephen-crawford Oct 12, 2022
5910b1e
Spotlessapply and bad tag, bad key tests
stephen-crawford Oct 13, 2022
ae1cd98
Extension Token Processor Test Suite
stephen-crawford Oct 13, 2022
be6b49f
Extension Token Processor Test Suite
stephen-crawford Oct 13, 2022
9fb4c62
Extension Token Processor Test Suite
stephen-crawford Oct 13, 2022
898abf2
Removed uneccessary lines
stephen-crawford Oct 19, 2022
ac9ba58
Gradle revert
stephen-crawford Oct 19, 2022
12f60fa
Rerun gradle
stephen-crawford Oct 20, 2022
e75496d
run gradle
stephen-crawford Oct 20, 2022
a872692
changelog
stephen-crawford Oct 20, 2022
3a4d899
Fixed illegal charset
stephen-crawford Oct 24, 2022
c0144c1
Typo fix
stephen-crawford Oct 24, 2022
846d75c
remove commenting mistake
stephen-crawford Oct 24, 2022
b0cddf7
revert
stephen-crawford Oct 24, 2022
50939eb
Remove javadoc '@'s
stephen-crawford Oct 24, 2022
ddd3045
Remove javadoc '@'s
stephen-crawford Oct 24, 2022
f21cc99
Remove prints
stephen-crawford Oct 24, 2022
e55ac0d
Spotless java
stephen-crawford Oct 24, 2022
b5a982c
Revert changelog
stephen-crawford Oct 26, 2022
583aee5
Merge branch 'opensearch-project:feature/identity' into feature/identity
stephen-crawford Oct 26, 2022
bef50b0
Readd changelog
stephen-crawford Oct 26, 2022
e9be606
Corrected PIT test; moved stream output to separate task
stephen-crawford Oct 26, 2022
eeb453b
Spotless
stephen-crawford Oct 26, 2022
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Addition of Missing Value feature in the GeoShape Aggregations.
- Install and configure Log4j JUL Adapter for Lucene 9.4 ([#4754](https://github.com/opensearch-project/OpenSearch/pull/4754))
- Added feature to ignore indexes starting with dot during shard limit validation.([#4695](https://github.com/opensearch-project/OpenSearch/pull/4695))
- Added encryption/decryption for principal identifier tokens with extensions ([#4730](https://github.com/opensearch-project/OpenSearch/pull/4730))
-
### Changed
### Deprecated
### Removed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
* specific language governing permissions and limitations
* under the License.
*/

include ":distribution:bwc:bugfix"
stephen-crawford marked this conversation as resolved.
Show resolved Hide resolved
include ":distribution:bwc:minor"
include ":distribution:archives:darwin-tar"
Original file line number Diff line number Diff line change
Expand Up @@ -2,84 +2,182 @@
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.identity;

import java.security.Principal;
import java.nio.charset.StandardCharsets;
import java.nio.ByteBuffer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.AEADBadTagException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
* Token processor class to handle token encryption/decryption
* This processor is will be instantiated for every extension
*/
public class ExtensionTokenProcessor {

public static final String INVALID_TOKEN_MESSAGE = "Token must not be null and must be a colon-separated String";
public static final String INVALID_EXTENSION_MESSAGE = "Token passed here is for a different extension";
public static final String INVALID_ALGO_MESSAGE = "Failed to create a token because an invalid hashing algorithm was used.";
public static final String INVALID_PRINCIPAL_MESSAGE = "Principal passed here does not have a name.";
public static final String INVALID_KEY_MESSAGE = "Could not verify the authenticity of the provided key.";
public static final String INVALID_TAG_MESSAGE = "Token extraction could not be processed because of an invalid tag.";
public static final int KEY_SIZE_BITS = 256;
public static final int INITIALIZATION_VECTOR_SIZE_BYTES = 96;
public static final int TAG_LENGTH_BITS = 128;

public final String extensionUniqueId;

private final String extensionUniqueId;
private SecretKey secretKey;
private SecretKeySpec secretKeySpec;
private Cipher encryptionCipher;

public ExtensionTokenProcessor(String extensionUniqueId) {

// The extension ID should ALWAYS stay the same
this.extensionUniqueId = extensionUniqueId;

}

/**
* Allow for the reseting of the extension processor key. This will remove all access to existing encryptions.
*/
public SecretKey generateKey() throws NoSuchAlgorithmException {

KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(KEY_SIZE_BITS, SecureRandom.getInstanceStrong());
this.secretKey = keyGen.generateKey();
return this.secretKey;
}

/**
* Getter for the extensionTokenProcessor's secretKey
*/
public SecretKey getSecretKey() {

return this.secretKey;
}

public String getExtensionUniqueId() {
return extensionUniqueId;
/**
* Creates a new initialization vector for encryption--CAN ONLY BE USED ONCE PER KEY
*/
public byte[] generateInitializationVector() {

byte[] initializationVector = new byte[INITIALIZATION_VECTOR_SIZE_BYTES];
SecureRandom random = new SecureRandom();
random.nextBytes(initializationVector);
return initializationVector;
}

/**
* Create a two-way encrypted access token for given principal for this extension
* @return token generated from principal
*/
public PrincipalIdentifierToken generateToken(Principal principal) {
// This is a placeholder implementation
// More concrete implementation will be covered in https://github.com/opensearch-project/OpenSearch/issues/4485
String token = principal.getName() + ":" + extensionUniqueId;
public PrincipalIdentifierToken generateToken(Principal principal) throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException {

ByteArrayOutputStream output = new ByteArrayOutputStream();
output.write(this.extensionUniqueId.getBytes(StandardCharsets.UTF_8));
try {
output.write(principal.getName().getBytes(StandardCharsets.UTF_8));
} catch (NullPointerException ex) {
throw new NullPointerException(INVALID_PRINCIPAL_MESSAGE);
}

byte[] combinedAttributes = output.toByteArray();

SecretKey secretKey = generateKey();
byte[] initializationVector = generateInitializationVector();
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

return new PrincipalIdentifierToken(token);
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BITS, initializationVector);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec);

byte[] combinedEncoding = cipher.doFinal(combinedAttributes);

byte[] combinedEncodingWithIV = ByteBuffer.allocate(INITIALIZATION_VECTOR_SIZE_BYTES + combinedEncoding.length)
.put(initializationVector)
.put(combinedEncoding)
.array();

String s = Base64.getEncoder().encodeToString(combinedEncodingWithIV);
return new PrincipalIdentifierToken(s);
}

/**
* Decrypt the token and extract Principal
* @param token the requester identity token, should not be null
* @return Principal
*
* @opensearch.internal
*
* This method contains a placeholder implementation.
* More concrete implementation will be covered in https://github.com/opensearch-project/OpenSearch/issues/4485
*/
public Principal extractPrincipal(PrincipalIdentifierToken token) throws IllegalArgumentException {
// check is token is valid, we don't do anything if it is valid
// else we re-throw the thrown exception
validateToken(token);

String[] parts = token.getToken().split(":");
final String principalName = parts[0];
return () -> principalName;
public String extractPrincipal(PrincipalIdentifierToken token, SecretKey secretKey) throws IllegalArgumentException,
InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException,
NoSuchPaddingException {

String tokenString = token.getToken();
byte[] tokenBytes = Base64.getDecoder().decode(tokenString);

ByteBuffer bb = ByteBuffer.wrap(tokenBytes);

byte[] iv = new byte[INITIALIZATION_VECTOR_SIZE_BYTES];
bb.get(iv);

byte[] cipherText = new byte[bb.remaining()];
bb.get(cipherText);

Cipher decryptionCipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BITS, iv);

try {
decryptionCipher.init(Cipher.DECRYPT_MODE, secretKey, spec);
} catch (InvalidKeyException ex) {
throw new InvalidKeyException(INVALID_KEY_MESSAGE);
}

byte[] combinedEncoding;
try {
combinedEncoding = decryptionCipher.doFinal(cipherText);
} catch (AEADBadTagException ex) {
throw new AEADBadTagException(INVALID_TAG_MESSAGE);
}

String decodedPrincipal = new String(combinedEncoding, StandardCharsets.UTF_8).replace(this.extensionUniqueId, "");
String decodedExtensionsID = new String(combinedEncoding, StandardCharsets.UTF_8).replace(decodedPrincipal, "");
if (decodedExtensionsID.equals(this.extensionUniqueId) == false) {
throw new IllegalArgumentException(INVALID_EXTENSION_MESSAGE);
}
return decodedPrincipal;
}

/**
* Checks validity of the requester identifier token
* @param token The requester identifier token
* @throws IllegalArgumentException when token is invalid
*
* This method contains a placeholder implementation.
* More concrete implementation will be covered in https://github.com/opensearch-project/OpenSearch/issues/4485
*/
public void validateToken(PrincipalIdentifierToken token) throws IllegalArgumentException {

// Check whether token exists
if (token == null || token.getToken() == null) {
throw new IllegalArgumentException(INVALID_TOKEN_MESSAGE);
}

String[] parts = token.getToken().split(":");

// check whether token is malformed
if (parts.length != 2) {
String tokenName = token.getWriteableName();
byte[] tokenBytes = Base64.getDecoder().decode(token.getToken());
// Check whether token is correct length
if (tokenBytes.length >= INITIALIZATION_VECTOR_SIZE_BYTES) {
throw new IllegalArgumentException(INVALID_TOKEN_MESSAGE);
}
// check whether token is for this extension
if (!parts[1].equals(extensionUniqueId)) {
throw new IllegalArgumentException(INVALID_EXTENSION_MESSAGE);
}
stephen-crawford marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class PrincipalIdentifierToken implements NamedWriteable {
*/
protected PrincipalIdentifierToken(String token) {
this.token = token;

}

public PrincipalIdentifierToken(StreamInput in) throws IOException {
Expand Down
Loading