Skip to content

Instantly share code, notes, and snippets.

@Mleekko
Created March 30, 2022 23:07
Show Gist options
  • Save Mleekko/92889469ca723a5b3d581ceec66bef1a to your computer and use it in GitHub Desktop.
Save Mleekko/92889469ca723a5b3d581ceec66bef1a to your computer and use it in GitHub Desktop.
KeyManager - manage multiple signing keys for Radix APIs.
import com.radixdlt.crypto.ECDSASignature;
import com.radixdlt.crypto.ECKeyPair;
import com.radixdlt.crypto.ECPublicKey;
import com.radixdlt.crypto.exception.PrivateKeyException;
import com.radixdlt.crypto.exception.PublicKeyException;
import com.radixdlt.identifiers.REAddr;
import com.radixdlt.networks.Addressing;
import com.radixdlt.networks.Network;
import com.radixdlt.utils.Bytes;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1OutputStream;
import org.bouncycastle.asn1.DLSequence;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import radix.api.gateway.model.PublicKey;
import radix.api.gateway.model.Signature;
import javax.annotation.PostConstruct;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;
/**
Requires `radixdlt-java-common` (https://github.com/radixdlt/radixdlt/tree/1.1.0/radixdlt-java-common),
also had to update `RadixKeyStore` to fix Bouncy Castle:
```
static {
Provider provider = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME);
System.out.println("Provider: " + provider);
if (provider == null) {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
System.out.println("BouncyCastleProvider inserted.");
}
}
```
*/
@Slf4j
@Component
public class KeyManager {
private final Map<String, ECKeyPair> accountKeys = new HashMap<>();
@Value("${app.keystore.file}")
private String keystoreFile;
@Value("${app.keystore.password}")
private String keystorePassword;
private Network network = Network.MAINNET;
public KeyManager() {
}
public KeyManager(String keystoreFile, String keystorePassword) {
this.keystoreFile = keystoreFile;
this.keystorePassword = keystorePassword;
}
@PostConstruct
public void init() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException,
com.radixdlt.crypto.exception.KeyStoreException, PrivateKeyException, PublicKeyException {
File file = new File(keystoreFile);
log.info("Loading keystore file: [{}]", file.getCanonicalPath());
readKeystore(accountKeys, file, keystorePassword, network);
keystorePassword = null;
log.info("Loaded [{}] accounts: [{}]", accountKeys.size(), new TreeSet<>(accountKeys.keySet()));
}
public static void readKeystore(Map<String, ECKeyPair> accountKeys, File file, String password, Network network)
throws java.security.KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
com.radixdlt.crypto.exception.KeyStoreException, PrivateKeyException, PublicKeyException {
KeyStore ks = KeyStore.getInstance("pkcs12");
try (var is = new FileInputStream(file)) {
ks.load(is, password.toCharArray());
}
log.info("KS file loaded.");
Enumeration<String> aliases = ks.aliases();
Addressing addressing = Addressing.ofNetwork(network);
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
log.info("Loading alias: {}", alias);
try (RadixKeyStore keyStore = RadixKeyStore.fromFile(file, password.toCharArray(), false)) {
ECKeyPair keyPair = keyStore.readKeyPair(alias, false);
REAddr addr = REAddr.ofPubKeyAccount(keyPair.getPublicKey());
String account = addressing.forAccounts().of(addr);
accountKeys.put(account, keyPair);
}
}
}
public ECPublicKey getKey(String account) {
ECKeyPair keyPair = accountKeys.get(account);
return keyPair.getPublicKey();
}
public Signature sign(String account, String payloadToSignHex) throws DecoderException, IOException {
ECKeyPair keyPair = accountKeys.get(account);
byte[] bytes = Hex.decodeHex(payloadToSignHex);
byte[] derSignature = toDerSignature(keyPair.sign(bytes));
return new Signature()
.publicKey(new PublicKey().hex(keyPair.getPublicKey().toHex()))
.bytes(Bytes.toHexString(derSignature));
}
private byte[] toDerSignature(ECDSASignature sign) throws IOException {
var os = new ByteArrayOutputStream();
var asn1OutputStream = ASN1OutputStream.create(os);
asn1OutputStream.writeObject(
new DLSequence(
new ASN1Encodable[]{new ASN1Integer(sign.getR()), new ASN1Integer(sign.getS())}));
return os.toByteArray();
}
public void setNetwork(Network network) {
this.network = network;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment