All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.ditto.connectivity.service.messaging.persistence.JsonFieldsEncryptor Maven / Gradle / Ivy

/*
 * Copyright (c) 2022 Contributors to the Eclipse Foundation
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 */

package org.eclipse.ditto.connectivity.service.messaging.persistence;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.eclipse.ditto.connectivity.model.ConnectionUriInvalidException;
import org.eclipse.ditto.connectivity.service.util.EncryptorAesGcm;
import org.eclipse.ditto.internal.utils.config.DittoConfigError;
import org.eclipse.ditto.json.JsonFactory;
import org.eclipse.ditto.json.JsonObject;
import org.eclipse.ditto.json.JsonPointer;
import org.eclipse.ditto.json.JsonValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *  Handles encryption of fields of a given json object.
 *  Json values which are considered a connection URI are treated differently.
 *  Only the password of the user info part if present will be encrypted.
 *  

* For example: *

* "amqps://user:[email protected]:5671"
* "amqps://user:[email protected]:5671" */ public final class JsonFieldsEncryptor { static final String ENCRYPTED_PREFIX = "encrypted_"; private static final Logger LOGGER = LoggerFactory.getLogger(JsonFieldsEncryptor.class); /** * Encrypts a json object fields based on a list of pointers with a provided symmetrical key. * After encryption values are prefixed with {@value ENCRYPTED_PREFIX} prefix. * * @param jsonObject the jsonObject whose fields should be encrypted * @param pointersPrefix a prefix to be added if needed to all pointers. * Use empty string if not needed else can be a string starting or not with "/" * @param jsonPointers the pointer to the values to be encrypted * @param symmetricKey the symmetrical key to be used for the encryption * @return a new encrypted {@link org.eclipse.ditto.json.JsonObject } * @throws IllegalStateException if encryption fails. * It is not advisable to catch it as in the case of failure it is not expected for the service to continue to work * without encryption being properly configured. */ public static JsonObject encrypt(final JsonObject jsonObject, final String pointersPrefix, final List jsonPointers, final String symmetricKey) { return handle(jsonObject, prefixPointers(pointersPrefix, jsonPointers).map(JsonPointer::of).collect(Collectors.toList()), symmetricKey, JsonFieldsEncryptor::encryptValue); } /** * Decrypts a json object fields based on a list of pointers with a provided symmetrical key. * Only fields prefixed with {@value ENCRYPTED_PREFIX} prefix will be decrypted even if configured with a pointer. * * @param jsonObject the jsonObject whose fields should be decrypted * @param pointersPrefix a prefix to be added if needed to all pointers. * Use empty string if not needed else can be a string starting or not with "/" * @param jsonPointers the pointer to the values to be decrypted * @param symmetricKey the symmetrical key to be used for the description * @return a new decrypted {@link org.eclipse.ditto.json.JsonObject } * @throws IllegalStateException if decryption fails. * It is not advisable to catch it as in the case of failure it is not expected for the service to continue to work * without encryption being properly configured. */ public static JsonObject decrypt(final JsonObject jsonObject, final String pointersPrefix, final List jsonPointers, final String symmetricKey) { return handle(jsonObject, prefixPointers(pointersPrefix, jsonPointers).map(JsonPointer::of) .collect(Collectors.toList()), symmetricKey, JsonFieldsEncryptor::decryptValue); } static String replaceUriPassword(final String uriStringRepresentation, final String patchedPassword) { final String userInfo = URI.create(uriStringRepresentation).getUserInfo(); final String newUserInfo = userInfo.substring(0, userInfo.indexOf(":") + 1) + patchedPassword; final int startOfPwd = uriStringRepresentation.indexOf(userInfo); final int endOfPassword = uriStringRepresentation.indexOf("@"); return uriStringRepresentation.substring(0, startOfPwd) + newUserInfo + uriStringRepresentation.substring(endOfPassword); } public static Stream prefixPointers(final String prefix, final List pointers) { final String thePrefix = (prefix.startsWith("/") | prefix.isEmpty()) ? prefix : "/" + prefix; return pointers.stream().map(pointer -> thePrefix + pointer); } private static JsonObject handle(final JsonObject jsonObject, final List jsonPointers, final String symmetricKey, final BiFunction encryptionHandler) { return jsonPointers.stream() .filter(pointer -> jsonObject.getValue(pointer).filter(JsonValue::isString).isPresent()) .map(pointer -> jsonObject.getValue(pointer).map(jsonValue -> createPatch(pointer, jsonValue.asString(), encryptionHandler, symmetricKey)).orElse(JsonObject.empty())) .filter(patch -> !patch.isEmpty()) .reduce(jsonObject, (updatedJsonObject, patch) -> JsonFactory.mergeJsonValues(patch, updatedJsonObject) .asObject()); } private static JsonObject createPatch(final JsonPointer pointer, final String oldValue, final BiFunction encryptionHandler, final String symmetricKey) { try { final Optional password = getUriPassword(oldValue); return password.map(pwd -> { final String patchedPwd = encryptionHandler.apply(pwd, symmetricKey); final String patchedUri = replaceUriPassword(oldValue, patchedPwd); return JsonFactory.newObject(pointer, JsonValue.of(patchedUri)); }).orElse(JsonObject.empty()); } catch (ConnectionUriInvalidException | URISyntaxException e) { LOGGER.trace("<{}> value is not a uri, will encrypt whole value.", pointer); final String encryptedValue = encryptionHandler.apply(oldValue, symmetricKey); return JsonFactory.newObject(pointer, JsonValue.of(encryptedValue)); } catch (Exception ise) { LOGGER.warn("{} of connection value at <{}> failed", ise.getMessage(), pointer, ise); throw new IllegalStateException( String.format("%s of connection value at <%s> failed", ise.getMessage(), pointer), ise); } } private static String decryptValue(final String value, final String symmetricKey) { if (value.startsWith(ENCRYPTED_PREFIX)) { final String striped = value.replace(ENCRYPTED_PREFIX, ""); try { return EncryptorAesGcm.decryptWithPrefixIV(striped, symmetricKey); } catch (Exception e) { throw new IllegalStateException("Decryption", e); } } return value; } private static String encryptValue(final String value, final String symmetricKey) { try { return ENCRYPTED_PREFIX + EncryptorAesGcm.encryptWithPrefixIV(value, symmetricKey); } catch (Exception e) { throw new IllegalStateException("Encryption", e); } } private static Optional getUriPassword(final String uriStringRepresentation) throws URISyntaxException { final URI uri = new URI(uriStringRepresentation); final String protocol = uri.getScheme(); if (protocol == null) { throw ConnectionUriInvalidException.newBuilder(uriStringRepresentation) .message("Not a valid connection URI") .build(); } final String userInfo = uri.getUserInfo(); if (userInfo == null) { return Optional.empty(); } final String[] userPass = userInfo.split(":", 2); return userPass.length == 2 ? Optional.of(userPass[1]) : Optional.empty(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy