diff --git a/app/src/androidTest/java/com/owncloud/android/UploadIT.java b/app/src/androidTest/java/com/owncloud/android/UploadIT.java index 8072bb5c1805..a7557c822429 100644 --- a/app/src/androidTest/java/com/owncloud/android/UploadIT.java +++ b/app/src/androidTest/java/com/owncloud/android/UploadIT.java @@ -27,6 +27,7 @@ import com.owncloud.android.operations.RemoveFileOperation; import com.owncloud.android.operations.UploadFileOperation; import com.owncloud.android.utils.FileStorageUtils; +import com.owncloud.android.utils.MimeType; import org.junit.Before; import org.junit.Test; @@ -514,6 +515,101 @@ public void testMetadata() throws IOException, AccountUtils.AccountNotFoundExcep assertEquals(new ImageDimension(300f, 200f), ocFile.getImageDimension()); } + @Test + public void testEncryptedUploadCallsEncryptedUploadNotNormalUpload() { + OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt", + FOLDER + "nonEmpty.txt", + account.name); + + OCFile encryptedFile = new OCFile(FOLDER + "nonEmpty.txt"); + encryptedFile.setEncrypted(true); + encryptedFile.setStoragePath(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt"); + + UploadFileOperation newUpload = new UploadFileOperation( + uploadsStorageManager, + connectivityServiceMock, + powerManagementServiceMock, + user, + encryptedFile, + ocUpload, + NameCollisionPolicy.DEFAULT, + FileUploadWorker.LOCAL_BEHAVIOUR_COPY, + targetContext, + false, + false, + getStorageManager() + ); + newUpload.setRemoteFolderToBeCreated(); + newUpload.addRenameUploadListener(() -> {}); + + newUpload.execute(client); + + assertNotNull(newUpload.getDecryptedRemotePath()); + assertFalse(newUpload.getDecryptedRemotePath().isEmpty()); + assertTrue(newUpload.getFile().isEncrypted()); + } + + @Test + public void testEncryptedAncestorTriggerEncryptedUpload() { + OCFile encryptedParentFolder = new OCFile(FOLDER); + encryptedParentFolder.setMimeType(MimeType.DIRECTORY); + encryptedParentFolder.setEncrypted(true); + encryptedParentFolder.setDecryptedRemotePath(FOLDER); + encryptedParentFolder.setParentId(getStorageManager().getFileByDecryptedRemotePath("/").getFileId()); + getStorageManager().saveFile(encryptedParentFolder); + + // Reload so we get the assigned fileId back from the DB + encryptedParentFolder = getStorageManager().getFileByDecryptedRemotePath(FOLDER); + assertNotNull("Encrypted parent folder must exist in storage manager", encryptedParentFolder); + assertTrue("Parent folder must be marked encrypted", encryptedParentFolder.isEncrypted()); + + // Create the child file pointing at the encrypted parent + OCFile childFile = new OCFile(FOLDER + "nonEmpty.txt"); + childFile.setStoragePath(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt"); + childFile.setParentId(encryptedParentFolder.getFileId()); + childFile.setEncrypted(false); // explicitly NOT encrypted itself; ancestor is + getStorageManager().saveFile(childFile); + + OCUpload ocUpload = new OCUpload( + FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt", + FOLDER + "nonEmpty.txt", + account.name + ); + + UploadFileOperation newUpload = new UploadFileOperation( + uploadsStorageManager, + connectivityServiceMock, + powerManagementServiceMock, + user, + childFile, + ocUpload, + NameCollisionPolicy.DEFAULT, + FileUploadWorker.LOCAL_BEHAVIOUR_COPY, + targetContext, + false, + false, + getStorageManager() + ); + newUpload.setRemoteFolderToBeCreated(); + newUpload.addRenameUploadListener(() -> { }); + + newUpload.execute(client); + + assertTrue( + "mFile must be marked encrypted, proving encryptedUpload() was called not normalUpload()", + newUpload.getFile().isEncrypted() + ); + + assertNotNull( + "decryptedRemotePath must be set, which only encryptedUpload() does", + newUpload.getDecryptedRemotePath() + ); + assertFalse( + "decryptedRemotePath must be non-empty, which only encryptedUpload() does", + newUpload.getDecryptedRemotePath().isEmpty() + ); + } + private void verifyStoragePath(OCFile file) { assertEquals(FileStorageUtils.getSavePath(account.name) + FOLDER + file.getDecryptedFileName(), file.getStoragePath()); diff --git a/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt b/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt index 3ab05fdeaf0e..49408fd9a812 100644 --- a/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt +++ b/app/src/androidTest/java/com/owncloud/android/utils/EncryptionUtilsV2IT.kt @@ -406,9 +406,57 @@ class EncryptionUtilsV2IT : EncryptionIT() { val signature = encryptionUtilsV2.getMessageSignature(enc1Cert, enc1PrivateKey, encrypted) - encryptionUtilsV2.verifyMetadata(encrypted, metadataFile, 0, signature) + encryptionUtilsV2.verifyMetadata(signature, encrypted, metadataFile, 0) - assertTrue(true) // if we reach this, test is successful + assertTrue(true) + } + + @Test + fun verifySignedDataWhenGivenValidArgumentsShouldReturnTrue() { + val message = """ + { + "metadata" : { + "authenticationTag" : "NMfZ0KsC4Q8Le/5BYg6iew==", + "ciphertext" : "EtVOZuwijECmRI/7ZGqybveA4yzkNtMJxDy07B+HtNEx75qv/RR897BEEuN+fWVCJ61mOvsg9/d5WdrUliGOzVA0nwa+V+eq08mDCs5ill19+9zCBv832OwfUBqF+UreNSfwlLB83QN0bVl5ItsYhy4HcIUzdHi8RV3ypYBV2lfcNJUibvk0x9nQqwLhDwt7/kFiDqJ7", + "nonce" : "fI5rq9dd2nMjiP/x" + }, + "users" : [ { + "certificate" : "-----BEGIN CERTIFICATE-----\nMIIC7jCCAdagAwIBAgIBADANBgkqhkiG9w0BAQUFADAQMQ4wDAYDVQQDEwVhZG1p\nbjAeFw0yNjA0MjIxMzQwMjJaFw00NjA0MTcxMzQwMjJaMBAxDjAMBgNVBAMTBWFk\nbWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsPloRFaKUf+Rb0W9\ngMWTf+3ASW4nUUG6IjAgtaCHSCAmvU7npzFZe84ZYJCU06BiDE/RyRfbNqgPQmsO\neqJJOcVojSKiicw20cwz+JLvtTKhZ/E0Y/E7y1b7pPx2gAdFX46KEUei+QcOA+Sh\nBhN67yAgmkB9JmXUfajpbnF1YlpXyKKUrtv/tV46sJIUSjA1x3K1xDrKKtSwhriK\nWcKAOLF1Do5Jaq0mFNPKXYr6vloic61A9cRjsCpwM5PcRLKh5vzqWKPMY7QgbIBu\nITGp8S2AAPrTSHCMGjSKExnpVnnmg22vh+LOR39JrqKcmBqd1fkGEBKGbGPYXinS\nSF6MLQIDAQABo1MwUTAdBgNVHQ4EFgQUoBFxUd+8eaa4T6wJWDyCrITFx6UwHwYD\nVR0jBBgwFoAUoBFxUd+8eaa4T6wJWDyCrITFx6UwDwYDVR0TAQH/BAUwAwEB/zAN\nBgkqhkiG9w0BAQUFAAOCAQEAa3sN5UjPbPGJ9Ne/qOdGFBoXkynQCLL3/zhhf2U2\nNSCIbmm2KJDNlbsgHTOShpUlWQlFUyXyELRIVUZWahcjgS4aAdnVyc5+j51Rfpns\njgtLSIwRFxCF/XlhL4OZwd0AcP+lVYpwqg/9oHjMsoEhlJf+R2dLg748346C95/8\nNb8YgAXC43eqBiz7JC7R2VyMW1n8Ce8c8III/tZpZJGzlyu1ckVoOvw4ZYZd/O3B\n/d/MfhgD5E5N+PhTPaIWpdyn0+9Q3WbNQtgjq7DW4NGXbwGiF34jJsLEE5rrhKWk\nhVM7gUpNr6YhtWzO64ZZPGHXb5f5HD9j3vYAAJ+8a9VTEg==\n-----END CERTIFICATE-----", + "encryptedMetadataKey" : "FanlaJty6rhcjFfLzERYH6UqeZq+KprKazD2KRqeqeNgonubGD/ckyrLtHSLHczfAGFxk0kL2p77MJ9sbSM+TIlAUrq1JLMOq1dnPldAI7tTb77+fApkiuqmJZhmOEe+dLyewEBegDGQS6M6rlQ+YfCQPnhYb+KAFUm+DzRL3iXCAeO/t+hqml1vb0zjI1F8zsPV5z0PhuqpOZcr2Ao+hpKVpozqNJhI2HSTRjCz/C++N83qE+YqvUYywp4U2LpLRGnGLl2QHyCcCmCFPhFlWuCxTHCe7E8LGcGQ31wRySjKDjO/6YXWEKQy2/k9H0G081YP89mUQzpi53hQ2UhEFw==", + "userId" : "admin" + } ], + "version" : "2.0" +} + """.trimIndent() + + val signature = """ + MIIE1wYJKoZIhvcNAQcCoIIEyDCCBMQCAQExDzANBglghkgBZQMEAgEFADALBgkqhkiG9w0BBwGgggLyMIIC7jCCAdagAwIBAgIBADANBgkqhkiG9w0BAQUFADAQMQ4wDAYDVQQDEwVhZG1pbjAeFw0yNjA0MjIxMzQwMjJaFw00NjA0MTcxMzQwMjJaMBAxDjAMBgNVBAMTBWFkbWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsPloRFaKUf+Rb0W9gMWTf+3ASW4nUUG6IjAgtaCHSCAmvU7npzFZe84ZYJCU06BiDE/RyRfbNqgPQmsOeqJJOcVojSKiicw20cwz+JLvtTKhZ/E0Y/E7y1b7pPx2gAdFX46KEUei+QcOA+ShBhN67yAgmkB9JmXUfajpbnF1YlpXyKKUrtv/tV46sJIUSjA1x3K1xDrKKtSwhriKWcKAOLF1Do5Jaq0mFNPKXYr6vloic61A9cRjsCpwM5PcRLKh5vzqWKPMY7QgbIBuITGp8S2AAPrTSHCMGjSKExnpVnnmg22vh+LOR39JrqKcmBqd1fkGEBKGbGPYXinSSF6MLQIDAQABo1MwUTAdBgNVHQ4EFgQUoBFxUd+8eaa4T6wJWDyCrITFx6UwHwYDVR0jBBgwFoAUoBFxUd+8eaa4T6wJWDyCrITFx6UwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAa3sN5UjPbPGJ9Ne/qOdGFBoXkynQCLL3/zhhf2U2NSCIbmm2KJDNlbsgHTOShpUlWQlFUyXyELRIVUZWahcjgS4aAdnVyc5+j51RfpnsjgtLSIwRFxCF/XlhL4OZwd0AcP+lVYpwqg/9oHjMsoEhlJf+R2dLg748346C95/8Nb8YgAXC43eqBiz7JC7R2VyMW1n8Ce8c8III/tZpZJGzlyu1ckVoOvw4ZYZd/O3B/d/MfhgD5E5N+PhTPaIWpdyn0+9Q3WbNQtgjq7DW4NGXbwGiF34jJsLEE5rrhKWkhVM7gUpNr6YhtWzO64ZZPGHXb5f5HD9j3vYAAJ+8a9VTEjGCAakwggGlAgEAMBUwEDEOMAwGA1UEAxMFYWRtaW4CAQAwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMC8GCSqGSIb3DQEJBDEiBCC0buwKgzbVlARGgIiuAZldgZrT4TWMt5IhjuxKwdlBwTAcBgkqhkiG9w0BCQUxDxcNMjYwNDIzMDgyNjI1WjALBgkqhkiG9w0BAQsEggEAetgtsJ1k6BmpbHzWwBsnNc5nEcKz+dHUn3c4+JmoukSCuoYRKmtNY0uJ3XlqqVKkBWD+a6rXBtJPZqH+Y0tc0s/IHY3lztoFB+v2KQwK1KI/8C67qmpwgPAGoMhpzpa1+s4tcsQbNN/hSH25giWT+MwbUBV8Z4yZhdZFrinjwN8teHOaA+jzIpz5s395uFtxznANoo5EX8g1JrN0iPtCWV4e+70+8TLSj0qzX0Kg+pAE4SyImM/6V2GEkR+js+vVvJcJaTAH+/+svBnfT6/W8ElJPr7zLD5tsgHLkyEr1ydgV+rEjE+M/Aoaz9x1iU5wBJ1tbak4fMvGR7DSExFUOA== + """.trimIndent() + + val cert = """ + -----BEGIN CERTIFICATE----- + MIIC7jCCAdagAwIBAgIBADANBgkqhkiG9w0BAQUFADAQMQ4wDAYDVQQDEwVhZG1p + bjAeFw0yNjA0MjIxMzQwMjJaFw00NjA0MTcxMzQwMjJaMBAxDjAMBgNVBAMTBWFk + bWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsPloRFaKUf+Rb0W9 + gMWTf+3ASW4nUUG6IjAgtaCHSCAmvU7npzFZe84ZYJCU06BiDE/RyRfbNqgPQmsO + eqJJOcVojSKiicw20cwz+JLvtTKhZ/E0Y/E7y1b7pPx2gAdFX46KEUei+QcOA+Sh + BhN67yAgmkB9JmXUfajpbnF1YlpXyKKUrtv/tV46sJIUSjA1x3K1xDrKKtSwhriK + WcKAOLF1Do5Jaq0mFNPKXYr6vloic61A9cRjsCpwM5PcRLKh5vzqWKPMY7QgbIBu + ITGp8S2AAPrTSHCMGjSKExnpVnnmg22vh+LOR39JrqKcmBqd1fkGEBKGbGPYXinS + SF6MLQIDAQABo1MwUTAdBgNVHQ4EFgQUoBFxUd+8eaa4T6wJWDyCrITFx6UwHwYD + VR0jBBgwFoAUoBFxUd+8eaa4T6wJWDyCrITFx6UwDwYDVR0TAQH/BAUwAwEB/zAN + BgkqhkiG9w0BAQUFAAOCAQEAa3sN5UjPbPGJ9Ne/qOdGFBoXkynQCLL3/zhhf2U2 + NSCIbmm2KJDNlbsgHTOShpUlWQlFUyXyELRIVUZWahcjgS4aAdnVyc5+j51Rfpns + jgtLSIwRFxCF/XlhL4OZwd0AcP+lVYpwqg/9oHjMsoEhlJf+R2dLg748346C95/8 + Nb8YgAXC43eqBiz7JC7R2VyMW1n8Ce8c8III/tZpZJGzlyu1ckVoOvw4ZYZd/O3B + /d/MfhgD5E5N+PhTPaIWpdyn0+9Q3WbNQtgjq7DW4NGXbwGiF34jJsLEE5rrhKWk + hVM7gUpNr6YhtWzO64ZZPGHXb5f5HD9j3vYAAJ+8a9VTEg== + -----END CERTIFICATE----- + """.trimIndent() + + val certs = listOf(EncryptionUtils.convertCertFromString(cert)) + val signedData = encryptionUtilsV2.getSignedData(signature, message) + assertTrue(encryptionUtilsV2.verifySignedData(signedData, certs)) } private fun generateDecryptedFileV1(): com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFile = @@ -641,77 +689,6 @@ class EncryptionUtilsV2IT : EncryptionIT() { assertEquals("this is a test.\n", gunzip) } -// @Test -// fun validate() { -// // ALEX -// val metadata1 = """{ -// "metadata": { -// "authenticationTag": "zMozev5R09UopLrq7Je1lw==", -// "ciphertext": "j0OBtUrEt4IveGiexjmGK7eKEaWrY70ZkteA5KxHDaZT/t2wwGy9j2FPQGpqXnW6OO3iAYPNgwFikI1smnfNvqdxzVDvhavl/IXa9Kg2niWyqK3D9zpz0YD6mDvl0XsOgTNVyGXNVREdWgzGEERCQoyHI1xowt/swe3KCXw+lf+XPF/t1PfHv0DiDVk70AeWGpPPPu6yggAIxB4Az6PEZhaQWweTC0an48l2FHj2MtB2PiMHtW2v7RMuE8Al3PtE4gOA8CMFrB+Npy6rKcFCXOgTZm5bp7q+J1qkhBDbiBYtvdsYujJ52Xa5SifTpEhGeWWLFnLLgPAQ8o6bXcWOyCoYfLfp4Jpft/Y7H8qzHbPewNSyD6maEv+xljjfU7hxibbszz5A4JjMdQy2BDGoTmJx7Mas+g6l6ZuHLVbdmgQOvD3waJBy6rOg0euux0Cn4bB4bIFEF2KvbhdGbY1Uiq9DYa7kEmSEnlcAYaHyroTkDg4ew7ER0vIBBMzKM3r+UdPVKKS66uyXtZc=", -// "nonce": "W+lxQJeGq7XAJiGfcDohkg==" -// }, -// "users": [{ -// "certificate": "-----BEGIN CERTIFICATE-----\nMIIDkDCCAnigAwIBAgIBADANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQGEwJERTEb\nMBkGA1UECAwSQmFkZW4tV3VlcnR0ZW1iZXJnMRIwEAYDVQQHDAlTdHV0dGdhcnQx\nEjAQBgNVBAoMCU5leHRjbG91ZDENMAsGA1UEAwwEam9objAeFw0yMzA3MTQwNzM0\nNTZaFw00MzA3MDkwNzM0NTZaMGExCzAJBgNVBAYTAkRFMRswGQYDVQQIDBJCYWRl\nbi1XdWVydHRlbWJlcmcxEjAQBgNVBAcMCVN0dXR0Z2FydDESMBAGA1UECgwJTmV4\ndGNsb3VkMQ0wCwYDVQQDDARqb2huMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\nCgKCAQEA7j3Er5YahJT0LAnSRLhpqbRo+E1AVnt98rvp3DmEfBHNzNB+DS9IBDkS\nSXM/YtfAci6Tcw8ujVBjrZX/WEmrf8ynQHxYmSaJSnP8uAT306/MceZpdpruEc9/\nS10a7vp54Zbld4NYdmfS71oVFVKgM7c/Vthx+rgu48fuxzbWAvVYLFcx47hz0DJT\nnjz2Za/R68uXpxfz7J9uEXYiqsAs/FobDsLZluT3RyywVRwKBed1EZxUeLIJiyxp\nUthhGfIb8b3Vf9jZoUVi3m5gmc4spJQHvYAkfZYHzd9ras8jBu1abQRxcu2CYnVo\n6Y0mTxhKhQS/n5gjv3ExiQF3wp/XYwIDAQABo1MwUTAdBgNVHQ4EFgQUmTeILVuB\ntv70fTGkXWGAueDp5kAwHwYDVR0jBBgwFoAUmTeILVuBtv70fTGkXWGAueDp5kAw\nDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAyVtq9XAvW7nxSW/8\nhp30z6xbzGiuviXhy/Jo91VEa8IRsWCCn3OmDFiVduTEowx76tf8clJP0gk7Pozi\n6dg/7Fin+FqQGXfCk8bLAh9gXKAikQ2GK8yRN3slRFwYC2mm23HrLdKXZHUqJcpB\nMz2zsSrOGPj1YsYOl/U8FU6KA7Yj7U3q7kDMYTAgzUPZAH+d1DISGWpZsMa0RYid\nvigCCLByiccmS/Co4Sb1esF58H+YtV5+nFBRwx881U2g2TgDKF1lPMK/y3d8B8mh\nUtW+lFxRpvyNUDpsMjOErOrtNFEYbgoUJLtqwBMmyGR+nmmh6xna331QWcRAmw0P\nnDO4ew==\n-----END CERTIFICATE-----\n", -// "encryptedMetadataKey": "HVT49bYmwXbGs/dJ2avgU9unrKnPf03MYUI5ZysSR1Bz5pqz64gzH2GBAuUJ+Q4VmHtEfcMaWW7VXgzfCQv5xLBrk+RSgcLOKnlIya8jaDlfttWxbe8jJK+/0+QVPOc6ycA/t5HNCPg09hzj+gnb2L89UHxL5accZD0iEzb5cQbGrc/N6GthjgGrgFKtFf0HhDVplUr+DL9aTyKuKLBPjrjuZbv8M6ZfXO93mOMwSZH3c3rwDUHb/KEaTR/Og4pWQmrqr1VxGLqeV/+GKWhzMYThrOZAUz+5gsbckU2M5V9i+ph0yBI5BjOZVhNuDwW8yP8WtyRJwQc+UBRei/RGBQ==", -// "userId": "john" -// }], -// "version": "2" -// } -// -// """ -// -// val signature1 = -// "ewogICAgIm1ldGFkYXRhIjogewogICAgICAgICJhdXRoZW50aWNhdGlvblRhZyI6ICJ6TW96ZXY1UjA5VW9wTHJxN0plMWx3PT0iLAogICAgICAgICJjaXBoZXJ0ZXh0IjogImowT0J0VXJFdDRJdmVHaWV4am1HSzdlS0VhV3JZNzBaa3RlQTVLeEhEYVpUL3Qyd3dHeTlqMkZQUUdwcVhuVzZPTzNpQVlQTmd3RmlrSTFzbW5mTnZxZHh6VkR2aGF2bC9JWGE5S2cybmlXeXFLM0Q5enB6MFlENm1EdmwwWHNPZ1ROVnlHWE5WUkVkV2d6R0VFUkNRb3lISTF4b3d0L3N3ZTNLQ1h3K2xmK1hQRi90MVBmSHYwRGlEVms3MEFlV0dwUFBQdTZ5Z2dBSXhCNEF6NlBFWmhhUVd3ZVRDMGFuNDhsMkZIajJNdEIyUGlNSHRXMnY3Uk11RThBbDNQdEU0Z09BOENNRnJCK05weTZyS2NGQ1hPZ1RabTVicDdxK0oxcWtoQkRiaUJZdHZkc1l1ako1MlhhNVNpZlRwRWhHZVdXTEZuTExnUEFROG82YlhjV095Q29ZZkxmcDRKcGZ0L1k3SDhxekhiUGV3TlN5RDZtYUV2K3hsampmVTdoeGliYnN6ejVBNEpqTWRReTJCREdvVG1KeDdNYXMrZzZsNlp1SExWYmRtZ1FPdkQzd2FKQnk2ck9nMGV1dXgwQ240YkI0YklGRUYyS3ZiaGRHYlkxVWlxOURZYTdrRW1TRW5sY0FZYUh5cm9Ua0RnNGV3N0VSMHZJQkJNektNM3IrVWRQVktLUzY2dXlYdFpjPSIsCiAgICAgICAgIm5vbmNlIjogIlcrbHhRSmVHcTdYQUppR2ZjRG9oa2c9PSIKICAgIH0sCiAgICAidXNlcnMiOiB7CiAgICAgICAgImNlcnRpZmljYXRlIjogIi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLVxuTUlJRGtEQ0NBbmlnQXdJQkFnSUJBREFOQmdrcWhraUc5dzBCQVFVRkFEQmhNUXN3Q1FZRFZRUUdFd0pFUlRFYlxuTUJrR0ExVUVDQXdTUW1Ga1pXNHRWM1ZsY25SMFpXMWlaWEpuTVJJd0VBWURWUVFIREFsVGRIVjBkR2RoY25ReFxuRWpBUUJnTlZCQW9NQ1U1bGVIUmpiRzkxWkRFTk1Bc0dBMVVFQXd3RWFtOW9iakFlRncweU16QTNNVFF3TnpNMFxuTlRaYUZ3MDBNekEzTURrd056TTBOVFphTUdFeEN6QUpCZ05WQkFZVEFrUkZNUnN3R1FZRFZRUUlEQkpDWVdSbFxuYmkxWGRXVnlkSFJsYldKbGNtY3hFakFRQmdOVkJBY01DVk4wZFhSMFoyRnlkREVTTUJBR0ExVUVDZ3dKVG1WNFxuZEdOc2IzVmtNUTB3Q3dZRFZRUUREQVJxYjJodU1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQlxuQ2dLQ0FRRUE3ajNFcjVZYWhKVDBMQW5TUkxocHFiUm8rRTFBVm50OThydnAzRG1FZkJITnpOQitEUzlJQkRrU1xuU1hNL1l0ZkFjaTZUY3c4dWpWQmpyWlgvV0VtcmY4eW5RSHhZbVNhSlNuUDh1QVQzMDYvTWNlWnBkcHJ1RWM5L1xuUzEwYTd2cDU0WmJsZDROWWRtZlM3MW9WRlZLZ003Yy9WdGh4K3JndTQ4ZnV4emJXQXZWWUxGY3g0N2h6MERKVFxubmp6MlphL1I2OHVYcHhmejdKOXVFWFlpcXNBcy9Gb2JEc0xabHVUM1J5eXdWUndLQmVkMUVaeFVlTElKaXl4cFxuVXRoaEdmSWI4YjNWZjlqWm9VVmkzbTVnbWM0c3BKUUh2WUFrZlpZSHpkOXJhczhqQnUxYWJRUnhjdTJDWW5Wb1xuNlkwbVR4aEtoUVMvbjVnanYzRXhpUUYzd3AvWFl3SURBUUFCbzFNd1VUQWRCZ05WSFE0RUZnUVVtVGVJTFZ1QlxudHY3MGZUR2tYV0dBdWVEcDVrQXdId1lEVlIwakJCZ3dGb0FVbVRlSUxWdUJ0djcwZlRHa1hXR0F1ZURwNWtBd1xuRHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBeVZ0cTlYQXZXN254U1cvOFxuaHAzMHo2eGJ6R2l1dmlYaHkvSm85MVZFYThJUnNXQ0NuM09tREZpVmR1VEVvd3g3NnRmOGNsSlAwZ2s3UG96aVxuNmRnLzdGaW4rRnFRR1hmQ2s4YkxBaDlnWEtBaWtRMkdLOHlSTjNzbFJGd1lDMm1tMjNIckxkS1haSFVxSmNwQlxuTXoyenNTck9HUGoxWXNZT2wvVThGVTZLQTdZajdVM3E3a0RNWVRBZ3pVUFpBSCtkMURJU0dXcFpzTWEwUllpZFxudmlnQ0NMQnlpY2NtUy9DbzRTYjFlc0Y1OEgrWXRWNStuRkJSd3g4ODFVMmcyVGdES0YxbFBNSy95M2Q4QjhtaFxuVXRXK2xGeFJwdnlOVURwc01qT0VyT3J0TkZFWWJnb1VKTHRxd0JNbXlHUitubW1oNnhuYTMzMVFXY1JBbXcwUFxubkRPNGV3PT1cbi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS1cbiIsCiAgICAgICAgImVuY3J5cHRlZE1ldGFkYXRhS2V5IjogIkhWVDQ5Ylltd1hiR3MvZEoyYXZnVTl1bnJLblBmMDNNWVVJNVp5c1NSMUJ6NXBxejY0Z3pIMkdCQXVVSitRNFZtSHRFZmNNYVdXN1ZYZ3pmQ1F2NXhMQnJrK1JTZ2NMT0tubEl5YThqYURsZnR0V3hiZThqSksrLzArUVZQT2M2eWNBL3Q1SE5DUGcwOWh6aitnbmIyTDg5VUh4TDVhY2NaRDBpRXpiNWNRYkdyYy9ONkd0aGpnR3JnRkt0RmYwSGhEVnBsVXIrREw5YVR5S3VLTEJQanJqdVpidjhNNlpmWE85M21PTXdTWkgzYzNyd0RVSGIvS0VhVFIvT2c0cFdRbXJxcjFWeEdMcWVWLytHS1doek1ZVGhyT1pBVXorNWdzYmNrVTJNNVY5aStwaDB5Qkk1QmpPWlZoTnVEd1c4eVA4V3R5Ukp3UWMrVUJSZWkvUkdCUT09IiwKICAgICAgICAidXNlcklkIjogImpvaG4iCiAgICB9LAogICAgInZlcnNpb24iOiAiMiIKfQo=" -// -// // TOBI -// val metadata = -// """{"metadata":{"authenticationTag":"qDcJnAAGtGDlHWiQMBfXgw\u003d\u003d","ciphertext":"3zUhwIgJWMB7DvrbsDaMvh8MbJdoTxL0OMPCCdYSfBt7gB+V/hwqelL1IOaLto3avhHGSebnrotF06iEP/jZwWg9hApIPTHc8B4XTOY0/kezqYyVqTyquTUZpDpqgVAheQskZZ8I4Ir0seajUkt4KtVRfzO6v8CePRrEg6uKwdYsqDcJnAAGtGDlHWiQMBfXgw\u003d\u003d|4hbOyn1ykQL+9D6SnPY3cQ\u003d\u003d","nonce":"4hbOyn1ykQL+9D6SnPY3cQ\u003d\u003d"},"users":[{"certificate":"-----BEGIN CERTIFICATE-----\nMIIC6DCCAdCgAwIBAgIBADANBgkqhkiG9w0BAQUFADANMQswCQYDVQQDDAJ0MTAe\nFw0yMzA3MjUwNzU3MTJaFw00MzA3MjAwNzU3MTJaMA0xCzAJBgNVBAMMAnQxMIIB\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtafHmDBcBqIu4HmMxMDW3j0S\ny+S0YaKwHnBRt85KSwcEov0B5FOLuLknoBGx4Dn3u93ilThXXxacPMHeXL7WPuAs\n21/G7vsqwvrRRnCduf+FUO/AZeDCNErzpsQ8LmTa4PUloLPUcImpSjrHwhMs9Ekv\nEbLRjbeSmSp9XvM+1fV/3jkT5jkOSnCFx5TGwGN5uHqwUir4UWXasvg253NK2XmW\nipKCDCR9TmH1baP3pNdoiChdmErT1c6E4DbBXpTw8XgP5ZbYH+qg1UQ/hC8nRJ3D\nyCcHL+dg/GYraBMhDn4w2Vvq77xNNoNWQ9cT5Ay6cJbQLBQoJQirygQFrobYRQID\nAQABo1MwUTAdBgNVHQ4EFgQUE9zCeA9/QMAtVgLxD23X6ZcodhMwHwYDVR0jBBgw\nFoAUE9zCeA9/QMAtVgLxD23X6ZcodhMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG\n9w0BAQUFAAOCAQEAZdy/YjJlvnz3FQwxp6oVtMJccpdxveEPfLzgaverhtd/vP8O\nAvDzOLgQJHmrDS91SG503eU4cYGyuNKwd77OyTnqMg+GUEmJhGfPpSVrEIdh65jv\nq61T4oqBdehevVmBq54rGiwL0DGv1DlXQlwiJZP4qni2KnOEFcnvL3gVtRnQjXQ+\nkHvlMshkK6w021EMV5NfjG2zg67wC65rLaej5f6Ssp2S7g2VtmE4aXq1bjAuEbqk\n4TiyZHLDdsJuqzyGyyOpMV7i9ucXDoaZt9cGS9hT2vRxTrSH63vKR8Xeig9+stLw\nt9ONcUqCKP7hd8rajtxM4JIIRExwD8OkgARWGg\u003d\u003d\n-----END CERTIFICATE-----\n","encryptedMetadataKey":"s4kDkkLpk1mSmXedP7huiCNC4DYmDAmA2VYGem5M8jIGPC6miVQoo4WXZrEBhdsLw7Msf5iT3A3fTaHhwsI8Jf4McsFyM9/FXT1mCEaGOEpNjbKOlJY1uPUFNOhLqUfFiBos6oBT53hWwoXWjytYvLBbXuXY5YLOysjgBh6URrgFUZAJAmcOJ6OFKgfIIthoqkQc7CQUY97VsRzAXzeYTANBc2yW1pSN51HqftvMzvewFRsJQLcu7a9NjpTdG9LiLhn5eLXOLymXEE/aaPHKXeprlXLzrdWU1xwZRJqV+to2FEiH6CQNsO4+9h5m0VjXekiNeAFrsXB5cJgUipGuzQ\u003d\u003d","userId":"t1"}],"version":"2.0"}""" -// -// val base = EncryptionUtils.encodeStringToBase64String(metadata) -// -// val signature = -// "MIAGCSqGSIb3DQEHAqCAMIACAQExDTALBglghkgBZQMEAgEwCwYJKoZIhvcNAQcBoIAwggLoMIIB0KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNVBAMMAnQxMB4XDTIzMDcyNTA3NTcxMloXDTQzMDcyMDA3NTcxMlowDTELMAkGA1UEAwwCdDEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1p8eYMFwGoi7geYzEwNbePRLL5LRhorAecFG3zkpLBwSi/QHkU4u4uSegEbHgOfe73eKVOFdfFpw8wd5cvtY+4CzbX8bu+yrC+tFGcJ25/4VQ78Bl4MI0SvOmxDwuZNrg9SWgs9RwialKOsfCEyz0SS8RstGNt5KZKn1e8z7V9X/eORPmOQ5KcIXHlMbAY3m4erBSKvhRZdqy+Dbnc0rZeZaKkoIMJH1OYfVto/ek12iIKF2YStPVzoTgNsFelPDxeA/lltgf6qDVRD+ELydEncPIJwcv52D8ZitoEyEOfjDZW+rvvE02g1ZD1xPkDLpwltAsFCglCKvKBAWuhthFAgMBAAGjUzBRMB0GA1UdDgQWBBQT3MJ4D39AwC1WAvEPbdfplyh2EzAfBgNVHSMEGDAWgBQT3MJ4D39AwC1WAvEPbdfplyh2EzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBl3L9iMmW+fPcVDDGnqhW0wlxyl3G94Q98vOBq96uG13+8/w4C8PM4uBAkeasNL3VIbnTd5ThxgbK40rB3vs7JOeoyD4ZQSYmEZ8+lJWsQh2HrmO+rrVPiioF16F69WYGrnisaLAvQMa/UOVdCXCIlk/iqeLYqc4QVye8veBW1GdCNdD6Qe+UyyGQrrDTbUQxXk1+MbbODrvALrmstp6Pl/pKynZLuDZW2YThperVuMC4RuqThOLJkcsN2wm6rPIbLI6kxXuL25xcOhpm31wZL2FPa9HFOtIfre8pHxd6KD36y0vC3041xSoIo/uF3ytqO3EzgkghETHAPw6SABFYaAAAxggHUMIIB0AIBATASMA0xCzAJBgNVBAMMAnQxAgEAMAsGCWCGSAFlAwQCAaCBljAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yMzA3MjgwNzMwMTJaMCsGCSqGSIb3DQEJNDEeMBwwCwYJYIZIAWUDBAIBoQ0GCSqGSIb3DQEBCwUAMC8GCSqGSIb3DQEJBDEiBCAx7RTJg7hbY5Mkzjw3f6qhX7k/J0FdVz2cL3ow0AmyYjANBgkqhkiG9w0BAQsFAASCAQAbUmb9e7eoIcPNzDSmnzbrueBzgT8YszNGEI+1YCq8XdWN4kDztvP1ZNV21VCO6BvcbfUAnXXgcX5BPeLZNsgXPj3c8TbD59GQl3oT/tIchgMsA20RdAtIwvItlZKh+X6sp0OHkRPYSk/mEYKCKPqrKdJicRWex8ItCwpDR91KSOiKJrN/+DKOGG0sVI9gjzbtrHsN8HmVKxOoNV+wwipcLsWsEmuV+wvPCQ9HJidLX9Q17Bgfc+qJg19aB6iKLWPhjgnfpKGbK5VJuQTdDWPUJ2O4G3W/iwxJ0hAJ7tks4zIATmgGzhgTWYx5LVXbKcuL04xhIOjqwedHeCSBZSSaAAAAAAAA" -// -// val metadataFile = EncryptionUtils.deserializeJSON( -// metadata, -// object : TypeToken() {} -// ) -// assertNotNull(metadataFile) -// -// val certJohnString = metadataFile.users[0].certificate -// val certJohn = EncryptionUtils.convertCertFromString(certJohnString) -// -// val t1String = """-----BEGIN CERTIFICATE----- -// MIIC6DCCAdCgAwIBAgIBADANBgkqhkiG9w0BAQUFADANMQswCQYDVQQDDAJ0MTAe -// Fw0yMzA3MjUwNzU3MTJaFw00MzA3MjAwNzU3MTJaMA0xCzAJBgNVBAMMAnQxMIIB -// IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtafHmDBcBqIu4HmMxMDW3j0S -// y+S0YaKwHnBRt85KSwcEov0B5FOLuLknoBGx4Dn3u93ilThXXxacPMHeXL7WPuAs -// 21/G7vsqwvrRRnCduf+FUO/AZeDCNErzpsQ8LmTa4PUloLPUcImpSjrHwhMs9Ekv -// EbLRjbeSmSp9XvM+1fV/3jkT5jkOSnCFx5TGwGN5uHqwUir4UWXasvg253NK2XmW -// ipKCDCR9TmH1baP3pNdoiChdmErT1c6E4DbBXpTw8XgP5ZbYH+qg1UQ/hC8nRJ3D -// yCcHL+dg/GYraBMhDn4w2Vvq77xNNoNWQ9cT5Ay6cJbQLBQoJQirygQFrobYRQID -// AQABo1MwUTAdBgNVHQ4EFgQUE9zCeA9/QMAtVgLxD23X6ZcodhMwHwYDVR0jBBgw -// FoAUE9zCeA9/QMAtVgLxD23X6ZcodhMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG -// 9w0BAQUFAAOCAQEAZdy/YjJlvnz3FQwxp6oVtMJccpdxveEPfLzgaverhtd/vP8O -// AvDzOLgQJHmrDS91SG503eU4cYGyuNKwd77OyTnqMg+GUEmJhGfPpSVrEIdh65jv -// q61T4oqBdehevVmBq54rGiwL0DGv1DlXQlwiJZP4qni2KnOEFcnvL3gVtRnQjXQ+ -// kHvlMshkK6w021EMV5NfjG2zg67wC65rLaej5f6Ssp2S7g2VtmE4aXq1bjAuEbqk -// 4TiyZHLDdsJuqzyGyyOpMV7i9ucXDoaZt9cGS9hT2vRxTrSH63vKR8Xeig9+stLw -// t9ONcUqCKP7hd8rajtxM4JIIRExwD8OkgARWGg== -// -----END CERTIFICATE-----""" -// -// val t1cert = EncryptionUtils.convertCertFromString(t1String) -// val t1PrivateKeyKey = EncryptionUtils.PEMtoPrivateKey(encryptionTestUtils.t1PrivateKey) -// -// // val signed = encryptionUtilsV2.getMessageSignature( -// // t1cert, -// // t1PrivateKeyKey, -// // metadataFile -// // ) -// -// assertTrue(encryptionUtilsV2.verifySignedMessage(signature1, metadata1, listOf(certJohn, t1cert))) -// } - @Throws(Throwable::class) @Test fun testSigning() { diff --git a/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java b/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java index 078bc598b357..fb686c8a894b 100644 --- a/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java +++ b/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java @@ -1274,6 +1274,7 @@ public static Pair retrieveMetadata(OCFile client, parentFile.getE2eCounter(), getMetadataOperationResult.getResultData().getSignature(), + getMetadataOperationResult.getResultData().getMetadata(), user, context, arbitraryDataProvider) diff --git a/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt b/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt index e87ef399d635..42c40e51cddc 100644 --- a/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt +++ b/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt @@ -137,25 +137,6 @@ class EncryptionUtilsV2 { encryptedUsers, mutableMapOf() ) - - // if (metadataFile.users.isEmpty()) { - // // we are in a subfolder, re-use users array - // retrieveTopMostMetadata( - // ocFile, - // storageManager, - // client - // ) - // } else { - // val encryptedUsers = metadataFile.users.map { - // encryptUser(it, metadataFile.metadata.metadataKey) - // } - // - // return EncryptedFolderMetadataFile( - // encryptedMetadata, - // encryptedUsers, - // emptyMap() - // ) - // } } @Throws(IllegalStateException::class, UploadException::class, Throwable::class) @@ -169,6 +150,7 @@ class EncryptionUtilsV2 { client: OwnCloudClient, oldCounter: Long, signature: String, + rawMetadata: String, user: User, context: Context, arbitraryDataProvider: ArbitraryDataProvider @@ -250,7 +232,7 @@ class EncryptionUtilsV2 { ) } - if (!verifyMetadata(metadataFile, decryptedFolderMetadataFile, oldCounter, signature)) { + if (!verifyMetadata(signature, rawMetadata, decryptedFolderMetadataFile, oldCounter)) { throw IllegalStateException("Metadata is corrupt!") } @@ -354,6 +336,7 @@ class EncryptionUtilsV2 { client, topMost.e2eCounter, result.resultData.signature, + result.resultData.metadata, user, context, arbitraryDataProvider @@ -625,6 +608,7 @@ class EncryptionUtilsV2 { client, folder.e2eCounter, metadataResponse.signature, + metadataResponse.metadata, user, context, arbitraryDataProvider @@ -679,86 +663,6 @@ class EncryptionUtilsV2 { } return decryptedFolderMetadata - - // handle filesDrops - // TODO re-add -// try { -// int filesDropCountBefore = encryptedFolderMetadata.getFiledrop().size(); -// DecryptedFolderMetadataFile decryptedFolderMetadata = new EncryptionUtilsV2().decryptFolderMetadataFile( -// encryptedFolderMetadata, -// privateKey); -// -// boolean transferredFiledrop = filesDropCountBefore > 0 && decryptedFolderMetadata.getFiles().size() == -// encryptedFolderMetadata.getFiles().size() + filesDropCountBefore; -// -// if (transferredFiledrop) { -// // lock folder, only if not already locked -// String token; -// if (existingLockToken == null) { -// token = EncryptionUtils.lockFolder(folder, client); -// } else { -// token = existingLockToken; -// } -// -// // upload metadata -// EncryptedFolderMetadataFile encryptedFolderMetadataNew = -// encryptFolderMetadata(decryptedFolderMetadata, privateKey); -// -// String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadataNew); -// -// EncryptionUtils.uploadMetadata(folder, -// serializedFolderMetadata, -// token, -// client, -// true); -// -// // unlock folder, only if not previously locked -// if (existingLockToken == null) { -// RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(folder, client, token); -// -// if (!unlockFolderResult.isSuccess()) { -// Log_OC.e(TAG, unlockFolderResult.getMessage()); -// -// return null; -// } -// } -// } -// -// return decryptedFolderMetadata; -// } catch (Exception e) { -// Log_OC.e(TAG, e.getMessage()); -// return null; -// } - - // TODO to check -// try { -// int filesDropCountBefore = 0; -// if (encryptedFolderMetadata.getFiledrop() != null) { -// filesDropCountBefore = encryptedFolderMetadata.getFiledrop().size(); -// } -// DecryptedFolderMetadataFile decryptedFolderMetadata = EncryptionUtils.decryptFolderMetaData( -// encryptedFolderMetadata, -// privateKey, -// arbitraryDataProvider, -// user, -// folder.getLocalId()); -// -// boolean transferredFiledrop = filesDropCountBefore > 0 && -// decryptedFolderMetadata.getFiles().size() == -// encryptedFolderMetadata.getFiles().size() + filesDropCountBefore; -// -// if (transferredFiledrop) { -// // lock folder -// String token = EncryptionUtils.lockFolder(folder, client); -// -// // upload metadata -// EncryptedFolderMetadata encryptedFolderMetadataNew = -// encryptFolderMetadata(decryptedFolderMetadata, -// publicKey, -// arbitraryDataProvider, -// user, -// folder.getLocalId()); -// } @Throws(UploadException::class) @@ -951,21 +855,59 @@ class EncryptionUtilsV2 { @Suppress("ReturnCount") fun verifyMetadata( - encryptedFolderMetadataFile: EncryptedFolderMetadataFile, + signature: String, + rawMetadata: String, decryptedFolderMetadataFile: DecryptedFolderMetadataFile, - oldCounter: Long, - signature: String + oldCounter: Long ): Boolean { if (decryptedFolderMetadataFile.metadata.counter < oldCounter) { MainApp.showMessage(R.string.e2e_counter_too_old) return false } - val message = EncryptionUtils.serializeJSON(encryptedFolderMetadataFile, true) - val certs = decryptedFolderMetadataFile.users.map { EncryptionUtils.convertCertFromString(it.certificate) } - val signedData = getSignedData(signature, message) + val prepared = prepareRawMetadata(rawMetadata) + val messageData = EncryptionUtils.encodeStringToBase64String(prepared).toByteArray(Charsets.ISO_8859_1) + val users = decryptedFolderMetadataFile.users + val signedData = getSignedData(signature, messageData) + + val signerInfo = signedData.signerInfos.signers.first() as SignerInformation + val issuer = org.bouncycastle.asn1.x500.X500Name.getInstance( + (signerInfo.sid as org.bouncycastle.cms.SignerId).issuer + ) + val rdns = issuer.getRDNs(org.bouncycastle.asn1.x500.style.BCStyle.CN) + val signerUserId = rdns.firstOrNull()?.first?.value?.toString() + + + Log_OC.d(TAG, "Signer userId from CMS: $signerUserId") + + val signerUser = users.find { it.userId == signerUserId } + if (signerUser == null) { + Log_OC.e(TAG, "Signer not found in users array: $signerUserId") + MainApp.showMessage(R.string.e2e_signature_does_not_match) + return false + } + + val signerCert = EncryptionUtils.convertCertFromString(signerUser.certificate) + + val bcProvider = org.bouncycastle.jce.provider.BouncyCastleProvider() + java.security.Security.removeProvider("BC") + java.security.Security.insertProviderAt(bcProvider, 1) + + val verifier = JcaSimpleSignerInfoVerifierBuilder() + .setProvider(bcProvider) + .build(signerCert) + + val directResult = runCatching { + signerInfo.verify(verifier) + }.getOrElse { + Log_OC.e(TAG, "signerInfo.verify() exception: ${it.javaClass.name}: ${it.message}") + it.cause?.let { c -> Log_OC.e(TAG, " caused by: ${c.javaClass.name}: ${c.message}") } + false + } + Log_OC.d(TAG, "signerInfo.verify() result: $directResult") - if (certs.isNotEmpty() && !verifySignedData(signedData, certs)) { + if (!directResult) { + Log_OC.e(TAG, "Signature verification failed for user: $signerUserId") MainApp.showMessage(R.string.e2e_signature_does_not_match) return false } @@ -975,26 +917,67 @@ class EncryptionUtilsV2 { MainApp.showMessage(R.string.e2e_hash_not_found) return false } + return true } - private fun getSignedData(base64encodedSignature: String, message: String): CMSSignedData { + @VisibleForTesting + fun prepareRawMetadata(rawMetadata: String): String { + val parsed = com.google.gson.JsonParser.parseString(rawMetadata) + val sorted = sortJsonElement(parsed) + return com.google.gson.GsonBuilder() + .disableHtmlEscaping() + .serializeNulls() + .create() + .toJson(sorted) + } + + private fun sortJsonElement(element: com.google.gson.JsonElement): com.google.gson.JsonElement = when { + element.isJsonObject -> { + val sortedObj = com.google.gson.JsonObject() + element.asJsonObject.entrySet() + .filter { it.key != "filedrop" } + .sortedBy { it.key } + .forEach { (key, value) -> sortedObj.add(key, sortJsonElement(value)) } + sortedObj + } + element.isJsonArray -> { + val sortedArr = com.google.gson.JsonArray() + element.asJsonArray.forEach { sortedArr.add(sortJsonElement(it)) } + sortedArr + } + else -> element + } + + fun getSignedData(base64encodedSignature: String, messageData: ByteArray): CMSSignedData { val signature = EncryptionUtils.decodeStringToBase64Bytes(base64encodedSignature) val asn1Signature = ASN1Sequence.fromByteArray(signature) val contentInfo = ContentInfo.getInstance(asn1Signature) - - val encodedMessage = EncryptionUtils.encodeStringToBase64String(message) - val messageData = encodedMessage.toByteArray() - val cmsProcessableByteArray = CMSProcessableByteArray(messageData) - - return CMSSignedData(cmsProcessableByteArray, contentInfo) + return CMSSignedData(CMSProcessableByteArray(messageData), contentInfo) } @Suppress("TooGenericExceptionCaught") fun verifySignedData(data: CMSSignedData, certs: List): Boolean { val signer = data.signerInfos.signers.first() as SignerInformation - val verifierBuilder = JcaSimpleSignerInfoVerifierBuilder() + val signedAttrs = signer.signedAttributes + if (signedAttrs != null) { + val messageDigestAttr = signedAttrs.get(org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.pkcs_9_at_messageDigest) + if (messageDigestAttr != null) { + val digestValue = (messageDigestAttr.attrValues.getObjectAt(0) as org.bouncycastle.asn1.DEROctetString).octets + Log_OC.d(TAG, "Expected message digest (from signature): ${EncryptionUtils.encodeBytesToBase64String(digestValue)}") + } + } + + val contentDigest = java.security.MessageDigest.getInstance("SHA-256") + .digest((data.signedContent as CMSProcessableByteArray).let { + val out = ByteArrayOutputStream() + it.write(out) + out.toByteArray() + }) + Log_OC.d(TAG, "Actual content digest: ${EncryptionUtils.encodeBytesToBase64String(contentDigest)}") + + val verifierBuilder = JcaSimpleSignerInfoVerifierBuilder() return certs.any { cert -> runCatching { signer.verify(verifierBuilder.build(cert.publicKey)) @@ -1089,11 +1072,6 @@ class EncryptionUtilsV2 { return extractSignedString(signedMessage) } - fun getMessageSignature(cert: X509Certificate, key: PrivateKey, string: String): String { - val signedMessage = signMessage(cert, key, string) - return extractSignedString(signedMessage) - } - companion object { private val TAG = EncryptionUtils::class.java.simpleName }