diff --git a/README.md b/README.md index 6a867c696..f160362dd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ -[![Build Status](https://travis-ci.com/adorsys/datasafe.svg?branch=develop)](https://travis-ci.com/adorsys/datasafe) [![codecov](https://codecov.io/gh/adorsys/datasafe/branch/develop/graph/badge.svg)](https://codecov.io/gh/adorsys/datasafe) -[![Maintainability](https://api.codeclimate.com/v1/badges/06ae7d4cafc3012cee85/maintainability)](https://codeclimate.com/github/adorsys/datasafe/maintainability) +[![Maintainability](https://codeclimate.com/github/adorsys/datasafe.png)](https://codeclimate.com/github/adorsys/datasafe/maintainability) # Secure, Encrypted and Versioned Data Storage Library diff --git a/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/BaseE2EIT.java b/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/BaseE2EIT.java index 9cf4118e6..7d3acd3eb 100644 --- a/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/BaseE2EIT.java +++ b/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/BaseE2EIT.java @@ -23,6 +23,7 @@ import de.adorsys.datasafe.types.api.actions.ListRequest; import de.adorsys.datasafe.types.api.actions.ReadRequest; import de.adorsys.datasafe.types.api.actions.RemoveRequest; +import de.adorsys.datasafe.types.api.actions.WriteInboxRequest; import de.adorsys.datasafe.types.api.actions.WriteRequest; import de.adorsys.datasafe.types.api.resource.AbsoluteLocation; import de.adorsys.datasafe.types.api.resource.BasePrivateResource; @@ -117,9 +118,9 @@ protected void writeDataToPrivate(UserIDAuth auth, String path, String data) { } @SneakyThrows - protected void writeDataToInbox(UserIDAuth auth, String path, String data) { + protected void writeDataToInbox(UserIDAuth owner, UserIDAuth auth, String path, String data) { try (OutputStream stream = writeToInbox.write( - WriteRequest.forDefaultPublic(Collections.singleton(auth.getUserID()), path) + WriteInboxRequest.forDefaultPublic(owner, Collections.singleton(auth.getUserID()), path) )) { stream.write(data.getBytes(UTF_8)); @@ -191,9 +192,9 @@ protected void registerJohnAndJane() { } @SneakyThrows - protected void sendToInbox(UserID to, String filename, String data) { + protected void sendToInbox(UserIDAuth from, UserID to, String filename, String data) { try (OutputStream stream = writeToInbox.write( - WriteRequest.forDefaultPublic(Collections.singleton(to), "./" + filename) + WriteInboxRequest.forDefaultPublic(from, Collections.singleton(to), "./" + filename) )) { stream.write(data.getBytes()); } diff --git a/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/BasicFunctionalityIT.java b/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/BasicFunctionalityIT.java index ce249ce74..511e7a50e 100644 --- a/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/BasicFunctionalityIT.java +++ b/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/BasicFunctionalityIT.java @@ -8,6 +8,7 @@ import de.adorsys.datasafe.types.api.actions.ListRequest; import de.adorsys.datasafe.types.api.actions.ReadRequest; import de.adorsys.datasafe.types.api.actions.RemoveRequest; +import de.adorsys.datasafe.types.api.actions.WriteInboxRequest; import de.adorsys.datasafe.types.api.actions.WriteRequest; import de.adorsys.datasafe.types.api.global.Version; import de.adorsys.datasafe.types.api.resource.AbsoluteLocation; @@ -194,12 +195,15 @@ void testUserIsRemovedWithFiles(WithStorageProvider.StorageDescriptor descriptor void testMultipleRecipientsSharing(WithStorageProvider.StorageDescriptor descriptor) { init(descriptor); + UserIDAuth owner = registerUser("owner"); + UserIDAuth john = registerUser("john"); UserIDAuth jane = registerUser("jane"); UserIDAuth jamie = registerUser("jamie"); String multiShareFile = "multishare.txt"; - try (OutputStream os = writeToInbox.write(WriteRequest.forDefaultPublic( + try (OutputStream os = writeToInbox.write(WriteInboxRequest.forDefaultPublic( + owner, ImmutableSet.of(john.getUserID(), jane.getUserID(), jamie.getUserID()), multiShareFile)) ) { @@ -217,6 +221,8 @@ void testMultipleRecipientsSharing(WithStorageProvider.StorageDescriptor descrip void testMultipleRecipientsSharingLargeChunk(WithStorageProvider.StorageDescriptor descriptor) { init(descriptor); + UserIDAuth owner = registerUser("owner"); + UserIDAuth john = registerUser("john"); UserIDAuth jane = registerUser("jane"); UserIDAuth jamie = registerUser("jamie"); @@ -224,7 +230,8 @@ void testMultipleRecipientsSharingLargeChunk(WithStorageProvider.StorageDescript String multiShareFile = "multishare.txt"; byte[] bytes = new byte[LARGE_SIZE]; ThreadLocalRandom.current().nextBytes(bytes); - try (OutputStream os = writeToInbox.write(WriteRequest.forDefaultPublic( + try (OutputStream os = writeToInbox.write(WriteInboxRequest.forDefaultPublic( + owner, ImmutableSet.of(john.getUserID(), jane.getUserID(), jamie.getUserID()), multiShareFile)) ) { @@ -253,7 +260,7 @@ void testWriteToPrivateListPrivateReadPrivateAndSendToAndReadFromInbox( String privateContentJane = readPrivateUsingPrivateKey(jane, privateJane.getResource().asPrivate()); - sendToInbox(john.getUserID(), SHARED_FILE_PATH, privateContentJane); + sendToInbox(jane, john.getUserID(), SHARED_FILE_PATH, privateContentJane); AbsoluteLocation inboxJohn = getFirstFileInInbox(john); @@ -308,9 +315,9 @@ void listingInboxValidation(WithStorageProvider.StorageDescriptor descriptor) { registerJohnAndJane(); - writeDataToInbox(jane, "root.file", MESSAGE_ONE); - writeDataToInbox(jane, "level1/file", MESSAGE_ONE); - writeDataToInbox(jane, "level1/level2/file", MESSAGE_ONE); + writeDataToInbox(john, jane, "root.file", MESSAGE_ONE); + writeDataToInbox(john, jane, "level1/file", MESSAGE_ONE); + writeDataToInbox(john, jane, "level1/level2/file", MESSAGE_ONE); assertInboxSpaceList(jane, "", "root.file", "level1/file", "level1/level2/file"); assertInboxSpaceList(jane, "./", "root.file", "level1/file", "level1/level2/file"); diff --git a/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/BasicFunctionalityUtf8IT.java b/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/BasicFunctionalityUtf8IT.java index 344099e55..69f5d46d3 100644 --- a/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/BasicFunctionalityUtf8IT.java +++ b/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/BasicFunctionalityUtf8IT.java @@ -86,11 +86,12 @@ void listingPrivatePathWithUnicode(WithStorageProvider.StorageDescriptor descrip void readInboxContentWithUnicodeUsingUnicodePath(WithStorageProvider.StorageDescriptor descriptor) { init(descriptor); + john = registerUser("john"); jane = registerUser("jane"); String unicodeMessage = "привет мир!"; - writeDataToInbox(jane, " привет/prüfungsdokument=/файл:&? с пробелом.док", unicodeMessage); + writeDataToInbox(john, jane, " привет/prüfungsdokument=/файл:&? с пробелом.док", unicodeMessage); String inboxContentJane = readInboxUsingPrivateKey( jane, @@ -107,9 +108,9 @@ void listingInboxPathWithUnicode(WithStorageProvider.StorageDescriptor descripto registerJohnAndJane(); - writeDataToInbox(jane, "prüfungsdokument.doc+doc", MESSAGE_ONE); - writeDataToInbox(jane, "уровень1/?файл+doc", MESSAGE_ONE); - writeDataToInbox(jane, "уровень1/уровень 2=+/&файл пробел+плюс", MESSAGE_ONE); + writeDataToInbox(john, jane, "prüfungsdokument.doc+doc", MESSAGE_ONE); + writeDataToInbox(john, jane, "уровень1/?файл+doc", MESSAGE_ONE); + writeDataToInbox(john, jane, "уровень1/уровень 2=+/&файл пробел+плюс", MESSAGE_ONE); assertInboxSpaceList(jane, "", "prüfungsdokument.doc+doc", "уровень1/?файл+doc", "уровень1/уровень 2=+/&файл пробел+плюс"); assertInboxSpaceList(jane, "./", "prüfungsdokument.doc+doc", "уровень1/?файл+doc", "уровень1/уровень 2=+/&файл пробел+плюс"); diff --git a/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/BasicFunctionalityWithPasswordChangeIT.java b/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/BasicFunctionalityWithPasswordChangeIT.java index 714696659..189128a74 100644 --- a/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/BasicFunctionalityWithPasswordChangeIT.java +++ b/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/BasicFunctionalityWithPasswordChangeIT.java @@ -5,7 +5,7 @@ import de.adorsys.datasafe.encrypiton.api.types.UserIDAuth; import de.adorsys.datasafe.teststorage.WithStorageProvider; import de.adorsys.datasafe.types.api.actions.ReadRequest; -import de.adorsys.datasafe.types.api.actions.WriteRequest; +import de.adorsys.datasafe.types.api.actions.WriteInboxRequest; import de.adorsys.datasafe.types.api.resource.AbsoluteLocation; import de.adorsys.datasafe.types.api.resource.ResolvedResource; import de.adorsys.datasafe.types.api.resource.Uri; @@ -60,12 +60,14 @@ void testUserIsRemovedWithFiles(WithStorageProvider.StorageDescriptor descriptor void testMultipleRecipientsSharing(WithStorageProvider.StorageDescriptor descriptor) { init(descriptor); + UserIDAuth sender = registerUser("sender"); + UserIDAuth john = registerUser("john"); UserIDAuth jane = registerUser("jane"); UserIDAuth jamie = registerUser("jamie"); String multiShareFile = "multishare.txt"; - multishareFiles(john, jane, jamie, multiShareFile); + multishareFiles(sender, john, jane, jamie, multiShareFile); Stream.of(john, jane, jamie).forEach( it -> checkUpdatedCredsWorkAndOldDont( @@ -102,7 +104,7 @@ void testWriteToPrivateListPrivateReadPrivateAndSendToAndReadFromInbox( String privateContentJane = readPrivateUsingPrivateKey(jane, privateJane.getResource().asPrivate()); - sendToInbox(john.getUserID(), SHARED_FILE_PATH, privateContentJane); + sendToInbox(jane, john.getUserID(), SHARED_FILE_PATH, privateContentJane); AbsoluteLocation inboxJohn = getFirstFileInInbox(john); @@ -188,8 +190,9 @@ void listingValidation(WithStorageProvider.StorageDescriptor descriptor) { } @SneakyThrows - private void multishareFiles(UserIDAuth userOne, UserIDAuth userTwo, UserIDAuth userThree, String multiShareFile) { - try (OutputStream os = writeToInbox.write(WriteRequest.forDefaultPublic( + private void multishareFiles(UserIDAuth sender, UserIDAuth userOne, UserIDAuth userTwo, UserIDAuth userThree, String multiShareFile) { + try (OutputStream os = writeToInbox.write(WriteInboxRequest.forDefaultPublic( + sender, ImmutableSet.of(userOne.getUserID(), userTwo.getUserID(), userThree.getUserID()), multiShareFile)) ) { diff --git a/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/DataTamperingResistanceIT.java b/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/DataTamperingResistanceIT.java index 9d2a623fb..65544fe61 100644 --- a/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/DataTamperingResistanceIT.java +++ b/datasafe-business/src/test/java/de/adorsys/datasafe/business/impl/e2e/DataTamperingResistanceIT.java @@ -3,6 +3,7 @@ import de.adorsys.datasafe.business.impl.service.DefaultDatasafeServices; import de.adorsys.datasafe.types.api.actions.ListRequest; import de.adorsys.datasafe.types.api.actions.ReadRequest; +import de.adorsys.datasafe.types.api.actions.WriteInboxRequest; import de.adorsys.datasafe.types.api.actions.WriteRequest; import de.adorsys.datasafe.types.api.resource.AbsoluteLocation; import de.adorsys.datasafe.types.api.resource.ResolvedResource; @@ -82,7 +83,7 @@ void testPrivateDocumentContentTamperResistance() { @SneakyThrows void testInboxDocumentContentTamperResistance() { try (OutputStream os = writeToInbox.write( - WriteRequest.forDefaultPublic(Collections.singleton(john.getUserID()), FILENAME)) + WriteInboxRequest.forDefaultPublic(jane, Collections.singleton(john.getUserID()), FILENAME)) ) { os.write(FILE_TEXT.getBytes(StandardCharsets.UTF_8)); } diff --git a/datasafe-cli/src/main/java/de/adorsys/datasafe/cli/commands/inbox/Share.java b/datasafe-cli/src/main/java/de/adorsys/datasafe/cli/commands/inbox/Share.java index 244bdf4d0..e64dc360e 100644 --- a/datasafe-cli/src/main/java/de/adorsys/datasafe/cli/commands/inbox/Share.java +++ b/datasafe-cli/src/main/java/de/adorsys/datasafe/cli/commands/inbox/Share.java @@ -3,7 +3,7 @@ import com.google.common.io.ByteStreams; import com.google.common.io.MoreFiles; import de.adorsys.datasafe.encrypiton.api.types.UserID; -import de.adorsys.datasafe.types.api.actions.WriteRequest; +import de.adorsys.datasafe.types.api.actions.WriteInboxRequest; import lombok.SneakyThrows; import picocli.CommandLine; @@ -41,7 +41,8 @@ public class Share implements Runnable { public void run() { try (OutputStream os = inbox.getCli().datasafe().inboxService() .write( - WriteRequest.forDefaultPublic( + WriteInboxRequest.forDefaultPublic( + inbox.getCli().auth(), recipients.stream().map(UserID::new).collect(Collectors.toSet()), filename ) diff --git a/datasafe-directory/datasafe-directory-api/src/main/java/de/adorsys/datasafe/directory/api/profile/keys/DocumentKeyStoreOperations.java b/datasafe-directory/datasafe-directory-api/src/main/java/de/adorsys/datasafe/directory/api/profile/keys/DocumentKeyStoreOperations.java index c7fa71171..3b3f7d11c 100644 --- a/datasafe-directory/datasafe-directory-api/src/main/java/de/adorsys/datasafe/directory/api/profile/keys/DocumentKeyStoreOperations.java +++ b/datasafe-directory/datasafe-directory-api/src/main/java/de/adorsys/datasafe/directory/api/profile/keys/DocumentKeyStoreOperations.java @@ -5,6 +5,7 @@ import de.adorsys.datasafe.types.api.types.ReadKeyPassword; import java.security.Key; +import java.security.KeyPair; import java.util.List; import java.util.Set; @@ -42,4 +43,6 @@ public interface DocumentKeyStoreOperations { * @return Key aliases from keystore. */ Set readAliases(UserIDAuth forUser); + + KeyPair getKeyPair(UserIDAuth forUser); } diff --git a/datasafe-directory/datasafe-directory-api/src/main/java/de/adorsys/datasafe/directory/api/profile/keys/PrivateKeyService.java b/datasafe-directory/datasafe-directory-api/src/main/java/de/adorsys/datasafe/directory/api/profile/keys/PrivateKeyService.java index 2acdc65de..7111415b0 100644 --- a/datasafe-directory/datasafe-directory-api/src/main/java/de/adorsys/datasafe/directory/api/profile/keys/PrivateKeyService.java +++ b/datasafe-directory/datasafe-directory-api/src/main/java/de/adorsys/datasafe/directory/api/profile/keys/PrivateKeyService.java @@ -5,6 +5,7 @@ import de.adorsys.datasafe.encrypiton.api.types.keystore.SecretKeyIDWithKey; import java.security.Key; +import java.security.KeyPair; import java.util.Map; import java.util.Set; @@ -45,4 +46,6 @@ public interface PrivateKeyService { * {@code keyIds} that are missing - they are silently ignored and not returned in result. */ Map keysByIds(UserIDAuth forUser, Set keyIds); + + KeyPair getKeyPair(UserIDAuth forUser); } diff --git a/datasafe-directory/datasafe-directory-impl/src/main/java/de/adorsys/datasafe/directory/impl/profile/keys/DFSPrivateKeyServiceImpl.java b/datasafe-directory/datasafe-directory-impl/src/main/java/de/adorsys/datasafe/directory/impl/profile/keys/DFSPrivateKeyServiceImpl.java index 94eb20ce5..9df1a2b23 100644 --- a/datasafe-directory/datasafe-directory-impl/src/main/java/de/adorsys/datasafe/directory/impl/profile/keys/DFSPrivateKeyServiceImpl.java +++ b/datasafe-directory/datasafe-directory-impl/src/main/java/de/adorsys/datasafe/directory/impl/profile/keys/DFSPrivateKeyServiceImpl.java @@ -14,6 +14,7 @@ import javax.crypto.SecretKey; import javax.inject.Inject; import java.security.Key; +import java.security.KeyPair; import java.security.KeyStoreException; import java.security.UnrecoverableKeyException; import java.util.Collection; @@ -100,6 +101,11 @@ public Map keysByIds(UserIDAuth forUser, Set keyIds) { ); } + @Override + public KeyPair getKeyPair(UserIDAuth forUser) { + return keyStoreOper.getKeyPair(forUser); + } + protected SecretKeyIDWithKey keyByPrefix(UserIDAuth forUser, String prefix) { return keyByPrefix( forUser, diff --git a/datasafe-directory/datasafe-directory-impl/src/main/java/de/adorsys/datasafe/directory/impl/profile/keys/DocumentKeyStoreOperationsImpl.java b/datasafe-directory/datasafe-directory-impl/src/main/java/de/adorsys/datasafe/directory/impl/profile/keys/DocumentKeyStoreOperationsImpl.java index 51f968c23..961399634 100644 --- a/datasafe-directory/datasafe-directory-impl/src/main/java/de/adorsys/datasafe/directory/impl/profile/keys/DocumentKeyStoreOperationsImpl.java +++ b/datasafe-directory/datasafe-directory-impl/src/main/java/de/adorsys/datasafe/directory/impl/profile/keys/DocumentKeyStoreOperationsImpl.java @@ -25,7 +25,9 @@ import javax.inject.Inject; import java.io.OutputStream; import java.security.Key; +import java.security.KeyPair; import java.security.KeyStore; +import java.security.PrivateKey; import java.util.List; import java.util.Set; @@ -103,6 +105,16 @@ public void updateReadKeyPassword(UserIDAuth forUser, ReadKeyPassword newPasswor genericOper.updateReadKeyPassword(keyStore(forUser), location, forUser, newPassword); } + @Override + @SneakyThrows + public KeyPair getKeyPair(UserIDAuth forUser) { + KeyStore keyStore = keyStore(forUser); + KeyStoreAuth auth = keystoreAuth(forUser, forUser.getReadKeyPassword()); + List publicKeys = keyStoreService.getPublicKeys(new KeyStoreAccess(keyStore, auth)); + Key key = genericOper.getKey(() -> keyStore(forUser), forUser, publicKeys.get(0).getKeyID().getValue()); + return new KeyPair(publicKeys.get(0).getPublicKey(), (PrivateKey) key); + } + private AbsoluteLocation keystoreLocationWithAccess(UserIDAuth forUser) { return this.access.privateAccessFor( forUser, diff --git a/datasafe-encryption/datasafe-encryption-api/src/main/java/de/adorsys/datasafe/encrypiton/api/cmsencryption/CMSEncryptionService.java b/datasafe-encryption/datasafe-encryption-api/src/main/java/de/adorsys/datasafe/encrypiton/api/cmsencryption/CMSEncryptionService.java index 6c051bd26..96e7966b8 100644 --- a/datasafe-encryption/datasafe-encryption-api/src/main/java/de/adorsys/datasafe/encrypiton/api/cmsencryption/CMSEncryptionService.java +++ b/datasafe-encryption/datasafe-encryption-api/src/main/java/de/adorsys/datasafe/encrypiton/api/cmsencryption/CMSEncryptionService.java @@ -7,6 +7,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.security.Key; +import java.security.KeyPair; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -25,7 +26,8 @@ public interface CMSEncryptionService { * @return Encrypted stream that wraps {@code dataContentStream} * @apiNote Closes underlying stream when result is closed */ - OutputStream buildEncryptionOutputStream(OutputStream dataContentStream, Set publicKeys); + OutputStream buildEncryptionOutputStream(OutputStream dataContentStream, Set publicKeys, + KeyPair senderKeyPair); /** * Builds symmetrically encrypted stream. diff --git a/datasafe-encryption/datasafe-encryption-api/src/main/java/de/adorsys/datasafe/encrypiton/api/document/EncryptedDocumentWriteService.java b/datasafe-encryption/datasafe-encryption-api/src/main/java/de/adorsys/datasafe/encrypiton/api/document/EncryptedDocumentWriteService.java index 257dd77e9..70830ea8a 100644 --- a/datasafe-encryption/datasafe-encryption-api/src/main/java/de/adorsys/datasafe/encrypiton/api/document/EncryptedDocumentWriteService.java +++ b/datasafe-encryption/datasafe-encryption-api/src/main/java/de/adorsys/datasafe/encrypiton/api/document/EncryptedDocumentWriteService.java @@ -8,6 +8,7 @@ import de.adorsys.datasafe.types.api.resource.WithCallback; import java.io.OutputStream; +import java.security.KeyPair; import java.util.Map; /** @@ -22,7 +23,7 @@ public interface EncryptedDocumentWriteService { * @param recipientsWithInbox Map of (recipient public key - recipients' inbox) of users with whom to share file. * @return Sink where you can send unencrypted data that will be encrypted and stored */ - OutputStream write(Map recipientsWithInbox); + OutputStream write(Map recipientsWithInbox, KeyPair senderKeyPair); /** * Writes and encrypts data using symmetric cryptography. diff --git a/datasafe-encryption/datasafe-encryption-api/src/main/java/de/adorsys/datasafe/encrypiton/api/types/encryption/KeyCreationConfig.java b/datasafe-encryption/datasafe-encryption-api/src/main/java/de/adorsys/datasafe/encrypiton/api/types/encryption/KeyCreationConfig.java index 9bab88b51..1bb85b17a 100644 --- a/datasafe-encryption/datasafe-encryption-api/src/main/java/de/adorsys/datasafe/encrypiton/api/types/encryption/KeyCreationConfig.java +++ b/datasafe-encryption/datasafe-encryption-api/src/main/java/de/adorsys/datasafe/encrypiton/api/types/encryption/KeyCreationConfig.java @@ -45,13 +45,16 @@ public static class SecretKeyCreationCfg { public static class EncryptingKeyCreationCfg { @Builder.Default - private final String algo = "RSA"; + private final String algo = "ECDH"; @Builder.Default - private final int size = 2048; + private final int size = 256; + + @Builder.Default + private final String sigAlgo = "SHA256withECDSA"; @Builder.Default - private final String sigAlgo = "SHA256withRSA"; + private final String curve = "secp256r1"; } @Getter @@ -59,12 +62,15 @@ public static class EncryptingKeyCreationCfg { public static class SigningKeyCreationCfg { @Builder.Default - private final String algo = "RSA"; + private final String algo = "ECDH"; + + @Builder.Default + private final int size = 256; @Builder.Default - private final int size = 2048; + private final String sigAlgo = "SHA256withECDSA"; @Builder.Default - private final String sigAlgo = "SHA256withRSA"; + private final String curve = "secp256r1"; } } diff --git a/datasafe-encryption/datasafe-encryption-api/src/main/java/de/adorsys/datasafe/encrypiton/api/types/encryption/MutableEncryptionConfig.java b/datasafe-encryption/datasafe-encryption-api/src/main/java/de/adorsys/datasafe/encrypiton/api/types/encryption/MutableEncryptionConfig.java index b2ffd8872..37f42ae24 100644 --- a/datasafe-encryption/datasafe-encryption-api/src/main/java/de/adorsys/datasafe/encrypiton/api/types/encryption/MutableEncryptionConfig.java +++ b/datasafe-encryption/datasafe-encryption-api/src/main/java/de/adorsys/datasafe/encrypiton/api/types/encryption/MutableEncryptionConfig.java @@ -183,6 +183,7 @@ public static class MutableEncryptingKeyCreationCfg { private String algo; private Integer size; private String sigAlgo; + private String curve; KeyCreationConfig.EncryptingKeyCreationCfg toEncryptingKeyCreationCfg() { KeyCreationConfig.EncryptingKeyCreationCfg.EncryptingKeyCreationCfgBuilder builder = @@ -199,6 +200,10 @@ KeyCreationConfig.EncryptingKeyCreationCfg toEncryptingKeyCreationCfg() { builder.sigAlgo(sigAlgo); } + if (null != curve) { + builder.curve(curve); + } + return builder.build(); } } @@ -209,6 +214,7 @@ public static class MutableSigningKeyCreationCfg { private String algo; private Integer size; private String sigAlgo; + private String curve; KeyCreationConfig.SigningKeyCreationCfg toSigningKeyCreationCfg() { KeyCreationConfig.SigningKeyCreationCfg.SigningKeyCreationCfgBuilder builder = @@ -225,6 +231,10 @@ KeyCreationConfig.SigningKeyCreationCfg toSigningKeyCreationCfg() { builder.sigAlgo(sigAlgo); } + if (null != curve) { + builder.curve(curve); + } + return builder.build(); } } diff --git a/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/cmsencryption/CMSEncryptionServiceImpl.java b/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/cmsencryption/CMSEncryptionServiceImpl.java index abca2737d..6833b667f 100644 --- a/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/cmsencryption/CMSEncryptionServiceImpl.java +++ b/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/cmsencryption/CMSEncryptionServiceImpl.java @@ -11,6 +11,7 @@ import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.cms.CMSAlgorithm; import org.bouncycastle.cms.CMSEnvelopedDataParser; import org.bouncycastle.cms.CMSEnvelopedDataStreamGenerator; import org.bouncycastle.cms.CMSException; @@ -19,6 +20,7 @@ import org.bouncycastle.cms.RecipientInformationStore; import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder; import org.bouncycastle.cms.jcajce.JceKEKRecipientInfoGenerator; +import org.bouncycastle.cms.jcajce.JceKeyAgreeRecipientInfoGenerator; import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator; import javax.crypto.SecretKey; @@ -27,6 +29,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.security.Key; +import java.security.KeyPair; import java.util.Collections; import java.util.Map; import java.util.Set; @@ -57,18 +60,36 @@ public CMSEncryptionServiceImpl(ASNCmsEncryptionConfig encryptionConfig) { @Override @SneakyThrows public OutputStream buildEncryptionOutputStream(OutputStream dataContentStream, - Set publicKeys) { + Set publicKeys, + KeyPair senderKeyPair) { return streamEncrypt( dataContentStream, - publicKeys.stream().map( - it -> new JceKeyTransRecipientInfoGenerator( - it.getKeyID().getValue().getBytes(), it.getPublicKey() - ) - ).collect(Collectors.toSet()), + publicKeys.stream().map(it -> getRecipientInfoGenerator(it, senderKeyPair)).collect(Collectors.toSet()), encryptionConfig.getAlgorithm() ); } + private RecipientInfoGenerator getRecipientInfoGenerator(PublicKeyIDWithPublicKey keyWithId, KeyPair senderKeyPair) { + if ("RSA".equals(keyWithId.getPublicKey().getAlgorithm())) { + return new JceKeyTransRecipientInfoGenerator(keyWithId.getKeyID().getValue().getBytes(), keyWithId.getPublicKey()); + } + if (Set.of("ECDH", "EC").contains(keyWithId.getPublicKey().getAlgorithm())) { + return getJceKeyAgreeRecipientInfoGenerator(senderKeyPair, keyWithId); + } + return null; + } + + @SneakyThrows + public JceKeyAgreeRecipientInfoGenerator getJceKeyAgreeRecipientInfoGenerator(KeyPair senderKeyPair, PublicKeyIDWithPublicKey publicKeyWithId) { + var jceKeyAgreeRecipientInfoGenerator = new JceKeyAgreeRecipientInfoGenerator( + CMSAlgorithm.ECDH_SHA256KDF, + senderKeyPair.getPrivate(), + senderKeyPair.getPublic(), + CMSAlgorithm.AES256_WRAP); + jceKeyAgreeRecipientInfoGenerator.addRecipient(publicKeyWithId.getKeyID().getValue().getBytes(), publicKeyWithId.getPublicKey()); + return jceKeyAgreeRecipientInfoGenerator; + } + /** * Symmetrical encryption-based stream, algorithm is provided by {@link ASNCmsEncryptionConfig#getAlgorithm()} * Uses {@link RecipientId#kek} recipient id. diff --git a/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/cmsencryption/decryptors/DecryptorFactory.java b/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/cmsencryption/decryptors/DecryptorFactory.java index 660df2d6c..404d18025 100644 --- a/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/cmsencryption/decryptors/DecryptorFactory.java +++ b/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/cmsencryption/decryptors/DecryptorFactory.java @@ -1,6 +1,5 @@ package de.adorsys.datasafe.encrypiton.impl.cmsencryption.decryptors; -import com.google.common.collect.ImmutableMap; import de.adorsys.datasafe.encrypiton.impl.cmsencryption.exceptions.DecryptionException; import lombok.experimental.UtilityClass; import org.bouncycastle.cms.RecipientInformation; @@ -15,13 +14,15 @@ public class DecryptorFactory { private static final Map> DECRYPTORS = - ImmutableMap.of( + Map.of( KekDecryptor.KEY_ID, KekDecryptor::new, - KeyTransDecryptor.KEY_ID, KeyTransDecryptor::new + KeyTransDecryptor.KEY_ID, KeyTransDecryptor::new, + KeyAgreeDecryptor.KEY_ID, KeyAgreeDecryptor::new ); /** * Get decryptor instance to decrypt data on {@code recipient} + * * @param information Container with encrypted data * @return Decryptor that, when supplied by proper key can decrypt data in {@code recipient} */ diff --git a/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/cmsencryption/decryptors/KeyAgreeDecryptor.java b/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/cmsencryption/decryptors/KeyAgreeDecryptor.java new file mode 100644 index 000000000..6dd6013aa --- /dev/null +++ b/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/cmsencryption/decryptors/KeyAgreeDecryptor.java @@ -0,0 +1,32 @@ +package de.adorsys.datasafe.encrypiton.impl.cmsencryption.decryptors; + +import lombok.SneakyThrows; +import org.bouncycastle.cms.KeyAgreeRecipientId; +import org.bouncycastle.cms.RecipientId; +import org.bouncycastle.cms.RecipientInformation; +import org.bouncycastle.cms.jcajce.JceKeyAgreeEnvelopedRecipient; + +import java.io.InputStream; +import java.security.Key; +import java.security.PrivateKey; + +public class KeyAgreeDecryptor extends Decryptor { + + static final int KEY_ID = RecipientId.keyAgree; + + KeyAgreeDecryptor(RecipientInformation recipientInformation) { + super( + new String(((KeyAgreeRecipientId) recipientInformation.getRID()).getSubjectKeyIdentifier()), + KEY_ID, + recipientInformation + ); + } + + @Override + @SneakyThrows + public InputStream decryptionStream(Key privateKey) { + return recipientInfo + .getContentStream(new JceKeyAgreeEnvelopedRecipient((PrivateKey) privateKey)) + .getContentStream(); + } +} \ No newline at end of file diff --git a/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/document/CMSDocumentWriteService.java b/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/document/CMSDocumentWriteService.java index ce592ddd7..96bf8d22b 100644 --- a/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/document/CMSDocumentWriteService.java +++ b/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/document/CMSDocumentWriteService.java @@ -18,6 +18,7 @@ import javax.inject.Inject; import java.io.IOException; import java.io.OutputStream; +import java.security.KeyPair; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -41,7 +42,7 @@ public CMSDocumentWriteService(StorageWriteService writeService, } @Override - public OutputStream write(Map recipientsWithInbox) { + public OutputStream write(Map recipientsWithInbox, KeyPair senderKeyPair) { int maxChunkSize = recipientsWithInbox.values().stream() .map(writeService::flushChunkSize) @@ -59,7 +60,8 @@ public OutputStream write(Map recipi OutputStream encryptionSink = cms.buildEncryptionOutputStream( dfsSink, - recipientsWithInbox.keySet() + recipientsWithInbox.keySet(), + senderKeyPair ); return new CloseCoordinatingStream(encryptionSink, ImmutableList.of(encryptionSink, dfsSink)); diff --git a/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/keystore/KeyStoreServiceImpl.java b/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/keystore/KeyStoreServiceImpl.java index fd1487d9f..6044df2ae 100644 --- a/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/keystore/KeyStoreServiceImpl.java +++ b/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/keystore/KeyStoreServiceImpl.java @@ -1,5 +1,6 @@ package de.adorsys.datasafe.encrypiton.impl.keystore; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import de.adorsys.datasafe.encrypiton.api.keystore.KeyStoreService; import de.adorsys.datasafe.encrypiton.api.types.encryption.KeyCreationConfig; @@ -19,6 +20,8 @@ import de.adorsys.keymanagement.api.types.template.generated.Secret; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.crypto.ec.CustomNamedCurves; +import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; @@ -28,6 +31,7 @@ import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.X509Certificate; +import java.security.spec.ECParameterSpec; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; @@ -82,6 +86,8 @@ public KeyStore createKeyStore(KeyStoreAuth keyStoreAuth, EncryptingKeyCreationCfg encConf = keyConfig.getEncrypting(); Supplier passSupplier = () -> keyStoreAuth.getReadKeyPassword().getValue(); + ECParameterSpec paramSpec = Strings.isNullOrEmpty(encConf.getCurve()) ? + null : EC5Util.convertToSpec(CustomNamedCurves.getByName(encConf.getCurve())); KeySetTemplate template = KeySetTemplate.builder() .generatedEncryptionKeys(Encrypting.with() .algo(encConf.getAlgo()) @@ -89,6 +95,7 @@ public KeyStore createKeyStore(KeyStoreAuth keyStoreAuth, .keySize(encConf.getSize()) .prefix("ENC") .password(passSupplier) + .paramSpec(paramSpec) .build() .repeat(keyConfig.getEncKeyNumber()) ) diff --git a/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/keystore/PublicKeySerdeImpl.java b/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/keystore/PublicKeySerdeImpl.java index c64e3fe9f..a688b0836 100644 --- a/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/keystore/PublicKeySerdeImpl.java +++ b/datasafe-encryption/datasafe-encryption-impl/src/main/java/de/adorsys/datasafe/encrypiton/impl/keystore/PublicKeySerdeImpl.java @@ -1,10 +1,13 @@ package de.adorsys.datasafe.encrypiton.impl.keystore; import de.adorsys.datasafe.encrypiton.api.keystore.PublicKeySerde; +import de.adorsys.datasafe.encrypiton.impl.cmsencryption.exceptions.DecryptionException; import de.adorsys.datasafe.types.api.context.annotations.RuntimeDelegate; import lombok.SneakyThrows; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import org.bouncycastle.jcajce.provider.asymmetric.rsa.KeyFactorySpi; +import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; import javax.inject.Inject; import java.security.PublicKey; @@ -16,6 +19,8 @@ @RuntimeDelegate public class PublicKeySerdeImpl implements PublicKeySerde { + private static final ASN1ObjectIdentifier RSA = PKCSObjectIdentifiers.rsaEncryption; + private static final ASN1ObjectIdentifier EC = X9ObjectIdentifiers.id_ecPublicKey; @Inject public PublicKeySerdeImpl() { @@ -26,7 +31,13 @@ public PublicKeySerdeImpl() { public PublicKey readPubKey(String encoded) { // FIXME: Legacy stuff byte[] bytes = Base64.getDecoder().decode(encoded); - return new KeyFactorySpi().generatePublic(SubjectPublicKeyInfo.getInstance(bytes)); + SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(bytes); + if (RSA.equals(subjectPublicKeyInfo.getAlgorithm().getAlgorithm())) { + return new org.bouncycastle.jcajce.provider.asymmetric.rsa.KeyFactorySpi().generatePublic(SubjectPublicKeyInfo.getInstance(bytes)); + } else if (EC.equals(subjectPublicKeyInfo.getAlgorithm().getAlgorithm())) { + return new org.bouncycastle.jcajce.provider.asymmetric.ec.KeyFactorySpi.ECDH().generatePublic(subjectPublicKeyInfo); + } + throw new DecryptionException("PublicKeySerdeImpl.UnsupportedEncodedKey"); } @Override diff --git a/datasafe-encryption/datasafe-encryption-impl/src/test/java/de/adorsys/datasafe/encrypiton/impl/cmsencryption/CmsEncryptionServiceImplTest.java b/datasafe-encryption/datasafe-encryption-impl/src/test/java/de/adorsys/datasafe/encrypiton/impl/cmsencryption/CmsEncryptionServiceImplTest.java index 0857d8ae7..3c0168b08 100644 --- a/datasafe-encryption/datasafe-encryption-impl/src/test/java/de/adorsys/datasafe/encrypiton/impl/cmsencryption/CmsEncryptionServiceImplTest.java +++ b/datasafe-encryption/datasafe-encryption-impl/src/test/java/de/adorsys/datasafe/encrypiton/impl/cmsencryption/CmsEncryptionServiceImplTest.java @@ -21,9 +21,8 @@ import org.assertj.core.util.Sets; import org.bouncycastle.cms.CMSAlgorithm; import org.bouncycastle.cms.CMSEnvelopedDataStreamGenerator; -import org.bouncycastle.cms.RecipientInfoGenerator; import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder; -import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator; +import org.bouncycastle.cms.jcajce.JceKeyAgreeRecipientInfoGenerator; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.util.encoders.Hex; import org.junit.jupiter.api.BeforeAll; @@ -42,10 +41,14 @@ import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Paths; +import java.security.Key; +import java.security.KeyPair; import java.security.KeyStore; import java.security.MessageDigest; +import java.security.PrivateKey; import java.util.Arrays; import java.util.Collections; +import java.util.List; import static com.google.common.io.ByteStreams.toByteArray; import static de.adorsys.datasafe.encrypiton.impl.cmsencryption.KeyStoreUtil.getKeys; @@ -83,9 +86,10 @@ void testCmsStreamEnvelopeEncryptAndDecryptTestWithMultipleRecipients() { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - OutputStream encryptionStream = cmsEncryptionService.buildEncryptionOutputStream(outputStream, Sets.newHashSet( - Arrays.asList(getPublicKeyIDWithPublicKey(keyStoreAccess1), getPublicKeyIDWithPublicKey(keyStoreAccess2)) - )); + OutputStream encryptionStream = cmsEncryptionService.buildEncryptionOutputStream(outputStream, + Sets.newHashSet(Arrays.asList(getPublicKeyIDWithPublicKey(keyStoreAccess1), getPublicKeyIDWithPublicKey(keyStoreAccess2))), + getKeyPair(keyStoreAccess3, "Suzanne") + ); encryptionStream.write(TEST_MESSAGE_CONTENT.getBytes()); encryptionStream.close(); @@ -117,11 +121,15 @@ void cmsStreamEnvelopeEncryptAndDecryptTest() { PublicKeyIDWithPublicKey publicKeyIDWithPublicKey = keyStoreService.getPublicKeys(keyStoreAccess).get(0); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - OutputStream encryptionStream = cmsEncryptionService.buildEncryptionOutputStream(outputStream, + KeyStoreAccess keyStoreAccessSender = getKeyStoreAccess("Sender"); + + OutputStream encryptionStream = cmsEncryptionService.buildEncryptionOutputStream( + outputStream, Collections.singleton(new PublicKeyIDWithPublicKey( publicKeyIDWithPublicKey.getKeyID(), publicKeyIDWithPublicKey.getPublicKey() - )) + )), + getKeyPair(keyStoreAccessSender, "Sender") ); encryptionStream.write(TEST_MESSAGE_CONTENT.getBytes()); @@ -148,11 +156,14 @@ void cmsStreamEnvelopeZeroKeyPairFailTest() { gen.open(outputStream, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC) .setProvider(BouncyCastleProvider.PROVIDER_NAME).build()); + KeyStoreAccess keyStoreAccessSender = getKeyStoreAccess("Sender"); + OutputStream encryptionStream = cmsEncryptionService.buildEncryptionOutputStream(outputStream, Collections.singleton(new PublicKeyIDWithPublicKey( publicKeyIDWithPublicKey.getKeyID(), publicKeyIDWithPublicKey.getPublicKey() - )) + )), + getKeyPair(keyStoreAccessSender, "Sender") ); encryptionStream.write(TEST_MESSAGE_CONTENT.getBytes()); @@ -170,12 +181,19 @@ void cmsStreamEnvelopeTwoKeysPairFailTest() { PublicKeyIDWithPublicKey publicKeyIDWithPublicKey = keyStoreService.getPublicKeys(keyStoreAccess).get(0); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - RecipientInfoGenerator recipientInfo1 = new JceKeyTransRecipientInfoGenerator("key1".getBytes(), publicKeyIDWithPublicKey.getPublicKey()); - RecipientInfoGenerator recipientInfo2 = new JceKeyTransRecipientInfoGenerator("key2".getBytes(), publicKeyIDWithPublicKey.getPublicKey()); + KeyStoreAccess keyStoreAccessSender = getKeyStoreAccess("Sender"); + KeyPair senderKeyPair = getKeyPair(keyStoreAccessSender, "Sender"); + + JceKeyAgreeRecipientInfoGenerator keyAgreeRecipientInfoGenerator = new JceKeyAgreeRecipientInfoGenerator( + CMSAlgorithm.ECDH_SHA1KDF, + senderKeyPair.getPrivate(), + senderKeyPair.getPublic(), + CMSAlgorithm.AES128_WRAP); + keyAgreeRecipientInfoGenerator.addRecipient("key1".getBytes(), publicKeyIDWithPublicKey.getPublicKey()); + keyAgreeRecipientInfoGenerator.addRecipient("key2".getBytes(), publicKeyIDWithPublicKey.getPublicKey()); CMSEnvelopedDataStreamGenerator gen = new CMSEnvelopedDataStreamGenerator(); - gen.addRecipientInfoGenerator(recipientInfo1); - gen.addRecipientInfoGenerator(recipientInfo2); + gen.addRecipientInfoGenerator(keyAgreeRecipientInfoGenerator); gen.open(outputStream, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC) .setProvider(BouncyCastleProvider.PROVIDER_NAME).build()); @@ -183,7 +201,8 @@ void cmsStreamEnvelopeTwoKeysPairFailTest() { Collections.singleton(new PublicKeyIDWithPublicKey( publicKeyIDWithPublicKey.getKeyID(), publicKeyIDWithPublicKey.getPublicKey() - )) + )), + senderKeyPair ); encryptionStream.write(TEST_MESSAGE_CONTENT.getBytes()); @@ -202,11 +221,15 @@ void cmsStreamEnvelopeOneKeyPairFailTest() { PublicKeyIDWithPublicKey publicKeyIDWithPublicKey = keyStoreService.getPublicKeys(keyStoreAccess).get(0); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + KeyStoreAccess keyStoreAccessSender = getKeyStoreAccess("Sender"); + OutputStream encryptionStream = cmsEncryptionService.buildEncryptionOutputStream(outputStream, Collections.singleton(new PublicKeyIDWithPublicKey( publicKeyIDWithPublicKey.getKeyID(), publicKeyIDWithPublicKey.getPublicKey() - )) + )), + getKeyPair(keyStoreAccessSender, "Sender") ); encryptionStream.write(TEST_MESSAGE_CONTENT.getBytes()); @@ -230,6 +253,15 @@ private static KeyStoreAccess getKeyStoreAccess(String label) { return new KeyStoreAccess(keyStore, keyStoreAuth); } + @SneakyThrows + private static KeyPair getKeyPair(KeyStoreAccess keyStoreAccess, String label) { + KeyStore keyStore = keyStoreAccess.getKeyStore(); + ReadKeyPassword readKeyPassword = ReadKeyPasswordTestFactory.getForString(label); + List publicKeys = keyStoreService.getPublicKeys(keyStoreAccess); + Key key = keyStore.getKey(publicKeys.get(0).getKeyID().getValue(), readKeyPassword.getValue()); + return new KeyPair(publicKeys.get(0).getPublicKey(), (PrivateKey) key); + } + private static KeyStoreAccess getKeyStoreAccess() { return getKeyStoreAccess("readkeypassword"); } @@ -262,12 +294,15 @@ void cmsEnvelopeEncryptAndDecryptFileStreamTest() { File encryptedFile = new File(encryptedFilePath); FileOutputStream fosEnFile = new FileOutputStream(encryptedFile); + KeyStoreAccess keyStoreAccessSender = getKeyStoreAccess("Sender"); + OutputStream encryptionStream = cmsEncryptionService.buildEncryptionOutputStream( fosEnFile, Collections.singleton(new PublicKeyIDWithPublicKey( publicKeyIDWithPublicKey.getKeyID(), publicKeyIDWithPublicKey.getPublicKey() - )) + )), + getKeyPair(keyStoreAccessSender, "Sender") ); Files.copy(Paths.get(testFilePath), encryptionStream); diff --git a/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/mutable-pbkdf2.yaml b/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/mutable-pbkdf2.yaml index 6eeed0a7d..c0c2dc6df 100644 --- a/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/mutable-pbkdf2.yaml +++ b/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/mutable-pbkdf2.yaml @@ -19,9 +19,11 @@ keys: algo: enc-algo size: 13 sigAlgo: srv-sig-algo + curve: curve signing: algo: sig-algo size: 14 sigAlgo: srv-sig-algo + curve: curve cms: algo: cms-algo1 diff --git a/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/mutable-scrypt.yaml b/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/mutable-scrypt.yaml index 35e8c75cf..73cff026a 100644 --- a/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/mutable-scrypt.yaml +++ b/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/mutable-scrypt.yaml @@ -20,9 +20,11 @@ keys: algo: enc-algo size: 13 sigAlgo: srv-sig-algo + curve: curve signing: algo: sig-algo size: 14 sigAlgo: srv-sig-algo + curve: curve cms: algo: cms-algo1 diff --git a/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/expectation/mutable-null-cms.yaml b/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/expectation/mutable-null-cms.yaml index 0ceb75fc9..40554add3 100644 --- a/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/expectation/mutable-null-cms.yaml +++ b/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/expectation/mutable-null-cms.yaml @@ -20,9 +20,11 @@ keys: algo: enc-algo size: 13 sigAlgo: srv-sig-algo + curve: curve signing: algo: sig-algo size: 14 sigAlgo: srv-sig-algo + curve: curve cms: algo: AES256_GCM diff --git a/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/expectation/mutable-null-keys.yaml b/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/expectation/mutable-null-keys.yaml index 5c78c8f3a..ab620df15 100644 --- a/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/expectation/mutable-null-keys.yaml +++ b/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/expectation/mutable-null-keys.yaml @@ -17,12 +17,14 @@ keys: algo: AES size: 256 encrypting: - algo: RSA - size: 2048 - sigAlgo: SHA256withRSA + algo: ECDH + size: 256 + sigAlgo: SHA256withECDSA + curve: secp256r1 signing: - algo: RSA - size: 2048 - sigAlgo: SHA256withRSA + algo: ECDH + size: 256 + sigAlgo: SHA256withECDSA + curve: secp256r1 cms: algo: cms-algo1 diff --git a/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/expectation/mutable-null-keystore.yaml b/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/expectation/mutable-null-keystore.yaml index 2f891c9c3..807f54be4 100644 --- a/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/expectation/mutable-null-keystore.yaml +++ b/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/expectation/mutable-null-keystore.yaml @@ -19,9 +19,11 @@ keys: algo: enc-algo size: 13 sigAlgo: srv-sig-algo + curve: curve signing: algo: sig-algo size: 14 sigAlgo: srv-sig-algo + curve: curve cms: algo: cms-algo1 diff --git a/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/mutable-null-cms.yaml b/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/mutable-null-cms.yaml index 87a1dcd0d..aa1bfdb85 100644 --- a/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/mutable-null-cms.yaml +++ b/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/mutable-null-cms.yaml @@ -20,7 +20,9 @@ keys: algo: enc-algo size: 13 sigAlgo: srv-sig-algo + curve: curve signing: algo: sig-algo size: 14 sigAlgo: srv-sig-algo + curve: curve diff --git a/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/mutable-null-keystore.yaml b/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/mutable-null-keystore.yaml index 2f431a353..9d596638e 100644 --- a/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/mutable-null-keystore.yaml +++ b/datasafe-encryption/datasafe-encryption-impl/src/test/resources/config-test/null-test/mutable-null-keystore.yaml @@ -9,9 +9,11 @@ keys: algo: enc-algo size: 13 sigAlgo: srv-sig-algo + curve: curve signing: algo: sig-algo size: 14 sigAlgo: srv-sig-algo + curve: curve cms: algo: cms-algo1 diff --git a/datasafe-examples/datasafe-examples-business/src/test/java/de/adorsys/datasafe/examples/business/filesystem/BaseUserOperationsTestWithDefaultDatasafeTest.java b/datasafe-examples/datasafe-examples-business/src/test/java/de/adorsys/datasafe/examples/business/filesystem/BaseUserOperationsTestWithDefaultDatasafeTest.java index 177f6ed2d..76273efd5 100644 --- a/datasafe-examples/datasafe-examples-business/src/test/java/de/adorsys/datasafe/examples/business/filesystem/BaseUserOperationsTestWithDefaultDatasafeTest.java +++ b/datasafe-examples/datasafe-examples-business/src/test/java/de/adorsys/datasafe/examples/business/filesystem/BaseUserOperationsTestWithDefaultDatasafeTest.java @@ -10,6 +10,7 @@ import de.adorsys.datasafe.storage.impl.fs.FileSystemStorageService; import de.adorsys.datasafe.types.api.actions.ListRequest; import de.adorsys.datasafe.types.api.actions.ReadRequest; +import de.adorsys.datasafe.types.api.actions.WriteInboxRequest; import de.adorsys.datasafe.types.api.actions.WriteRequest; import de.adorsys.datasafe.types.api.resource.AbsoluteLocation; import de.adorsys.datasafe.types.api.resource.ResolvedResource; @@ -121,13 +122,16 @@ void readFileFromPrivateSpace() { @SneakyThrows void shareWithJane() { // BEGIN_SNIPPET:Send file to INBOX + // create sender user + UserIDAuth sender = registerUser("sender"); + // create Jane, so her INBOX does exist UserIDAuth jane = registerUser("jane"); UserID janeUsername = new UserID("jane"); // We send message "Hello John" to John just by his username try (OutputStream os = defaultDatasafeServices.inboxService() - .write(WriteRequest.forDefaultPublic(Collections.singleton(janeUsername), "hello.txt"))) { + .write(WriteInboxRequest.forDefaultPublic(sender, Collections.singleton(janeUsername), "hello.txt"))) { os.write("Hello Jane".getBytes(StandardCharsets.UTF_8)); } // END_SNIPPET @@ -143,6 +147,8 @@ void shareWithJane() { @SneakyThrows void shareWithJaneAndJamie() { // BEGIN_SNIPPET:Send file to INBOX - multiple users + // create Jack, who wants to send a file to Jane and Jamie + UserIDAuth jack = registerUser("jack"); // create Jane, so her INBOX does exist UserIDAuth jane = registerUser("jane"); // create Jamie, so his INBOX does exist @@ -150,7 +156,7 @@ void shareWithJaneAndJamie() { // We send message to both users by using their username: try (OutputStream os = defaultDatasafeServices.inboxService().write( - WriteRequest.forDefaultPublic(ImmutableSet.of(jane.getUserID(), jamie.getUserID()), "hello.txt")) + WriteInboxRequest.forDefaultPublic(jack, ImmutableSet.of(jane.getUserID(), jamie.getUserID()), "hello.txt")) ) { os.write("Hello Jane and Jamie".getBytes(StandardCharsets.UTF_8)); } @@ -206,13 +212,16 @@ void listPrivate() { @Test void listInbox() { // BEGIN_SNIPPET:List INBOX + // creating sender user + UserIDAuth sender = registerUser("sender"); + // creating new user UserIDAuth user = registerUser("john"); UserID johnUsername = new UserID("john"); // let's share 2 messages: - shareMessage(johnUsername, "home/my/secret.txt", "Hi there"); - shareMessage(johnUsername, "home/watch/films.txt", "Films you will like"); + shareMessage(sender, johnUsername, "home/my/secret.txt", "Hi there"); + shareMessage(sender, johnUsername, "home/watch/films.txt", "Films you will like"); // Here's how to list inbox folder root List> johnsInboxFilesInRoot = defaultDatasafeServices.inboxService() @@ -265,12 +274,15 @@ void readFromPrivate() { @Test void readFromInbox() { // BEGIN_SNIPPET:Read file from INBOX + // creating sender user + UserIDAuth sender = registerUser("sender"); + // creating new user UserIDAuth user = registerUser("john"); UserID johnUsername = new UserID("john"); // let's create 1 file: - shareMessage(johnUsername, "home/my/shared.txt", "shared message"); + shareMessage(sender, johnUsername, "home/my/shared.txt", "shared message"); // Lets list our INBOX List> johnsInboxFilesInMy = defaultDatasafeServices.inboxService() @@ -298,9 +310,9 @@ private UserIDAuth registerUser(String username) { } @SneakyThrows - private void shareMessage(UserID forUser, String messageName, String message) { + private void shareMessage(UserIDAuth fromUser, UserID forUser, String messageName, String message) { try (OutputStream os = defaultDatasafeServices.inboxService() - .write(WriteRequest.forDefaultPublic(Collections.singleton(forUser), messageName))) { + .write(WriteInboxRequest.forDefaultPublic(fromUser, Collections.singleton(forUser), messageName))) { os.write(message.getBytes(StandardCharsets.UTF_8)); } } diff --git a/datasafe-inbox/datasafe-inbox-api/src/main/java/de/adorsys/datasafe/inbox/api/actions/WriteToInbox.java b/datasafe-inbox/datasafe-inbox-api/src/main/java/de/adorsys/datasafe/inbox/api/actions/WriteToInbox.java index 5ed574c69..953a88557 100644 --- a/datasafe-inbox/datasafe-inbox-api/src/main/java/de/adorsys/datasafe/inbox/api/actions/WriteToInbox.java +++ b/datasafe-inbox/datasafe-inbox-api/src/main/java/de/adorsys/datasafe/inbox/api/actions/WriteToInbox.java @@ -1,7 +1,8 @@ package de.adorsys.datasafe.inbox.api.actions; import de.adorsys.datasafe.encrypiton.api.types.UserID; -import de.adorsys.datasafe.types.api.actions.WriteRequest; +import de.adorsys.datasafe.encrypiton.api.types.UserIDAuth; +import de.adorsys.datasafe.types.api.actions.WriteInboxRequest; import de.adorsys.datasafe.types.api.resource.PublicResource; import java.io.OutputStream; @@ -19,5 +20,5 @@ public interface WriteToInbox { * @return Stream that will get encrypted and stored within user INBOX. * @apiNote Returned stream should be closed properly */ - OutputStream write(WriteRequest, PublicResource> request); + OutputStream write(WriteInboxRequest, PublicResource> request); } diff --git a/datasafe-inbox/datasafe-inbox-impl/src/main/java/de/adorsys/datasafe/inbox/impl/actions/WriteToInboxImpl.java b/datasafe-inbox/datasafe-inbox-impl/src/main/java/de/adorsys/datasafe/inbox/impl/actions/WriteToInboxImpl.java index 3052de39f..87ad41a2b 100644 --- a/datasafe-inbox/datasafe-inbox-impl/src/main/java/de/adorsys/datasafe/inbox/impl/actions/WriteToInboxImpl.java +++ b/datasafe-inbox/datasafe-inbox-impl/src/main/java/de/adorsys/datasafe/inbox/impl/actions/WriteToInboxImpl.java @@ -1,12 +1,14 @@ package de.adorsys.datasafe.inbox.impl.actions; +import de.adorsys.datasafe.directory.api.profile.keys.PrivateKeyService; import de.adorsys.datasafe.directory.api.profile.keys.PublicKeyService; import de.adorsys.datasafe.directory.api.resource.ResourceResolver; import de.adorsys.datasafe.encrypiton.api.document.EncryptedDocumentReadService; import de.adorsys.datasafe.encrypiton.api.document.EncryptedDocumentWriteService; import de.adorsys.datasafe.encrypiton.api.types.UserID; +import de.adorsys.datasafe.encrypiton.api.types.UserIDAuth; import de.adorsys.datasafe.inbox.api.actions.WriteToInbox; -import de.adorsys.datasafe.types.api.actions.WriteRequest; +import de.adorsys.datasafe.types.api.actions.WriteInboxRequest; import de.adorsys.datasafe.types.api.context.annotations.RuntimeDelegate; import de.adorsys.datasafe.types.api.resource.PublicResource; @@ -24,13 +26,16 @@ public class WriteToInboxImpl implements WriteToInbox { private final PublicKeyService publicKeyService; + + private final PrivateKeyService privateKeyService; private final ResourceResolver resolver; private final EncryptedDocumentWriteService writer; @Inject - public WriteToInboxImpl(PublicKeyService publicKeyService, ResourceResolver resolver, + public WriteToInboxImpl(PublicKeyService publicKeyService, PrivateKeyService privateKeyService, ResourceResolver resolver, EncryptedDocumentWriteService writer) { this.publicKeyService = publicKeyService; + this.privateKeyService = privateKeyService; this.resolver = resolver; this.writer = writer; } @@ -41,13 +46,13 @@ public WriteToInboxImpl(PublicKeyService publicKeyService, ResourceResolver reso * @return Stream sink for data to be shared and encrypted */ @Override - public OutputStream write(WriteRequest, PublicResource> request) { - // No access check - anyone who knows user id can send a message to that user + public OutputStream write(WriteInboxRequest, PublicResource> request) { return writer.write( - request.getOwner().stream().collect(Collectors.toMap( + request.getRecipients().stream().collect(Collectors.toMap( publicKeyService::publicKey, it -> resolver.resolveRelativeToPublicInbox(it, request.getLocation()) - )) + )), + privateKeyService.getKeyPair(request.getOwner()) ); } } diff --git a/datasafe-inbox/datasafe-inbox-impl/src/test/java/de/adorsys/datasafe/inbox/api/actions/WriteToInboxImplTest.java b/datasafe-inbox/datasafe-inbox-impl/src/test/java/de/adorsys/datasafe/inbox/api/actions/WriteToInboxImplTest.java index d78f864a3..52d2f7395 100644 --- a/datasafe-inbox/datasafe-inbox-impl/src/test/java/de/adorsys/datasafe/inbox/api/actions/WriteToInboxImplTest.java +++ b/datasafe-inbox/datasafe-inbox-impl/src/test/java/de/adorsys/datasafe/inbox/api/actions/WriteToInboxImplTest.java @@ -1,13 +1,15 @@ package de.adorsys.datasafe.inbox.api.actions; +import de.adorsys.datasafe.directory.api.profile.keys.PrivateKeyService; import de.adorsys.datasafe.directory.api.profile.keys.PublicKeyService; import de.adorsys.datasafe.directory.api.resource.ResourceResolver; import de.adorsys.datasafe.encrypiton.api.document.EncryptedDocumentWriteService; import de.adorsys.datasafe.encrypiton.api.types.UserID; +import de.adorsys.datasafe.encrypiton.api.types.UserIDAuth; import de.adorsys.datasafe.encrypiton.api.types.keystore.KeyID; import de.adorsys.datasafe.encrypiton.api.types.keystore.PublicKeyIDWithPublicKey; import de.adorsys.datasafe.inbox.impl.actions.WriteToInboxImpl; -import de.adorsys.datasafe.types.api.actions.WriteRequest; +import de.adorsys.datasafe.types.api.actions.WriteInboxRequest; import de.adorsys.datasafe.types.api.resource.AbsoluteLocation; import de.adorsys.datasafe.types.api.resource.BasePublicResource; import de.adorsys.datasafe.types.api.resource.PublicResource; @@ -20,6 +22,7 @@ import java.io.ByteArrayOutputStream; import java.net.URI; +import java.security.KeyPair; import java.security.PublicKey; import java.util.Collections; import java.util.Set; @@ -33,15 +36,21 @@ class WriteToInboxImplTest extends BaseMockitoTest { private static final URI ABSOLUTE_PATH = URI.create("s3://absolute"); private UserID auth = new UserID(""); + private UserIDAuth ownerAuth = new UserIDAuth("owner", () -> "pass".toCharArray()); private PublicKeyIDWithPublicKey publicKeyWithId; + private KeyPair senderKeyPair; + @Mock private PublicKey publicKey; @Mock private PublicKeyService publicKeyService; + @Mock + private PrivateKeyService privateKeyService; + @Mock private ResourceResolver resolver; @@ -60,12 +69,13 @@ void init() { @SneakyThrows void write() { AbsoluteLocation resource = BasePublicResource.forAbsolutePublic(ABSOLUTE_PATH); - WriteRequest, PublicResource> request = WriteRequest - .forDefaultPublic(Collections.singleton(auth), ABSOLUTE_PATH); + WriteInboxRequest, PublicResource> request = WriteInboxRequest + .forDefaultPublic(ownerAuth, Collections.singleton(auth), ABSOLUTE_PATH); when(publicKeyService.publicKey(auth)).thenReturn(publicKeyWithId); + when(privateKeyService.getKeyPair(ownerAuth)).thenReturn(senderKeyPair); when(resolver.resolveRelativeToPublicInbox(auth, request.getLocation())).thenReturn(resource); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - when(writeService.write(Collections.singletonMap(publicKeyWithId, resource))).thenReturn(outputStream); + when(writeService.write(Collections.singletonMap(publicKeyWithId, resource), senderKeyPair)).thenReturn(outputStream); inbox.write(request).write(BYTES.getBytes()); diff --git a/datasafe-long-run-tests/datasafe-business-tests-random-actions/src/test/java/de/adorsys/datasafe/business/impl/e2e/randomactions/RandomActionsOnSimpleDatasafeAdapterIT.java b/datasafe-long-run-tests/datasafe-business-tests-random-actions/src/test/java/de/adorsys/datasafe/business/impl/e2e/randomactions/RandomActionsOnSimpleDatasafeAdapterIT.java index 4e782034a..e677c74c4 100644 --- a/datasafe-long-run-tests/datasafe-business-tests-random-actions/src/test/java/de/adorsys/datasafe/business/impl/e2e/randomactions/RandomActionsOnSimpleDatasafeAdapterIT.java +++ b/datasafe-long-run-tests/datasafe-business-tests-random-actions/src/test/java/de/adorsys/datasafe/business/impl/e2e/randomactions/RandomActionsOnSimpleDatasafeAdapterIT.java @@ -33,6 +33,7 @@ import de.adorsys.datasafe.types.api.actions.ListRequest; import de.adorsys.datasafe.types.api.actions.ReadRequest; import de.adorsys.datasafe.types.api.actions.RemoveRequest; +import de.adorsys.datasafe.types.api.actions.WriteInboxRequest; import de.adorsys.datasafe.types.api.actions.WriteRequest; import de.adorsys.datasafe.types.api.resource.AbsoluteLocation; import de.adorsys.datasafe.types.api.resource.BasePrivateResource; @@ -169,7 +170,7 @@ public void remove(RemoveRequest request) { } @Override - public OutputStream write(WriteRequest, PublicResource> request) { + public OutputStream write(WriteInboxRequest, PublicResource> request) { throw new IllegalStateException("Not implemented"); } }; diff --git a/datasafe-long-run-tests/datasafe-business-tests-random-actions/src/test/java/de/adorsys/datasafe/business/impl/e2e/randomactions/framework/services/OperationExecutor.java b/datasafe-long-run-tests/datasafe-business-tests-random-actions/src/test/java/de/adorsys/datasafe/business/impl/e2e/randomactions/framework/services/OperationExecutor.java index 2453d6b52..b3348670d 100644 --- a/datasafe-long-run-tests/datasafe-business-tests-random-actions/src/test/java/de/adorsys/datasafe/business/impl/e2e/randomactions/framework/services/OperationExecutor.java +++ b/datasafe-long-run-tests/datasafe-business-tests-random-actions/src/test/java/de/adorsys/datasafe/business/impl/e2e/randomactions/framework/services/OperationExecutor.java @@ -16,6 +16,7 @@ import de.adorsys.datasafe.types.api.actions.ListRequest; import de.adorsys.datasafe.types.api.actions.ReadRequest; import de.adorsys.datasafe.types.api.actions.RemoveRequest; +import de.adorsys.datasafe.types.api.actions.WriteInboxRequest; import de.adorsys.datasafe.types.api.actions.WriteRequest; import de.adorsys.datasafe.types.api.resource.AbsoluteLocation; import de.adorsys.datasafe.types.api.resource.PrivateResource; @@ -52,7 +53,7 @@ public class OperationExecutor { private final Map> handlers = ImmutableMap.>builder() .put(OperationType.CREATE_USER, this::doCreate) .put(OperationType.WRITE, this::doWrite) - .put(OperationType.SHARE, this::doWrite) + .put(OperationType.SHARE, this::doShare) .put(OperationType.READ, this::doRead) .put(OperationType.LIST, this::doList) .put(OperationType.DELETE, this::doDelete) @@ -153,6 +154,18 @@ private void doWrite(Operation oper) { } } + @SneakyThrows + private void doShare(Operation oper) { + UserSpec user = requireUser(oper); + + UserIDAuth auth = new UserIDAuth(oper.getUserId(), ReadKeyPasswordTestFactory.getForString(oper.getUserId())); + UserSpec sender = new UserSpec(auth, new ContentGenerator(fileContentSize)); + + try (OutputStream os = openShareStream(sender, user, oper)) { + ByteStreams.copy(user.getGenerator().generate(oper.getContentId().getId()), os); + } + } + @SneakyThrows private void doRead(Operation oper) { UserSpec user = requireUser(oper); @@ -204,16 +217,17 @@ private void doDelete(Operation oper) { privateSpace.remove(request); } - private OutputStream openWriteStream(UserSpec user, Operation oper) { - if (StorageType.INBOX.equals(oper.getStorageType())) { - return inboxService.write(WriteRequest.forDefaultPublic( - oper.getRecipients().stream() - .map(it -> requireUser(it).getAuth().getUserID()) - .collect(Collectors.toSet()), - oper.getLocation()) - ); - } + private OutputStream openShareStream(UserSpec sender, UserSpec user, Operation oper) { + return inboxService.write(WriteInboxRequest.forDefaultPublic( + user.getAuth(), + oper.getRecipients().stream() + .map(it -> requireUser(it).getAuth().getUserID()) + .collect(Collectors.toSet()), + oper.getLocation()) + ); + } + private OutputStream openWriteStream(UserSpec user, Operation oper) { return privateSpace.write(WriteRequest.forDefaultPrivate(user.getAuth(), oper.getLocation())); } diff --git a/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/controller/InboxController.java b/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/controller/InboxController.java index 0415df7b8..5f86a1f1b 100644 --- a/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/controller/InboxController.java +++ b/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/controller/InboxController.java @@ -8,7 +8,7 @@ import de.adorsys.datasafe.types.api.actions.ListRequest; import de.adorsys.datasafe.types.api.actions.ReadRequest; import de.adorsys.datasafe.types.api.actions.RemoveRequest; -import de.adorsys.datasafe.types.api.actions.WriteRequest; +import de.adorsys.datasafe.types.api.actions.WriteInboxRequest; import de.adorsys.datasafe.types.api.resource.BasePrivateResource; import de.adorsys.datasafe.types.api.resource.PrivateResource; import jakarta.servlet.http.HttpServletResponse; @@ -52,12 +52,15 @@ public class InboxController { */ @SneakyThrows @PutMapping(value = "/inbox/document/{*path}", consumes = MULTIPART_FORM_DATA_VALUE) - public void writeToInbox(@RequestHeader Set users, + public void writeToInbox(@RequestHeader String user, + @RequestHeader String password, + @RequestHeader Set recipients, @PathVariable String path, @RequestParam("file") MultipartFile file) { + UserIDAuth fromUser = new UserIDAuth(new UserID(user), ReadKeyPasswordHelper.getForString(password)); + Set toUsers = recipients.stream().map(UserID::new).collect(Collectors.toSet()); path = path.replaceAll("^/", ""); - Set toUsers = users.stream().map(UserID::new).collect(Collectors.toSet()); - try (OutputStream os = dataSafeService.inboxService().write(WriteRequest.forDefaultPublic(toUsers, path)); + try (OutputStream os = dataSafeService.inboxService().write(WriteInboxRequest.forDefaultPublic(fromUser, toUsers, path)); InputStream is = file.getInputStream()) { StreamUtils.copy(is, os); } @@ -110,9 +113,6 @@ public List listInbox(@RequestHeader String user, @PathVariable(required = false) String path) { path = path.replaceAll("^/", ""); UserIDAuth userIDAuth = new UserIDAuth(new UserID(user), ReadKeyPasswordHelper.getForString(password)); - path = Optional.ofNullable(path) - .map(it -> it.replaceAll("^\\.$", "")) - .orElse("./"); try { List inboxList = dataSafeService.inboxService().list(ListRequest.forDefaultPrivate(userIDAuth, path)) .map(e -> e.getResource().asPrivate().decryptedPath().asString()) diff --git a/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/controller/VersionController.java b/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/controller/VersionController.java index a373e31d2..ac91da98e 100644 --- a/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/controller/VersionController.java +++ b/datasafe-rest-impl/src/main/java/de/adorsys/datasafe/rest/impl/controller/VersionController.java @@ -32,7 +32,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.List; -import java.util.Optional; import static org.springframework.http.HttpHeaders.CONTENT_TYPE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @@ -55,7 +54,7 @@ public List listVersionedDocuments(@RequestHeader String user, @RequestHeader(defaultValue = StorageIdentifier.DEFAULT_ID) String storageId, @PathVariable(required = false) String path) { UserIDAuth userIDAuth = new UserIDAuth(new UserID(user), ReadKeyPasswordHelper.getForString(password)); - path = Optional.ofNullable(path).orElse("./"); + path = path.replaceAll("^/", ""); try { List documentList = versionedDatasafeServices.latestPrivate().listWithDetails( ListRequest.forPrivate(userIDAuth, new StorageIdentifier(storageId), path)) @@ -79,6 +78,7 @@ public void readVersionedDocument(@RequestHeader String user, @RequestHeader(defaultValue = StorageIdentifier.DEFAULT_ID) String storageId, @PathVariable String path, HttpServletResponse response) { + path = path.replaceAll("^/", ""); UserIDAuth userIDAuth = new UserIDAuth(new UserID(user), ReadKeyPasswordHelper.getForString(password)); ReadRequest request = ReadRequest.forPrivate(userIDAuth, new StorageIdentifier(storageId), path); @@ -102,6 +102,7 @@ public void writeVersionedDocument(@RequestHeader String user, @RequestHeader(defaultValue = StorageIdentifier.DEFAULT_ID) String storageId, @PathVariable String path, @RequestParam("file") MultipartFile file) { + path = path.replaceAll("^/", ""); UserIDAuth userIDAuth = new UserIDAuth(new UserID(user), ReadKeyPasswordHelper.getForString(password)); WriteRequest request = WriteRequest.forPrivate(userIDAuth, new StorageIdentifier(storageId), path); @@ -120,6 +121,7 @@ public void deleteVersionedDocument(@RequestHeader String user, @RequestHeader String password, @RequestHeader(defaultValue = StorageIdentifier.DEFAULT_ID) String storageId, @PathVariable String path) { + path = path.replaceAll("^/", ""); UserIDAuth userIDAuth = new UserIDAuth(new UserID(user), ReadKeyPasswordHelper.getForString(password)); RemoveRequest request = RemoveRequest.forPrivate(userIDAuth, new StorageIdentifier(storageId), path); @@ -136,9 +138,7 @@ public List versionsOf(@RequestHeader String user, @RequestHeader(defaultValue = StorageIdentifier.DEFAULT_ID) String storageId, @PathVariable(required = false) String path) { UserIDAuth userIDAuth = new UserIDAuth(new UserID(user), ReadKeyPasswordHelper.getForString(password)); - path = Optional.ofNullable(path) - .map(it -> it.replaceAll("^\\.$", "")) - .orElse("./"); + path = path.replaceAll("^/", ""); ListRequest request = ListRequest.forPrivate(userIDAuth, new StorageIdentifier(storageId), path); diff --git a/datasafe-rest-impl/src/main/resources/application.properties b/datasafe-rest-impl/src/main/resources/application.properties index 0c7f28841..5b4490b30 100644 --- a/datasafe-rest-impl/src/main/resources/application.properties +++ b/datasafe-rest-impl/src/main/resources/application.properties @@ -36,3 +36,13 @@ datasafe.encryption.keystore.pbkdf.scrypt.parallelization=1 datasafe.encryption.keystore.pbkdf.scrypt.saltLength=16 datasafe.encryption.keystore.macAlgo=HmacSHA3_512 datasafe.encryption.cms.algo=AES256_GCM + +#datasafe.encryption.keys.encrypting.algo=RSA +#datasafe.encryption.keys.encrypting.size=4096 +#datasafe.encryption.keys.encrypting.sigAlgo=SHA256withRSA +#datasafe.encryption.keys.encrypting.curve= +# +#datasafe.encryption.keys.signing.algo=RSA +#datasafe.encryption.keys.signing.size=4096 +#datasafe.encryption.keys.signing.sigAlgo=SHA256withRSA +#datasafe.encryption.keys.signing.curve= diff --git a/datasafe-rest-impl/src/test/java/de/adorsys/datasafe/rest/impl/controller/AuthenticateControllerTest.java b/datasafe-rest-impl/src/test/java/de/adorsys/datasafe/rest/impl/controller/AuthenticateControllerTest.java index b333dd7ae..2f6446144 100644 --- a/datasafe-rest-impl/src/test/java/de/adorsys/datasafe/rest/impl/controller/AuthenticateControllerTest.java +++ b/datasafe-rest-impl/src/test/java/de/adorsys/datasafe/rest/impl/controller/AuthenticateControllerTest.java @@ -13,6 +13,9 @@ import java.io.ByteArrayOutputStream; +import static de.adorsys.datasafe.rest.impl.controller.BaseTokenDatasafeEndpointTest.TEST_PASS; +import static de.adorsys.datasafe.rest.impl.controller.BaseTokenDatasafeEndpointTest.TEST_USER; +import static de.adorsys.datasafe.rest.impl.controller.BaseTokenDatasafeEndpointTest.TEST_USER_RECIPIENT; import static de.adorsys.datasafe.rest.impl.controller.TestHelper.putFileBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -26,8 +29,6 @@ public class AuthenticateControllerTest extends BaseDatasafeEndpointTest { private static final String DEFAULT_TEST_USERNAME = "username"; private static final String DEFAULT_TEST_PASSWORD = "password"; - - private static final String TEST_USER = "test"; private static final String TEST_PATH = "test.txt"; @MockBean @@ -81,12 +82,14 @@ void testPutDataWithToken() { String token = sendAuthenticateRequest(userDTO).getResponse().getHeader(SecurityConstants.TOKEN_HEADER); mvc.perform( - putFileBuilder("/inbox/document/{path}", TEST_PATH). - contentType(MediaType.MULTIPART_FORM_DATA_VALUE). - content("file content".getBytes()). - header("users", TEST_USER). - header(SecurityConstants.TOKEN_HEADER, token)) - .andExpect(status().isOk()); + putFileBuilder("/inbox/document/{path}", TEST_PATH) + .contentType(MediaType.MULTIPART_FORM_DATA_VALUE) + .content("file content".getBytes()) + .header("user", TEST_USER) + .header("password", TEST_PASS) + .header("recipients", TEST_USER_RECIPIENT) + .header(SecurityConstants.TOKEN_HEADER, token)) + .andExpect(status().isOk()); } @SneakyThrows diff --git a/datasafe-rest-impl/src/test/java/de/adorsys/datasafe/rest/impl/controller/BaseTokenDatasafeEndpointTest.java b/datasafe-rest-impl/src/test/java/de/adorsys/datasafe/rest/impl/controller/BaseTokenDatasafeEndpointTest.java index e5e416797..a5b2791e6 100644 --- a/datasafe-rest-impl/src/test/java/de/adorsys/datasafe/rest/impl/controller/BaseTokenDatasafeEndpointTest.java +++ b/datasafe-rest-impl/src/test/java/de/adorsys/datasafe/rest/impl/controller/BaseTokenDatasafeEndpointTest.java @@ -15,6 +15,9 @@ public abstract class BaseTokenDatasafeEndpointTest extends BaseDatasafeEndpoint static final String PASSWORD_DESCRIPTION = "datasafe user's password"; static final String TEST_USER = "test"; + + static final String TEST_USER_RECIPIENT = "recipient"; + static final ReadKeyPassword TEST_PASS = ReadKeyPasswordHelper.getForString("test"); String token; diff --git a/datasafe-rest-impl/src/test/java/de/adorsys/datasafe/rest/impl/controller/InboxControllerTest.java b/datasafe-rest-impl/src/test/java/de/adorsys/datasafe/rest/impl/controller/InboxControllerTest.java index 22b431751..3c0b7f8f9 100644 --- a/datasafe-rest-impl/src/test/java/de/adorsys/datasafe/rest/impl/controller/InboxControllerTest.java +++ b/datasafe-rest-impl/src/test/java/de/adorsys/datasafe/rest/impl/controller/InboxControllerTest.java @@ -64,14 +64,18 @@ void writeToInboxTest() { ), requestHeaders( headerWithName("token").description(TOKEN_DESCRIPTION), - headerWithName("users").description("recipients array") + headerWithName("user").description(USER_DESCRIPTION), + headerWithName("password").description(PASSWORD_DESCRIPTION), + headerWithName("recipients").description("recipients array") ) ); mvc.perform(putFileBuilder("/inbox/document/{path}", TEST_PATH) - .contentType(MediaType.MULTIPART_FORM_DATA_VALUE) - .header("users", TEST_USER) - .header("token", token) + .contentType(MediaType.MULTIPART_FORM_DATA_VALUE) + .header("user", TEST_USER) + .header("password", TEST_PASS) + .header("recipients", TEST_USER_RECIPIENT) + .header("token", token) ) .andExpect(status().isOk()) .andDo(document); diff --git a/datasafe-simple-adapter/datasafe-simple-adapter-impl/src/main/java/de/adorsys/datasafe/simple/adapter/impl/cmsencryption/SwitchableCmsEncryptionImpl.java b/datasafe-simple-adapter/datasafe-simple-adapter-impl/src/main/java/de/adorsys/datasafe/simple/adapter/impl/cmsencryption/SwitchableCmsEncryptionImpl.java index ecb0d0cf5..b2873bae3 100644 --- a/datasafe-simple-adapter/datasafe-simple-adapter-impl/src/main/java/de/adorsys/datasafe/simple/adapter/impl/cmsencryption/SwitchableCmsEncryptionImpl.java +++ b/datasafe-simple-adapter/datasafe-simple-adapter-impl/src/main/java/de/adorsys/datasafe/simple/adapter/impl/cmsencryption/SwitchableCmsEncryptionImpl.java @@ -11,6 +11,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.security.Key; +import java.security.KeyPair; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -27,9 +28,9 @@ public SwitchableCmsEncryptionImpl(ASNCmsEncryptionConfig encryptionConfig) { } @Override - public OutputStream buildEncryptionOutputStream(OutputStream dataContentStream, Set publicKeys) { + public OutputStream buildEncryptionOutputStream(OutputStream dataContentStream, Set publicKeys, KeyPair senderKeyPair) { if (withCmsEncryption) { - return super.buildEncryptionOutputStream(dataContentStream, publicKeys); + return super.buildEncryptionOutputStream(dataContentStream, publicKeys, senderKeyPair); } return dataContentStream; } diff --git a/datasafe-types-api/src/main/java/de/adorsys/datasafe/types/api/actions/WriteInboxRequest.java b/datasafe-types-api/src/main/java/de/adorsys/datasafe/types/api/actions/WriteInboxRequest.java new file mode 100644 index 000000000..2d9797086 --- /dev/null +++ b/datasafe-types-api/src/main/java/de/adorsys/datasafe/types/api/actions/WriteInboxRequest.java @@ -0,0 +1,59 @@ +package de.adorsys.datasafe.types.api.actions; + +import de.adorsys.datasafe.types.api.callback.ResourceWriteCallback; +import de.adorsys.datasafe.types.api.resource.BasePublicResource; +import de.adorsys.datasafe.types.api.resource.PublicResource; +import de.adorsys.datasafe.types.api.resource.ResourceLocation; +import de.adorsys.datasafe.types.api.resource.StorageIdentifier; +import de.adorsys.datasafe.types.api.resource.Uri; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.NonNull; +import lombok.Singular; +import lombok.Value; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +/** + * Request to write data at some location. + * @param Resource owner. + * @param Resource path (either relative or absolute). + */ +@Value +@AllArgsConstructor +@Builder(toBuilder = true) +public class WriteInboxRequest { + + @NonNull T owner; + + @NonNull R recipients; + + @NonNull L location; + + @NonNull StorageIdentifier storageIdentifier; + + @Singular + List callbacks; + + private WriteInboxRequest(@NonNull T owner, @NonNull R recipients, @NonNull L location, List callbacks) { + this.owner = owner; + this.recipients = recipients; + this.location = location; + this.callbacks = callbacks; + this.storageIdentifier = StorageIdentifier.DEFAULT; + } + + public static WriteInboxRequest forDefaultPublic(T owner, R recipients, String path) { + return new WriteInboxRequest<>(owner, recipients, new BasePublicResource(new Uri(path)), new ArrayList<>()); + } + + public static WriteInboxRequest forDefaultPublic(T owner, R recipients, Uri path) { + return new WriteInboxRequest<>(owner, recipients, new BasePublicResource(path), new ArrayList<>()); + } + + public static WriteInboxRequest forDefaultPublic(T owner, R recipients, URI path) { + return forDefaultPublic(owner, recipients, new Uri(path)); + } +} diff --git a/datasafe-types-api/src/main/java/de/adorsys/datasafe/types/api/actions/WriteRequest.java b/datasafe-types-api/src/main/java/de/adorsys/datasafe/types/api/actions/WriteRequest.java index 6c4008578..06d4d5576 100644 --- a/datasafe-types-api/src/main/java/de/adorsys/datasafe/types/api/actions/WriteRequest.java +++ b/datasafe-types-api/src/main/java/de/adorsys/datasafe/types/api/actions/WriteRequest.java @@ -2,9 +2,7 @@ import de.adorsys.datasafe.types.api.callback.ResourceWriteCallback; import de.adorsys.datasafe.types.api.resource.BasePrivateResource; -import de.adorsys.datasafe.types.api.resource.BasePublicResource; import de.adorsys.datasafe.types.api.resource.PrivateResource; -import de.adorsys.datasafe.types.api.resource.PublicResource; import de.adorsys.datasafe.types.api.resource.ResourceLocation; import de.adorsys.datasafe.types.api.resource.StorageIdentifier; import de.adorsys.datasafe.types.api.resource.Uri; @@ -28,17 +26,14 @@ @Builder(toBuilder = true) public class WriteRequest { - @NonNull - private final T owner; + @NonNull T owner; - @NonNull - private final L location; + @NonNull L location; - @NonNull - private final StorageIdentifier storageIdentifier; + @NonNull StorageIdentifier storageIdentifier; @Singular - private final List callbacks; + List callbacks; private WriteRequest(@NonNull T owner, @NonNull L location, List callbacks) { this.owner = owner; @@ -51,35 +46,23 @@ public static WriteRequest forDefaultPrivate(T owner, St return new WriteRequest<>(owner, BasePrivateResource.forPrivate(new Uri(path)), new ArrayList<>()); } - public static WriteRequest forDefaultPublic(T owner, String path) { - return new WriteRequest<>(owner, new BasePublicResource(new Uri(path)), new ArrayList<>()); - } - - public static WriteRequest forDefaultPrivate(T owner, URI path) { - return forDefaultPrivate(owner, new Uri(path)); - } - public static WriteRequest forDefaultPrivate(T owner, Uri path) { return new WriteRequest<>(owner, BasePrivateResource.forPrivate(path), new ArrayList<>()); } - public static WriteRequest forDefaultPublic(T owner, URI path) { - return forDefaultPublic(owner, new Uri(path)); - } - - public static WriteRequest forDefaultPublic(T owner, Uri path) { - return new WriteRequest<>(owner, new BasePublicResource(path), new ArrayList<>()); + public static WriteRequest forDefaultPrivate(T owner, URI path) { + return forDefaultPrivate(owner, new Uri(path)); } public static WriteRequest forPrivate(T owner, StorageIdentifier storage, String path) { return new WriteRequest<>(owner, BasePrivateResource.forPrivate(new Uri(path)), storage, new ArrayList<>()); } - public static WriteRequest forPrivate(T owner, StorageIdentifier storage, URI path) { - return forPrivate(owner, storage, new Uri(path)); - } - public static WriteRequest forPrivate(T owner, StorageIdentifier storage, Uri path) { return new WriteRequest<>(owner, BasePrivateResource.forPrivate(path), storage, new ArrayList<>()); } + + public static WriteRequest forPrivate(T owner, StorageIdentifier storage, URI path) { + return forPrivate(owner, storage, new Uri(path)); + } } diff --git a/pom.xml b/pom.xml index cc049524c..10e86c109 100644 --- a/pom.xml +++ b/pom.xml @@ -74,13 +74,13 @@ src/main/java src/test/java 19.2.0 - 3.11.0 + 3.12.1 17 17 3.6.1 1.18.30 2.8.9 - 2.46.1 + 2.50 32.1.1-jre 4.0.3 5.10.0 @@ -113,7 +113,7 @@ 3.1.2 1.4.4 2.16.1 - 0.0.9 + 0.0.11 2.1.1 2.3.1 2.8.1 @@ -720,6 +720,16 @@ + + + sonatype + https://oss.sonatype.org/content/repositories/snapshots + + true + + + + sonatype