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

Add PCLI command to sign account balance files #6264

Merged
merged 19 commits into from Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions hedera-node/cli-clients/build.gradle.kts
Expand Up @@ -33,6 +33,7 @@ configurations.all {
}

dependencies {
implementation(project(mapOf("path" to ":hedera-node:hapi-utils")))
compileOnly(libs.spotbugs.annotations)
implementation(libs.bundles.swirlds)
implementation(project(":hedera-node:hedera-mono-service"))
Expand Down
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2016-2023 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.hedera.services.cli.sign;

import com.swirlds.cli.PlatformCli;
import com.swirlds.cli.utility.AbstractCommand;
import com.swirlds.cli.utility.SubcommandOf;
import picocli.CommandLine;

/**
* A subcommand of the {@link PlatformCli}, for account balance files
*/
@CommandLine.Command(
name = "account-balance",
mixinStandardHelpOptions = true,
description = "Operations on account balance files.")
@SubcommandOf(PlatformCli.class)
public final class AccountBalanceCommand extends AbstractCommand {

private AccountBalanceCommand() {}
}
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2023 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.hedera.services.cli.sign;

import com.swirlds.cli.utility.SubcommandOf;
import com.swirlds.platform.cli.SignCommand;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.nio.file.Path;
import java.security.KeyPair;
import picocli.CommandLine;

/**
* A subcommand of the {@link SignCommand}, for signing account balance files
*/
@CommandLine.Command(name = "sign", mixinStandardHelpOptions = true, description = "Sign account balance files")
@SubcommandOf(AccountBalanceCommand.class)
public final class AccountBalanceSignCommand extends SignCommand {
/**
* {@inheritDoc}
*/
@Override
public boolean generateSignatureFile(
@NonNull Path signatureFileDestination, @NonNull Path fileToSign, @NonNull KeyPair keyPair) {

return AccountBalanceSigningUtils.signAccountBalanceFile(signatureFileDestination, fileToSign, keyPair);
}

/**
* {@inheritDoc}
*/
@Override
public boolean isFileSupported(@NonNull final Path path) {
return AccountBalanceType.getInstance().isCorrectFile(path.toFile().getName());
}
}
@@ -0,0 +1,98 @@
/*
* Copyright (C) 2023 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.hedera.services.cli.sign;

import static com.hedera.services.cli.sign.SignUtils.TYPE_FILE_HASH;
import static com.hedera.services.cli.sign.SignUtils.TYPE_SIGNATURE;
import static com.hedera.services.cli.sign.SignUtils.integerToBytes;
import static com.swirlds.common.stream.LinkedObjectStreamUtilities.computeEntireHash;
import static com.swirlds.platform.util.FileSigningUtils.signData;

import com.swirlds.common.crypto.Hash;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.util.Objects;

/**
* Utility class for signing account balance files
*/
public class AccountBalanceSigningUtils {

/**
* Hidden constructor
*/
private AccountBalanceSigningUtils() {}

/**
* Generates a signature file for the account balance file
*
* @param signatureFileDestination the full path where the signature file will be generated
* @param streamFileToSign the stream file to be signed
* @param keyPair the keyPair used for signing
* @return true if the signature file was generated successfully, false otherwise
*/
public static boolean signAccountBalanceFile(
@NonNull final Path signatureFileDestination,
@NonNull final Path streamFileToSign,
@NonNull final KeyPair keyPair) {

Objects.requireNonNull(signatureFileDestination, "signatureFileDestination must not be null");
Objects.requireNonNull(streamFileToSign, "streamFileToSign must not be null");
Objects.requireNonNull(keyPair, "keyPair must not be null");

try {
final Hash entireHash = computeEntireHash(streamFileToSign.toFile());
final byte[] fileHashByte = entireHash.getValue();
final byte[] signature = signData(fileHashByte, keyPair);

generateSigBalanceFile(signatureFileDestination.toFile(), signature, fileHashByte);

System.out.println("Generated signature file: " + signatureFileDestination);
iwsimon marked this conversation as resolved.
Show resolved Hide resolved

return true;
} catch (final SignatureException | IOException e) {
System.err.println("Failed to sign file " + streamFileToSign.getFileName() + ". Exception: " + e);
iwsimon marked this conversation as resolved.
Show resolved Hide resolved
return false;
} catch (final InvalidKeyException | NoSuchAlgorithmException | NoSuchProviderException e) {
System.err.println("Irrecoverable error encountered: " + e);
throw new RuntimeException("Irrecoverable error encountered", e);
iwsimon marked this conversation as resolved.
Show resolved Hide resolved
}
}

private static void generateSigBalanceFile(final File filePath, final byte[] signature, final byte[] fileHash) {
try (final BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(filePath))) {
output.write(TYPE_FILE_HASH);
output.write(fileHash);
output.write(TYPE_SIGNATURE);
output.write(integerToBytes(signature.length));
output.write(signature);
output.flush();
iwsimon marked this conversation as resolved.
Show resolved Hide resolved
} catch (final IOException e) {
System.err.println("generateSigBalanceFile :: Fail to generate signature file for " + filePath
+ " with exception :" + e);
}
}
}
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2020-2023 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.hedera.services.cli.sign;

