mirror of https://github.com/certusone/aiakos.git
181 lines
4.7 KiB
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
|
|
}
|