blob: 244ce2daddbc716be0e1adbc463e63b3e6c48b08 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.encryption;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
/**
* Constants and methods for encryption.
*/
public class EncryptionUtil {
/**
* Crypto parameter prefix, in the commit user data.
* It includes the {@link EncryptionUpdateHandler#TRANSFERABLE_COMMIT_DATA} prefix to be transferred from a
* commit to the next one automatically.
*/
public static final String COMMIT_CRYPTO = EncryptionUpdateHandler.TRANSFERABLE_COMMIT_DATA + "crypto.";
/**
* Active encryption key ref parameter, in the commit user data.
* If this parameter is not present, it means the index is in cleartext and has never been encrypted.
* This parameter value is the reference number of the key in the {@link #COMMIT_KEY_ID} and
* {@link #COMMIT_KEY_COOKIE} mappings.
*/
public static final String COMMIT_ACTIVE_KEY = COMMIT_CRYPTO + "active";
/**
* Commit user data parameter that maps a key reference number to its corresponding key id.
* The complete parameter name is the concatenation of this parameter prefix and the key reference number.
*/
public static final String COMMIT_KEY_ID = COMMIT_CRYPTO + "id.";
/**
* Commit user data parameter that maps a key reference number to its corresponding crypto cookie.
* The complete parameter name is the concatenation of this parameter prefix and the key reference number.
*/
public static final String COMMIT_KEY_COOKIE = COMMIT_CRYPTO + "cookie.";
/**
* Number of inactive key ids to keep when clearing the old inactive key ids.
* @see #clearOldInactiveKeyIdsFromCommit
*/
private static final int INACTIVE_KEY_IDS_TO_KEEP = 5;
/**
* Sets the new active encryption key id, and its optional cookie in the provided commit user data.
* New index files will be encrypted using this new key.
*
* @param keyId the new active encryption key id; must not be null.
* @param keyCookie may be null if none.
* @param commitUserData read to retrieve the current active key ref, and then updated with the new
* active key ref.
*/
public static void setNewActiveKeyIdInCommit(String keyId,
@Nullable byte[] keyCookie,
Map<String, String> commitUserData) {
// Key references are integers stored as strings. They are ordered by the natural ordering of
// integers. This method is the only location where key references are created. Outside, key
// references are simply considered as strings, except clearOldInactiveKeyIdsFromCommit() which
// sorts key references by the integer ordering.
assert keyId != null;
String oldKeyRef = getActiveKeyRefFromCommit(commitUserData);
String newKeyRef = oldKeyRef == null ? "0" : Integer.toString(Integer.parseInt(oldKeyRef) + 1);
commitUserData.put(COMMIT_ACTIVE_KEY, newKeyRef);
commitUserData.put(COMMIT_KEY_ID + newKeyRef, keyId);
if (keyCookie != null) {
commitUserData.put(COMMIT_KEY_COOKIE + newKeyRef, Base64.getEncoder().encodeToString(keyCookie));
}
}
/**
* Removes the active encryption key id.
* New index files will be cleartext.
*
* @param commitUserData updated to remove the active key ref.
*/
public static void removeActiveKeyRefFromCommit(Map<String, String> commitUserData) {
commitUserData.remove(COMMIT_ACTIVE_KEY);
}
/**
* Gets the reference number of the currently active encryption key, from the provided commit user data.
*
* @return the reference number of the active encryption key; or null if none, which means cleartext.
*/
@Nullable
public static String getActiveKeyRefFromCommit(Map<String, String> commitUserData) {
return commitUserData.get(COMMIT_ACTIVE_KEY);
}
/**
* Gets the key id from the provided commit user data, for the given key reference number.
*/
public static String getKeyIdFromCommit(String keyRef, Map<String, String> commitUserData) {
String keyId = commitUserData.get(COMMIT_KEY_ID + keyRef);
if (keyId == null) {
throw new NoSuchElementException("No key id for key ref=" + keyRef);
}
return keyId;
}
/**
* Gets the key cookie from the provided commit user data, for the given key reference number.
*
* @return the key cookie bytes; or null if none.
*/
@Nullable
public static byte[] getKeyCookieFromCommit(String keyRef, Map<String, String> commitUserData) {
String cookieString = commitUserData.get(COMMIT_KEY_COOKIE + keyRef);
return cookieString == null ? null : Base64.getDecoder().decode(cookieString);
}
/**
* @return Whether the provided commit user data contain some encryption key ids (active or not).
*/
public static boolean hasKeyIdInCommit(Map<String, String> commitUserData) {
for (String entryKey : commitUserData.keySet()) {
if (entryKey.startsWith(COMMIT_KEY_ID)) {
return true;
}
}
return false;
}
/**
* Clear the oldest inactive key ids to keep only the most recent ones.
* We don't clear all the inactive key ids just in the improbable case there would be pending
* segment creations using previous key id(s) still in flight. This is really to be safe.
*/
public static void clearOldInactiveKeyIdsFromCommit(Map<String, String> commitUserData) {
// List the inactive key references.
String activeKeyRef = getActiveKeyRefFromCommit(commitUserData);
List<Integer> inactiveKeyRefs = new ArrayList<>();
for (String dataKey : commitUserData.keySet()) {
if (dataKey.startsWith(COMMIT_KEY_ID)) {
String keyRef = dataKey.substring(COMMIT_KEY_ID.length());
if (!keyRef.equals(activeKeyRef)) {
inactiveKeyRefs.add(Integer.parseInt(keyRef));
}
}
}
// Clear them except the most recent ones.
if (inactiveKeyRefs.size() > INACTIVE_KEY_IDS_TO_KEEP) {
inactiveKeyRefs.sort(Comparator.naturalOrder());
for (Integer keyRef : inactiveKeyRefs.subList(0, inactiveKeyRefs.size() - INACTIVE_KEY_IDS_TO_KEEP)) {
commitUserData.remove(COMMIT_KEY_ID + keyRef);
commitUserData.remove(COMMIT_KEY_COOKIE + keyRef);
}
}
}
}