/**
* Contains properties related to Account Balance file type;
* Its constructor is private. Users need to use the singleton to denote this type
*/
public final class AccountBalanceType {
/**
* description of the streamType, used for logging
*/
private static final String ACCOUNT_BALANCE_DESCRIPTION = "account balance";
/**
* file name extension
*/
private static final String ACCOUNT_BALANCE_EXTENSION = "pb";
/**
* file name extension of signature file
*/
private static final String ACCOUNT_BALANCE_SIG_EXTENSION = "pb_sig";
/**
* a singleton denotes AccountBalanceType
*/
private static final AccountBalanceType INSTANCE = new AccountBalanceType();

private AccountBalanceType() {}

public static AccountBalanceType getInstance() {
return INSTANCE;
}

public String getDescription() {
return ACCOUNT_BALANCE_DESCRIPTION;
}

public String getExtension() {
return ACCOUNT_BALANCE_EXTENSION;
}

public String getSigExtension() {
return ACCOUNT_BALANCE_SIG_EXTENSION;
}

public boolean isCorrectFile(final String fileName) {
return fileName.endsWith(ACCOUNT_BALANCE_EXTENSION);
}
}
@@ -0,0 +1,40 @@
/*
* Copyright (C) 2023 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.hedera.services.cli.sign;

import java.nio.ByteBuffer;

public class SignUtils {
/**
* next bytes are signature
*/
public static final byte TYPE_SIGNATURE = 3;
povolev15 marked this conversation as resolved.
Show resolved Hide resolved
/**
* next 48 bytes are hash384 of content of the file to be signed
iwsimon marked this conversation as resolved.
Show resolved Hide resolved
*/
public static final byte TYPE_FILE_HASH = 4;

private static final int BYTES_COUNT_IN_INT = 4;

private SignUtils() {}

public static byte[] integerToBytes(final int number) {
final ByteBuffer b = ByteBuffer.allocate(BYTES_COUNT_IN_INT);
b.putInt(number);
return b.array();
iwsimon marked this conversation as resolved.
Show resolved Hide resolved
}
}
5 changes: 5 additions & 0 deletions hedera-node/cli-clients/src/main/java/module-info.java
@@ -1,5 +1,6 @@
module com.hedera.services.cli {
exports com.hedera.services.cli;
exports com.hedera.services.cli.sign;

requires static com.github.spotbugs.annotations;
requires com.hedera.node.app.service.mono;
Expand All @@ -8,4 +9,8 @@
requires com.swirlds.platform;
requires com.swirlds.common;
requires info.picocli;
requires com.hedera.hashgraph.protobuf.java.api;
requires org.apache.commons.lang3;
requires com.hedera.node.app.hapi.utils;
requires com.google.protobuf;
}
2 changes: 1 addition & 1 deletion settings.gradle.kts
Expand Up @@ -144,7 +144,7 @@ dependencyResolutionManagement {
version("netty-version", "4.1.66.Final")
version("protobuf-java-version", "3.19.4")
version("slf4j-version", "2.0.3")
version("swirlds-version", "0.38.0-adhoc.x11d2d789")
version("swirlds-version", "0.38.0-adhoc.xb74a8ba8")
version("tuweni-version", "2.2.0")
version("jna-version", "5.12.1")
version("jsr305-version", "3.0.2")
Expand Down