Repository: wultra/powerauth-crypto
Pull request #186 (2018-09-10 18:37:24)

Crypto 3.0: update activation status blob


Patch:
diff --git a/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/client/activation/PowerAuthClientActivation.java b/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/client/activation/PowerAuthClientActivation.java
index 49373f60..cf0faf49 100644
--- a/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/client/activation/PowerAuthClientActivation.java
+++ b/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/client/activation/PowerAuthClientActivation.java
@@ -227,7 +227,8 @@ public class PowerAuthClientActivation {
                 // return mock status in case byte array has weird length
                 ActivationStatusBlobInfo statusInfo = new ActivationStatusBlobInfo();
                 statusInfo.setActivationStatus((byte) 5);
-                statusInfo.setCounter(0L);
+                statusInfo.setCurrentVersion((byte) 3);
+                statusInfo.setUpgradeVersion((byte) 3);
                 statusInfo.setFailedAttempts((byte) 0);
                 statusInfo.setMaxFailedAttempts((byte) 5);
                 statusInfo.setValid(false);
@@ -245,13 +246,16 @@ public class PowerAuthClientActivation {
 
             // check if the prefix is OK
             int prefix = buffer.getInt(0);
-            statusInfo.setValid(prefix == 0xDEC0DED1);
+            statusInfo.setValid(prefix == ActivationStatusBlobInfo.ACTIVATION_STATUS_MAGIC_VALUE);
 
             // fetch the activation status byte
             statusInfo.setActivationStatus(buffer.get(4));
 
-            // fetch the counter info
-            statusInfo.setCounter(buffer.getLong(5));
+            // fetch the current version status byte
+            statusInfo.setCurrentVersion(buffer.get(5));
+
+            // fetch the upgrade version status byte
+            statusInfo.setUpgradeVersion(buffer.get(6));
 
             // fetch the failed attempt count
             statusInfo.setFailedAttempts(buffer.get(13));
diff --git a/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/lib/model/ActivationStatusBlobInfo.java b/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/lib/model/ActivationStatusBlobInfo.java
index d2ca7f6a..82703f79 100644
--- a/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/lib/model/ActivationStatusBlobInfo.java
+++ b/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/lib/model/ActivationStatusBlobInfo.java
@@ -24,9 +24,12 @@ package io.getlime.security.powerauth.crypto.lib.model;
  */
 public class ActivationStatusBlobInfo {
 
+    public static final int ACTIVATION_STATUS_MAGIC_VALUE = 0xDEC0DED1;
+
     private boolean valid;
     private byte activationStatus;
-    private long counter;
+    private byte currentVersion;
+    private byte upgradeVersion;
     private byte failedAttempts;
     private byte maxFailedAttempts;
 
@@ -64,19 +67,35 @@ public class ActivationStatusBlobInfo {
     }
 
     /**
-     * Get activation counter value.
-     * @return Activation counter value.
+     * Get current crypto protocol version.
+     * @return Current crypto protocol version.
+     */
+    public byte getCurrentVersion() {
+        return currentVersion;
+    }
+
+    /**
+     * Set current crypto protocol version.
+     * @param currentVersion Current crypto protocol version.
+     */
+    public void setCurrentVersion(byte currentVersion) {
+        this.currentVersion = currentVersion;
+    }
+
+    /**
+     * Get crypto version for possible migration.
+     * @return Crypto version for possible migration.
      */
-    public long getCounter() {
-        return counter;
+    public byte getUpgradeVersion() {
+        return upgradeVersion;
     }
 
     /**
-     * Set activation counter value.
-     * @param counter Åctivation counter value.
+     * Set crypto version for possible migration.
+     * @param upgradeVersion Crypto version for possible migration.
      */
-    public void setCounter(long counter) {
-        this.counter = counter;
+    public void setUpgradeVersion(byte upgradeVersion) {
+        this.upgradeVersion = upgradeVersion;
     }
 
     /**
diff --git a/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/server/activation/PowerAuthServerActivation.java b/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/server/activation/PowerAuthServerActivation.java
index eb8850b2..40071326 100644
--- a/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/server/activation/PowerAuthServerActivation.java
+++ b/powerauth-java-crypto/src/main/java/io/getlime/security/powerauth/crypto/server/activation/PowerAuthServerActivation.java
@@ -20,6 +20,7 @@ import io.getlime.security.powerauth.crypto.client.activation.PowerAuthClientAct
 import io.getlime.security.powerauth.crypto.lib.config.PowerAuthConfiguration;
 import io.getlime.security.powerauth.crypto.lib.generator.IdentifierGenerator;
 import io.getlime.security.powerauth.crypto.lib.generator.KeyGenerator;
+import io.getlime.security.powerauth.crypto.lib.model.ActivationStatusBlobInfo;
 import io.getlime.security.powerauth.crypto.lib.util.AESEncryptionUtils;
 import io.getlime.security.powerauth.crypto.lib.util.ECPublicKeyFingerprint;
 import io.getlime.security.powerauth.crypto.lib.util.HMACHashUtilities;
@@ -243,25 +244,29 @@ public class PowerAuthServerActivation {
     /**
      * Returns an encrypted status blob as described in PowerAuth 2.0 Specification.
      * @param statusByte Byte determining the status of the activation.
-     * @param counter Bytes with a counter information.
+     * @param currentVersionByte Current crypto protocol version.
+     * @param upgradeVersionByte Crypto version for possible migration.
      * @param failedAttempts Number of failed attempts at the moment.
      * @param maxFailedAttempts Number of allowed failed attempts.
      * @param transportKey A key used to protect the transport.
      * @return Encrypted status blob
      * @throws InvalidKeyException When invalid key is provided.
      */
-    public byte[] encryptedStatusBlob(byte statusByte, long counter, byte failedAttempts, byte maxFailedAttempts, SecretKey transportKey)
+    public byte[] encryptedStatusBlob(byte statusByte, byte currentVersionByte, byte upgradeVersionByte, byte failedAttempts, byte maxFailedAttempts, SecretKey transportKey)
             throws InvalidKeyException {
         try {
             byte[] padding = new KeyGenerator().generateRandomBytes(17);
             byte[] zeroIv = new byte[16];
+            byte[] reservedBytes = new byte[6];
             byte[] statusBlob = ByteBuffer.allocate(32)
-                    .putInt(0xDEC0DED1)     // 4 bytes
-                    .put(statusByte)        // 1 byte
-                    .putLong(counter)        // 8 bytes
-                    .put(failedAttempts)    // 1 byte
-                    .put(maxFailedAttempts) // 1 byte
-                    .put(padding)           // 17 bytes
+                    .putInt(ActivationStatusBlobInfo.ACTIVATION_STATUS_MAGIC_VALUE)     // 4 bytes
+                    .put(statusByte)         // 1 byte
+                    .put(currentVersionByte) // 1 byte
+                    .put(upgradeVersionByte) // 1 byte
+                    .put(reservedBytes)      // 6 bytes
+                    .put(failedAttempts)     // 1 byte
+                    .put(maxFailedAttempts)  // 1 byte
+                    .put(padding)            // 17 bytes
                     .array();
             AESEncryptionUtils aes = new AESEncryptionUtils();
             return aes.encrypt(statusBlob, zeroIv, transportKey, "AES/CBC/NoPadding");


Test patch:
diff --git a/powerauth-java-crypto/src/test/java/io/getlime/security/powerauth/crypto/activation/ActivationStatusBlobInfoTest.java b/powerauth-java-crypto/src/test/java/io/getlime/security/powerauth/crypto/activation/ActivationStatusBlobInfoTest.java
new file mode 100644
index 00000000..cf2e9127
--- /dev/null
+++ b/powerauth-java-crypto/src/test/java/io/getlime/security/powerauth/crypto/activation/ActivationStatusBlobInfoTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2018 Wultra s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.getlime.security.powerauth.crypto.activation;
+
+import io.getlime.security.powerauth.crypto.client.activation.PowerAuthClientActivation;
+import io.getlime.security.powerauth.crypto.lib.config.PowerAuthConfiguration;
+import io.getlime.security.powerauth.crypto.lib.generator.KeyGenerator;
+import io.getlime.security.powerauth.crypto.lib.model.ActivationStatusBlobInfo;
+import io.getlime.security.powerauth.crypto.lib.util.AESEncryptionUtils;
+import io.getlime.security.powerauth.crypto.server.activation.PowerAuthServerActivation;
+import io.getlime.security.powerauth.crypto.server.keyfactory.PowerAuthServerKeyFactory;
+import io.getlime.security.powerauth.provider.CryptoProviderUtilFactory;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.SecretKey;
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.Security;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test activation status blob.
+ */
+public class ActivationStatusBlobInfoTest {
+
+    private final PowerAuthServerKeyFactory powerAuthServerKeyFactory = new PowerAuthServerKeyFactory();
+
+    /**
+     * Add crypto providers.
+     */
+    @Before
+    public void setUp() {
+        // Add Bouncy Castle Security Provider
+        Security.addProvider(new BouncyCastleProvider());
+        PowerAuthConfiguration.INSTANCE.setKeyConvertor(CryptoProviderUtilFactory.getCryptoProviderUtils());
+    }
+
+    @Test
+    public void testActivationStatusBlob() throws  InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
+        final PowerAuthServerActivation serverActivation = new PowerAuthServerActivation();
+        final PowerAuthClientActivation clientActivation = new PowerAuthClientActivation();
+        // Simulate generating of device and server key pairs
+        final KeyGenerator keyGenerator = new KeyGenerator();
+        final KeyPair keyPairDevice = keyGenerator.generateKeyPair();
+        final KeyPair keyPairServer = keyGenerator.generateKeyPair();
+        // Compute shared master secret key
+        final SecretKey masterSecretKey = powerAuthServerKeyFactory.generateServerMasterSecretKey(keyPairServer.getPrivate(), keyPairDevice.getPublic());
+        // Derive transport key
+        final SecretKey transportKey = powerAuthServerKeyFactory.generateServerTransportKey(masterSecretKey);
+        // Encrypt status blob with transport key
+        byte[] encryptedStatusBlob = serverActivation.encryptedStatusBlob((byte) 3,(byte) 2,(byte) 3,(byte) 1,(byte) 5, transportKey);
+        // Decrypt status blob with transport key
+        AESEncryptionUtils aes = new AESEncryptionUtils();
+        byte[] zeroIv = new byte[16];
+        byte[] statusBlob = aes.decrypt(encryptedStatusBlob, zeroIv, transportKey, "AES/CBC/NoPadding");
+        // Status blob bytes 0 ... 14 are deterministic, verify them
+        ByteBuffer buffer = ByteBuffer.wrap(statusBlob);
+        assertEquals(ActivationStatusBlobInfo.ACTIVATION_STATUS_MAGIC_VALUE, buffer.getInt(0));
+        assertEquals((byte) 3, buffer.get(4));
+        assertEquals((byte) 2, buffer.get(5));
+        assertEquals((byte) 3, buffer.get(6));
+        assertEquals((byte) 0, buffer.get(7));
+        assertEquals((byte) 0, buffer.get(8));
+        assertEquals((byte) 0, buffer.get(9));
+        assertEquals((byte) 0, buffer.get(10));
+        assertEquals((byte) 0, buffer.get(11));
+        assertEquals((byte) 0, buffer.get(12));
+        assertEquals((byte) 1, buffer.get(13));
+        assertEquals((byte) 5, buffer.get(14));
+        // Verify decoded status blob used in client activation
+        final ActivationStatusBlobInfo statusBlobDecoded = clientActivation.getStatusFromEncryptedBlob(encryptedStatusBlob, transportKey);
+        assertEquals(3, statusBlobDecoded.getActivationStatus());
+        assertEquals(2, statusBlobDecoded.getCurrentVersion());
+        assertEquals(3, statusBlobDecoded.getUpgradeVersion());
+        assertEquals(1, statusBlobDecoded.getFailedAttempts());
+        assertEquals(5, statusBlobDecoded.getMaxFailedAttempts());
+        assertTrue(statusBlobDecoded.isValid());
+    }
+}