Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Address stores X509Certificate #11318

Merged
merged 19 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import static com.hedera.node.app.service.token.impl.TokenServiceImpl.STAKING_INFO_KEY;
import static com.hedera.node.app.state.merkle.AddresBookUtils.createPretendBookFrom;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.verify;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@

package com.hedera.node.app.state.merkle;

import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;

import com.swirlds.common.crypto.SerializablePublicKey;
import com.swirlds.common.platform.NodeId;
import com.swirlds.platform.crypto.SerializableX509Certificate;
import com.swirlds.platform.system.Platform;
import com.swirlds.platform.system.address.Address;
import com.swirlds.platform.system.address.AddressBook;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.List;

/**
Expand All @@ -33,8 +32,7 @@
public class AddresBookUtils {

public static AddressBook createPretendBookFrom(final Platform platform, final boolean withKeyDetails) {
final var pubKey = mock(PublicKey.class);
given(pubKey.getAlgorithm()).willReturn("EC");
final var cert = mock(X509Certificate.class);
final var address1 = new Address(
platform.getSelfId(),
"",
Expand All @@ -44,9 +42,8 @@ public static AddressBook createPretendBookFrom(final Platform platform, final b
-1,
"123456789",
-1,
new SerializablePublicKey(pubKey),
null,
new SerializablePublicKey(pubKey),
new SerializableX509Certificate(cert),
new SerializableX509Certificate(cert),
"");
final var address2 = new Address(
new NodeId(1),
Expand All @@ -57,9 +54,8 @@ public static AddressBook createPretendBookFrom(final Platform platform, final b
-1,
"123456789",
-1,
new SerializablePublicKey(pubKey),
null,
new SerializablePublicKey(pubKey),
new SerializableX509Certificate(cert),
new SerializableX509Certificate(cert),
"");
return new AddressBook(List.of(address1, address2));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.google.protobuf.ByteString;
import com.hedera.node.app.service.mono.cache.EntityMapWarmer;
Expand Down Expand Up @@ -925,7 +930,7 @@ void testGenesisState() {
ClassLoaderHelper.loadClassPathDependencies();
final var servicesState = tracked(new ServicesState());
final var platform = createMockPlatformWithCrypto();
final var addressBook = createPretendBookFrom(platform, true);
final var addressBook = createPretendBookFrom(platform, true, true);
given(platform.getAddressBook()).willReturn(addressBook);
final var recordsRunningHashLeaf = new RecordsRunningHashLeaf();
recordsRunningHashLeaf.setRunningHash(new RunningHash(EMPTY_HASH));
Expand Down Expand Up @@ -957,7 +962,7 @@ void updatesAddressBookWithZeroWeightOnGenesisStart() {
final var node1 = new NodeId(1);
given(platform.getSelfId()).willReturn(node0);

final var pretendAddressBook = createPretendBookFrom(platform, true);
final var pretendAddressBook = createPretendBookFrom(platform, true, false);

final MerkleMap<EntityNum, MerkleStakingInfo> stakingMap = subject.getChild(StateChildIndices.STAKING_INFO);
assertEquals(1, stakingMap.size());
Expand All @@ -981,7 +986,7 @@ void updatesAddressBookWithZeroWeightForNewNodes() {

given(platform.getSelfId()).willReturn(node0);

final var pretendAddressBook = createPretendBookFrom(platform, true);
final var pretendAddressBook = createPretendBookFrom(platform, true, false);
final MerkleMap<EntityNum, MerkleStakingInfo> stakingMap = subject.getChild(StateChildIndices.STAKING_INFO);
assertEquals(1, stakingMap.size());
assertEquals(0, stakingMap.get(EntityNum.fromLong(0L)).getWeight());
Expand All @@ -1008,7 +1013,7 @@ void updatesAddressBookWithNonZeroWeightsOnGenesisStartIfStakesExist() {
final var node0 = new NodeId(0);
final var node1 = new NodeId(1);
given(platform.getSelfId()).willReturn(node0);
final var pretendAddressBook = createPretendBookFrom(platform, true);
final var pretendAddressBook = createPretendBookFrom(platform, true, false);

final MerkleMap<EntityNum, MerkleStakingInfo> stakingMap = subject.getChild(StateChildIndices.STAKING_INFO);
assertEquals(1, stakingMap.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,30 @@

package com.hedera.test.utils;

import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;

import com.swirlds.common.crypto.SerializablePublicKey;
import com.swirlds.common.platform.NodeId;
import com.swirlds.platform.crypto.SerializableX509Certificate;
import com.swirlds.platform.system.Platform;
import com.swirlds.platform.system.address.Address;
import com.swirlds.platform.system.address.AddressBook;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.List;
import org.mockito.Mockito;

/**
* Utilities for constructing AddressBook needed for tests
*/
public class AddresBookUtils {

public static AddressBook createPretendBookFrom(final Platform platform, final boolean withKeyDetails) {
final var pubKey = mock(PublicKey.class);
given(pubKey.getAlgorithm()).willReturn("EC");
public static AddressBook createPretendBookFrom(
final Platform platform, final boolean withKeyDetails, final boolean mockPublicKey) {
final var publicKey = mock(PublicKey.class);
final var cert = mock(X509Certificate.class);
if (mockPublicKey) {
Mockito.when(cert.getPublicKey()).thenReturn(publicKey);
}
final var address1 = new Address(
platform.getSelfId(),
"",
Expand All @@ -44,9 +49,8 @@ public static AddressBook createPretendBookFrom(final Platform platform, final b
-1,
"123456789",
-1,
new SerializablePublicKey(pubKey),
null,
new SerializablePublicKey(pubKey),
new SerializableX509Certificate(cert),
new SerializableX509Certificate(cert),
"");
final var address2 = new Address(
new NodeId(1),
Expand All @@ -57,9 +61,8 @@ public static AddressBook createPretendBookFrom(final Platform platform, final b
-1,
"123456789",
-1,
new SerializablePublicKey(pubKey),
null,
new SerializablePublicKey(pubKey),
new SerializableX509Certificate(cert),
new SerializableX509Certificate(cert),
"");
return new AddressBook(List.of(address1, address2));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public final class CryptoConstants {
public static final int SIG_SIZE_BYTES = 384;
// size of each symmetric key, in bytes
public static final int SYM_KEY_SIZE_BYTES = 32; // 256 bits
// the algorithms and providers to use (AGR is key agreement, ENC is encryption, SIG is signatures)
// the algorithms and providers to use (AGR is key agreement, SIG is signatures)
public static final String AGR_TYPE = "EC";
public static final String AGR_PROVIDER = "SunEC";
public static final String SIG_TYPE1 = "RSA"; // or SHA384withRSA
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ static String distinguishedName(String commonName) {
* by a Certificate Authority (CA), whose name is CaDistinguishedName and whose key pair is CaPair.
* <p>
* In Swirlds, each member creates a separate certificate for each of their 3 key pairs (signing,
* agreement, encryption). The signing certificate is self-signed, and is treated as if it were a CA.
* agreement). The signing certificate is self-signed, and is treated as if it were a CA.
* The other two certificates are each signed by the signing key pair. So for either of them, the
* complete certificate chain consists of two certificates.
* <p>
Expand All @@ -205,7 +205,7 @@ static String distinguishedName(String commonName) {
* @return the self-signed certificate
* @throws KeyGeneratingException in any issue occurs
*/
static X509Certificate generateCertificate(
public static X509Certificate generateCertificate(
String distinguishedName,
KeyPair pair,
String caDistinguishedName,
Expand Down Expand Up @@ -381,7 +381,7 @@ static Map<NodeId, KeysAndCerts> loadKeysAndCerts(
* @throws KeyStoreException if there is no provider that supports {@link CryptoConstants#KEYSTORE_TYPE}
*/
@NonNull
static Map<NodeId, KeysAndCerts> generateKeysAndCerts(@NonNull final AddressBook addressBook)
public static Map<NodeId, KeysAndCerts> generateKeysAndCerts(@NonNull final AddressBook addressBook)
throws ExecutionException, InterruptedException, KeyStoreException {
Objects.requireNonNull(addressBook, ADDRESS_BOOK_MUST_NOT_BE_NULL);

Expand Down Expand Up @@ -448,10 +448,10 @@ static void copyPublicKeys(final PublicStores publicStores, final AddressBook ad
final NodeId nodeId = addressBook.getNodeId(i);
final Address add = addressBook.getAddress(nodeId);
final String name = nameToAlias(add.getSelfName());
PublicKey sigKey = publicStores.getPublicKey(KeyCertPurpose.SIGNING, name);
PublicKey agrKey = publicStores.getPublicKey(KeyCertPurpose.AGREEMENT, name);
final X509Certificate sigCert = publicStores.getCertificate(KeyCertPurpose.SIGNING, name);
final X509Certificate agrCert = publicStores.getCertificate(KeyCertPurpose.AGREEMENT, name);
addressBook.add(
addressBook.getAddress(nodeId).copySetSigPublicKey(sigKey).copySetAgreePublicKey(agrKey));
addressBook.getAddress(nodeId).copySetSigCert(sigCert).copySetAgreeCert(agrCert));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public record KeysAndCerts(
* pairs will be named "s-alice", "e-alice", "a-alice" for signing, encrypting, and key
* agreement.
* @param privateKeyStore
* read the 3 keyPairs (signing,agreement,encryption) from this store
* read the 2 keyPairs (signing,agreement) from this store
* @param publicStores
* all public certificates
* @throws KeyStoreException
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* 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.swirlds.platform.crypto;

import com.swirlds.common.io.SelfSerializable;
import com.swirlds.common.io.streams.SerializableDataInputStream;
import com.swirlds.common.io.streams.SerializableDataOutputStream;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Objects;

/**
* A serializable wrapper for an {@link X509Certificate} instance.
*/
public class SerializableX509Certificate implements SelfSerializable {

/**
* There is no upper bound on the size of certs.
* A basic cert is around 1KB, with the possibility of certificate chains, this increases.
* Our use case includes RSA self-signed certificates with an EC TLS certificate signed by the RSA cert.
* 8KB is probably more space than needed for our use case, but better to be safe than sorry.
*/
public static final int MAX_CERT_LENGTH = 1024 * 8;

private static final long CLASS_ID = 0x2332728f044c1c87L;

private static final class ClassVersion {
public static final int ORIGINAL = 1;
}

private X509Certificate certificate;

/**
* Constructs a new instance of {@link SerializableX509Certificate}.
*
* @param certificate the {@link X509Certificate} instance
*/
public SerializableX509Certificate(@NonNull final X509Certificate certificate) {
this.certificate = Objects.requireNonNull(certificate);
}

/**
* Constructs a new instance of {@link SerializableX509Certificate} for deserialization.
*/
public SerializableX509Certificate() {
// empty constructor for deserialization
}

/**
* Gets the {@link X509Certificate} instance.
*
* @return the {@link X509Certificate} instance
*/
@NonNull
public X509Certificate getCertificate() {
return Objects.requireNonNull(certificate);
}

/**
* {@inheritDoc}
*/
@Override
public long getClassId() {
return CLASS_ID;
}

/**
* {@inheritDoc}
*/
@Override
public int getVersion() {
return ClassVersion.ORIGINAL;
}

/**
* {@inheritDoc}
*/
@Override
public void serialize(@NonNull SerializableDataOutputStream out) throws IOException {
try {
final byte[] encoded = certificate.getEncoded();
out.writeByteArray(encoded);
} catch (CertificateEncodingException e) {
throw new IOException("Not able to serialize x509 certificate", e);
}
}

/**
* {@inheritDoc}
*/
@Override
public void deserialize(@NonNull SerializableDataInputStream in, int version) throws IOException {
final byte[] encoded = in.readByteArray(MAX_CERT_LENGTH);
try {
certificate = (X509Certificate)
CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(encoded));
} catch (final CertificateException e) {
throw new IOException("Not able to deserialize x509 certificate", e);
}
}

/**
* Gets the public key from the certificate.
*
* @return the public key
*/
@NonNull
public PublicKey getPublicKey() {
return Objects.requireNonNull(certificate.getPublicKey(), "PublicKey is null");
}
}