From fcb38100d91cdd30c2506d4c7ffcac3795147223 Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Mon, 9 Feb 2026 22:26:42 -0500 Subject: [PATCH 01/19] Use common PropertyManager for sdk and keyknox tests --- build.gradle | 7 +- .../sdk/client/VirgilCardClient.java | 40 +- .../sdk/utils/ConvertionUtils.java | 29 +- .../sdk/cards/CardsManagerTest.java | 20 +- .../sdk/cards/SignerAndVerifierTest.java | 1158 ++++++++--------- .../sdk/client/VirgilCardClientTest.java | 17 +- .../sdk/common/PropertyManager.java | 63 +- .../sdk/jwt/JwtCrossCompatibilityTest.java | 8 +- .../keyknox/KeyknoxManagerTest.kt | 13 +- .../com/virgilsecurity/keyknox/TestConfig.kt | 75 -- .../keyknox/client/KeyknoxClientTest.kt | 9 +- .../keyknox/cloud/CloudKeyStorageTest.kt | 15 +- .../keyknox/storage/SyncKeyStorageTests.kt | 59 +- settings.gradle | 26 +- 14 files changed, 798 insertions(+), 741 deletions(-) delete mode 100644 sdk/src/test/kotlin/com/virgilsecurity/keyknox/TestConfig.kt diff --git a/build.gradle b/build.gradle index a3150fd..7ab009d 100644 --- a/build.gradle +++ b/build.gradle @@ -31,7 +31,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -buildscript { +buildscript { ext.versions = [ @@ -63,7 +63,7 @@ buildscript { jsonsimple : '1.5.1', mockito : '3.0.0', mockitoJunit : '3.0.0', - mannodermaus : '1.5.0.0', + mannodermaus : '1.7.1.1', mannodermausJunit5: '1.1.0', // Android Tests @@ -180,6 +180,9 @@ subprojects { test { useJUnitPlatform() + if (System.getProperty('environment') != null) { + systemProperty 'environment', System.getProperty('environment') + } testLogging { events "passed", "skipped", "failed" } diff --git a/sdk/src/main/java/com/virgilsecurity/sdk/client/VirgilCardClient.java b/sdk/src/main/java/com/virgilsecurity/sdk/client/VirgilCardClient.java index 3028168..914af70 100644 --- a/sdk/src/main/java/com/virgilsecurity/sdk/client/VirgilCardClient.java +++ b/sdk/src/main/java/com/virgilsecurity/sdk/client/VirgilCardClient.java @@ -61,8 +61,8 @@ public class VirgilCardClient implements CardClient { private static final Logger LOGGER = Logger.getLogger(VirgilCardClient.class.getName()); - private static final String BASE_URL = "https://api.virgilsecurity.com/card"; - private static final String SERVICE_VERSION = "/v5/"; + private static final String BASE_SERVICE_URL = "https://api.virgilsecurity.com"; + private static final String CARDS_V5_PATH = "/card/v5/"; private URL serviceUrl; private HttpClient httpClient; @@ -71,7 +71,7 @@ public class VirgilCardClient implements CardClient { * Create a new instance of {@code CardClient} with default HttpClient. */ public VirgilCardClient() { - this(BASE_URL + SERVICE_VERSION); + this(BASE_SERVICE_URL); } /** @@ -80,7 +80,7 @@ public VirgilCardClient() { * @param httpClient http client that will be used for firing requests */ public VirgilCardClient(HttpClient httpClient) { - this(BASE_URL + SERVICE_VERSION, httpClient); + this(BASE_SERVICE_URL, httpClient); } /** @@ -88,13 +88,15 @@ public VirgilCardClient(HttpClient httpClient) { * * @param serviceUrl the service url to fire requests to */ - public VirgilCardClient(String serviceUrl) { + public VirgilCardClient(String baseServiceUrl) { try { - this.serviceUrl = new URL(serviceUrl); + URL url = new URL(baseServiceUrl); + this.serviceUrl = new URL(url.getProtocol(), url.getHost(), url.getPort(), CARDS_V5_PATH); + httpClient = new HttpClient(); } catch (MalformedURLException e) { - throw new IllegalArgumentException("CardClient -> 'serviceUrl' has wrong format"); + LOGGER.log(Level.SEVERE, "Some issue occurred during service URL creating", e); + throw new IllegalArgumentException("Incorrect service URL", e); } - httpClient = new HttpClient(); } /** @@ -105,9 +107,11 @@ public VirgilCardClient(String serviceUrl) { */ public VirgilCardClient(String serviceUrl, HttpClient httpClient) { try { - this.serviceUrl = new URL(serviceUrl); + URL url = new URL(serviceUrl); + this.serviceUrl = new URL(url.getProtocol(), url.getHost(), url.getPort(), CARDS_V5_PATH); } catch (MalformedURLException e) { - throw new IllegalArgumentException("CardClient -> 'serviceUrl' has wrong format"); + LOGGER.log(Level.SEVERE, "Some issue occurred during service URL creating", e); + throw new IllegalArgumentException("Incorrect service URL", e); } this.httpClient = httpClient; } @@ -117,8 +121,13 @@ public VirgilCardClient(String serviceUrl, HttpClient httpClient) { * * @param serviceUrl the service url to fire requests to */ - public VirgilCardClient(URL serviceUrl) { - this.serviceUrl = serviceUrl; + public VirgilCardClient(URL baseServiceUrl) { + try { + this.serviceUrl = new URL(baseServiceUrl.getProtocol(), baseServiceUrl.getHost(), baseServiceUrl.getPort(), CARDS_V5_PATH); + } catch (MalformedURLException e) { + LOGGER.log(Level.SEVERE, "Some issue occurred during service URL creating", e); + throw new IllegalArgumentException("Incorrect service URL", e); + } httpClient = new HttpClient(); } @@ -129,7 +138,12 @@ public VirgilCardClient(URL serviceUrl) { * @param httpClient http client that will be used for firing requests */ public VirgilCardClient(URL serviceUrl, HttpClient httpClient) { - this.serviceUrl = serviceUrl; + try { + this.serviceUrl = new URL(serviceUrl.getProtocol(), serviceUrl.getHost(), serviceUrl.getPort(), CARDS_V5_PATH); + } catch (MalformedURLException e) { + LOGGER.log(Level.SEVERE, "Some issue occurred during service URL creating", e); + throw new IllegalArgumentException("Incorrect service URL", e); + } this.httpClient = httpClient; } diff --git a/sdk/src/main/java/com/virgilsecurity/sdk/utils/ConvertionUtils.java b/sdk/src/main/java/com/virgilsecurity/sdk/utils/ConvertionUtils.java index 5173cf8..1454194 100644 --- a/sdk/src/main/java/com/virgilsecurity/sdk/utils/ConvertionUtils.java +++ b/sdk/src/main/java/com/virgilsecurity/sdk/utils/ConvertionUtils.java @@ -181,7 +181,8 @@ public static byte[] concatenate(byte[] first, byte[] second) { * * @param serializedObject the string presentation of object serialized to JSON * @param objectType the type of object - * @return the {@code objectType} object deserialized from {@code serializedObject} + * @return the {@code objectType} object deserialized from + * {@code serializedObject} */ public static T deserializeFromJson(String serializedObject, Class objectType) { return getGson().fromJson(serializedObject, objectType); @@ -200,7 +201,8 @@ public static Map deserializeMapFromJson(String serializedObject } /** - * Get {@link Gson} which supports Virgil rules for date and byte array conversions. + * Get {@link Gson} which supports Virgil rules for date and byte array + * conversions. * * @return the Gson instance. */ @@ -243,9 +245,11 @@ public static byte[] hexToBytes(String value) { } /** - * Deserialize object of objectType type from binary presentation of JSON String. + * Deserialize object of objectType type from binary presentation + * of JSON String. * - * @param snapshot the object snapshot which is a binary presentation of JSON string + * @param snapshot the object snapshot which is a binary presentation of JSON + * string * @param objectType the type of object * @return the {@code objectType} object deserialized from {@code snapshot} */ @@ -274,6 +278,17 @@ public static byte[] toBase64Bytes(String value) { return toBytes(str); } + /** + * Convert byte array to Base64 byte array. + * + * @param value the byte array to be converted. + * @return the byte array. + */ + public static byte[] toBase64Bytes(byte[] value) { + String str = toBase64String(value); + return toBytes(str); + } + /** * Encode byte array as Base64 string. * @@ -340,7 +355,8 @@ public static String toString(byte[] bytes) { } /** - * Decodes the current bytes to a string according to the specified character encoding. + * Decodes the current bytes to a string according to the specified character + * encoding. * * @param inputBytes bytes to decode * @param encoding The character encoding to decode to. @@ -369,7 +385,8 @@ public static String toString(byte[] inputBytes, StringEncoding encoding) { } /** - * Get the contents of an InputStream as a String using UTF-8 character encoding. + * Get the contents of an InputStream as a String using UTF-8 + * character encoding. * * @param is the input stream. * @return the input stream data as string. diff --git a/sdk/src/test/java/com/virgilsecurity/sdk/cards/CardsManagerTest.java b/sdk/src/test/java/com/virgilsecurity/sdk/cards/CardsManagerTest.java index 4ba5c68..a056339 100644 --- a/sdk/src/test/java/com/virgilsecurity/sdk/cards/CardsManagerTest.java +++ b/sdk/src/test/java/com/virgilsecurity/sdk/cards/CardsManagerTest.java @@ -60,7 +60,6 @@ import com.virgilsecurity.sdk.utils.StringUtils; import com.virgilsecurity.sdk.utils.TestUtils; import com.virgilsecurity.sdk.utils.Tuple; -import com.virgilsecurity.testcommon.utils.PropertyUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -90,7 +89,6 @@ public class CardsManagerTest extends PropertyManager { private static final String SIGNER_TYPE_EXTRA = "bestsignerever"; - private static final String CARDS_SERVICE_PUBLIC_KEY = "bestsignerever"; private Mocker mocker; private VirgilCrypto crypto; @@ -105,16 +103,13 @@ public void setUp() { mocker = new Mocker(); crypto = new VirgilCrypto(); cardCrypto = new VirgilCardCrypto(); - String url = getCardsServiceUrl(); + String url = getServiceBaseUrl(); if (StringUtils.isBlank(url)) { cardClient = new VirgilCardClient(); } else { cardClient = new VirgilCardClient(url); } cardVerifier = new VirgilCardVerifier(cardCrypto); - if (!StringUtils.isBlank(PropertyUtils.getSystemProperty(CARDS_SERVICE_PUBLIC_KEY))) { - cardVerifier.setServiceKey(PropertyUtils.getSystemProperty(CARDS_SERVICE_PUBLIC_KEY)); - } dataProvider = new CompatibilityDataProvider(); } @@ -250,7 +245,8 @@ public void stc_19() throws CryptoException, VirgilServiceException, Interrupted // Call publishCard(privateKey2, publicKey2, Card1.identifier) Card publishedCard2 = cardManager.publishCard(cardModel2); - // Card2 is successfully created and matches card that was generated on the client side + // Card2 is successfully created and matches card that was generated on the + // client side assertNotNull(publishedCard2); assertFalse(publishedCard2.isOutdated()); assertCardsEquals(generatedCard2, publishedCard2); @@ -258,7 +254,8 @@ public void stc_19() throws CryptoException, VirgilServiceException, Interrupted // Get Card2 using getCard Card cardFromService2 = cardManager.getCard(generatedCard2.getIdentifier()); - // Card2 is successfully retrieved and matches card that was generated on the client side + // Card2 is successfully retrieved and matches card that was generated on the + // client side // (including // previousCardId and isOutdated=false). assertNotNull(cardFromService2); @@ -268,7 +265,8 @@ public void stc_19() throws CryptoException, VirgilServiceException, Interrupted // Get Card1 using getCard Card outdatedCard = cardManager.getCard(generatedCard1.getIdentifier()); - // Card1 is successfully retrieved and matches card that was generated on the client side. + // Card1 is successfully retrieved and matches card that was generated on the + // client side. // (isOutdated=true) assertNotNull(outdatedCard); assertTrue(outdatedCard.isOutdated()); @@ -402,8 +400,8 @@ public void stc_35_1() throws VirgilServiceException, CryptoException { RawCardContent cardContent = new RawCardContent(testIdentity, dataProvider.getJsonByKey(34, "public_key_base64"), new Date()); - RawSignedModel rawSignedModelTwo = - new RawSignedModel(ConvertionUtils.base64ToBytes(cardContent.exportAsBase64String())); + RawSignedModel rawSignedModelTwo = new RawSignedModel( + ConvertionUtils.base64ToBytes(cardContent.exportAsBase64String())); ModelSigner signer = new ModelSigner(cardCrypto); VirgilPrivateKey privateKey = crypto.importPrivateKey( diff --git a/sdk/src/test/java/com/virgilsecurity/sdk/cards/SignerAndVerifierTest.java b/sdk/src/test/java/com/virgilsecurity/sdk/cards/SignerAndVerifierTest.java index b4000e2..50faa35 100644 --- a/sdk/src/test/java/com/virgilsecurity/sdk/cards/SignerAndVerifierTest.java +++ b/sdk/src/test/java/com/virgilsecurity/sdk/cards/SignerAndVerifierTest.java @@ -83,586 +83,586 @@ public class SignerAndVerifierTest extends PropertyManager { - private static final String TEST_SIGNER_TYPE = "test_custom_type"; - - private static final String TEST_KEY_ONE = "TEST_KEY_ONE"; - private static final String TEST_VALUE_ONE = "TEST_VALUE_ONE"; - private static final String TEST_KEY_TWO = "TEST_KEY_TWO"; - private static final String TEST_VALUE_TWO = "TEST_VALUE_TWO"; - - private VirgilCrypto virgilCrypto; - private VirgilCardCrypto cardCrypto; - private ModelSigner modelSigner; - private Mocker mocker; - private CompatibilityDataProvider dataProvider; - private CtrDrbg random; - - @BeforeEach - public void setUp() { - virgilCrypto = new VirgilCrypto(); - this.random = new CtrDrbg(); - this.random.setupDefaults(); - cardCrypto = new VirgilCardCrypto(); - modelSigner = new ModelSigner(cardCrypto); - mocker = new Mocker(); - dataProvider = new CompatibilityDataProvider(); - } - - @Test - public void stc_10_emptyVerifier_should_verifyCard() throws CryptoException { - // STC-10 - RawSignedModel rawSignedModel = RawSignedModel - .fromString(dataProvider.getTestDataAs(10, STRING)); - Card card = Card.parse(cardCrypto, rawSignedModel); - - VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, false, - new ArrayList()); - - assertTrue(virgilCardVerifier.verifyCard(card), "Card should be verified"); - } - - @SuppressWarnings("unused") - @Test - public void stc_10_verifier_should_verifyCard_ifCardHasAtLeastOneSignatureFromWhiteList() - throws CryptoException { - // STC-10 - RawSignedModel rawSignedModel = RawSignedModel - .fromString(dataProvider.getTestDataAs(10, STRING)); - final Card card = Card.parse(cardCrypto, rawSignedModel); - - VirgilPrivateKey privateKey1 = virgilCrypto - .importPrivateKey( - ConvertionUtils.base64ToBytes(dataProvider.getJsonByKey(10, "private_key1_base64"))) - .getPrivateKey(); - VirgilPublicKey publicKey1 = virgilCrypto.extractPublicKey(privateKey1); - - VirgilKeyPair keyPair2 = this.virgilCrypto.generateKeyPair(); - VirgilPrivateKey privateKey2 = keyPair2.getPrivateKey(); - VirgilPublicKey publicKey2 = keyPair2.getPublicKey(); - - VirgilKeyPair keyPair3 = this.virgilCrypto.generateKeyPair(); - VirgilPrivateKey privateKey3 = keyPair3.getPrivateKey(); - VirgilPublicKey publicKey3 = keyPair3.getPublicKey(); - - List verifierCredentialsList1 = new ArrayList<>(); - verifierCredentialsList1 - .add(new VerifierCredentials("extra", virgilCrypto.exportPublicKey(publicKey1))); - verifierCredentialsList1 - .add(new VerifierCredentials("extra2", virgilCrypto.exportPublicKey(publicKey2))); - Whitelist whitelist1 = new Whitelist(verifierCredentialsList1); - - List verifierCredentialsList2 = new ArrayList<>(); - verifierCredentialsList2 - .add(new VerifierCredentials("extra3", virgilCrypto.exportPublicKey(publicKey3))); - Whitelist whitelist2 = new Whitelist(verifierCredentialsList2); - - List whitelists = new ArrayList<>(); - whitelists.add(whitelist1); - whitelists.add(whitelist2); - VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, false, - whitelists); - - assertTrue(virgilCardVerifier.verifyCard(card), "Card should be verified"); - } - - @Test - public void stc_10_verifier_shouldNot_verifyCard_ifCardDoesntHaveSignatureOfAtLeastOneWhiteList() - throws CryptoException { - // STC-10 - RawSignedModel rawSignedModel = RawSignedModel - .fromString(dataProvider.getTestDataAs(10, STRING)); - final Card card = Card.parse(cardCrypto, rawSignedModel); - - VirgilPrivateKey privateKey1 = virgilCrypto - .importPrivateKey( - ConvertionUtils.base64ToBytes(dataProvider.getJsonByKey(10, "private_key1_base64"))) - .getPrivateKey(); - VirgilPublicKey publicKey1 = virgilCrypto.extractPublicKey(privateKey1); - VirgilPublicKey publicKey2 = this.virgilCrypto.generateKeyPair().getPublicKey(); - - List verifierCredentialsList1 = new ArrayList<>(); - verifierCredentialsList1.add( - new VerifierCredentials("extra1", TestUtils.exportPublicKey(publicKey1.getPublicKey()))); - verifierCredentialsList1.add( - new VerifierCredentials("extra2", TestUtils.exportPublicKey(publicKey2.getPublicKey()))); - Whitelist whitelist1 = new Whitelist(verifierCredentialsList1); - List whitelists = new ArrayList<>(); - whitelists.add(whitelist1); - VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, false, - whitelists); - - assertFalse(virgilCardVerifier.verifyCard(card), "Card should NOT be verified"); - } - - @Test - public void stc_10_verifier_shouldNot_verifyCard_ifMissedRequiredSelfSignature() - throws CryptoException { - // STC-10 - RawSignedModel rawSignedModel = RawSignedModel - .fromString(dataProvider.getTestDataAs(10, STRING)); - for (RawSignature signature : rawSignedModel.getSignatures()) { - if (SignerType.SELF.getRawValue().equals(signature.getSigner())) { - rawSignedModel.getSignatures().remove(signature); - break; - } + private static final String TEST_SIGNER_TYPE = "test_custom_type"; + + private static final String TEST_KEY_ONE = "TEST_KEY_ONE"; + private static final String TEST_VALUE_ONE = "TEST_VALUE_ONE"; + private static final String TEST_KEY_TWO = "TEST_KEY_TWO"; + private static final String TEST_VALUE_TWO = "TEST_VALUE_TWO"; + + private VirgilCrypto virgilCrypto; + private VirgilCardCrypto cardCrypto; + private ModelSigner modelSigner; + private Mocker mocker; + private CompatibilityDataProvider dataProvider; + private CtrDrbg random; + + @BeforeEach + public void setUp() { + virgilCrypto = new VirgilCrypto(); + this.random = new CtrDrbg(); + this.random.setupDefaults(); + cardCrypto = new VirgilCardCrypto(); + modelSigner = new ModelSigner(cardCrypto); + mocker = new Mocker(); + dataProvider = new CompatibilityDataProvider(); } - Card card = Card.parse(cardCrypto, rawSignedModel); - - List whitelists = new ArrayList<>(); - VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, true, false, - whitelists); - - assertFalse(virgilCardVerifier.verifyCard(card), "Card should NOT be verified"); - } - - @Test - public void stc_10_verifier_shouldNot_verifyCard_ifVerifierHasEmptyWhiteList() - throws CryptoException { - // STC-10 - RawSignedModel rawSignedModel = RawSignedModel - .fromString(dataProvider.getTestDataAs(10, STRING)); - Card card = Card.parse(cardCrypto, rawSignedModel); - - List whitelists = new ArrayList<>(); - whitelists.add(new Whitelist(new ArrayList())); - VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, false, - whitelists); - - assertFalse(virgilCardVerifier.verifyCard(card), "Card should NOT be verified"); - } - - @Test - public void stc_10_verifier_shouldNot_verifyCard_ifWrongSelfSignature() throws CryptoException { - // STC-10 - RawSignedModel rawSignedModel = RawSignedModel - .fromString(dataProvider.getTestDataAs(10, STRING)); - for (RawSignature signature : rawSignedModel.getSignatures()) { - if (SignerType.SELF.getRawValue().equals(signature.getSigner())) { - String sign = ConvertionUtils - .toBase64String(this.virgilCrypto.generateSignature(rawSignedModel.getContentSnapshot(), - this.virgilCrypto.generateKeyPair().getPrivateKey())); - signature.setSignature(sign); - break; - } + + @Test + public void stc_10_emptyVerifier_should_verifyCard() throws CryptoException { + // STC-10 + RawSignedModel rawSignedModel = RawSignedModel + .fromString(dataProvider.getTestDataAs(10, STRING)); + Card card = Card.parse(cardCrypto, rawSignedModel); + + VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, false, + new ArrayList()); + + assertTrue(virgilCardVerifier.verifyCard(card), "Card should be verified"); + } + + @SuppressWarnings("unused") + @Test + public void stc_10_verifier_should_verifyCard_ifCardHasAtLeastOneSignatureFromWhiteList() + throws CryptoException { + // STC-10 + RawSignedModel rawSignedModel = RawSignedModel + .fromString(dataProvider.getTestDataAs(10, STRING)); + final Card card = Card.parse(cardCrypto, rawSignedModel); + + VirgilPrivateKey privateKey1 = virgilCrypto + .importPrivateKey( + ConvertionUtils.base64ToBytes(dataProvider.getJsonByKey(10, "private_key1_base64"))) + .getPrivateKey(); + VirgilPublicKey publicKey1 = virgilCrypto.extractPublicKey(privateKey1); + + VirgilKeyPair keyPair2 = this.virgilCrypto.generateKeyPair(); + VirgilPrivateKey privateKey2 = keyPair2.getPrivateKey(); + VirgilPublicKey publicKey2 = keyPair2.getPublicKey(); + + VirgilKeyPair keyPair3 = this.virgilCrypto.generateKeyPair(); + VirgilPrivateKey privateKey3 = keyPair3.getPrivateKey(); + VirgilPublicKey publicKey3 = keyPair3.getPublicKey(); + + List verifierCredentialsList1 = new ArrayList<>(); + verifierCredentialsList1 + .add(new VerifierCredentials("extra", virgilCrypto.exportPublicKey(publicKey1))); + verifierCredentialsList1 + .add(new VerifierCredentials("extra2", virgilCrypto.exportPublicKey(publicKey2))); + Whitelist whitelist1 = new Whitelist(verifierCredentialsList1); + + List verifierCredentialsList2 = new ArrayList<>(); + verifierCredentialsList2 + .add(new VerifierCredentials("extra3", virgilCrypto.exportPublicKey(publicKey3))); + Whitelist whitelist2 = new Whitelist(verifierCredentialsList2); + + List whitelists = new ArrayList<>(); + whitelists.add(whitelist1); + whitelists.add(whitelist2); + VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, false, + whitelists); + + assertTrue(virgilCardVerifier.verifyCard(card), "Card should be verified"); + } + + @Test + public void stc_10_verifier_shouldNot_verifyCard_ifCardDoesntHaveSignatureOfAtLeastOneWhiteList() + throws CryptoException { + // STC-10 + RawSignedModel rawSignedModel = RawSignedModel + .fromString(dataProvider.getTestDataAs(10, STRING)); + final Card card = Card.parse(cardCrypto, rawSignedModel); + + VirgilPrivateKey privateKey1 = virgilCrypto + .importPrivateKey( + ConvertionUtils.base64ToBytes(dataProvider.getJsonByKey(10, "private_key1_base64"))) + .getPrivateKey(); + VirgilPublicKey publicKey1 = virgilCrypto.extractPublicKey(privateKey1); + VirgilPublicKey publicKey2 = this.virgilCrypto.generateKeyPair().getPublicKey(); + + List verifierCredentialsList1 = new ArrayList<>(); + verifierCredentialsList1.add( + new VerifierCredentials("extra1", TestUtils.exportPublicKey(publicKey1.getPublicKey()))); + verifierCredentialsList1.add( + new VerifierCredentials("extra2", TestUtils.exportPublicKey(publicKey2.getPublicKey()))); + Whitelist whitelist1 = new Whitelist(verifierCredentialsList1); + List whitelists = new ArrayList<>(); + whitelists.add(whitelist1); + VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, false, + whitelists); + + assertFalse(virgilCardVerifier.verifyCard(card), "Card should NOT be verified"); + } + + @Test + public void stc_10_verifier_shouldNot_verifyCard_ifMissedRequiredSelfSignature() + throws CryptoException { + // STC-10 + RawSignedModel rawSignedModel = RawSignedModel + .fromString(dataProvider.getTestDataAs(10, STRING)); + for (RawSignature signature : rawSignedModel.getSignatures()) { + if (SignerType.SELF.getRawValue().equals(signature.getSigner())) { + rawSignedModel.getSignatures().remove(signature); + break; + } + } + Card card = Card.parse(cardCrypto, rawSignedModel); + + List whitelists = new ArrayList<>(); + VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, true, false, + whitelists); + + assertFalse(virgilCardVerifier.verifyCard(card), "Card should NOT be verified"); + } + + @Test + public void stc_10_verifier_shouldNot_verifyCard_ifVerifierHasEmptyWhiteList() + throws CryptoException { + // STC-10 + RawSignedModel rawSignedModel = RawSignedModel + .fromString(dataProvider.getTestDataAs(10, STRING)); + Card card = Card.parse(cardCrypto, rawSignedModel); + + List whitelists = new ArrayList<>(); + whitelists.add(new Whitelist(new ArrayList())); + VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, false, + whitelists); + + assertFalse(virgilCardVerifier.verifyCard(card), "Card should NOT be verified"); + } + + @Test + public void stc_10_verifier_shouldNot_verifyCard_ifWrongSelfSignature() throws CryptoException { + // STC-10 + RawSignedModel rawSignedModel = RawSignedModel + .fromString(dataProvider.getTestDataAs(10, STRING)); + for (RawSignature signature : rawSignedModel.getSignatures()) { + if (SignerType.SELF.getRawValue().equals(signature.getSigner())) { + String sign = ConvertionUtils + .toBase64String(this.virgilCrypto.generateSignature(rawSignedModel.getContentSnapshot(), + this.virgilCrypto.generateKeyPair().getPrivateKey())); + signature.setSignature(sign); + break; + } + } + Card card = Card.parse(cardCrypto, rawSignedModel); + + List whitelists = new ArrayList<>(); + VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, true, false, + whitelists); + + assertFalse(virgilCardVerifier.verifyCard(card), "Card should NOT be verified"); + } + + @Test + public void stc_10_verifier_shouldNot_verifyCard_ifWrongVirgilSignature() throws CryptoException { + // STC-10 + RawSignedModel rawSignedModel = RawSignedModel + .fromString(dataProvider.getTestDataAs(10, STRING)); + for (RawSignature signature : rawSignedModel.getSignatures()) { + if (SignerType.VIRGIL.getRawValue().equals(signature.getSigner())) { + String sign = ConvertionUtils + .toBase64String(this.virgilCrypto.generateSignature(rawSignedModel.getContentSnapshot(), + this.virgilCrypto.generateKeyPair().getPrivateKey())); + signature.setSignature(sign); + break; + } + } + Card card = Card.parse(cardCrypto, rawSignedModel); + + List whitelists = new ArrayList<>(); + VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, true, + whitelists); + + assertFalse(virgilCardVerifier.verifyCard(card), "Card should NOT be verified"); + } + + @Test + public void stc_10_verifySelfAndVirgilSignWithEmptyWhiteList() throws CryptoException { + // STC-10 + RawSignedModel rawSignedModel = RawSignedModel + .fromString(dataProvider.getTestDataAs(10, STRING)); + Card card = Card.parse(cardCrypto, rawSignedModel); + + VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, true, true, + new ArrayList()); + + assertTrue(virgilCardVerifier.verifyCard(card), "Card should be verified"); + } + + @Test + public void stc_10_verifySelfSignWithEmptyWhiteList() throws CryptoException { + // STC-10 + RawSignedModel rawSignedModel = RawSignedModel + .fromString(dataProvider.getTestDataAs(10, STRING)); + Card card = Card.parse(cardCrypto, rawSignedModel); + + VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, true, false, + new ArrayList()); + + assertTrue(virgilCardVerifier.verifyCard(card), "Card should be verified"); + } + + @Test + public void stc_10_verifyVirgilSignWithEmptyWhiteList() throws CryptoException { + // STC-10 + RawSignedModel rawSignedModel = RawSignedModel + .fromString(dataProvider.getTestDataAs(10, STRING)); + Card card = Card.parse(cardCrypto, rawSignedModel); + + VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, true, + new ArrayList()); + + assertTrue(virgilCardVerifier.verifyCard(card), "Card should be verified"); + } + + @Test + public void stc_10_whiteList1Key() throws CryptoException { + // STC-10 + RawSignedModel rawSignedModel = RawSignedModel + .fromString(dataProvider.getTestDataAs(10, STRING)); + Card card = Card.parse(cardCrypto, rawSignedModel); + + VirgilPrivateKey privateKey1 = virgilCrypto + .importPrivateKey( + ConvertionUtils.base64ToBytes(dataProvider.getJsonByKey(10, "private_key1_base64"))) + .getPrivateKey(); + VirgilPublicKey publicKey1 = virgilCrypto.extractPublicKey(privateKey1); + + VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, false, + new ArrayList()); + + List verifierCredentialsList = new ArrayList<>(); + verifierCredentialsList + .add(new VerifierCredentials("extra", virgilCrypto.exportPublicKey(publicKey1))); + Whitelist whitelist1 = new Whitelist(verifierCredentialsList); + virgilCardVerifier.addWhiteList(whitelist1); + + assertTrue(virgilCardVerifier.verifyCard(card), "Card should be verified"); + } + + @Test + public void stc_11() throws CryptoException { + // STC-11 + RawSignedModel rawSignedModel = RawSignedModel + .fromString(dataProvider.getTestDataAs(11, STRING)); + Card card = Card.parse(cardCrypto, rawSignedModel); + VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, false, + Collections.emptyList()); + assertTrue(virgilCardVerifier.verifyCard(card)); + + virgilCardVerifier.setVerifySelfSignature(true); + assertFalse(virgilCardVerifier.verifyCard(card)); + } + + @Test + public void stc_12() throws CryptoException { + // STC-12 + RawSignedModel rawSignedModel = RawSignedModel + .fromString(dataProvider.getTestDataAs(12, STRING)); + Card card = Card.parse(cardCrypto, rawSignedModel); + + VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, false); + assertTrue(virgilCardVerifier.verifyCard(card)); + + virgilCardVerifier.setVerifyVirgilSignature(true); + assertFalse(virgilCardVerifier.verifyCard(card)); + } + + @Test + public void stc_14() throws CryptoException { + // STC-14 + RawSignedModel rawSignedModel = RawSignedModel + .fromString(dataProvider.getTestDataAs(14, STRING)); + Card card = Card.parse(cardCrypto, rawSignedModel); + VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, true); + assertFalse(virgilCardVerifier.verifyCard(card)); } - Card card = Card.parse(cardCrypto, rawSignedModel); - - List whitelists = new ArrayList<>(); - VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, true, false, - whitelists); - - assertFalse(virgilCardVerifier.verifyCard(card), "Card should NOT be verified"); - } - - @Test - public void stc_10_verifier_shouldNot_verifyCard_ifWrongVirgilSignature() throws CryptoException { - // STC-10 - RawSignedModel rawSignedModel = RawSignedModel - .fromString(dataProvider.getTestDataAs(10, STRING)); - for (RawSignature signature : rawSignedModel.getSignatures()) { - if (SignerType.VIRGIL.getRawValue().equals(signature.getSigner())) { - String sign = ConvertionUtils - .toBase64String(this.virgilCrypto.generateSignature(rawSignedModel.getContentSnapshot(), - this.virgilCrypto.generateKeyPair().getPrivateKey())); - signature.setSignature(sign); - break; - } + + @Test + public void stc_15() throws CryptoException { + // STC-15 + RawSignedModel rawSignedModel = RawSignedModel + .fromString(dataProvider.getTestDataAs(15, STRING)); + Card card = Card.parse(cardCrypto, rawSignedModel); + VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, true, false); + assertFalse(virgilCardVerifier.verifyCard(card)); + } + + @Test + public void stc_16() throws CryptoException { + // STC-16 + RawSignedModel rawSignedModel = RawSignedModel + .fromString(dataProvider.getTestDataAs(16, STRING)); + final VirgilPublicKey publicKey = (VirgilPublicKey) cardCrypto.importPublicKey( + ConvertionUtils.base64ToBytes(dataProvider.getJsonByKey(16, "public_key1_base64"))); + + VirgilPublicKey publicKeyTwo = mocker.generatePublicKey(); + List verifierCredentialsList = new ArrayList<>(); + verifierCredentialsList.add(new VerifierCredentials(TEST_SIGNER_TYPE, + TestUtils.exportPublicKey(publicKeyTwo.getPublicKey()))); + Whitelist whitelistOne = new Whitelist(verifierCredentialsList); + List whitelists = new ArrayList<>(); + whitelists.add(whitelistOne); + + Card card = Card.parse(cardCrypto, rawSignedModel); + VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, false, + whitelists); + assertFalse(virgilCardVerifier.verifyCard(card)); + + verifierCredentialsList + .add(new VerifierCredentials("extra", virgilCrypto.exportPublicKey(publicKey))); + + assertTrue(virgilCardVerifier.verifyCard(card)); + } + + @Test + public void stc_26() throws CryptoException, VirgilServiceException, InterruptedException { + // STC-26 + AccessTokenProvider accessTokenProvider = Mockito.mock(AccessTokenProvider.class); + final CardVerifier cardVerifier = new VirgilCardVerifier(this.cardCrypto, false, false); + String cardsServiceUrl = getServiceBaseUrl(); + + VirgilCardClient cardClient; + if (StringUtils.isBlank(cardsServiceUrl)) { + cardClient = new VirgilCardClient(); + } else { + cardClient = new VirgilCardClient(cardsServiceUrl); + } + SignCallback signCallback = Mockito.mock(SignCallback.class); + + String identity = Generator.identity(); + RawSignedModel rawSignedModel = mocker.generateCardModel(identity); + AccessToken expiredToken = mocker.generateExpiredAccessToken(identity); + AccessToken token = mocker.generateAccessToken(identity); + when(accessTokenProvider.getToken(Mockito.any(TokenContext.class))).thenReturn(expiredToken, + token); + when(signCallback.onSign(Mockito.any(RawSignedModel.class))).thenReturn(rawSignedModel); + + CardManager cardManager = new CardManager(this.cardCrypto, accessTokenProvider, cardVerifier, + cardClient, signCallback, true); + + // Let expiredToken to expire + Thread.sleep(2000); + + Card card = cardManager.publishCard(rawSignedModel); + assertNotNull(card); + + Card loadedCard = cardManager.getCard(card.getIdentifier()); + assertNotNull(loadedCard); + + List foundCards = cardManager.searchCards(card.getIdentity()); + assertNotNull(foundCards); + assertEquals(1, foundCards.size()); + } + + @Test + public void stc_8_extraSign_should_addValidSignature() throws CryptoException { + // STC-8 + VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); + RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); + modelSigner.selfSign(cardModel, keyPair.getPrivateKey()); + + VirgilKeyPair keyPair2 = virgilCrypto.generateKeyPair(); + modelSigner.sign(cardModel, "test_id", keyPair2.getPrivateKey()); + + assertEquals(2, cardModel.getSignatures().size()); + RawSignature extraSignature = cardModel.getSignatures().get(1); + assertEquals("test_id", extraSignature.getSigner()); + assertNull(extraSignature.getSnapshot()); + assertTrue( + virgilCrypto.verifySignature(ConvertionUtils.base64ToBytes(extraSignature.getSignature()), + cardModel.getContentSnapshot(), keyPair2.getPublicKey())); + } + + @Test + public void stc_8_secondExtraSign_should_throwException() throws CryptoException { + // STC-8 + VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); + RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); + + VirgilKeyPair keyPair2 = virgilCrypto.generateKeyPair(); + modelSigner.sign(cardModel, "test_id", keyPair2.getPrivateKey()); + assertThrows(SignatureNotUniqueException.class, () -> { + modelSigner.sign(cardModel, "test_id", keyPair2.getPrivateKey()); + }); + } + + @Test + public void stc_8_secondSelfSign_should_throwException() throws CryptoException { + // STC-8 + VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); + RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); + assertTrue(cardModel.getSignatures().isEmpty()); + + modelSigner.selfSign(cardModel, keyPair.getPrivateKey()); + assertThrows(SignatureNotUniqueException.class, () -> { + modelSigner.selfSign(cardModel, keyPair.getPrivateKey()); + }); + } + + @Test + public void stc_8_selfSign_should_addValidSignature() throws CryptoException { + // STC-8 + VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); + RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); + assertTrue(cardModel.getSignatures().isEmpty()); + + modelSigner.selfSign(cardModel, keyPair.getPrivateKey()); + assertEquals(1, cardModel.getSignatures().size()); + + RawSignature selfSignature = cardModel.getSignatures().get(0); + assertEquals(SignerType.SELF.getRawValue(), selfSignature.getSigner()); + assertNull(selfSignature.getSnapshot()); + assertTrue( + virgilCrypto.verifySignature(ConvertionUtils.base64ToBytes(selfSignature.getSignature()), + cardModel.getContentSnapshot(), keyPair.getPublicKey())); } - Card card = Card.parse(cardCrypto, rawSignedModel); - - List whitelists = new ArrayList<>(); - VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, true, - whitelists); - - assertFalse(virgilCardVerifier.verifyCard(card), "Card should NOT be verified"); - } - - @Test - public void stc_10_verifySelfAndVirgilSignWithEmptyWhiteList() throws CryptoException { - // STC-10 - RawSignedModel rawSignedModel = RawSignedModel - .fromString(dataProvider.getTestDataAs(10, STRING)); - Card card = Card.parse(cardCrypto, rawSignedModel); - - VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, true, true, - new ArrayList()); - - assertTrue(virgilCardVerifier.verifyCard(card), "Card should be verified"); - } - - @Test - public void stc_10_verifySelfSignWithEmptyWhiteList() throws CryptoException { - // STC-10 - RawSignedModel rawSignedModel = RawSignedModel - .fromString(dataProvider.getTestDataAs(10, STRING)); - Card card = Card.parse(cardCrypto, rawSignedModel); - - VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, true, false, - new ArrayList()); - - assertTrue(virgilCardVerifier.verifyCard(card), "Card should be verified"); - } - - @Test - public void stc_10_verifyVirgilSignWithEmptyWhiteList() throws CryptoException { - // STC-10 - RawSignedModel rawSignedModel = RawSignedModel - .fromString(dataProvider.getTestDataAs(10, STRING)); - Card card = Card.parse(cardCrypto, rawSignedModel); - - VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, true, - new ArrayList()); - - assertTrue(virgilCardVerifier.verifyCard(card), "Card should be verified"); - } - - @Test - public void stc_10_whiteList1Key() throws CryptoException { - // STC-10 - RawSignedModel rawSignedModel = RawSignedModel - .fromString(dataProvider.getTestDataAs(10, STRING)); - Card card = Card.parse(cardCrypto, rawSignedModel); - - VirgilPrivateKey privateKey1 = virgilCrypto - .importPrivateKey( - ConvertionUtils.base64ToBytes(dataProvider.getJsonByKey(10, "private_key1_base64"))) - .getPrivateKey(); - VirgilPublicKey publicKey1 = virgilCrypto.extractPublicKey(privateKey1); - - VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, false, - new ArrayList()); - - List verifierCredentialsList = new ArrayList<>(); - verifierCredentialsList - .add(new VerifierCredentials("extra", virgilCrypto.exportPublicKey(publicKey1))); - Whitelist whitelist1 = new Whitelist(verifierCredentialsList); - virgilCardVerifier.addWhiteList(whitelist1); - - assertTrue(virgilCardVerifier.verifyCard(card), "Card should be verified"); - } - - @Test - public void stc_11() throws CryptoException { - // STC-11 - RawSignedModel rawSignedModel = RawSignedModel - .fromString(dataProvider.getTestDataAs(11, STRING)); - Card card = Card.parse(cardCrypto, rawSignedModel); - VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, false, - Collections.emptyList()); - assertTrue(virgilCardVerifier.verifyCard(card)); - - virgilCardVerifier.setVerifySelfSignature(true); - assertFalse(virgilCardVerifier.verifyCard(card)); - } - - @Test - public void stc_12() throws CryptoException { - // STC-12 - RawSignedModel rawSignedModel = RawSignedModel - .fromString(dataProvider.getTestDataAs(12, STRING)); - Card card = Card.parse(cardCrypto, rawSignedModel); - - VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, false); - assertTrue(virgilCardVerifier.verifyCard(card)); - - virgilCardVerifier.setVerifyVirgilSignature(true); - assertFalse(virgilCardVerifier.verifyCard(card)); - } - - @Test - public void stc_14() throws CryptoException { - // STC-14 - RawSignedModel rawSignedModel = RawSignedModel - .fromString(dataProvider.getTestDataAs(14, STRING)); - Card card = Card.parse(cardCrypto, rawSignedModel); - VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, true); - assertFalse(virgilCardVerifier.verifyCard(card)); - } - - @Test - public void stc_15() throws CryptoException { - // STC-15 - RawSignedModel rawSignedModel = RawSignedModel - .fromString(dataProvider.getTestDataAs(15, STRING)); - Card card = Card.parse(cardCrypto, rawSignedModel); - VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, true, false); - assertFalse(virgilCardVerifier.verifyCard(card)); - } - - @Test - public void stc_16() throws CryptoException { - // STC-16 - RawSignedModel rawSignedModel = RawSignedModel - .fromString(dataProvider.getTestDataAs(16, STRING)); - final VirgilPublicKey publicKey = (VirgilPublicKey) cardCrypto.importPublicKey( - ConvertionUtils.base64ToBytes(dataProvider.getJsonByKey(16, "public_key1_base64"))); - - VirgilPublicKey publicKeyTwo = mocker.generatePublicKey(); - List verifierCredentialsList = new ArrayList<>(); - verifierCredentialsList.add(new VerifierCredentials(TEST_SIGNER_TYPE, - TestUtils.exportPublicKey(publicKeyTwo.getPublicKey()))); - Whitelist whitelistOne = new Whitelist(verifierCredentialsList); - List whitelists = new ArrayList<>(); - whitelists.add(whitelistOne); - - Card card = Card.parse(cardCrypto, rawSignedModel); - VirgilCardVerifier virgilCardVerifier = new VirgilCardVerifier(cardCrypto, false, false, - whitelists); - assertFalse(virgilCardVerifier.verifyCard(card)); - - verifierCredentialsList - .add(new VerifierCredentials("extra", virgilCrypto.exportPublicKey(publicKey))); - - assertTrue(virgilCardVerifier.verifyCard(card)); - } - - @Test - public void stc_26() throws CryptoException, VirgilServiceException, InterruptedException { - // STC-26 - AccessTokenProvider accessTokenProvider = Mockito.mock(AccessTokenProvider.class); - final CardVerifier cardVerifier = new VirgilCardVerifier(this.cardCrypto, false, false); - String cardsServiceUrl = getCardsServiceUrl(); - - VirgilCardClient cardClient; - if (StringUtils.isBlank(cardsServiceUrl)) { - cardClient = new VirgilCardClient(); - } else { - cardClient = new VirgilCardClient(cardsServiceUrl); + + @Test + public void stc_8_selfSignWithSignatureSnapshot_should_addValidSignature() + throws CryptoException { + // STC-8 + VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); + RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); + assertTrue(cardModel.getSignatures().isEmpty()); + + byte[] signatureSnapshot = new byte[32]; + new Random().nextBytes(signatureSnapshot); + + modelSigner.selfSign(cardModel, keyPair.getPrivateKey(), signatureSnapshot); + assertEquals(1, cardModel.getSignatures().size()); + + RawSignature selfSignature = cardModel.getSignatures().get(0); + assertEquals(SignerType.SELF.getRawValue(), selfSignature.getSigner()); + assertEquals(ConvertionUtils.toBase64String(signatureSnapshot), selfSignature.getSnapshot()); + + byte[] extendedSnapshot = ConvertionUtils.concatenate(cardModel.getContentSnapshot(), + signatureSnapshot); + + assertTrue( + virgilCrypto.verifySignature(ConvertionUtils.base64ToBytes(selfSignature.getSignature()), + extendedSnapshot, keyPair.getPublicKey())); } - SignCallback signCallback = Mockito.mock(SignCallback.class); - - String identity = Generator.identity(); - RawSignedModel rawSignedModel = mocker.generateCardModel(identity); - AccessToken expiredToken = mocker.generateExpiredAccessToken(identity); - AccessToken token = mocker.generateAccessToken(identity); - when(accessTokenProvider.getToken(Mockito.any(TokenContext.class))).thenReturn(expiredToken, - token); - when(signCallback.onSign(Mockito.any(RawSignedModel.class))).thenReturn(rawSignedModel); - - CardManager cardManager = new CardManager(this.cardCrypto, accessTokenProvider, cardVerifier, - cardClient, signCallback, true); - - // Let expiredToken to expire - Thread.sleep(2000); - - Card card = cardManager.publishCard(rawSignedModel); - assertNotNull(card); - - Card loadedCard = cardManager.getCard(card.getIdentifier()); - assertNotNull(loadedCard); - - List foundCards = cardManager.searchCards(card.getIdentity()); - assertNotNull(foundCards); - assertEquals(1, foundCards.size()); - } - - @Test - public void stc_8_extraSign_should_addValidSignature() throws CryptoException { - // STC-8 - VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); - RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); - modelSigner.selfSign(cardModel, keyPair.getPrivateKey()); - - VirgilKeyPair keyPair2 = virgilCrypto.generateKeyPair(); - modelSigner.sign(cardModel, "test_id", keyPair2.getPrivateKey()); - - assertEquals(2, cardModel.getSignatures().size()); - RawSignature extraSignature = cardModel.getSignatures().get(1); - assertEquals("test_id", extraSignature.getSigner()); - assertNull(extraSignature.getSnapshot()); - assertTrue( - virgilCrypto.verifySignature(ConvertionUtils.base64ToBytes(extraSignature.getSignature()), - cardModel.getContentSnapshot(), keyPair2.getPublicKey())); - } - - @Test - public void stc_8_secondExtraSign_should_throwException() throws CryptoException { - // STC-8 - VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); - RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); - - VirgilKeyPair keyPair2 = virgilCrypto.generateKeyPair(); - modelSigner.sign(cardModel, "test_id", keyPair2.getPrivateKey()); - assertThrows(SignatureNotUniqueException.class, () -> { - modelSigner.sign(cardModel, "test_id", keyPair2.getPrivateKey()); - }); - } - - @Test - public void stc_8_secondSelfSign_should_throwException() throws CryptoException { - // STC-8 - VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); - RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); - assertTrue(cardModel.getSignatures().isEmpty()); - - modelSigner.selfSign(cardModel, keyPair.getPrivateKey()); - assertThrows(SignatureNotUniqueException.class, () -> { - modelSigner.selfSign(cardModel, keyPair.getPrivateKey()); - }); - } - - @Test - public void stc_8_selfSign_should_addValidSignature() throws CryptoException { - // STC-8 - VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); - RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); - assertTrue(cardModel.getSignatures().isEmpty()); - - modelSigner.selfSign(cardModel, keyPair.getPrivateKey()); - assertEquals(1, cardModel.getSignatures().size()); - - RawSignature selfSignature = cardModel.getSignatures().get(0); - assertEquals(SignerType.SELF.getRawValue(), selfSignature.getSigner()); - assertNull(selfSignature.getSnapshot()); - assertTrue( - virgilCrypto.verifySignature(ConvertionUtils.base64ToBytes(selfSignature.getSignature()), - cardModel.getContentSnapshot(), keyPair.getPublicKey())); - } - - @Test - public void stc_8_selfSignWithSignatureSnapshot_should_addValidSignature() - throws CryptoException { - // STC-8 - VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); - RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); - assertTrue(cardModel.getSignatures().isEmpty()); - - byte[] signatureSnapshot = new byte[32]; - new Random().nextBytes(signatureSnapshot); - - modelSigner.selfSign(cardModel, keyPair.getPrivateKey(), signatureSnapshot); - assertEquals(1, cardModel.getSignatures().size()); - - RawSignature selfSignature = cardModel.getSignatures().get(0); - assertEquals(SignerType.SELF.getRawValue(), selfSignature.getSigner()); - assertEquals(ConvertionUtils.toBase64String(signatureSnapshot), selfSignature.getSnapshot()); - - byte[] extendedSnapshot = ConvertionUtils.concatenate(cardModel.getContentSnapshot(), - signatureSnapshot); - - assertTrue( - virgilCrypto.verifySignature(ConvertionUtils.base64ToBytes(selfSignature.getSignature()), - extendedSnapshot, keyPair.getPublicKey())); - } - - @Test - public void stc_9_extraSignWithExtraFields_should_addValidSignature() throws CryptoException { - // STC-9 - VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); - RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); - modelSigner.selfSign(cardModel, keyPair.getPrivateKey()); - - Map additionalData = new HashMap<>(); - additionalData.put(TEST_KEY_ONE, TEST_VALUE_ONE); - additionalData.put(TEST_KEY_TWO, TEST_VALUE_TWO); - - final byte[] signatureSnapshot = ConvertionUtils.captureSnapshot(additionalData); - - VirgilKeyPair keyPair2 = virgilCrypto.generateKeyPair(); - modelSigner.sign(cardModel, "test_id", keyPair2.getPrivateKey(), additionalData); - - assertEquals(2, cardModel.getSignatures().size()); - RawSignature extraSignature = cardModel.getSignatures().get(1); - assertEquals("test_id", extraSignature.getSigner()); - assertEquals(ConvertionUtils.toBase64String(signatureSnapshot), extraSignature.getSnapshot()); - - byte[] extendedSnapshot = ConvertionUtils.concatenate(cardModel.getContentSnapshot(), - signatureSnapshot); - assertTrue( - virgilCrypto.verifySignature(ConvertionUtils.base64ToBytes(extraSignature.getSignature()), - extendedSnapshot, keyPair2.getPublicKey())); - } - - @Test - public void stc_9_extraSignWithSignatureSnapshot_should_addValidSignature() - throws CryptoException { - // STC-9 - VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); - RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); - modelSigner.selfSign(cardModel, keyPair.getPrivateKey()); - - byte[] signatureSnapshot = new byte[32]; - new Random().nextBytes(signatureSnapshot); - - VirgilKeyPair keyPair2 = virgilCrypto.generateKeyPair(); - modelSigner.sign(cardModel, "test_id", keyPair2.getPrivateKey(), signatureSnapshot); - - assertEquals(2, cardModel.getSignatures().size()); - RawSignature extraSignature = cardModel.getSignatures().get(1); - assertEquals("test_id", extraSignature.getSigner()); - assertEquals(ConvertionUtils.toBase64String(signatureSnapshot), extraSignature.getSnapshot()); - - byte[] extendedSnapshot = ConvertionUtils.concatenate(cardModel.getContentSnapshot(), - signatureSnapshot); - assertTrue( - virgilCrypto.verifySignature(ConvertionUtils.base64ToBytes(extraSignature.getSignature()), - extendedSnapshot, keyPair2.getPublicKey())); - } - - @Test - public void stc_9_secondExtraSignWithSignatureSnapshot_should_throwException() - throws CryptoException { - // STC-9 - VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); - RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); - - byte[] signatureSnapshot = new byte[32]; - new Random().nextBytes(signatureSnapshot); - - VirgilKeyPair keyPair2 = virgilCrypto.generateKeyPair(); - modelSigner.sign(cardModel, "test_id", keyPair2.getPrivateKey(), signatureSnapshot); - assertThrows(SignatureNotUniqueException.class, () -> { - modelSigner.sign(cardModel, "test_id", keyPair2.getPrivateKey(), signatureSnapshot); - }); - } - - @Test - public void stc_9_selfSignWithExtraFields_should_addValidSignature() throws CryptoException { - // STC-9 - VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); - RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); - - Map additionalData = new HashMap<>(); - additionalData.put(TEST_KEY_ONE, TEST_VALUE_ONE); - additionalData.put(TEST_KEY_TWO, TEST_VALUE_TWO); - - final byte[] signatureSnapshot = ConvertionUtils.captureSnapshot(additionalData); - modelSigner.selfSign(cardModel, keyPair.getPrivateKey(), additionalData); - assertEquals(1, cardModel.getSignatures().size()); - - RawSignature selfSignature = cardModel.getSignatures().get(0); - assertEquals(SignerType.SELF.getRawValue(), selfSignature.getSigner()); - assertEquals(ConvertionUtils.toBase64String(signatureSnapshot), selfSignature.getSnapshot()); - - byte[] extendedSnapshot = ConvertionUtils.concatenate(cardModel.getContentSnapshot(), - signatureSnapshot); - assertTrue( - virgilCrypto.verifySignature(ConvertionUtils.base64ToBytes(selfSignature.getSignature()), - extendedSnapshot, keyPair.getPublicKey())); - } - - @Test - public void stc_9_selfSignWithSignatureSnapshot_should_addValidSignature() - throws CryptoException { - // STC-9 - VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); - RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); - - byte[] signatureSnapshot = new byte[32]; - new Random().nextBytes(signatureSnapshot); - - modelSigner.selfSign(cardModel, keyPair.getPrivateKey(), signatureSnapshot); - assertEquals(1, cardModel.getSignatures().size()); - - RawSignature selfSignature = cardModel.getSignatures().get(0); - assertEquals(SignerType.SELF.getRawValue(), selfSignature.getSigner()); - assertEquals(ConvertionUtils.toBase64String(signatureSnapshot), selfSignature.getSnapshot()); - - byte[] extendedSnapshot = ConvertionUtils.concatenate(cardModel.getContentSnapshot(), - signatureSnapshot); - assertTrue( - virgilCrypto.verifySignature(ConvertionUtils.base64ToBytes(selfSignature.getSignature()), - extendedSnapshot, keyPair.getPublicKey())); - } - -} \ No newline at end of file + + @Test + public void stc_9_extraSignWithExtraFields_should_addValidSignature() throws CryptoException { + // STC-9 + VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); + RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); + modelSigner.selfSign(cardModel, keyPair.getPrivateKey()); + + Map additionalData = new HashMap<>(); + additionalData.put(TEST_KEY_ONE, TEST_VALUE_ONE); + additionalData.put(TEST_KEY_TWO, TEST_VALUE_TWO); + + final byte[] signatureSnapshot = ConvertionUtils.captureSnapshot(additionalData); + + VirgilKeyPair keyPair2 = virgilCrypto.generateKeyPair(); + modelSigner.sign(cardModel, "test_id", keyPair2.getPrivateKey(), additionalData); + + assertEquals(2, cardModel.getSignatures().size()); + RawSignature extraSignature = cardModel.getSignatures().get(1); + assertEquals("test_id", extraSignature.getSigner()); + assertEquals(ConvertionUtils.toBase64String(signatureSnapshot), extraSignature.getSnapshot()); + + byte[] extendedSnapshot = ConvertionUtils.concatenate(cardModel.getContentSnapshot(), + signatureSnapshot); + assertTrue( + virgilCrypto.verifySignature(ConvertionUtils.base64ToBytes(extraSignature.getSignature()), + extendedSnapshot, keyPair2.getPublicKey())); + } + + @Test + public void stc_9_extraSignWithSignatureSnapshot_should_addValidSignature() + throws CryptoException { + // STC-9 + VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); + RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); + modelSigner.selfSign(cardModel, keyPair.getPrivateKey()); + + byte[] signatureSnapshot = new byte[32]; + new Random().nextBytes(signatureSnapshot); + + VirgilKeyPair keyPair2 = virgilCrypto.generateKeyPair(); + modelSigner.sign(cardModel, "test_id", keyPair2.getPrivateKey(), signatureSnapshot); + + assertEquals(2, cardModel.getSignatures().size()); + RawSignature extraSignature = cardModel.getSignatures().get(1); + assertEquals("test_id", extraSignature.getSigner()); + assertEquals(ConvertionUtils.toBase64String(signatureSnapshot), extraSignature.getSnapshot()); + + byte[] extendedSnapshot = ConvertionUtils.concatenate(cardModel.getContentSnapshot(), + signatureSnapshot); + assertTrue( + virgilCrypto.verifySignature(ConvertionUtils.base64ToBytes(extraSignature.getSignature()), + extendedSnapshot, keyPair2.getPublicKey())); + } + + @Test + public void stc_9_secondExtraSignWithSignatureSnapshot_should_throwException() + throws CryptoException { + // STC-9 + VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); + RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); + + byte[] signatureSnapshot = new byte[32]; + new Random().nextBytes(signatureSnapshot); + + VirgilKeyPair keyPair2 = virgilCrypto.generateKeyPair(); + modelSigner.sign(cardModel, "test_id", keyPair2.getPrivateKey(), signatureSnapshot); + assertThrows(SignatureNotUniqueException.class, () -> { + modelSigner.sign(cardModel, "test_id", keyPair2.getPrivateKey(), signatureSnapshot); + }); + } + + @Test + public void stc_9_selfSignWithExtraFields_should_addValidSignature() throws CryptoException { + // STC-9 + VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); + RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); + + Map additionalData = new HashMap<>(); + additionalData.put(TEST_KEY_ONE, TEST_VALUE_ONE); + additionalData.put(TEST_KEY_TWO, TEST_VALUE_TWO); + + final byte[] signatureSnapshot = ConvertionUtils.captureSnapshot(additionalData); + modelSigner.selfSign(cardModel, keyPair.getPrivateKey(), additionalData); + assertEquals(1, cardModel.getSignatures().size()); + + RawSignature selfSignature = cardModel.getSignatures().get(0); + assertEquals(SignerType.SELF.getRawValue(), selfSignature.getSigner()); + assertEquals(ConvertionUtils.toBase64String(signatureSnapshot), selfSignature.getSnapshot()); + + byte[] extendedSnapshot = ConvertionUtils.concatenate(cardModel.getContentSnapshot(), + signatureSnapshot); + assertTrue( + virgilCrypto.verifySignature(ConvertionUtils.base64ToBytes(selfSignature.getSignature()), + extendedSnapshot, keyPair.getPublicKey())); + } + + @Test + public void stc_9_selfSignWithSignatureSnapshot_should_addValidSignature() + throws CryptoException { + // STC-9 + VirgilKeyPair keyPair = virgilCrypto.generateKeyPair(); + RawSignedModel cardModel = mocker.generateCardModelUnsigned(keyPair.getPublicKey()); + + byte[] signatureSnapshot = new byte[32]; + new Random().nextBytes(signatureSnapshot); + + modelSigner.selfSign(cardModel, keyPair.getPrivateKey(), signatureSnapshot); + assertEquals(1, cardModel.getSignatures().size()); + + RawSignature selfSignature = cardModel.getSignatures().get(0); + assertEquals(SignerType.SELF.getRawValue(), selfSignature.getSigner()); + assertEquals(ConvertionUtils.toBase64String(signatureSnapshot), selfSignature.getSnapshot()); + + byte[] extendedSnapshot = ConvertionUtils.concatenate(cardModel.getContentSnapshot(), + signatureSnapshot); + assertTrue( + virgilCrypto.verifySignature(ConvertionUtils.base64ToBytes(selfSignature.getSignature()), + extendedSnapshot, keyPair.getPublicKey())); + } + +} diff --git a/sdk/src/test/java/com/virgilsecurity/sdk/client/VirgilCardClientTest.java b/sdk/src/test/java/com/virgilsecurity/sdk/client/VirgilCardClientTest.java index 20a91f4..5235577 100644 --- a/sdk/src/test/java/com/virgilsecurity/sdk/client/VirgilCardClientTest.java +++ b/sdk/src/test/java/com/virgilsecurity/sdk/client/VirgilCardClientTest.java @@ -67,7 +67,7 @@ public class VirgilCardClientTest extends PropertyManager { @BeforeEach public void setUp() { - String url = getCardsServiceUrl(); + String url = getServiceBaseUrl(); if (StringUtils.isBlank(url)) { cardClient = new VirgilCardClient(); } else { @@ -184,9 +184,8 @@ public void revoke_card() throws CryptoException, VirgilServiceException { RawSignedModel cardModelBeforePublish = mocker.generateCardModel(identity); assertNotNull(cardModelBeforePublish); - RawSignedModel cardModelAfterPublish = - cardClient.publishCard(cardModelBeforePublish, - mocker.generateAccessToken(identity).stringRepresentation()); + RawSignedModel cardModelAfterPublish = cardClient.publishCard(cardModelBeforePublish, + mocker.generateAccessToken(identity).stringRepresentation()); assertNotNull(cardModelAfterPublish); TestUtils.assertCardModelsEquals(cardModelBeforePublish, cardModelAfterPublish); @@ -196,15 +195,13 @@ public void revoke_card() throws CryptoException, VirgilServiceException { cardClient.revokeCard(publishedCard.getIdentifier(), mocker.generateAccessToken(identity).stringRepresentation()); - Tuple revokedTuple = - cardClient.getCard(publishedCard.getIdentifier(), - mocker.generateAccessToken(identity).stringRepresentation()); + Tuple revokedTuple = cardClient.getCard(publishedCard.getIdentifier(), + mocker.generateAccessToken(identity).stringRepresentation()); assertTrue(revokedTuple.getRight()); - List searchedModels = - cardClient.searchCards(identity, - mocker.generateAccessToken(identity).stringRepresentation()); + List searchedModels = cardClient.searchCards(identity, + mocker.generateAccessToken(identity).stringRepresentation()); assertEquals(searchedModels.size(), 0); } } diff --git a/sdk/src/test/java/com/virgilsecurity/sdk/common/PropertyManager.java b/sdk/src/test/java/com/virgilsecurity/sdk/common/PropertyManager.java index 61c38b8..d6d7db1 100644 --- a/sdk/src/test/java/com/virgilsecurity/sdk/common/PropertyManager.java +++ b/sdk/src/test/java/com/virgilsecurity/sdk/common/PropertyManager.java @@ -33,22 +33,27 @@ package com.virgilsecurity.sdk.common; +import com.virgilsecurity.sdk.crypto.HashAlgorithm; import com.virgilsecurity.sdk.crypto.VirgilCrypto; import com.virgilsecurity.sdk.crypto.VirgilPrivateKey; import com.virgilsecurity.sdk.crypto.VirgilPublicKey; import com.virgilsecurity.sdk.crypto.exceptions.CryptoException; import com.virgilsecurity.sdk.utils.ConvertionUtils; +import com.virgilsecurity.common.util.HexUtils; import com.virgilsecurity.testcommon.property.EnvPropertyReader; import com.virgilsecurity.testcommon.utils.PropertyUtils; +import org.opentest4j.TestAbortedException; + +import java.io.File; +import java.security.PublicKey; + public class PropertyManager { private static final String ENVIRONMENT_SYS_VAR = "environment"; private static final String APP_ID = "APP_ID"; - private static final String APP_PRIVATE_KEY = "APP_PRIVATE_KEY"; - private static final String APP_PUBLIC_KEY = "APP_PUBLIC_KEY"; - private static final String APP_PUBLIC_KEY_ID = "APP_PUBLIC_KEY_ID"; - private static final String CARDS_SERVICE_ADDRESS = "CARDS_SERVICE_ADDRESS"; + private static final String APP_PRIVATE_KEY = "APP_KEY"; + private static final String BASE_SERVICE_URL = "BASE_SERVICE_URL"; private final EnvPropertyReader propertyReader; private final VirgilCrypto crypto; @@ -57,17 +62,24 @@ public class PropertyManager { * Create new instance of {@link PropertyManager}. */ public PropertyManager() { + File expectedEnvFile = new File(new File(System.getProperty("user.dir")).getParentFile(), "env.json"); + if (!expectedEnvFile.exists()) { + throw new TestAbortedException( + "Missing " + expectedEnvFile.getAbsolutePath() + ". " + + "Cards-service integration tests are skipped."); + } + String environment = PropertyUtils.getSystemProperty(ENVIRONMENT_SYS_VAR); if (environment != null) { this.propertyReader = new EnvPropertyReader.Builder() - .environment(EnvPropertyReader.Environment.fromType(environment)) - .isDefaultSubmodule(true) - .build(); + .environment(EnvPropertyReader.Environment.fromType(environment)) + .isDefaultSubmodule(true) + .build(); } else { this.propertyReader = new EnvPropertyReader.Builder() - .isDefaultSubmodule(true) - .build(); + .isDefaultSubmodule(true) + .build(); } this.crypto = new VirgilCrypto(); @@ -87,20 +99,41 @@ public VirgilPrivateKey getAppPrivateKey() { } public VirgilPublicKey getAppPublicKey() { - String publicKeyString = propertyReader.getProperty(APP_PUBLIC_KEY); - + String privateKeyString = propertyReader.getProperty(APP_PRIVATE_KEY); try { - return crypto.importPublicKey(ConvertionUtils.base64ToBytes(publicKeyString)); + return crypto.importPrivateKey(ConvertionUtils.base64ToBytes(privateKeyString)).getPublicKey(); } catch (CryptoException e) { throw new IllegalStateException(e); } } public String getAppPublicKeyId() { - return propertyReader.getProperty(APP_PUBLIC_KEY_ID); + String privateKeyString = propertyReader.getProperty(APP_PRIVATE_KEY); + try { + + VirgilPublicKey publicKey = crypto.importPrivateKey(ConvertionUtils.base64ToBytes(privateKeyString)) + .getPublicKey(); + byte[] publicKeyBase64Bytes = ConvertionUtils.toBase64Bytes(crypto.exportPublicKey(publicKey)); + byte[] publicKeyHash = crypto.computeHash(publicKeyBase64Bytes, HashAlgorithm.SHA512); + String keyId = ConvertionUtils.toHex(publicKeyHash).substring(0, 32).toLowerCase(); + return keyId; + + } catch (CryptoException e) { + throw new IllegalStateException(e); + } } - public String getCardsServiceUrl() { - return propertyReader.getProperty(CARDS_SERVICE_ADDRESS); + public String getServiceBaseUrl() { + return getOptionalProperty(BASE_SERVICE_URL); } + + private String getOptionalProperty(String name) { + try { + return propertyReader.getProperty(name); + } catch (RuntimeException e) { + // EnvPropertyReader throws when property is absent. + return null; + } + } + } diff --git a/sdk/src/test/java/com/virgilsecurity/sdk/jwt/JwtCrossCompatibilityTest.java b/sdk/src/test/java/com/virgilsecurity/sdk/jwt/JwtCrossCompatibilityTest.java index 2050c4e..95c86c3 100644 --- a/sdk/src/test/java/com/virgilsecurity/sdk/jwt/JwtCrossCompatibilityTest.java +++ b/sdk/src/test/java/com/virgilsecurity/sdk/jwt/JwtCrossCompatibilityTest.java @@ -53,6 +53,7 @@ import org.mockito.stubbing.Answer; import java.io.InputStreamReader; +import java.util.Date; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -202,8 +203,11 @@ public void stc_29() { assertEquals(sampleJson.get("STC-29.jwt_signature_base64").getAsString(), ConvertionUtils.toBase64String(jwt.getSignatureData())); - // Call isExpired() - assertFalse(jwt.isExpired()); + // Call isExpired(Date) + // (avoid relying on wall-clock time because STC-29.jwt eventually becomes expired) + long expiresAt = jwt.getBodyContent().getExpiresAt(); + assertFalse(jwt.isExpired(new Date((expiresAt - 1) * 1000)), "Token should not be expired just before exp"); + assertTrue(jwt.isExpired(new Date(expiresAt * 1000)), "Token should be expired at exp"); // Call stringRepresentation() assertEquals(token, jwt.stringRepresentation()); diff --git a/sdk/src/test/kotlin/com/virgilsecurity/keyknox/KeyknoxManagerTest.kt b/sdk/src/test/kotlin/com/virgilsecurity/keyknox/KeyknoxManagerTest.kt index eb06893..53cc0d0 100644 --- a/sdk/src/test/kotlin/com/virgilsecurity/keyknox/KeyknoxManagerTest.kt +++ b/sdk/src/test/kotlin/com/virgilsecurity/keyknox/KeyknoxManagerTest.kt @@ -42,12 +42,14 @@ import com.virgilsecurity.keyknox.exception.SignerNotFoundException import com.virgilsecurity.keyknox.utils.base64Encode import com.virgilsecurity.keyknox.utils.random import com.virgilsecurity.sdk.common.TimeSpan +import com.virgilsecurity.sdk.common.PropertyManager import com.virgilsecurity.sdk.crypto.* import com.virgilsecurity.sdk.jwt.JwtGenerator import com.virgilsecurity.sdk.jwt.accessProviders.CachingJwtProvider import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import java.net.URL import java.util.* import java.util.concurrent.TimeUnit @@ -66,6 +68,7 @@ class KeyknoxManagerTest { private lateinit var keyknoxClient: KeyknoxClient private lateinit var keyknoxManager: KeyknoxManager private lateinit var provider: CachingJwtProvider + private lateinit var propertyManager: PropertyManager @BeforeEach fun setup() { @@ -75,13 +78,19 @@ class KeyknoxManagerTest { this.privateKey = keyPair.privateKey this.publicKey = keyPair.publicKey this.publicKeys = arrayListOf(this.publicKey) + this.propertyManager = PropertyManager() - val jwtGenerator = JwtGenerator(TestConfig.appId, TestConfig.appPrivateKey, TestConfig.appPublicKeyId, TimeSpan.fromTime(600, TimeUnit.SECONDS), + val jwtGenerator = JwtGenerator(this.propertyManager.getAppId(), this.propertyManager.getAppPrivateKey(), this.propertyManager.getAppPublicKeyId(), TimeSpan.fromTime(600, TimeUnit.SECONDS), VirgilAccessTokenSigner(this.virgilCrypto)) this.provider = CachingJwtProvider(CachingJwtProvider.RenewJwtCallback { jwtGenerator.generateToken(identity) }) - this.keyknoxClient = KeyknoxClient(this.provider) + val serviceBaseUrl = this.propertyManager.getServiceBaseUrl() + this.keyknoxClient = if (serviceBaseUrl.isNullOrBlank()) { + KeyknoxClient(this.provider) + } else { + KeyknoxClient(this.provider, URL(serviceBaseUrl)) + } this.keyknoxManager = KeyknoxManager(this.keyknoxClient, this.keyknoxCrypto) } diff --git a/sdk/src/test/kotlin/com/virgilsecurity/keyknox/TestConfig.kt b/sdk/src/test/kotlin/com/virgilsecurity/keyknox/TestConfig.kt deleted file mode 100644 index 45bea91..0000000 --- a/sdk/src/test/kotlin/com/virgilsecurity/keyknox/TestConfig.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2015-2020, Virgil Security, Inc. - * - * Lead Maintainer: Virgil Security Inc. - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * (1) Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * (2) Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * (3) Neither the name of virgil nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.virgilsecurity.keyknox - -import com.virgilsecurity.keyknox.utils.base64Decode -import com.virgilsecurity.sdk.crypto.VirgilCrypto -import com.virgilsecurity.sdk.crypto.VirgilPrivateKey -import com.virgilsecurity.testcommon.property.EnvPropertyReader -import com.virgilsecurity.testcommon.utils.PropertyUtils - -class TestConfig { - companion object { - private const val APP_ID = "APP_ID" - private const val APP_PRIVATE_KEY = "APP_PRIVATE_KEY" - private const val APP_PUBLIC_KEY_ID = "APP_PUBLIC_KEY_ID" - private const val KEYKNOX_SERVICE_ADDRESS = "KEYKNOX_SERVICE_ADDRESS" - - private const val ENVIRONMENT_PARAMETER = "environment" - - private val propertyReader: EnvPropertyReader by lazy { - val environment = PropertyUtils.getSystemProperty(ENVIRONMENT_PARAMETER) - - if (environment != null) - EnvPropertyReader.Builder() - .environment(EnvPropertyReader.Environment.fromType(environment)) - .isDefaultSubmodule(true) - .build() - else - EnvPropertyReader.Builder() - .isDefaultSubmodule(true) - .build() - } - - val virgilCrypto = VirgilCrypto(false) - val appId: String by lazy { propertyReader.getProperty(APP_ID) } - val appPrivateKey: VirgilPrivateKey by lazy { - with(propertyReader.getProperty(APP_PRIVATE_KEY)) { - virgilCrypto.importPrivateKey(base64Decode(this)).privateKey - } - } - val appPublicKeyId: String by lazy { propertyReader.getProperty(APP_PUBLIC_KEY_ID) } - val keyknoxServiceAddress: String by lazy { propertyReader.getProperty(KEYKNOX_SERVICE_ADDRESS) } - } -} diff --git a/sdk/src/test/kotlin/com/virgilsecurity/keyknox/client/KeyknoxClientTest.kt b/sdk/src/test/kotlin/com/virgilsecurity/keyknox/client/KeyknoxClientTest.kt index 1ccb0f1..9bf4f56 100644 --- a/sdk/src/test/kotlin/com/virgilsecurity/keyknox/client/KeyknoxClientTest.kt +++ b/sdk/src/test/kotlin/com/virgilsecurity/keyknox/client/KeyknoxClientTest.kt @@ -37,9 +37,9 @@ import com.virgilsecurity.crypto.foundation.Aes256Gcm import com.virgilsecurity.crypto.foundation.RecipientCipher import com.virgilsecurity.crypto.foundation.Sha512 import com.virgilsecurity.crypto.foundation.Signer -import com.virgilsecurity.keyknox.TestConfig import com.virgilsecurity.keyknox.utils.base64Encode import com.virgilsecurity.sdk.common.TimeSpan +import com.virgilsecurity.sdk.common.PropertyManager import com.virgilsecurity.sdk.crypto.* import com.virgilsecurity.sdk.jwt.JwtGenerator import com.virgilsecurity.sdk.jwt.accessProviders.CachingJwtProvider @@ -60,6 +60,7 @@ class KeyknoxClientTest { private lateinit var keyknoxClient: KeyknoxClientProtocol private lateinit var publicKey: VirgilPublicKey private lateinit var privateKey: VirgilPrivateKey + private lateinit var propertyManager: PropertyManager @BeforeEach fun setup() { @@ -70,12 +71,12 @@ class KeyknoxClientTest { val keyPair = this.virgilCrypto.generateKeyPair(KeyPairType.ED25519) this.privateKey = keyPair.privateKey this.publicKey = keyPair.publicKey + this.propertyManager = PropertyManager() - - val jwtGenerator = JwtGenerator(TestConfig.appId, TestConfig.appPrivateKey, TestConfig.appPublicKeyId, TimeSpan.fromTime(600, TimeUnit.SECONDS), + val jwtGenerator = JwtGenerator(this.propertyManager.getAppId(), this.propertyManager.getAppPrivateKey(), this.propertyManager.getAppPublicKeyId(), TimeSpan.fromTime(600, TimeUnit.SECONDS), VirgilAccessTokenSigner(this.virgilCrypto)) val tokenProvider = CachingJwtProvider(CachingJwtProvider.RenewJwtCallback { jwtGenerator.generateToken(identity) }) - this.keyknoxClient = KeyknoxClient(tokenProvider, URL(TestConfig.keyknoxServiceAddress)) + this.keyknoxClient = KeyknoxClient(tokenProvider, URL(this.propertyManager.getServiceBaseUrl())) } @Test diff --git a/sdk/src/test/kotlin/com/virgilsecurity/keyknox/cloud/CloudKeyStorageTest.kt b/sdk/src/test/kotlin/com/virgilsecurity/keyknox/cloud/CloudKeyStorageTest.kt index 5a0690c..d36cd8a 100644 --- a/sdk/src/test/kotlin/com/virgilsecurity/keyknox/cloud/CloudKeyStorageTest.kt +++ b/sdk/src/test/kotlin/com/virgilsecurity/keyknox/cloud/CloudKeyStorageTest.kt @@ -34,7 +34,6 @@ package com.virgilsecurity.keyknox.cloud import com.virgilsecurity.keyknox.KeyknoxManager -import com.virgilsecurity.keyknox.TestConfig import com.virgilsecurity.keyknox.client.KeyknoxClient import com.virgilsecurity.keyknox.crypto.KeyknoxCrypto import com.virgilsecurity.keyknox.crypto.KeyknoxCryptoProtocol @@ -42,6 +41,7 @@ import com.virgilsecurity.keyknox.exception.CloudKeyStorageException import com.virgilsecurity.keyknox.exception.CloudStorageOutOfSyncException import com.virgilsecurity.keyknox.exception.EntryNotFoundException import com.virgilsecurity.sdk.common.TimeSpan +import com.virgilsecurity.sdk.common.PropertyManager import com.virgilsecurity.sdk.crypto.* import com.virgilsecurity.sdk.jwt.JwtGenerator import com.virgilsecurity.sdk.jwt.accessProviders.CachingJwtProvider @@ -50,6 +50,7 @@ import com.virgilsecurity.sdk.storage.KeyEntry import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import java.net.URL import java.util.* import java.util.concurrent.TimeUnit @@ -65,22 +66,30 @@ class CloudKeyStorageTest { private lateinit var keyknoxManager: KeyknoxManager private lateinit var provider: CachingJwtProvider private lateinit var keyStorage: CloudKeyStorageProtocol + private lateinit var propertyManager: PropertyManager @BeforeEach fun setup() { this.virgilCrypto = VirgilCrypto(false) this.keyknoxCrypto = KeyknoxCrypto() + this.propertyManager = PropertyManager() val keyPair = this.virgilCrypto.generateKeyPair(KeyPairType.ED25519) this.privateKey = keyPair.privateKey this.publicKey = keyPair.publicKey this.publicKeys = arrayListOf(this.publicKey) - val jwtGenerator = JwtGenerator(TestConfig.appId, TestConfig.appPrivateKey, TestConfig.appPublicKeyId, TimeSpan.fromTime(600, TimeUnit.SECONDS), + + val jwtGenerator = JwtGenerator(this.propertyManager.getAppId(), this.propertyManager.getAppPrivateKey(), this.propertyManager.getAppPublicKeyId(), TimeSpan.fromTime(600, TimeUnit.SECONDS), VirgilAccessTokenSigner(this.virgilCrypto)) this.provider = CachingJwtProvider(CachingJwtProvider.RenewJwtCallback { jwtGenerator.generateToken(identity) }) - this.keyknoxClient = KeyknoxClient(this.provider) + val serviceBaseUrl = this.propertyManager.getServiceBaseUrl() + this.keyknoxClient = if (serviceBaseUrl.isNullOrBlank()) { + KeyknoxClient(this.provider) + } else { + KeyknoxClient(this.provider, URL(serviceBaseUrl)) + } this.keyknoxManager = KeyknoxManager(this.keyknoxClient, this.keyknoxCrypto) this.keyStorage = CloudKeyStorage(this.keyknoxManager, this.publicKeys, this.privateKey) diff --git a/sdk/src/test/kotlin/com/virgilsecurity/keyknox/storage/SyncKeyStorageTests.kt b/sdk/src/test/kotlin/com/virgilsecurity/keyknox/storage/SyncKeyStorageTests.kt index 7470fdb..a2bfabe 100644 --- a/sdk/src/test/kotlin/com/virgilsecurity/keyknox/storage/SyncKeyStorageTests.kt +++ b/sdk/src/test/kotlin/com/virgilsecurity/keyknox/storage/SyncKeyStorageTests.kt @@ -34,7 +34,6 @@ package com.virgilsecurity.keyknox.storage import com.virgilsecurity.keyknox.KeyknoxManager -import com.virgilsecurity.keyknox.TestConfig import com.virgilsecurity.keyknox.client.KeyknoxClient import com.virgilsecurity.keyknox.cloud.CloudKeyStorage import com.virgilsecurity.keyknox.cloud.CloudKeyStorageProtocol @@ -44,6 +43,7 @@ import com.virgilsecurity.keyknox.exception.DecryptionFailedException import com.virgilsecurity.keyknox.exception.SignerNotFoundException import com.virgilsecurity.keyknox.model.CloudEntry import com.virgilsecurity.sdk.common.TimeSpan +import com.virgilsecurity.sdk.common.PropertyManager import com.virgilsecurity.sdk.crypto.* import com.virgilsecurity.sdk.jwt.JwtGenerator import com.virgilsecurity.sdk.jwt.accessProviders.CachingJwtProvider @@ -54,6 +54,7 @@ import com.virgilsecurity.sdk.utils.ConvertionUtils import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import java.net.URL import java.util.* import java.util.concurrent.TimeUnit @@ -68,6 +69,8 @@ class SyncKeyStorageTests { private lateinit var keyStorage: KeyStorage private lateinit var keychainStorageWrapper: KeyStorageWrapper private lateinit var syncKeyStorage: SyncKeyStorage + private lateinit var propertyManager: PropertyManager + @BeforeEach fun setup() { @@ -76,18 +79,24 @@ class SyncKeyStorageTests { val keyPair = this.virgilCrypto.generateKeyPair(KeyPairType.ED25519) this.privateKey = keyPair.privateKey this.publicKey = keyPair.publicKey + this.propertyManager = PropertyManager() val jwtGenerator = JwtGenerator( - TestConfig.appId, - TestConfig.appPrivateKey, - TestConfig.appPublicKeyId, + this.propertyManager.getAppId(), + this.propertyManager.getAppPrivateKey(), + this.propertyManager.getAppPublicKeyId(), TimeSpan.fromTime(100, TimeUnit.SECONDS), VirgilAccessTokenSigner(this.virgilCrypto) ) val provider = CachingJwtProvider(CachingJwtProvider.RenewJwtCallback { jwtGenerator.generateToken(identity) }) - val keyknoxClient = KeyknoxClient(provider) + val serviceBaseUrl = this.propertyManager.getServiceBaseUrl() + val keyknoxClient = if (serviceBaseUrl.isNullOrBlank()) { + KeyknoxClient(provider) + } else { + KeyknoxClient(provider, URL(serviceBaseUrl)) + } this.keyknoxManager = KeyknoxManager(keyknoxClient) this.cloudKeyStorage = @@ -109,9 +118,9 @@ class SyncKeyStorageTests { @Test fun init() { val jwtGenerator = JwtGenerator( - TestConfig.appId, - TestConfig.appPrivateKey, - TestConfig.appPublicKeyId, + this.propertyManager.getAppId(), + this.propertyManager.getAppPrivateKey(), + this.propertyManager.getAppPublicKeyId(), TimeSpan.fromTime(100, TimeUnit.SECONDS), VirgilAccessTokenSigner(this.virgilCrypto) ) @@ -129,13 +138,18 @@ class SyncKeyStorageTests { // Load private key from Keychain val privateKey = this.privateKey - val syncKeyStorage = SyncKeyStorage( - identity = "Alice", - accessTokenProvider = accessTokenProvider, - crypto = this.virgilCrypto, + val serviceBaseUrl = this.propertyManager.getServiceBaseUrl() + val keyknoxClient = if (serviceBaseUrl.isNullOrBlank()) { + KeyknoxClient(accessTokenProvider) + } else { + KeyknoxClient(accessTokenProvider, URL(serviceBaseUrl)) + } + val cloudKeyStorage = CloudKeyStorage( + keyknoxManager = KeyknoxManager(keyknoxClient), publicKeys = publicKeys, privateKey = privateKey ) + val syncKeyStorage = SyncKeyStorage(identity = "Alice", cloudKeyStorage = cloudKeyStorage) syncKeyStorage.sync() assertTrue(syncKeyStorage.retrieveAll().isEmpty()) @@ -331,7 +345,7 @@ class SyncKeyStorageTests { assertEquals(newPublicKeys.map { it.identifier }, pubIds) assertEquals( newPrivateKey.identifier, - (cloudKeyStorage.privateKey as VirgilPrivateKey).identifier + cloudKeyStorage.privateKey.identifier ) val keychainEntry2 = this.syncKeyStorage.retrieve(name) @@ -566,16 +580,21 @@ class SyncKeyStorageTests { val publicKey2 = keyPair2.publicKey val jwtGenerator = JwtGenerator( - TestConfig.appId, - TestConfig.appPrivateKey, - TestConfig.appPublicKeyId, + this.propertyManager.getAppId(), + this.propertyManager.getAppPrivateKey(), + this.propertyManager.getAppPublicKeyId(), TimeSpan.fromTime(100, TimeUnit.SECONDS), VirgilAccessTokenSigner(this.virgilCrypto) ) val provider = CachingJwtProvider(CachingJwtProvider.RenewJwtCallback { jwtGenerator.generateToken(identity) }) - var keyknoxManager = KeyknoxManager(KeyknoxClient(provider)) + val serviceBaseUrl = this.propertyManager.getServiceBaseUrl() + var keyknoxManager = if (serviceBaseUrl.isNullOrBlank()) { + KeyknoxManager(KeyknoxClient(provider)) + } else { + KeyknoxManager(KeyknoxClient(provider, URL(serviceBaseUrl))) + } val keyStorage = DefaultKeyStorage(System.getProperty("java.io.tmpdir"), UUID.randomUUID().toString()) @@ -606,7 +625,11 @@ class SyncKeyStorageTests { } // Reinit syncKeyStorage2 - keyknoxManager = KeyknoxManager(KeyknoxClient(provider)) + keyknoxManager = if (serviceBaseUrl.isNullOrBlank()) { + KeyknoxManager(KeyknoxClient(provider)) + } else { + KeyknoxManager(KeyknoxClient(provider, URL(serviceBaseUrl))) + } syncKeyStorage2 = SyncKeyStorage( identity = this.identity, keyStorage = keyStorage, diff --git a/settings.gradle b/settings.gradle index a22d0f5..2896e93 100644 --- a/settings.gradle +++ b/settings.gradle @@ -32,4 +32,28 @@ */ rootProject.name = 'Virgil SDK' -include ':api', ':sdk', ':crypto', ':crypto-android', ':android-utils', 'examples', 'common', 'test-common', 'sdk-android' + +include ':api', ':sdk', ':crypto', 'examples', 'common', 'test-common' + +def hasAndroidSdk = { + def envSdkRoot = System.getenv('ANDROID_SDK_ROOT') ?: System.getenv('ANDROID_HOME') + if (envSdkRoot != null && !envSdkRoot.trim().isEmpty()) { + return true + } + + def localProperties = file('local.properties') + if (!localProperties.exists()) { + return false + } + + def props = new Properties() + localProperties.withInputStream { props.load(it) } + def sdkDir = props.getProperty('sdk.dir') + return sdkDir != null && !sdkDir.trim().isEmpty() +} + +if (hasAndroidSdk()) { + include ':crypto-android', ':android-utils', ':sdk-android' +} else { + println 'Android SDK not configured (set ANDROID_SDK_ROOT/ANDROID_HOME or local.properties sdk.dir); skipping Android subprojects.' +} From f074ce283f94417328b7b2e0957fb5ea3aa0ab12 Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Tue, 10 Feb 2026 20:36:34 -0500 Subject: [PATCH 02/19] Update crypto to v0.17.2 that supports 16 KB page sizes for android --- android-utils/build.gradle | 55 +++++++++------------ android-utils/src/main/AndroidManifest.xml | 3 +- build.gradle | 37 +++++++------- crypto-android/build.gradle | 48 ++++++++---------- crypto-android/src/main/AndroidManifest.xml | 3 +- gradle/wrapper/gradle-wrapper.properties | 2 +- sdk-android/build.gradle | 48 ++++++++---------- sdk-android/src/main/AndroidManifest.xml | 2 +- test-common/build.gradle | 2 +- 9 files changed, 86 insertions(+), 114 deletions(-) diff --git a/android-utils/build.gradle b/android-utils/build.gradle index fcb9317..0740bca 100644 --- a/android-utils/build.gradle +++ b/android-utils/build.gradle @@ -32,27 +32,35 @@ */ apply plugin: 'com.android.library' -apply plugin: 'digital.wup.android-maven-publish' android { - compileSdkVersion 28 + namespace "com.virgilsecurity.sdk.androidutils" + compileSdk 34 defaultConfig { - minSdkVersion 23 - targetSdkVersion 28 + minSdk 23 + targetSdk 34 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } + consumerProguardFiles 'proguard-rules.txt' + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } - packagingOptions { - exclude "META-INF/LICENSE*" + packaging { + resources { + excludes += ["META-INF/LICENSE*"] } + } - consumerProguardFiles 'proguard-rules.txt' + publishing { + singleVariant("release") { + withSourcesJar() + } } } @@ -73,31 +81,12 @@ dependencies { androidTestImplementation "com.android.support.test:runner:$versions.testsRunner" } -task androidJavadocs(type: Javadoc, dependsOn: 'assembleRelease') { - classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) - android.libraryVariants.all { variant -> - owner.classpath += variant.javaCompileProvider.get().classpath - } - source = android.sourceSets.main.java.srcDirs - exclude '**/R.html', '**/R.*.html', '**/index.html' -} - -task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { - archiveClassifier = 'javadoc' - from androidJavadocs.destinationDir -} - -task androidSourcesJar(type: Jar) { - archiveClassifier = 'sources' - from android.sourceSets.main.java.srcDirs -} - publishing { publications { mavenJava(MavenPublication) { - from components.android - artifact androidJavadocsJar - artifact androidSourcesJar + afterEvaluate { + from components.release + } pom { name = 'Virgil Android Utils' diff --git a/android-utils/src/main/AndroidManifest.xml b/android-utils/src/main/AndroidManifest.xml index aa0d6e6..5c26bcc 100644 --- a/android-utils/src/main/AndroidManifest.xml +++ b/android-utils/src/main/AndroidManifest.xml @@ -30,8 +30,7 @@ ~ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ~ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --> - + diff --git a/build.gradle b/build.gradle index 7ab009d..b85f9c5 100644 --- a/build.gradle +++ b/build.gradle @@ -36,7 +36,7 @@ buildscript { ext.versions = [ // Virgil - virgilCrypto : '0.15.2', + virgilCrypto : '0.17.2', // Common commonsLang : '3.9', @@ -44,14 +44,14 @@ buildscript { gson : '2.8.5', // Gradle - gradle : '3.4.2', + gradle : '8.5.2', // Android appCompat : '28.0.0', androidx : '1.1.1', // Kotlin - kotlinVersion : '1.3.61', + kotlinVersion : '1.9.24', coroutines : '1.3.0-M1', @@ -70,22 +70,17 @@ buildscript { junitAndroid : '4.12', // Publish - mavenPublishPlugin: '3.6.2', - dokka : '0.10.0' + mavenPublishPlugin: '3.6.2' ] repositories { google() - jcenter() mavenCentral() } dependencies { classpath "com.android.tools.build:gradle:$versions.gradle" - classpath "de.mannodermaus.gradle.plugins:android-junit5:$versions.mannodermaus" - classpath "digital.wup:android-maven-publish:$versions.mavenPublishPlugin" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlinVersion" - classpath "org.jetbrains.dokka:dokka-gradle-plugin:$versions.dokka" } } @@ -98,7 +93,6 @@ allprojects { repositories { mavenCentral() google() - jcenter() maven { url "https://oss.sonatype.org/service/local/repositories/snapshots/content/" } @@ -152,7 +146,6 @@ subprojects { group SDK_PACKAGE version SDK_VERSION - apply plugin: 'org.jetbrains.dokka' apply plugin: 'maven-publish' apply plugin: 'checkstyle' apply from: '../tests-verbal-output.gradle' @@ -189,24 +182,25 @@ subprojects { } } + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { + kotlinOptions { + jvmTarget = "1.8" + } + } + // Don't publish examples subproject if (it.name != 'examples') { apply plugin: 'signing' - dokka { - outputFormat = 'html' - outputDirectory = "$buildDir/javadoc" - } - if (!isAndroid) { task sourcesJar(type: Jar) { from sourceSets.main.allSource archiveClassifier = 'sources' } - task javadocJar(type: Jar, dependsOn: 'dokka') { - from "$buildDir/javadoc" + task javadocJar(type: Jar, dependsOn: 'javadoc') { + from javadoc.destinationDir archiveClassifier = 'javadoc' } } @@ -265,6 +259,13 @@ subprojects { } signing { + def signingKey = getGradleOrSystemProperty('signingKey', project) + def signingPassword = getGradleOrSystemProperty('signingPassword', project) + + if (signingKey != null && signingPassword != null) { + useInMemoryPgpKeys(signingKey, signingPassword) + } + sign publishing.publications.mavenJava } } diff --git a/crypto-android/build.gradle b/crypto-android/build.gradle index be59c37..3b12961 100644 --- a/crypto-android/build.gradle +++ b/crypto-android/build.gradle @@ -32,25 +32,33 @@ */ apply plugin: 'com.android.library' -apply plugin: 'digital.wup.android-maven-publish' android { - compileSdkVersion 28 + namespace "com.virgilsecurity.sdk.crypto" + compileSdk 34 defaultConfig { - minSdkVersion 21 - targetSdkVersion 28 + minSdk 21 + targetSdk 34 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + packaging { + resources { + excludes += ["META-INF/LICENSE*"] } + } - packagingOptions { - exclude "META-INF/LICENSE*" + publishing { + singleVariant("release") { + withSourcesJar() } } } @@ -71,28 +79,12 @@ dependencies { androidTestImplementation "org.apache.commons:commons-lang3:$versions.commonsLang" } -task androidJavadocs(type: Javadoc) { - source = android.sourceSets.main.java.srcDirs - classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) - exclude '**/R.html', '**/R.*.html', '**/index.html' -} - -task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { - archiveClassifier = 'javadoc' - from androidJavadocs.destinationDir -} - -task androidSourcesJar(type: Jar) { - archiveClassifier = 'sources' - from android.sourceSets.main.java.srcDirs -} - publishing { publications { mavenJava(MavenPublication) { - from components.android - artifact androidJavadocsJar - artifact androidSourcesJar + afterEvaluate { + from components.release + } pom { name = 'Virgil Crypto for Android' diff --git a/crypto-android/src/main/AndroidManifest.xml b/crypto-android/src/main/AndroidManifest.xml index 0af5c1c..b7f5faf 100644 --- a/crypto-android/src/main/AndroidManifest.xml +++ b/crypto-android/src/main/AndroidManifest.xml @@ -31,5 +31,4 @@ ~ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ~ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --> - + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 97f2726..22032f1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -36,4 +36,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip diff --git a/sdk-android/build.gradle b/sdk-android/build.gradle index da52a91..37b10dc 100644 --- a/sdk-android/build.gradle +++ b/sdk-android/build.gradle @@ -32,25 +32,33 @@ */ apply plugin: 'com.android.library' -apply plugin: 'digital.wup.android-maven-publish' android { - compileSdkVersion 28 + namespace "com.virgilsecurity.sdk.sdk" + compileSdk 34 defaultConfig { - minSdkVersion 21 - targetSdkVersion 28 + minSdk 21 + targetSdk 34 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + packaging { + resources { + excludes += ["META-INF/LICENSE*"] } + } - packagingOptions { - exclude "META-INF/LICENSE*" + publishing { + singleVariant("release") { + withSourcesJar() } } } @@ -72,28 +80,12 @@ dependencies { androidTestImplementation "org.apache.commons:commons-lang3:$versions.commonsLang" } -task androidJavadocs(type: Javadoc) { - source = android.sourceSets.main.java.srcDirs - classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) - exclude '**/R.html', '**/R.*.html', '**/index.html' -} - -task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { - archiveClassifier = 'javadoc' - from androidJavadocs.destinationDir -} - -task androidSourcesJar(type: Jar) { - archiveClassifier = 'sources' - from android.sourceSets.main.java.srcDirs -} - publishing { publications { mavenJava(MavenPublication) { - from components.android - artifact androidJavadocsJar - artifact androidSourcesJar + afterEvaluate { + from components.release + } pom { name = 'Virgil SDK for Android' diff --git a/sdk-android/src/main/AndroidManifest.xml b/sdk-android/src/main/AndroidManifest.xml index 64d2623..b7f5faf 100644 --- a/sdk-android/src/main/AndroidManifest.xml +++ b/sdk-android/src/main/AndroidManifest.xml @@ -31,4 +31,4 @@ ~ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ~ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --> - + diff --git a/test-common/build.gradle b/test-common/build.gradle index 2aaf107..ea8a520 100644 --- a/test-common/build.gradle +++ b/test-common/build.gradle @@ -43,7 +43,7 @@ dependencies { // Tests testImplementation "org.junit.jupiter:junit-jupiter-api:$versions.junit" - testRuntime "org.junit.jupiter:junit-jupiter-engine:$versions.junit" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$versions.junit" } sourceCompatibility = 1.8 From 3f7d92fd9b7163f2c51687b837da30a2d403232f Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Tue, 10 Feb 2026 20:47:16 -0500 Subject: [PATCH 03/19] Remove deprecations from gradle configs --- build.gradle | 6 ++++-- test-common/build.gradle | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index b85f9c5..a92a279 100644 --- a/build.gradle +++ b/build.gradle @@ -165,8 +165,6 @@ subprojects { if (!isAndroid) { apply plugin: 'java-library' - sourceCompatibility = JavaVersion.VERSION_1_8 - dependencies { testImplementation "org.junit.jupiter:junit-jupiter:$versions.junit" } @@ -180,6 +178,10 @@ subprojects { events "passed", "skipped", "failed" } } + + tasks.withType(JavaCompile).configureEach { + options.release.set(8) + } } tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { diff --git a/test-common/build.gradle b/test-common/build.gradle index ea8a520..e7153b0 100644 --- a/test-common/build.gradle +++ b/test-common/build.gradle @@ -46,7 +46,9 @@ dependencies { testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$versions.junit" } -sourceCompatibility = 1.8 +tasks.withType(JavaCompile).configureEach { + options.release.set(8) +} compileKotlin { kotlinOptions.jvmTarget = "1.8" From 8bf794de3273dafb4e6cd1d33cbf9455507f144b Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Wed, 11 Feb 2026 15:03:08 -0500 Subject: [PATCH 04/19] Restore CardClient backward compatibility --- .../sdk/client/VirgilCardClient.java | 18 ++--- .../VirgilCardClientConstructorTest.java | 69 +++++++++++++++++++ 2 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 sdk/src/test/java/com/virgilsecurity/sdk/client/VirgilCardClientConstructorTest.java diff --git a/sdk/src/main/java/com/virgilsecurity/sdk/client/VirgilCardClient.java b/sdk/src/main/java/com/virgilsecurity/sdk/client/VirgilCardClient.java index 914af70..86ff5c3 100644 --- a/sdk/src/main/java/com/virgilsecurity/sdk/client/VirgilCardClient.java +++ b/sdk/src/main/java/com/virgilsecurity/sdk/client/VirgilCardClient.java @@ -88,10 +88,9 @@ public VirgilCardClient(HttpClient httpClient) { * * @param serviceUrl the service url to fire requests to */ - public VirgilCardClient(String baseServiceUrl) { + public VirgilCardClient(String serviceUrl) { try { - URL url = new URL(baseServiceUrl); - this.serviceUrl = new URL(url.getProtocol(), url.getHost(), url.getPort(), CARDS_V5_PATH); + this.serviceUrl = resolveServiceUrl(new URL(serviceUrl)); httpClient = new HttpClient(); } catch (MalformedURLException e) { LOGGER.log(Level.SEVERE, "Some issue occurred during service URL creating", e); @@ -107,8 +106,7 @@ public VirgilCardClient(String baseServiceUrl) { */ public VirgilCardClient(String serviceUrl, HttpClient httpClient) { try { - URL url = new URL(serviceUrl); - this.serviceUrl = new URL(url.getProtocol(), url.getHost(), url.getPort(), CARDS_V5_PATH); + this.serviceUrl = resolveServiceUrl(new URL(serviceUrl)); } catch (MalformedURLException e) { LOGGER.log(Level.SEVERE, "Some issue occurred during service URL creating", e); throw new IllegalArgumentException("Incorrect service URL", e); @@ -121,9 +119,9 @@ public VirgilCardClient(String serviceUrl, HttpClient httpClient) { * * @param serviceUrl the service url to fire requests to */ - public VirgilCardClient(URL baseServiceUrl) { + public VirgilCardClient(URL serviceUrl) { try { - this.serviceUrl = new URL(baseServiceUrl.getProtocol(), baseServiceUrl.getHost(), baseServiceUrl.getPort(), CARDS_V5_PATH); + this.serviceUrl = resolveServiceUrl(serviceUrl); } catch (MalformedURLException e) { LOGGER.log(Level.SEVERE, "Some issue occurred during service URL creating", e); throw new IllegalArgumentException("Incorrect service URL", e); @@ -139,7 +137,7 @@ public VirgilCardClient(URL baseServiceUrl) { */ public VirgilCardClient(URL serviceUrl, HttpClient httpClient) { try { - this.serviceUrl = new URL(serviceUrl.getProtocol(), serviceUrl.getHost(), serviceUrl.getPort(), CARDS_V5_PATH); + this.serviceUrl = resolveServiceUrl(serviceUrl); } catch (MalformedURLException e) { LOGGER.log(Level.SEVERE, "Some issue occurred during service URL creating", e); throw new IllegalArgumentException("Incorrect service URL", e); @@ -147,6 +145,10 @@ public VirgilCardClient(URL serviceUrl, HttpClient httpClient) { this.httpClient = httpClient; } + private static URL resolveServiceUrl(URL serviceUrl) throws MalformedURLException { + return new URL(serviceUrl.getProtocol(), serviceUrl.getHost(), serviceUrl.getPort(), CARDS_V5_PATH); + } + /** * Get card from Virgil Services by specified identifier. * diff --git a/sdk/src/test/java/com/virgilsecurity/sdk/client/VirgilCardClientConstructorTest.java b/sdk/src/test/java/com/virgilsecurity/sdk/client/VirgilCardClientConstructorTest.java new file mode 100644 index 0000000..5e38f08 --- /dev/null +++ b/sdk/src/test/java/com/virgilsecurity/sdk/client/VirgilCardClientConstructorTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2015-2020, Virgil Security, Inc. + * + * Lead Maintainer: Virgil Security Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * (1) Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * (2) Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * (3) Neither the name of virgil nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.virgilsecurity.sdk.client; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class VirgilCardClientConstructorTest { + + @Test + public void shouldAppendCardsPathForBaseUrl() { + VirgilCardClient cardClient = new VirgilCardClient("https://api.virgilsecurity.com"); + + assertEquals("https://api.virgilsecurity.com/card/v5/", cardClient.getServiceUrl().toString()); + } + + @Test + public void shouldNormalizeCardsPathWithoutTrailingSlash() { + VirgilCardClient cardClient = new VirgilCardClient("https://api.virgilsecurity.com/card/v5"); + + assertEquals("https://api.virgilsecurity.com/card/v5/", cardClient.getServiceUrl().toString()); + } + + @Test + public void shouldKeepCardsPathWithTrailingSlash() { + VirgilCardClient cardClient = new VirgilCardClient("https://api.virgilsecurity.com/card/v5/"); + + assertEquals("https://api.virgilsecurity.com/card/v5/", cardClient.getServiceUrl().toString()); + } + + @Test + public void shouldKeepCustomPathUntouched() { + VirgilCardClient cardClient = new VirgilCardClient("https://localhost:9443/custom/cards/"); + + assertEquals("https://localhost:9443/card/v5/", cardClient.getServiceUrl().toString()); + } +} From f19fa875c3d4d8fae5bb69cc606f06b4ac2b013b Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Wed, 11 Feb 2026 15:03:37 -0500 Subject: [PATCH 05/19] Add scripts to encrypt/decrypt env.json --- scripts/decrypt-env.sh | 25 +++++++++++++++++++++++++ scripts/encrypt-env.sh | 25 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100755 scripts/decrypt-env.sh create mode 100755 scripts/encrypt-env.sh diff --git a/scripts/decrypt-env.sh b/scripts/decrypt-env.sh new file mode 100755 index 0000000..00530e3 --- /dev/null +++ b/scripts/decrypt-env.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +INPUT_FILE="${1:-$ROOT_DIR/env.json.enc}" +OUTPUT_FILE="${2:-$ROOT_DIR/env.json}" +PASSPHRASE="${ENV_JSON_PASSPHRASE:-}" + +if [[ -z "${PASSPHRASE}" ]]; then + echo "ENV_JSON_PASSPHRASE is required." + echo "Usage: ENV_JSON_PASSPHRASE=... $0 [input_enc] [output_json]" + exit 1 +fi + +if [[ ! -f "${INPUT_FILE}" ]]; then + echo "Input file not found: ${INPUT_FILE}" + exit 1 +fi + +openssl enc -d -aes-256-cbc -pbkdf2 -a \ + -in "${INPUT_FILE}" \ + -out "${OUTPUT_FILE}" \ + -pass "pass:${PASSPHRASE}" + +echo "Decrypted ${INPUT_FILE} -> ${OUTPUT_FILE}" diff --git a/scripts/encrypt-env.sh b/scripts/encrypt-env.sh new file mode 100755 index 0000000..2d6661a --- /dev/null +++ b/scripts/encrypt-env.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +INPUT_FILE="${1:-$ROOT_DIR/env.json}" +OUTPUT_FILE="${2:-$ROOT_DIR/env.json.enc}" +PASSPHRASE="${ENV_JSON_PASSPHRASE:-}" + +if [[ -z "${PASSPHRASE}" ]]; then + echo "ENV_JSON_PASSPHRASE is required." + echo "Usage: ENV_JSON_PASSPHRASE=... $0 [input_json] [output_enc]" + exit 1 +fi + +if [[ ! -f "${INPUT_FILE}" ]]; then + echo "Input file not found: ${INPUT_FILE}" + exit 1 +fi + +openssl enc -aes-256-cbc -pbkdf2 -salt -a \ + -in "${INPUT_FILE}" \ + -out "${OUTPUT_FILE}" \ + -pass "pass:${PASSPHRASE}" + +echo "Encrypted ${INPUT_FILE} -> ${OUTPUT_FILE}" From 2770ace96732c99805136800ef3a43a1385d0dbc Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Wed, 11 Feb 2026 15:03:48 -0500 Subject: [PATCH 06/19] Update README --- README.md | 78 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f462187..bc4eb8b 100644 --- a/README.md +++ b/README.md @@ -26,15 +26,20 @@ In case you need additional security functionality for multi-device support, gro The Virgil Core SDK is provided as set of packages named *com.virgilsecurity.sdk*. Packages are distributed via Maven repository. In this guide you'll also find one more package - Virgil Crypto Library, that is used by the SDK to perform cryptographic operations. -### Target +### Runtime targets -* Java 7+. -* Android API 16+. +* JVM artifacts target Java 8 bytecode. +* Android artifacts target: + * `sdk-android`: minSdk 21, targetSdk 34. + * `crypto-android`: minSdk 21, targetSdk 34. + * `android-utils`: minSdk 23, targetSdk 34. -### Prerequisites +### Build prerequisites -* Java Development Kit (JDK) 7+ -* Maven 3+ +* JDK 17 (required by Android Gradle Plugin 8.5.x). +* Android SDK Platform 34 (for Android modules/tests). +* Android emulator or device (for instrumentation tests). +* Gradle Wrapper (already included in this repository). ### Installing the package @@ -56,11 +61,11 @@ Use this packages for Java projects: #### Gradle -Add jcenter() repository if missing: +Add Maven Central if missing: ``` repositories { - jcenter() + mavenCentral() } ``` @@ -72,6 +77,62 @@ Use this packages for Android projects: The **\** of the SDK can be found in the [Maven Central Repository](https://mvnrepository.com/artifact/com.virgilsecurity.sdk/sdk) or in the header of current readme. +## Build and Test + +### Local setup + +Create `local.properties` in the project root if Android SDK is not auto-detected: + +``` +sdk.dir=/Users//Library/Android/sdk +``` + +For integration tests that hit Virgil services, keep `env.json` in the project root: + +* `env.json` is ignored by git. +* `env.json.enc` can be stored in git and decrypted in CI. + +Encrypt/decrypt commands: + +``` +ENV_JSON_PASSPHRASE='' ./scripts/encrypt-env.sh +ENV_JSON_PASSPHRASE='' ./scripts/decrypt-env.sh +``` + +### Build all modules + +``` +./gradlew clean build --no-daemon +``` + +### Run JVM tests + +Default environment is `pro`: + +``` +./gradlew :sdk:test --no-daemon --stacktrace +``` + +Use staging environment explicitly: + +``` +./gradlew :sdk:test -Denvironment=stg --no-daemon --stacktrace +``` + +### Run Android unit tests + +``` +./gradlew :crypto-android:testReleaseUnitTest :sdk-android:testReleaseUnitTest --no-daemon +``` + +### Run Android instrumentation tests + +Start an emulator (or connect a device), then run: + +``` +./gradlew :crypto-android:connectedReleaseAndroidTest :sdk-android:connectedReleaseAndroidTest --no-daemon --stacktrace +``` + ## Configure SDK This section contains guides on how to set up Virgil Core SDK modules for authenticating users, managing Virgil Cards and storing private keys. @@ -352,4 +413,3 @@ Our developer support team is here to help you. Find out more information on our You can find us on [Twitter](https://twitter.com/VirgilSecurity) or send us email support@VirgilSecurity.com. Also, get extra help from our support team on [Slack](https://virgilsecurity.com/join-community). - From 4ba14d2acece6672164a9bb67df493e66280e6d6 Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Wed, 11 Feb 2026 15:04:09 -0500 Subject: [PATCH 07/19] Encrypt env.json for CI tests --- env.json.enc | Bin 1600 -> 610 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/env.json.enc b/env.json.enc index c6b8da5a27754c2299651f84d3024805b9833fa3..1fb3d9a604761cc4a0a21f35a6c6f5f869ef7d78 100644 GIT binary patch literal 610 zcmV~$$$~>L007XtU&+Bzst$87Bx+Z?-Yiv-DkaqT`gt!A(9#$w$$1cbW#AZncI~!z zHA(7+9M0H6sO=L{eeO|t+1PULs;ia`jRzbD!spCof6vl+BTD((=$-P1=W_9+RhOem zRBS6b&qd|vkrygBRM7mElJ;o=Zava2XKi|BAl8F+oqnY(g&L6SVMDBn3(=&U2}721 z10s{evnxK%UIV{k(NFnczkqd$qd-s?-P*lnlD4>>C0K(RZOW3)W(xON3~pVcM}^oK zsQ@H6Nb%eJ*0bw{hRt>S;NTE5e{SR4xV3Y?mHNo(hbjoDZb6L(=BX0qW~OCRN5kWL zZI2iFRLwo&*k;*4(TzTSF>p8vx{&-zD=_d42=Cr$}mmwKB-fMk-#czC>ME1xI0RTql|{ndjktK9VK-e$QHCw z!aA1CgAHeG%g|+L58M#8&`A>O@5gz@W&aE1;J(tcr2XI0#-D5e1JUKJ0= zb6gT#C>eD9(==-CCx5r3Wv4F?)rI47pS}|T`4Z!J#d&@En6%N8V>_uh@M|L`ma_0W abalpnW45OjCsgg}^f-?=K??Z({4oF9NXYU4 literal 1600 zcmV-G2EX}Y!lIN&HaK#O3`H(WMMM5$0yet!v*4|ZTD@;0ds7{NH9PT2ONC?)xf(uqDRy5ar;$s zQw5Pc77o{UauC;gaSD>}xAB)kill8wffQPHD%BZv;^Wr^X902!X+nk6_EelFB%3Id znns%kGO>YBGR>c~^{2?#0Tp3C!RCqP{pKUs-)>13La>%|<~o}hTGf6HVyNTYf>SQ2 zwDsT*uPhc+ANvPp!o^1QZ+Z=+75p@jXMu6|3GqH_vm?|r9sp(U^5xDTwGF`?)^XzO zMAc*%fc?2!3T9)!p3o=;PSQzLQGxuAu39#7)Q3ykG|t9tS+MHb)moIF>-jdwPYUcR zMpm}EAV2$icOOeZ5s7K<=P*KEjKYqno8{OC76$UlRetEc+Bja;nb9nz9$PS?wD*H!E+y??94THj?#fQ? z@k!%!wbicmKa8v15`_TI(C?*N3TO53%P7w631hTg(}4DV42yk)xQDSo^Fmd4+JCf) zBXAwY1~EoBvC*Wz{ubiu644Pg5IVq+56tRqw3)3%o?)uHufwBQ&?yzIRG|=4xozy;5KLGq{Q0iWnhJMO z;(dsXp>JZiKwU6IgR4yr;H+BMNV@c7(Vy&I5BCL_D^?~4$Cn-FB~?BRb@M4gp>y@p zJeo?%I!f;^kRuE6a{vn=R-#5~8;o_95Q9R9STH())axR)1pbgV!9eM!dng%W-w&C9 zyp#93tR=`|lW`M7o%@2^o<3fbiA-kW2LmE;I zYcv7!veEOR(o6G77}-;$Ll@b2Va97grfXx4B!45$@-3)=D-DaJ?L~LQ{Ph7U#z z2zLF+43X_y{L#684Jkbu_nh+Q(}}pl?jA%jpU^<6D?+g$cr0`s4=}Y1fibi9%a`Hs z6)mAY^?@HgA{)-Ew z1e5xLOEvRkqFa8OqJCesJGc$IiichtYc~mCR+!Zn6VJqch1M;r2_0u){o-zW^=ePdV9^^ zt9WvryY6tMFH_p4k#Lw&kCgB(lOQL;0?kgPwb397cotZ{L$ykp1L1C%c<=q!l^7#S z>jD=TS1F0_MaEtAk}~UK7BDA;a#L4b85329ueAEYXZK;KX+BEW}U(PEd)-q5M6TBB{2?u_mr$xhEHW0N$gORBmqQITz;nsCg zQ0j*8#9dl*O|S0b=EE$%-=}(*a6(j?-T(}z2=jf&QGz0)>`O|Toutq_d|ceE?X3?- z1H}l#GD7R-|7jYX7>l9=6wN0X(6O$#*6)wAsKGV=Hmi`A yvVd8V%OGBTRVM>=JEy|0XeIan8DjjM%4u_ed<}rXy(UX6ze_+z*C|w469E9XCnQk- From b1cbba4719d52ba2f15506af2a0e323276e8df5d Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Wed, 11 Feb 2026 15:23:37 -0500 Subject: [PATCH 08/19] Change CI from TravisCI to GitHub Workflows --- .github/workflows/build-and-test.yml | 100 +++++++++++++++++++ .github/workflows/publish-release.yml | 134 ++++++++++++++++++++++++++ .travis.yml | 65 ------------- 3 files changed, 234 insertions(+), 65 deletions(-) create mode 100644 .github/workflows/build-and-test.yml create mode 100644 .github/workflows/publish-release.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..a342187 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,100 @@ +name: Build and Test + +on: + push: + branches: + - main + - master + - develop + - dev + - "release/**" + - "hotfix/**" + - "feature/**" + pull_request: + branches: + - main + - master + - develop + - dev + workflow_dispatch: + workflow_call: + secrets: + ENV_JSON_PASSPHRASE: + required: false + +jobs: + build-and-test: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "17" + cache: "gradle" + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Install Android SDK packages + run: | + yes | sdkmanager --licenses >/dev/null + yes | sdkmanager \ + "platform-tools" \ + "platforms;android-34" \ + "build-tools;34.0.0" + + - name: Decrypt env.json for integration tests + if: hashFiles('env.json.enc') != '' + env: + ENV_JSON_PASSPHRASE: ${{ secrets.ENV_JSON_PASSPHRASE }} + run: | + if [ -z "${ENV_JSON_PASSPHRASE:-}" ]; then + echo "ENV_JSON_PASSPHRASE is not set. Integration tests that need env.json will be skipped." + exit 0 + fi + ./scripts/decrypt-env.sh + + - name: Build JVM and Android modules + run: | + ./gradlew \ + :api:assemble \ + :common:assemble \ + :crypto:assemble \ + :test-common:assemble \ + :sdk:assemble \ + :crypto-android:assembleRelease \ + :android-utils:assembleRelease \ + :sdk-android:assembleRelease \ + --no-daemon \ + --stacktrace + + - name: Run JVM tests + run: | + ./gradlew \ + :api:test \ + :common:test \ + :crypto:test \ + :test-common:test \ + :sdk:test \ + --no-daemon \ + --stacktrace + + - name: Cleanup decrypted env.json + if: always() + run: rm -f env.json + + - name: Upload test reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: jvm-test-reports + path: | + **/build/reports/tests/**/* + **/build/test-results/**/* + if-no-files-found: warn diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000..b2af8b0 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,134 @@ +name: Publish Release + +on: + push: + tags: + - "v*.*.*" + - "v*.*.*-rc*" + workflow_dispatch: + inputs: + tag: + description: "Tag to release (for example v7.3.2)" + required: true + type: string + +jobs: + verify-version: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }} + + - name: Verify SDK version matches tag + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + TAG="${{ inputs.tag }}" + else + TAG="${GITHUB_REF#refs/tags/}" + fi + + TAG_VERSION="${TAG#v}" + SDK_VERSION=$(grep -E "final String SDK_VERSION = '[^']+'" build.gradle | sed -E "s/.*'([^']+)'.*/\1/" | tr -d '[:space:]') + SDK_VERSION_BASE="${SDK_VERSION%-SNAPSHOT}" + + echo "Tag version: ${TAG_VERSION}" + echo "SDK version: ${SDK_VERSION_BASE}" + + if [ -z "${SDK_VERSION_BASE}" ]; then + echo "ERROR: Could not extract SDK_VERSION from build.gradle" + exit 1 + fi + + if [ "${TAG_VERSION}" != "${SDK_VERSION_BASE}" ]; then + echo "ERROR: Tag version (${TAG_VERSION}) doesn't match SDK version (${SDK_VERSION_BASE})" + exit 1 + fi + + build: + name: Build and Test + needs: verify-version + uses: ./.github/workflows/build-and-test.yml + secrets: inherit + + publish-jvm: + name: Publish JVM artifacts + needs: build + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }} + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "17" + cache: "gradle" + + - name: Publish JVM artifacts + env: + ORG_GRADLE_PROJECT_authentication_username: ${{ secrets.CENTRAL_SONATYPE_TOKEN_USERNAME }} + ORG_GRADLE_PROJECT_authentication_password: ${{ secrets.CENTRAL_SONATYPE_TOKEN_PASSWORD }} + ORG_GRADLE_PROJECT_signingKey: ${{ secrets.CENTRAL_SONATYPE_SIGNING_KEY }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.CENTRAL_SONATYPE_SIGNING_PASSWORD }} + run: | + ./gradlew \ + :api:publishMavenJavaPublicationToMavenRepository \ + :common:publishMavenJavaPublicationToMavenRepository \ + :crypto:publishMavenJavaPublicationToMavenRepository \ + :test-common:publishMavenJavaPublicationToMavenRepository \ + :sdk:publishMavenJavaPublicationToMavenRepository \ + --no-daemon \ + --stacktrace + + publish-android: + name: Publish Android artifacts + needs: publish-jvm + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }} + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "17" + cache: "gradle" + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Install Android SDK packages + run: | + yes | sdkmanager --licenses >/dev/null + yes | sdkmanager \ + "platform-tools" \ + "platforms;android-34" \ + "build-tools;34.0.0" + + - name: Publish Android artifacts + env: + ORG_GRADLE_PROJECT_authentication_username: ${{ secrets.CENTRAL_SONATYPE_TOKEN_USERNAME }} + ORG_GRADLE_PROJECT_authentication_password: ${{ secrets.CENTRAL_SONATYPE_TOKEN_PASSWORD }} + ORG_GRADLE_PROJECT_signingKey: ${{ secrets.CENTRAL_SONATYPE_SIGNING_KEY }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.CENTRAL_SONATYPE_SIGNING_PASSWORD }} + run: | + ./gradlew \ + :crypto-android:publishMavenJavaPublicationToMavenRepository \ + :android-utils:publishMavenJavaPublicationToMavenRepository \ + :sdk-android:publishMavenJavaPublicationToMavenRepository \ + --no-daemon \ + --stacktrace diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 26008f9..0000000 --- a/.travis.yml +++ /dev/null @@ -1,65 +0,0 @@ -language: android -sudo: required -jdk: oraclejdk8 - -before_cache: - - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ - -cache: - directories: - - $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ - - $HOME/.android/build-cache - -env: - global: - - ANDROID_BUILD_API=28 - - ANDROID_BUILD_TOOLS=28.0.3 - - ANDROID_EMULATOR_API=24 - - ADB_INSTALL_TIMEOUT=5 # minutes - - ANDROID_ABI=default/armeabi-v7a - -android: - components: - - tools # to get the new `repository-11.xml` - - tools # to install Android SDK tools - - platform-tools - - build-tools-$ANDROID_BUILD_TOOLS - - android-$ANDROID_BUILD_API - - android-$ANDROID_EMULATOR_API - - extra-android-m2repository # for design library - - extra-google-m2repository - - extra-google-google_play_services - - addon-google_apis-google-28 # google play services - - sys-img-armeabi-v7a-addon-google_apis-google-$ANDROID_BUILD_API - - sys-img-armeabi-v7a-android-$ANDROID_EMULATOR_API - - licenses: - - 'android-sdk-preview-license-52d11cd2' - - 'android-sdk-license-.+' - - 'google-gdk-license-.+' - -before_install: - - yes | sdkmanager "platforms;android-28" - - mkdir -p "$ANDROID_HOME/licenses" - - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "$ANDROID_HOME/licenses/android-sdk-license" - - echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license" - - chmod +x gradlew - - ./gradlew dependencies || true - - openssl aes-256-cbc -K $encrypted_c870b79a80ef_key -iv $encrypted_c870b79a80ef_iv -in env.json.enc -out env.json -d - -before_script: - - android list targets - - android list sdk --extended --no-ui --all - - echo "y" | android update sdk -a --no-ui --filter sys-img-armeabi-v7a-android-$ANDROID_EMULATOR_API,sys-img-x86_64-android-$ANDROID_EMULATOR_API - - echo no | android create avd --force -n test -t android-$ANDROID_EMULATOR_API --abi $ANDROID_ABI - - QEMU_AUDIO_DRV=none emulator -avd test -no-window & - - android-wait-for-emulator - - adb shell input keyevent 82 & - -script: - - "./gradlew :crypto:clean :crypto:test" - - "./gradlew :sdk:clean :sdk:test" - - "./gradlew :crypto-android:clean :crypto-android:connectedCheck -PdisablePreDex --stacktrace" - - "./gradlew :android-utils:clean :android-utils:connectedCheck -PdisablePreDex --stacktrace" From 8e4a052594a134021fe518d7cd5d036c2de823a1 Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Wed, 11 Feb 2026 15:48:08 -0500 Subject: [PATCH 09/19] Bump SDK version to 7.4.0-rc1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a92a279..200f124 100644 --- a/build.gradle +++ b/build.gradle @@ -139,7 +139,7 @@ final String BASE_VIRGIL_PACKAGE = 'com.virgilsecurity' final String SDK_PACKAGE = BASE_VIRGIL_PACKAGE + '.sdk' // Packages versions -final String SDK_VERSION = '7.3.2' +final String SDK_VERSION = '7.4.0-rc1' final String TEST_COMMON_VERSION = '0.1.3' subprojects { From 801db8117e0a9a7537ed8e2b1d039832594c4b1b Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Wed, 11 Feb 2026 16:13:55 -0500 Subject: [PATCH 10/19] Fix sonatype publishing urls --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 200f124..d002213 100644 --- a/build.gradle +++ b/build.gradle @@ -94,7 +94,7 @@ allprojects { mavenCentral() google() maven { - url "https://oss.sonatype.org/service/local/repositories/snapshots/content/" + url "https://central.sonatype.com/repository/maven-snapshots/" } } } @@ -249,8 +249,8 @@ subprojects { repositories { maven { - def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" - def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots" + def releasesRepoUrl = "https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/" + def snapshotsRepoUrl = "https://central.sonatype.com/repository/maven-snapshots/" url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl credentials { username "${authentication_username}" From 2e44d21e5c74b4ae5898720bfeaa11abe6d33043 Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Wed, 11 Feb 2026 16:15:34 -0500 Subject: [PATCH 11/19] Bump SDK version to 7.4.0-rc2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d002213..e9099c2 100644 --- a/build.gradle +++ b/build.gradle @@ -139,7 +139,7 @@ final String BASE_VIRGIL_PACKAGE = 'com.virgilsecurity' final String SDK_PACKAGE = BASE_VIRGIL_PACKAGE + '.sdk' // Packages versions -final String SDK_VERSION = '7.4.0-rc1' +final String SDK_VERSION = '7.4.0-rc2' final String TEST_COMMON_VERSION = '0.1.3' subprojects { From d964b6772b9d5ce23841cd557ef99ea941831532 Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Wed, 11 Feb 2026 16:26:54 -0500 Subject: [PATCH 12/19] Fix publishing artifacts --- sdk/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sdk/build.gradle b/sdk/build.gradle index 9fcdfbf..e0c3d66 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -70,6 +70,9 @@ task generateVirgilInfo(type: Copy) { compileKotlin.dependsOn(generateVirgilInfo) compileJava.dependsOn(generateVirgilInfo) +tasks.matching { it.name == "sourcesJar" }.configureEach { + dependsOn(generateVirgilInfo) +} publishing { publications { From 6946f78b15af4bd8a288eb90ecd456ee8986487f Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Wed, 11 Feb 2026 16:27:15 -0500 Subject: [PATCH 13/19] Bump SDK version to 7.4.0-rc3 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e9099c2..1c45790 100644 --- a/build.gradle +++ b/build.gradle @@ -139,7 +139,7 @@ final String BASE_VIRGIL_PACKAGE = 'com.virgilsecurity' final String SDK_PACKAGE = BASE_VIRGIL_PACKAGE + '.sdk' // Packages versions -final String SDK_VERSION = '7.4.0-rc2' +final String SDK_VERSION = '7.4.0-rc3' final String TEST_COMMON_VERSION = '0.1.3' subprojects { From 787e41e0411057a2bf9ea1c3999298d89a292d20 Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Wed, 11 Feb 2026 16:40:44 -0500 Subject: [PATCH 14/19] Fix publishing artifacts 2 --- sdk/build.gradle | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sdk/build.gradle b/sdk/build.gradle index e0c3d66..2035798 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -57,13 +57,15 @@ dependencies { testImplementation "com.virgilsecurity.crypto:foundation:$versions.virgilCrypto" } -sourceSets.main.java.srcDirs = ["${buildDir}/generated/", "src/main/java"] +def virgilGeneratedSourcesDir = "${buildDir}/generated/virgil-info" + +sourceSets.main.java.srcDirs = [virgilGeneratedSourcesDir, "src/main/java"] task generateVirgilInfo(type: Copy) { from('src/main/templates') { include '**/*.java' } - into "$buildDir/generated" + into virgilGeneratedSourcesDir expand(version: "$project.version") includeEmptyDirs = false } From 865784cc42628a309b492c129e1973aaf01af707 Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Wed, 11 Feb 2026 16:41:02 -0500 Subject: [PATCH 15/19] Bump SDK version to 7.4.0-rc4 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1c45790..c0efbe3 100644 --- a/build.gradle +++ b/build.gradle @@ -139,7 +139,7 @@ final String BASE_VIRGIL_PACKAGE = 'com.virgilsecurity' final String SDK_PACKAGE = BASE_VIRGIL_PACKAGE + '.sdk' // Packages versions -final String SDK_VERSION = '7.4.0-rc3' +final String SDK_VERSION = '7.4.0-rc4' final String TEST_COMMON_VERSION = '0.1.3' subprojects { From 640c5617e618d3628b577c5c2dffd3e913ee57bc Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Wed, 11 Feb 2026 18:51:40 -0500 Subject: [PATCH 16/19] Publish artifacts as a single bundle --- .github/workflows/publish-release.yml | 164 +++++++++++++++++++------- build.gradle | 39 +++--- 2 files changed, 137 insertions(+), 66 deletions(-) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index b2af8b0..c636440 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -54,45 +54,10 @@ jobs: uses: ./.github/workflows/build-and-test.yml secrets: inherit - publish-jvm: - name: Publish JVM artifacts + publish: + name: Publish Artifacts needs: build runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }} - - - name: Setup Java - uses: actions/setup-java@v4 - with: - distribution: "temurin" - java-version: "17" - cache: "gradle" - - - name: Publish JVM artifacts - env: - ORG_GRADLE_PROJECT_authentication_username: ${{ secrets.CENTRAL_SONATYPE_TOKEN_USERNAME }} - ORG_GRADLE_PROJECT_authentication_password: ${{ secrets.CENTRAL_SONATYPE_TOKEN_PASSWORD }} - ORG_GRADLE_PROJECT_signingKey: ${{ secrets.CENTRAL_SONATYPE_SIGNING_KEY }} - ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.CENTRAL_SONATYPE_SIGNING_PASSWORD }} - run: | - ./gradlew \ - :api:publishMavenJavaPublicationToMavenRepository \ - :common:publishMavenJavaPublicationToMavenRepository \ - :crypto:publishMavenJavaPublicationToMavenRepository \ - :test-common:publishMavenJavaPublicationToMavenRepository \ - :sdk:publishMavenJavaPublicationToMavenRepository \ - --no-daemon \ - --stacktrace - - publish-android: - name: Publish Android artifacts - needs: publish-jvm - runs-on: ubuntu-latest permissions: contents: read steps: @@ -119,16 +84,129 @@ jobs: "platforms;android-34" \ "build-tools;34.0.0" - - name: Publish Android artifacts + - name: Verify Central bundle publish tasks exist + run: | + ./gradlew -q help --task :api:publishMavenJavaPublicationToCentralBundleRepository + ./gradlew -q help --task :common:publishMavenJavaPublicationToCentralBundleRepository + ./gradlew -q help --task :crypto:publishMavenJavaPublicationToCentralBundleRepository + ./gradlew -q help --task :test-common:publishMavenJavaPublicationToCentralBundleRepository + ./gradlew -q help --task :sdk:publishMavenJavaPublicationToCentralBundleRepository + ./gradlew -q help --task :crypto-android:publishMavenJavaPublicationToCentralBundleRepository + ./gradlew -q help --task :android-utils:publishMavenJavaPublicationToCentralBundleRepository + ./gradlew -q help --task :sdk-android:publishMavenJavaPublicationToCentralBundleRepository + + - name: Publish artifacts to local bundle repo env: - ORG_GRADLE_PROJECT_authentication_username: ${{ secrets.CENTRAL_SONATYPE_TOKEN_USERNAME }} - ORG_GRADLE_PROJECT_authentication_password: ${{ secrets.CENTRAL_SONATYPE_TOKEN_PASSWORD }} ORG_GRADLE_PROJECT_signingKey: ${{ secrets.CENTRAL_SONATYPE_SIGNING_KEY }} ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.CENTRAL_SONATYPE_SIGNING_PASSWORD }} run: | + rm -rf build/central-bundle-repo build/central-bundle.zip ./gradlew \ - :crypto-android:publishMavenJavaPublicationToMavenRepository \ - :android-utils:publishMavenJavaPublicationToMavenRepository \ - :sdk-android:publishMavenJavaPublicationToMavenRepository \ + :api:publishMavenJavaPublicationToCentralBundleRepository \ + :common:publishMavenJavaPublicationToCentralBundleRepository \ + :crypto:publishMavenJavaPublicationToCentralBundleRepository \ + :test-common:publishMavenJavaPublicationToCentralBundleRepository \ + :sdk:publishMavenJavaPublicationToCentralBundleRepository \ + :crypto-android:publishMavenJavaPublicationToCentralBundleRepository \ + :android-utils:publishMavenJavaPublicationToCentralBundleRepository \ + :sdk-android:publishMavenJavaPublicationToCentralBundleRepository \ --no-daemon \ --stacktrace + + - name: Validate bundle repo contains POMs + run: | + test -d build/central-bundle-repo + POM_COUNT=$(find build/central-bundle-repo -type f -name '*.pom' | wc -l | tr -d '[:space:]') + echo "POM count: ${POM_COUNT}" + if [ "${POM_COUNT}" = "0" ]; then + echo "ERROR: No .pom files found in build/central-bundle-repo" + find build/central-bundle-repo -maxdepth 6 -type f | head -n 200 + exit 1 + fi + + - name: Build Central bundle zip + run: | + test -d build/central-bundle-repo + + # Central bundle should not include repository metadata/module metadata. + find build/central-bundle-repo -type f -name 'maven-metadata.xml*' -delete + find build/central-bundle-repo -type f -name '*.module*' -delete + + while IFS= read -r -d '' file; do + md5sum "$file" | awk '{print $1}' > "$file.md5" + sha1sum "$file" | awk '{print $1}' > "$file.sha1" + done < <(find build/central-bundle-repo -type f \ + ! -name '*.asc' \ + ! -name '*.md5' \ + ! -name '*.sha1' \ + -print0) + + (cd build/central-bundle-repo && zip -q -r ../central-bundle.zip .) + ls -la build/central-bundle.zip + + - name: Upload Central bundle (debug) + if: always() + uses: actions/upload-artifact@v4 + with: + name: central-portal-bundle + path: build/central-bundle.zip + + - name: Upload Central bundle repo (debug) + if: always() + uses: actions/upload-artifact@v4 + with: + name: central-portal-bundle-repo + path: build/central-bundle-repo/ + + - name: Upload bundle to Sonatype Central Portal + env: + CENTRAL_USERNAME: ${{ secrets.CENTRAL_SONATYPE_TOKEN_USERNAME }} + CENTRAL_PASSWORD: ${{ secrets.CENTRAL_SONATYPE_TOKEN_PASSWORD }} + run: | + if [ -z "${CENTRAL_USERNAME}" ] || [ -z "${CENTRAL_PASSWORD}" ]; then + echo "ERROR: Missing Central token credentials" + exit 1 + fi + + CENTRAL_BEARER=$(printf "%s:%s" "${CENTRAL_USERNAME}" "${CENTRAL_PASSWORD}" | base64 | tr -d '\n') + DEPLOYMENT_ID=$(curl -sS \ + --fail \ + --header "Authorization: Bearer ${CENTRAL_BEARER}" \ + --form "bundle=@build/central-bundle.zip" \ + "https://central.sonatype.com/api/v1/publisher/upload?publishingType=AUTOMATIC&name=${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}") + + if [ -z "${DEPLOYMENT_ID}" ]; then + echo "ERROR: Central Portal did not return deployment ID" + exit 1 + fi + echo "Central deployment id: ${DEPLOYMENT_ID}" + echo "DEPLOYMENT_ID=${DEPLOYMENT_ID}" >> "$GITHUB_ENV" + + - name: Wait for Central Portal publish + env: + CENTRAL_USERNAME: ${{ secrets.CENTRAL_SONATYPE_TOKEN_USERNAME }} + CENTRAL_PASSWORD: ${{ secrets.CENTRAL_SONATYPE_TOKEN_PASSWORD }} + run: | + CENTRAL_BEARER=$(printf "%s:%s" "${CENTRAL_USERNAME}" "${CENTRAL_PASSWORD}" | base64 | tr -d '\n') + + for i in $(seq 1 60); do + STATUS_JSON=$(curl -sS --fail --request POST \ + --header "Authorization: Bearer ${CENTRAL_BEARER}" \ + "https://central.sonatype.com/api/v1/publisher/status?id=${DEPLOYMENT_ID}") + STATE=$(python3 -c 'import json,sys; print(json.load(sys.stdin).get("deploymentState",""))' <<<"${STATUS_JSON}") + echo "Central state: ${STATE}" + + if [ "${STATE}" = "PUBLISHED" ]; then + exit 0 + fi + if [ "${STATE}" = "FAILED" ]; then + echo "Central deployment FAILED" + echo "${STATUS_JSON}" + exit 1 + fi + + sleep 30 + done + + echo "Timed out waiting for Central Portal publish" + exit 1 diff --git a/build.gradle b/build.gradle index c0efbe3..b2670d7 100644 --- a/build.gradle +++ b/build.gradle @@ -139,8 +139,10 @@ final String BASE_VIRGIL_PACKAGE = 'com.virgilsecurity' final String SDK_PACKAGE = BASE_VIRGIL_PACKAGE + '.sdk' // Packages versions -final String SDK_VERSION = '7.4.0-rc4' -final String TEST_COMMON_VERSION = '0.1.3' +final String SDK_VERSION = '7.4.0-rc5' + +group SDK_PACKAGE +version SDK_VERSION subprojects { group SDK_PACKAGE @@ -150,8 +152,10 @@ subprojects { apply plugin: 'checkstyle' apply from: '../tests-verbal-output.gradle' - def authentication_username = getGradleOrSystemProperty('authentication_username', project) - def authentication_password = getGradleOrSystemProperty('authentication_password', project) + // Central bundle upload uses Maven repository layout; Gradle module metadata is unnecessary. + tasks.withType(org.gradle.api.publish.tasks.GenerateModuleMetadata).configureEach { + enabled = false + } def isAndroid = (it.name == 'crypto-android' || it.name == 'android-utils' @@ -159,7 +163,7 @@ subprojects { if (it.name == 'test-common') { group BASE_VIRGIL_PACKAGE - version TEST_COMMON_VERSION + version SDK_VERSION } if (!isAndroid) { @@ -226,16 +230,10 @@ subprojects { } developers { developer { - id = 'BuddahLD' - name = 'Danylo Oliinyk' - email = 'doliinyk@virgilsecurity.com' - organizationUrl = 'https://github.com/BuddahLD' - } - developer { - id = 'andrii-iakovenko' - name = 'Andrii Iakovenko' - email = 'andrii-iakovenko@gmail.com' - organizationUrl = 'https://github.com/andrii-iakovenko' + id = 'virgil-security-inc' + name = 'Virgil Security, Inc.' + email = 'support@virgilsecurity.com' + organizationUrl = 'https://www.virgilsecurity.com/' } } scm { @@ -246,18 +244,13 @@ subprojects { } } } - repositories { maven { - def releasesRepoUrl = "https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/" - def snapshotsRepoUrl = "https://central.sonatype.com/repository/maven-snapshots/" - url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl - credentials { - username "${authentication_username}" - password "${authentication_password}" - } + name = 'centralBundle' + url = uri("${rootProject.buildDir}/central-bundle-repo") } } + } signing { From 876f4d73d4c9380ec77817f2cd81554bf67a66c5 Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Thu, 12 Feb 2026 09:57:04 -0500 Subject: [PATCH 17/19] Add CHANGELOG.md --- CHANGELOG.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..26bdd70 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,41 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## 7.4.0-rc5 - 2026-02-12 + +### Added +- Added GitHub Actions CI workflow `.github/workflows/build-and-test.yml` for JVM and Android build/test coverage. +- Added GitHub Actions release workflow `.github/workflows/publish-release.yml` with tag/version validation and automated publishing. +- Added `scripts/encrypt-env.sh` and `scripts/decrypt-env.sh` for `env.json` management in local/CI environments. +- Added constructor-focused tests for `VirgilCardClient` URL handling in `sdk/src/test/java/com/virgilsecurity/sdk/client/VirgilCardClientConstructorTest.java`. + +### Changed +- Migrated publishing pipeline to Sonatype Central Portal bundle upload flow: + - publish all artifacts to local `build/central-bundle-repo`, + - build and upload `build/central-bundle.zip`, + - poll deployment status until `PUBLISHED`. +- Upgraded build toolchain and dependencies: + - Android Gradle Plugin to `8.5.2`, + - Gradle wrapper to `8.7`, + - Kotlin to `1.9.24`, + - `com.virgilsecurity.crypto` dependency to `0.17.2`. +- Migrated Android modules (`crypto-android`, `android-utils`, `sdk-android`) to modern AGP configuration: + - `namespace`, + - `compileSdk 34`, + - `targetSdk 34`, + - `singleVariant("release")` publishing. +- Updated `settings.gradle` to include Android modules only when Android SDK is configured. +- Updated `sdk` generated source wiring (`generateVirgilInfo`) to use a dedicated directory and explicit task dependencies for Gradle 8 task validation. +- Unified `test-common` published version with `SDK_VERSION` to avoid manual version drift. + +### Fixed +- Fixed `sourcesJar`/`compileJava` task ordering issues under Gradle 8 by declaring explicit dependencies and isolating generated sources. +- Updated `VirgilCardClient` service URL normalization to always resolve to `/card/v5/` on the same host/protocol/port. + +### Documentation +- Updated README build and runtime guidance (JDK/Android SDK requirements, local setup, test commands, encrypted env usage). +- Removed Travis CI in favor of GitHub Actions. + +### Notes +- `common` artifact remains published under `com.virgilsecurity:common` due to explicit group override in `common/build.gradle`. From 68df39efd16ed17e61b77b5ba0a8e1095cef0284 Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Thu, 12 Feb 2026 09:57:20 -0500 Subject: [PATCH 18/19] Bump SDK version to 7.4.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b2670d7..2d4a42d 100644 --- a/build.gradle +++ b/build.gradle @@ -139,7 +139,7 @@ final String BASE_VIRGIL_PACKAGE = 'com.virgilsecurity' final String SDK_PACKAGE = BASE_VIRGIL_PACKAGE + '.sdk' // Packages versions -final String SDK_VERSION = '7.4.0-rc5' +final String SDK_VERSION = '7.4.0' group SDK_PACKAGE version SDK_VERSION From f9bf0035454f250dacb917c5dffef1f865973b11 Mon Sep 17 00:00:00 2001 From: SergeySeroshtan Date: Thu, 12 Feb 2026 10:08:53 -0500 Subject: [PATCH 19/19] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26bdd70..84ff492 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file. -## 7.4.0-rc5 - 2026-02-12 +## 7.4.0 - 2026-02-12 ### Added - Added GitHub Actions CI workflow `.github/workflows/build-and-test.yml` for JVM and Android build/test coverage.