@@ -959,6 +959,28 @@ public CompletableFuture<String> hashPassword(String password) {
959959 });
960960 }
961961
962+ /**
963+ * Hashes a password using secure byte buffer.
964+ * The password is converted to Base64 for transmission (matching JS pattern).
965+ *
966+ * @param password The password as byte array
967+ * @return CompletableFuture with the hashed password
968+ */
969+ public CompletableFuture <String > hashPassword (byte [] password ) {
970+ // Convert password to Base64 for transmission
971+ String passwordBase64 = com .pears .pass .autofill .utils .SecureBufferUtils .toBase64 (password );
972+
973+ return sendRequest (API .ENCRYPTION_HASH_PASSWORD .getValue (), createMap ("password" , passwordBase64 ))
974+ .thenApply (result -> {
975+ String hashedPassword = (String ) result .get ("hashedPassword" );
976+ if (hashedPassword == null ) {
977+ throw new RuntimeException ("Encryption operation failed" );
978+ }
979+ log ("Successfully hashed password (buffer)" );
980+ return hashedPassword ;
981+ });
982+ }
983+
962984 public CompletableFuture <DecryptionKeyResult > getDecryptionKey (String salt , String password ) {
963985 Map <String , Object > params = new HashMap <>();
964986 params .put ("salt" , salt );
@@ -983,6 +1005,41 @@ public CompletableFuture<DecryptionKeyResult> getDecryptionKey(String salt, Stri
9831005 });
9841006 }
9851007
1008+ /**
1009+ * Gets the decryption key using secure byte buffer for password.
1010+ * The password is converted to Base64 for transmission (matching JS pattern).
1011+ *
1012+ * @param salt The salt to use for key derivation
1013+ * @param password The password as byte array
1014+ * @return CompletableFuture with the decryption key result
1015+ */
1016+ public CompletableFuture <DecryptionKeyResult > getDecryptionKey (String salt , byte [] password ) {
1017+ // Convert password to Base64 for transmission
1018+ String passwordBase64 = com .pears .pass .autofill .utils .SecureBufferUtils .toBase64 (password );
1019+
1020+ Map <String , Object > params = new HashMap <>();
1021+ params .put ("salt" , salt );
1022+ params .put ("password" , passwordBase64 );
1023+
1024+ return sendRequest (API .ENCRYPTION_GET_DECRYPTION_KEY .getValue (), params )
1025+ .thenApply (result -> {
1026+ String key = (String ) result .get ("value" );
1027+ if (key == null ) {
1028+ key = (String ) result .get ("key" );
1029+ }
1030+ if (key == null ) {
1031+ key = (String ) result .get ("hashedPassword" );
1032+ }
1033+ if (key == null ) {
1034+ logError ("Failed to extract key from getDecryptionKey response: " + result );
1035+ throw new RuntimeException ("Decryption failed" );
1036+ }
1037+
1038+ log ("Successfully generated decryption key (buffer)" );
1039+ return new DecryptionKeyResult (key , salt );
1040+ });
1041+ }
1042+
9861043 public CompletableFuture <Map <String , Object >> decryptVaultKey (String ciphertext , String nonce , String hashedPassword ) {
9871044 Map <String , Object > params = new HashMap <>();
9881045 params .put ("ciphertext" , ciphertext );
@@ -1243,6 +1300,205 @@ public CompletableFuture<Boolean> getVaultById(String vaultId, String password)
12431300 });
12441301 }
12451302
1303+ /**
1304+ * Validate vault password using secure byte buffer.
1305+ * The password is converted to Base64 for transmission (matching JS pattern).
1306+ *
1307+ * @param vaultId The vault ID to validate password for
1308+ * @param password The password as byte array
1309+ * @return CompletableFuture with true if password is valid, false otherwise
1310+ */
1311+ public CompletableFuture <Boolean > validateVaultPassword (String vaultId , byte [] password ) {
1312+ log ("Validating password for vault (buffer): " + vaultId );
1313+
1314+ // Convert password to Base64 for transmission
1315+ String passwordBase64 = com .pears .pass .autofill .utils .SecureBufferUtils .toBase64 (password );
1316+
1317+ return listVaults ()
1318+ .thenCompose (vaults -> {
1319+ // Find the vault with the matching ID
1320+ Vault targetVault = null ;
1321+ for (Vault vault : vaults ) {
1322+ if (vault .id .equals (vaultId )) {
1323+ targetVault = vault ;
1324+ break ;
1325+ }
1326+ }
1327+
1328+ if (targetVault == null ) {
1329+ throw new RuntimeException ("Vault not found with ID: " + vaultId );
1330+ }
1331+
1332+ final Vault vault = targetVault ;
1333+
1334+ // If vault has no encryption, it's not protected
1335+ if (vault .encryption == null ) {
1336+ log ("Vault " + vault .name + " is not protected, validation successful" );
1337+ return CompletableFuture .completedFuture (true );
1338+ }
1339+
1340+ // Check if vault has its own salt (for password-protected vaults)
1341+ String saltToUse = vault .encryption .salt ;
1342+
1343+ // If vault doesn't have salt, it's encrypted with master password
1344+ if (saltToUse == null || saltToUse .isEmpty ()) {
1345+ log ("Vault " + vault .name + " doesn't have its own salt, using master password" );
1346+ return getMasterPasswordEncryption (null )
1347+ .thenCompose (masterPasswordEncryption -> {
1348+ if (masterPasswordEncryption == null || masterPasswordEncryption .hashedPassword == null ) {
1349+ throw new RuntimeException ("No master password available" );
1350+ }
1351+ return decryptVaultKey (vault .encryption .ciphertext , vault .encryption .nonce , masterPasswordEncryption .hashedPassword );
1352+ })
1353+ .thenApply (decryptedData -> {
1354+ if (decryptedData == null ) {
1355+ throw new RuntimeException ("Failed to decrypt vault key" );
1356+ }
1357+ String encryptionKey = extractValue (decryptedData , "value" , "key" , "data" );
1358+ if (encryptionKey == null ) {
1359+ throw new RuntimeException ("Failed to decrypt vault key" );
1360+ }
1361+ log ("Password validation successful" );
1362+ return true ;
1363+ });
1364+ }
1365+
1366+ log ("Vault " + vault .name + " has its own salt, using vault password (buffer)" );
1367+
1368+ // Get decryption key using the vault's salt and password (Base64 encoded)
1369+ Map <String , Object > decryptionParams = new HashMap <>();
1370+ decryptionParams .put ("password" , passwordBase64 );
1371+ decryptionParams .put ("salt" , saltToUse );
1372+
1373+ return sendRequest (API .ENCRYPTION_GET_DECRYPTION_KEY .getValue (), decryptionParams )
1374+ .thenCompose (decryptionResult -> {
1375+ String hashedPassword = extractValue (decryptionResult , "value" , "key" , "hashedPassword" );
1376+ if (hashedPassword == null ) {
1377+ throw new RuntimeException ("Failed to get decryption key" );
1378+ }
1379+ log ("Got decryption key, attempting to decrypt vault key" );
1380+ return decryptVaultKey (vault .encryption .ciphertext , vault .encryption .nonce , hashedPassword )
1381+ .thenApply (decryptedData -> {
1382+ if (decryptedData == null ) {
1383+ throw new RuntimeException ("Failed to decrypt vault key - incorrect password" );
1384+ }
1385+ String encryptionKey = extractValue (decryptedData , "value" , "key" , "data" );
1386+ if (encryptionKey == null ) {
1387+ throw new RuntimeException ("Failed to decrypt vault key - incorrect password" );
1388+ }
1389+ log ("Password validation successful (buffer)" );
1390+ return true ;
1391+ });
1392+ });
1393+ })
1394+ .exceptionally (ex -> {
1395+ log ("Failed to validate vault password: " + ex .getMessage ());
1396+ return false ;
1397+ });
1398+ }
1399+
1400+ /**
1401+ * Get vault by ID and unlock it using secure byte buffer for password.
1402+ * The password is converted to Base64 for transmission (matching JS pattern).
1403+ *
1404+ * @param vaultId The vault ID to unlock
1405+ * @param password The password as byte array
1406+ * @return CompletableFuture with true if vault was unlocked, false otherwise
1407+ */
1408+ public CompletableFuture <Boolean > getVaultById (String vaultId , byte [] password ) {
1409+ log ("Getting vault by ID (buffer): " + vaultId );
1410+
1411+ // Convert password to Base64 for transmission
1412+ String passwordBase64 = com .pears .pass .autofill .utils .SecureBufferUtils .toBase64 (password );
1413+
1414+ return listVaults ()
1415+ .thenCompose (vaults -> {
1416+ // Find the vault with the matching ID
1417+ Vault targetVault = null ;
1418+ for (Vault vault : vaults ) {
1419+ if (vault .id .equals (vaultId )) {
1420+ targetVault = vault ;
1421+ break ;
1422+ }
1423+ }
1424+
1425+ if (targetVault == null ) {
1426+ throw new RuntimeException ("Vault not found with ID: " + vaultId );
1427+ }
1428+
1429+ final Vault vault = targetVault ;
1430+
1431+ // If vault has no encryption, it's not protected
1432+ if (vault .encryption == null ) {
1433+ log ("Vault " + vault .name + " is not protected, initializing directly" );
1434+ return activeVaultInit (vault .id , null )
1435+ .thenApply (result -> true );
1436+ }
1437+
1438+ // Check if vault has its own salt (for password-protected vaults)
1439+ String saltToUse = vault .encryption .salt ;
1440+
1441+ // If vault doesn't have salt, it's encrypted with master password
1442+ if (saltToUse == null || saltToUse .isEmpty ()) {
1443+ log ("Vault " + vault .name + " doesn't have its own salt, using master password" );
1444+ return getMasterPasswordEncryption (null )
1445+ .thenCompose (masterPasswordEncryption -> {
1446+ if (masterPasswordEncryption == null || masterPasswordEncryption .hashedPassword == null ) {
1447+ throw new RuntimeException ("No master password available" );
1448+ }
1449+ return decryptVaultKey (vault .encryption .ciphertext , vault .encryption .nonce , masterPasswordEncryption .hashedPassword );
1450+ })
1451+ .thenCompose (decryptedData -> {
1452+ if (decryptedData == null ) {
1453+ throw new RuntimeException ("Failed to decrypt vault key" );
1454+ }
1455+ String encryptionKey = extractValue (decryptedData , "value" , "key" , "data" );
1456+ if (encryptionKey == null ) {
1457+ throw new RuntimeException ("Failed to decrypt vault key" );
1458+ }
1459+ return activeVaultInit (vault .id , encryptionKey )
1460+ .thenApply (result -> true );
1461+ });
1462+ }
1463+
1464+ log ("Vault " + vault .name + " has its own salt, using vault password (buffer)" );
1465+
1466+ // Get decryption key using the vault's salt and password (Base64 encoded)
1467+ Map <String , Object > decryptionParams = new HashMap <>();
1468+ decryptionParams .put ("password" , passwordBase64 );
1469+ decryptionParams .put ("salt" , saltToUse );
1470+
1471+ return sendRequest (API .ENCRYPTION_GET_DECRYPTION_KEY .getValue (), decryptionParams )
1472+ .thenCompose (decryptionResult -> {
1473+ String hashedPassword = extractValue (decryptionResult , "value" , "key" , "hashedPassword" );
1474+ if (hashedPassword == null ) {
1475+ throw new RuntimeException ("Failed to get decryption key" );
1476+ }
1477+ log ("Got decryption key, attempting to decrypt vault key (buffer)" );
1478+ return decryptVaultKey (vault .encryption .ciphertext , vault .encryption .nonce , hashedPassword )
1479+ .thenCompose (decryptedData -> {
1480+ if (decryptedData == null ) {
1481+ throw new RuntimeException ("Failed to decrypt vault key - incorrect password" );
1482+ }
1483+ String encryptionKey = extractValue (decryptedData , "value" , "key" , "data" );
1484+ if (encryptionKey == null ) {
1485+ throw new RuntimeException ("Failed to decrypt vault key - incorrect password" );
1486+ }
1487+ log ("Successfully decrypted vault key, initializing active vault (buffer)" );
1488+ return activeVaultInit (vault .id , encryptionKey )
1489+ .thenApply (result -> {
1490+ log ("Active vault initialized successfully (buffer)" );
1491+ return true ;
1492+ });
1493+ });
1494+ });
1495+ })
1496+ .exceptionally (ex -> {
1497+ log ("Failed to get vault by ID: " + ex .getMessage ());
1498+ return false ;
1499+ });
1500+ }
1501+
12461502 // General Methods
12471503 public CompletableFuture <Map <String , Object >> closeAllInstances () {
12481504 log ("Closing all vault instances and cleaning up resources" );
0 commit comments