aiakos/hsm.go

181 lines
4.7 KiB
Go

package aiakos
import (
"errors"
"github.com/certusone/yubihsm-go"
"github.com/certusone/yubihsm-go/commands"
"github.com/certusone/yubihsm-go/connector"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
cmn "github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/types"
)
type (
// AiakosPV implements PrivValidator using Aiakos
AiakosPV struct {
hsmSessionManager *yubihsm.SessionManager
hsmURL string
authKeyID uint16
password string
signingKeyID uint16
cachedPubKey crypto.PubKey
cmn.BaseService
}
)
// NewAiakosPV returns a new instance of AiakosPV
func NewAiakosPV(hsmURL string, signingKeyID uint16, authKeyID uint16, password string, logger log.Logger) (*AiakosPV, error) {
pv := &AiakosPV{
hsmURL: hsmURL,
authKeyID: authKeyID,
password: password,
signingKeyID: signingKeyID,
}
pv.BaseService = *cmn.NewBaseService(logger, "AiakosPV", pv)
return pv, nil
}
// OnStart implements cmn.Service.
func (a *AiakosPV) OnStart() error {
sessionManager, err := yubihsm.NewSessionManager(connector.NewHTTPConnector(a.hsmURL), a.authKeyID, a.password)
if err != nil {
return err
}
a.hsmSessionManager = sessionManager
return nil
}
// OnStop implements cmn.Service.
func (a *AiakosPV) OnStop() {
a.hsmSessionManager.Destroy()
}
// GetPubKey returns the public key of the validator.
// Implements PrivValidator.
func (a *AiakosPV) GetPubKey() crypto.PubKey {
if a.cachedPubKey != nil {
return a.cachedPubKey
}
command, err := commands.CreateGetPubKeyCommand(a.signingKeyID)
if err != nil {
panic(err)
}
resp, err := a.hsmSessionManager.SendEncryptedCommand(command)
if err != nil {
panic(err)
}
parsedResp, matched := resp.(*commands.GetPubKeyResponse)
if !matched {
a.Logger.Error("invalid response type")
panic(errors.New("invalid response type"))
}
if parsedResp.Algorithm != commands.AlgorighmED25519 {
a.Logger.Error("invalid response type", "algorithm", parsedResp.Algorithm)
panic(errors.New("invalid pubKey algorithm"))
}
if len(parsedResp.KeyData) != ed25519.PubKeyEd25519Size {
a.Logger.Error("invalid pubKey size", "size", len(parsedResp.KeyData))
panic(errors.New("invalid pubKey size"))
}
// Convert raw key data to tendermint PubKey type
publicKey := new(ed25519.PubKeyEd25519)
copy(publicKey[:], parsedResp.KeyData[:])
// Cache publicKey
a.cachedPubKey = publicKey
return publicKey
}
// SignVote signs a canonical representation of the vote, along with the
// chainID. Implements PrivValidator.
func (a *AiakosPV) SignVote(chainID string, vote *types.Vote) error {
signature, err := a.signBytes(vote.SignBytes(chainID))
if err != nil {
return err
}
vote.Signature = signature
return nil
}
// SignProposal signs a canonical representation of the proposal, along with
// the chainID. Implements PrivValidator.
func (a *AiakosPV) SignProposal(chainID string, proposal *types.Proposal) error {
signature, err := a.signBytes(proposal.SignBytes(chainID))
if err != nil {
return err
}
proposal.Signature = signature
return nil
}
// ImportKey imports a Eddsa private key to the specified key slot on the HSM.
// This fails if the key slot already contains a key.
// This should be used for testing purposes only. Wrap and import keys in production.
func (a *AiakosPV) ImportKey(keyID uint16, key []byte) error {
command, err := commands.CreatePutAsymmetricKeyCommand(keyID, []byte("imported"), commands.Domain1, commands.CapabilityAsymmetricSignEddsa, commands.AlgorighmED25519, key, []byte{})
if err != nil {
return err
}
resp, err := a.hsmSessionManager.SendEncryptedCommand(command)
if err != nil {
return err
}
parsedResp, matched := resp.(*commands.PutAsymmetricKeyResponse)
if !matched {
a.Logger.Error("invalid response type")
return errors.New("invalid response type")
}
if parsedResp.KeyID != keyID {
a.Logger.Error("imported KeyID mismatch")
return errors.New("imported KeyID mismatch")
}
return nil
}
func (a *AiakosPV) signBytes(data []byte) ([]byte, error) {
command, err := commands.CreateSignDataEddsaCommand(a.signingKeyID, data)
if err != nil {
return nil, err
}
resp, err := a.hsmSessionManager.SendEncryptedCommand(command)
if err != nil {
return nil, err
}
parsedResp, matched := resp.(*commands.SignDataEddsaResponse)
if !matched {
a.Logger.Error("invalid response type")
return nil, errors.New("invalid response type")
}
if len(parsedResp.Signature) != ed25519.SignatureSize {
a.Logger.Error("invalid signature length", "size", len(parsedResp.Signature))
return nil, errors.New("invalid signature length")
}
return parsedResp.Signature, nil
}