Azure Key Vaultで秘密鍵の管理する


2018年 10月 30日

AZUREソリューション部の土田です。

ブロックチェーン上にサービスを構築する場合、秘密鍵の管理は重要な要素です。

現状、ユーザーに秘密鍵を生成してもらい、各自で安全に管理してもらうといった方式は、現実的ではない場合が多いです。

何らかの手段で、秘密鍵を管理する必要があります。

今回は、Azure Key Vaultを使用した管理方法を紹介します。

Azure Key VaultをWebアプリケーションから使用するためには、
以下のものが必要です。

  • Azure Key Vault のシークレットURI
  • クライアント ID
  • クライアントのシークレット URI

取得方法、アプリでの設定方法は公式ドキュメント、Azure Key Vaultの概要およびWebアプリからAzure Key Vaultの使用するを参照してください。
Key Vaultのクライアントクラスは以下の通りです。

public class KeyVault : ISecretsStore
{
    public string Url { get; }

    #region private members

    private readonly KeyVaultClient m_kvClient;
    private readonly string m_applicationId;
    private readonly string m_applicationSecret;

    #endregion

    /// <summary>
    /// Ctor for Key vault class
    /// </summary>
    /// <param name="kvUrl">The Azure keyvault url</param>
    /// <param name="applicationId">The azure service principal application id</param>
    /// <param name="applicationSecret">The azure service principal application secret</param>
    public KeyVault(string kvUrl, string applicationId, string applicationSecret)
    {
        Url = kvUrl;
        m_applicationId = applicationId;
        m_applicationSecret = applicationSecret;

        m_kvClient = new KeyVaultClient(GetAccessTokenAsync, new HttpClient());
    }

    /// <summary>
    /// Gets the specified secret
    /// </summary>
    /// <returns>The secret</returns>
    /// <param name="secretName">Secret identifier</param>
    public async Task<string> GetSecretAsync(string secretName)
    {
        try
        {
            return (await m_kvClient.GetSecretAsync(Url, secretName)).Value;
        }
        catch (KeyVaultErrorException ex)
        {
            Console.WriteLine($"Exception while trying to get secret {secretName}, {ex}");
            throw;
        }
    }

    /// <summary>
    /// Sets a secret in Azure keyvault
    /// </summary>
    /// <returns>The secret.</returns>
    /// <param name="secretName">Secret identifier.</param>
    /// <param name="value">The value to be stored.</param>
    public async Task SetSecretAsync(string secretName, string value)
    {
        try
        {
            await m_kvClient.SetSecretAsync(Url, secretName, value);
        }
        catch (KeyVaultErrorException ex)
        {
            Console.WriteLine($"Exception while trying to set secret {secretName}, {ex}");
            throw;
        }
    }

    #region Private Methods

    private async Task<string> GetAccessTokenAsync(
        string authority,
        string resource,
        string scope)
    {
        var clientCredential = new ClientCredential(m_applicationId, m_applicationSecret);
        var context = new AuthenticationContext(authority, TokenCache.DefaultShared);
        var result = await context.AcquireTokenAsync(resource, clientCredential);

        return result.AccessToken;
    }

    #endregion
}

このKey Vaultクライアントを使用して、秘密鍵を管理するAccountクラスを作成します。

Accountクラスには以下のような機能が実装されています。

  • 新規に秘密鍵・公開鍵のペアを生成する
  • 秘密鍵をKey Vaultに保存する
  • 秘密鍵をKey Vaultから取り出す
  • 取得した秘密鍵で署名する
  • トランザクションを作成する(送信する)
  • 公開鍵を取得する
  • 現在の残高を取得する

サンプルはEthereum版ですが、以下のようになります。

public class EthereumAccount : IBlockchainAccount
{
    private readonly Web3 m_web3;
    private readonly ISecretsStore m_db;

    #region Public Methods

    /// <summary>
    /// Ctor for EthereumAccount class
    /// </summary>
    /// <param name="database">The database which holds the clients' private keys.</param>
    /// <param name="nodeUrl">The Ethereum node Url. If it's empty, it will work with the local Ethereum testnet.</param>
    public EthereumAccount(ISecretsStore database, string nodeUrl = "")
    {
        m_db = database;
        m_web3 = string.IsNullOrEmpty(nodeUrl) ? new Web3() : new Web3(nodeUrl);
    }

