diff --git a/gradle.properties b/gradle.properties index 830a0e9e..e00ad894 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=6.0.0 -iexecCommonVersion=5.4.0 +version=6.1.0 +iexecCommonVersion=5.9.0 nexusUser= nexusPassword= \ No newline at end of file diff --git a/src/main/java/com/iexec/sms/tee/challenge/TeeChallenge.java b/src/main/java/com/iexec/sms/tee/challenge/TeeChallenge.java index 7d64a2a8..91e9336c 100644 --- a/src/main/java/com/iexec/sms/tee/challenge/TeeChallenge.java +++ b/src/main/java/com/iexec/sms/tee/challenge/TeeChallenge.java @@ -42,6 +42,6 @@ public class TeeChallenge { public TeeChallenge(String taskId) throws Exception { this.taskId = taskId; - this.credentials = new EthereumCredentials(); + this.credentials = EthereumCredentials.generate(); } } diff --git a/src/main/java/com/iexec/sms/tee/challenge/TeeChallengeService.java b/src/main/java/com/iexec/sms/tee/challenge/TeeChallengeService.java index ee4fafa3..992db397 100644 --- a/src/main/java/com/iexec/sms/tee/challenge/TeeChallengeService.java +++ b/src/main/java/com/iexec/sms/tee/challenge/TeeChallengeService.java @@ -71,8 +71,7 @@ public void encryptChallengeKeys(TeeChallenge teeChallenge) { EthereumCredentials credentials = teeChallenge.getCredentials(); if (!credentials.isEncrypted()) { String encPrivateKey = encryptionService.encrypt(credentials.getPrivateKey()); - String encPublicKey = encryptionService.encrypt(credentials.getPublicKey()); - credentials.setEncryptedKeys(encPrivateKey, encPublicKey); + credentials.setEncryptedPrivateKey(encPrivateKey); } } @@ -80,8 +79,7 @@ public void decryptChallengeKeys(TeeChallenge teeChallenge) { EthereumCredentials credentials = teeChallenge.getCredentials(); if (credentials.isEncrypted()) { String privateKey = encryptionService.decrypt(credentials.getPrivateKey()); - String publicKey = encryptionService.decrypt(credentials.getPublicKey()); - credentials.setPlainKeys(privateKey, publicKey); + credentials.setPlainTextPrivateKey(privateKey); } } } diff --git a/src/main/java/com/iexec/sms/utils/EthereumCredentials.java b/src/main/java/com/iexec/sms/utils/EthereumCredentials.java index baa71c74..8c16a909 100644 --- a/src/main/java/com/iexec/sms/utils/EthereumCredentials.java +++ b/src/main/java/com/iexec/sms/utils/EthereumCredentials.java @@ -16,23 +16,26 @@ package com.iexec.sms.utils; -import java.math.BigInteger; - -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; - +import com.iexec.common.utils.CredentialsUtils; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; import org.hibernate.annotations.GenericGenerator; import org.web3j.crypto.ECKeyPair; import org.web3j.crypto.Keys; import org.web3j.utils.Numeric; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.Getter; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +/** + * Domain entity + */ @Data @Getter +@NoArgsConstructor //for hibernate @AllArgsConstructor @Entity public class EthereumCredentials { @@ -42,33 +45,43 @@ public class EthereumCredentials { @GenericGenerator(name = "system-uuid", strategy = "uuid") private String id; - private String address; private String privateKey; - private String publicKey; - private boolean isEncrypted; // private & public keys + private boolean isEncrypted; + /* + * Address is required since recovering from private key is not possible + * from an encrypted private key state. + */ + private String address; - public EthereumCredentials() throws Exception { - ECKeyPair ecKeyPair = Keys.createEcKeyPair(); - this.address = Numeric.prependHexPrefix(Keys.getAddress(ecKeyPair)); - setPlainKeys(toHex(ecKeyPair.getPrivateKey()), - toHex(ecKeyPair.getPublicKey())); + private EthereumCredentials(String privateKey, String address) { + this.setPlainTextPrivateKey(privateKey); + this.address = address; } - public void setPlainKeys(String privateKey, String publicKey) { - this.setKeys(privateKey, publicKey, false); + /** + * Build EthereumCredentials from a random private key (generated from a + * secure random source). + * + * @return Ethereum credentials + * @throws java.security.GeneralSecurityException exception if failed to + * generate credentials + */ + public static EthereumCredentials generate() throws java.security.GeneralSecurityException { + ECKeyPair randomEcKeyPair = Keys.createEcKeyPair(); + String privateKey = + Numeric.toHexStringWithPrefixZeroPadded(randomEcKeyPair.getPrivateKey(), + Keys.PRIVATE_KEY_LENGTH_IN_HEX);//hex-string size of 32 bytes (64) + return new EthereumCredentials(privateKey, CredentialsUtils.getAddress(privateKey)); } - public void setEncryptedKeys(String privateKey, String publicKey) { - this.setKeys(privateKey, publicKey, true); + public void setPlainTextPrivateKey(String privateKey) { + this.privateKey = privateKey; + this.isEncrypted = false; } - private void setKeys(String privateKey, String publicKey, boolean isEncrypted) { + public void setEncryptedPrivateKey(String privateKey) { this.privateKey = privateKey; - this.publicKey = publicKey; - this.isEncrypted = isEncrypted; + this.isEncrypted = true; } - private String toHex(BigInteger input) { - return Numeric.prependHexPrefix(input.toString(16)); - } -} +} \ No newline at end of file diff --git a/src/test/java/com/iexec/sms/tee/challenge/TeeChallengeServiceTests.java b/src/test/java/com/iexec/sms/tee/challenge/TeeChallengeServiceTests.java index cf4e1a75..50f96a84 100644 --- a/src/test/java/com/iexec/sms/tee/challenge/TeeChallengeServiceTests.java +++ b/src/test/java/com/iexec/sms/tee/challenge/TeeChallengeServiceTests.java @@ -38,9 +38,7 @@ public class TeeChallengeServiceTests { private final static String TASK_ID = "0x123"; private final static String PLAIN_PRIVATE = "plainPrivate"; - private final static String PLAIN_PUBLIC = "plainPublic"; private final static String ENC_PRIVATE = "encPrivate"; - private final static String ENC_PUBLIC = "encPublic"; @Mock private TeeChallengeRepository teeChallengeRepository; @@ -58,7 +56,7 @@ public void beforeEach() { private TeeChallenge getEncryptedTeeChallengeStub() throws Exception { TeeChallenge teeChallenge = new TeeChallenge(TASK_ID); - teeChallenge.getCredentials().setEncryptedKeys(ENC_PRIVATE, ENC_PUBLIC); + teeChallenge.getCredentials().setEncryptedPrivateKey(ENC_PRIVATE); return teeChallenge; } @@ -68,8 +66,8 @@ public void shouldGetExistingChallengeWithoutDecryptingKeys() throws Exception { when(teeChallengeRepository.findByTaskId(TASK_ID)).thenReturn(Optional.of(encryptedTeeChallengeStub)); Optional oTeeChallenge = teeChallengeService.getOrCreate(TASK_ID, false); + assertThat(oTeeChallenge).isPresent(); assertThat(oTeeChallenge.get().getCredentials().getPrivateKey()).isEqualTo(ENC_PRIVATE); - assertThat(oTeeChallenge.get().getCredentials().getPublicKey()).isEqualTo(ENC_PUBLIC); verify(encryptionService, never()).decrypt(anyString()); } @@ -77,24 +75,24 @@ public void shouldGetExistingChallengeWithoutDecryptingKeys() throws Exception { public void shouldGetExistingChallengeAndDecryptKeys() throws Exception { TeeChallenge encryptedTeeChallengeStub = getEncryptedTeeChallengeStub(); when(teeChallengeRepository.findByTaskId(TASK_ID)).thenReturn(Optional.of(encryptedTeeChallengeStub)); - when(encryptionService.decrypt(anyString())).thenReturn(PLAIN_PRIVATE, PLAIN_PUBLIC); + when(encryptionService.decrypt(anyString())).thenReturn(PLAIN_PRIVATE); Optional oTeeChallenge = teeChallengeService.getOrCreate(TASK_ID, true); + assertThat(oTeeChallenge).isPresent(); assertThat(oTeeChallenge.get().getCredentials().getPrivateKey()).isEqualTo(PLAIN_PRIVATE); - assertThat(oTeeChallenge.get().getCredentials().getPublicKey()).isEqualTo(PLAIN_PUBLIC); - verify(encryptionService, times(2)).decrypt(anyString()); + verify(encryptionService, times(1)).decrypt(anyString()); } @Test public void shouldCreateNewChallengeWithoutDecryptingKeys() throws Exception { TeeChallenge encryptedTeeChallengeStub = getEncryptedTeeChallengeStub(); when(teeChallengeRepository.findByTaskId(TASK_ID)).thenReturn(Optional.empty()); - when(encryptionService.encrypt(anyString())).thenReturn(ENC_PRIVATE, ENC_PUBLIC); + when(encryptionService.encrypt(anyString())).thenReturn(ENC_PRIVATE); when(teeChallengeRepository.save(any())).thenReturn(encryptedTeeChallengeStub); Optional oTeeChallenge = teeChallengeService.getOrCreate(TASK_ID, false); + assertThat(oTeeChallenge).isPresent(); assertThat(oTeeChallenge.get().getCredentials().getPrivateKey()).isEqualTo(ENC_PRIVATE); - assertThat(oTeeChallenge.get().getCredentials().getPublicKey()).isEqualTo(ENC_PUBLIC); verify(encryptionService, never()).decrypt(anyString()); } @@ -102,33 +100,31 @@ public void shouldCreateNewChallengeWithoutDecryptingKeys() throws Exception { public void shouldCreateNewChallengeAndDecryptKeys() throws Exception { TeeChallenge encryptedTeeChallengeStub = getEncryptedTeeChallengeStub(); when(teeChallengeRepository.findByTaskId(TASK_ID)).thenReturn(Optional.empty()); - when(encryptionService.encrypt(anyString())).thenReturn(ENC_PRIVATE, ENC_PUBLIC); + when(encryptionService.encrypt(anyString())).thenReturn(ENC_PRIVATE); when(teeChallengeRepository.save(any())).thenReturn(encryptedTeeChallengeStub); - when(encryptionService.decrypt(anyString())).thenReturn(PLAIN_PRIVATE, PLAIN_PUBLIC); + when(encryptionService.decrypt(anyString())).thenReturn(PLAIN_PRIVATE); Optional oTeeChallenge = teeChallengeService.getOrCreate(TASK_ID, true); + assertThat(oTeeChallenge).isPresent(); assertThat(oTeeChallenge.get().getCredentials().getPrivateKey()).isEqualTo(PLAIN_PRIVATE); - assertThat(oTeeChallenge.get().getCredentials().getPublicKey()).isEqualTo(PLAIN_PUBLIC); } @Test public void shouldEncryptChallengeKeys() throws Exception { TeeChallenge teeChallenge = new TeeChallenge(TASK_ID); - when(encryptionService.encrypt(anyString())).thenReturn(ENC_PRIVATE, ENC_PUBLIC); + when(encryptionService.encrypt(anyString())).thenReturn(ENC_PRIVATE); teeChallengeService.encryptChallengeKeys(teeChallenge); assertThat(teeChallenge.getCredentials().getPrivateKey()).isEqualTo(ENC_PRIVATE); - assertThat(teeChallenge.getCredentials().getPublicKey()).isEqualTo(ENC_PUBLIC); } @Test public void shouldDecryptChallengeKeys() throws Exception { TeeChallenge teeChallenge = new TeeChallenge(TASK_ID); teeChallenge.getCredentials().setEncrypted(true); - when(encryptionService.decrypt(anyString())).thenReturn(PLAIN_PRIVATE, PLAIN_PUBLIC); + when(encryptionService.decrypt(anyString())).thenReturn(PLAIN_PRIVATE); teeChallengeService.decryptChallengeKeys(teeChallenge); assertThat(teeChallenge.getCredentials().getPrivateKey()).isEqualTo(PLAIN_PRIVATE); - assertThat(teeChallenge.getCredentials().getPublicKey()).isEqualTo(PLAIN_PUBLIC); } } \ No newline at end of file diff --git a/src/test/java/com/iexec/sms/tee/session/palaemon/PalaemonSessionServiceTests.java b/src/test/java/com/iexec/sms/tee/session/palaemon/PalaemonSessionServiceTests.java index ff2d3891..51ffb39a 100644 --- a/src/test/java/com/iexec/sms/tee/session/palaemon/PalaemonSessionServiceTests.java +++ b/src/test/java/com/iexec/sms/tee/session/palaemon/PalaemonSessionServiceTests.java @@ -234,7 +234,7 @@ public void shouldGetPostComputePalaemonTokens() throws Exception { .thenReturn(Optional.of(storageSecret)); TeeChallenge challenge = TeeChallenge.builder() - .credentials(new EthereumCredentials()) + .credentials(EthereumCredentials.generate()) .build(); when(teeChallengeService.getOrCreate(TASK_ID, true)) .thenReturn(Optional.of(challenge)); diff --git a/src/test/java/com/iexec/sms/utils/EthereumCredentialsTest.java b/src/test/java/com/iexec/sms/utils/EthereumCredentialsTest.java new file mode 100644 index 00000000..97847712 --- /dev/null +++ b/src/test/java/com/iexec/sms/utils/EthereumCredentialsTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021 IEXEC BLOCKCHAIN TECH + * + * 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 com.iexec.sms.utils; + +import com.iexec.common.utils.BytesUtils; +import com.iexec.common.utils.EthAddress; +import org.junit.jupiter.api.Test; +import org.web3j.crypto.Credentials; + +import java.security.GeneralSecurityException; + +import static org.junit.jupiter.api.Assertions.*; + +class EthereumCredentialsTest { + + @Test + void generate() throws GeneralSecurityException { + EthereumCredentials ethereumCredentials = EthereumCredentials.generate(); + assertFalse(ethereumCredentials.isEncrypted()); + assertTrue(BytesUtils.isNonZeroedBytes32(ethereumCredentials.getPrivateKey())); + String address = ethereumCredentials.getAddress(); + assertTrue(EthAddress.validate(address) + && BytesUtils.isNonZeroedHexStringWithPrefixAndProperBytesSize(address, + 20)); // non zeroed + } + + @Test + void shouldMatchAddressOfGeneratedCredentials() throws GeneralSecurityException { + EthereumCredentials generatedCredentials = EthereumCredentials.generate(); + String expectedAddress = + Credentials.create(generatedCredentials.getPrivateKey()).getAddress(); + assertEquals(expectedAddress, generatedCredentials.getAddress()); + } + +} \ No newline at end of file