    /// <summary>
    /// Creates blockchain account if needed and store the private key in Azure KeyVault 
    /// </summary>
    /// <param name="identifier">key pair identifier.</param>
    /// <param name="privateKey">The given private key to store, if not supplied a new private key will be generated</param>
    public async Task<string> CreateAccountAsync(string identifier, string privateKey = "")
    {
        if (string.IsNullOrEmpty(privateKey))
        {
            var account = EthECKey.GenerateKey();
            privateKey = account.GetPrivateKey();
        }

        await StoreAccountAsync(identifier, privateKey);
        return new EthECKey(privateKey).GetPublicAddress();
    }

    /// <summary>
    /// Returns the public key by the key vault identifier
    /// </summary>
    /// <param name="identifier">The user id</param>
    /// <returns>The user's public address</returns>
    public async Task<string> GetPublicAddressAsync(string identifier)
    {
        var privatekey = await GetPrivateKeyAsync(identifier);
        return new EthECKey(privatekey).GetPublicAddress();
    }

    /// <summary>
    /// Sign a blockchain transaction
    /// </summary>
    /// <param name="senderIdentifier">The sender identifier, as it saved in the Azure KeyVault (Id, name etc.)</param>
    /// <param name="recieverAddress">The receiver public address</param>
    /// <param name="amountInWei">The amount to send in Wei (ethereum units)</param>
    /// <returns>The transaction hash</returns>
    public async Task<string> SignTransactionAsync(string senderIdentifier, string recieverAddress,
        BigInteger amountInWei)
    {
        var senderPrivateKey = await GetPrivateKeyAsync(senderIdentifier);
        var senderEthKey = new EthECKey(senderPrivateKey);

        var txCount =
            await m_web3.Eth.Transactions.GetTransactionCount.SendRequestAsync(senderEthKey.GetPublicAddress());
        return Web3.OfflineTransactionSigner.SignTransaction(senderPrivateKey, recieverAddress, amountInWei,
            txCount.Value);
    }

    /// <summary>
    /// Send the transaction to the public node. 
    /// </summary>
    /// <param name="hash">The transaction hash</param>
    /// <returns>The transaction result</returns>
    public async Task<string> SendRawTransactionAsync(string hash)
    {
        return await m_web3.Eth.Transactions.SendRawTransaction.SendRequestAsync(hash);
    }

    /// <summary>
    /// Gets the balance of the provided account - if public address provided get balance by address
    /// Otherwise get balance by identifier
    /// </summary>
    /// <param name="publicAddress">The public address of the account</param>
    /// <returns>Returns the balance in ether.</returns>
    public async Task<decimal> GetCurrentBalance(string publicAddress = "", string identifier = "")
    {
        if (string.IsNullOrEmpty(publicAddress) && string.IsNullOrEmpty(identifier))
        {
            throw new ArgumentNullException("public address or identifier should be provided");
        }

        if (string.IsNullOrEmpty(publicAddress))
        {
            publicAddress = await GetPublicAddressAsync(identifier);
        }

        var unitConverion = new UnitConversion();
        return unitConverion.FromWei(await m_web3.Eth.GetBalance.SendRequestAsync(publicAddress));
    }

    #endregion

    #region Private Methods

    /// <summary>
    /// Stores the account async.
    /// </summary>
    /// <returns>If the account was created successfully</returns>
    /// <param name="identifier">Identifier.</param>
    /// <param name="privateKey">The private key.</param>
    private async Task StoreAccountAsync(string identifier, string privateKey)
    {
        await m_db.SetSecretAsync(identifier, privateKey);
    }

    /// <summary>
    /// Returns the private key by the key vault identifier
    /// </summary>
    /// <param name="identifier">The user id</param>
    /// <returns>The user's public key</returns>
    private async Task<string> GetPrivateKeyAsync(string identifier)
    {
        return await m_db.GetSecretAsync(identifier);
    }

    #endregion
}

参考資料

サンプルリポジトリ

https://github.com/Azure/Secured-SaaS-Wallet

公式ドキュメント

https://docs.microsoft.com/ja-jp/azure/key-vault/key-vault-get-started
https://docs.microsoft.com/ja-jp/azure/key-vault/key-vault-use-from-web-application

公式ブログ

https://blogs.msdn.microsoft.com/uk_faculty_connection/2018/03/22/blockchain-azure-samples-and-resources-for-building-interesting-blockchain-projects-and-solutions/