storj/pkg/auth/signedMessage.go

141 lines
4.0 KiB
Go

// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package auth
import (
"crypto/ecdsa"
"github.com/gogo/protobuf/proto"
"github.com/gtank/cryptopasta"
"github.com/zeebo/errs"
"storj.io/storj/pkg/identity"
"storj.io/storj/pkg/peertls"
"storj.io/storj/pkg/pkcrypto"
"storj.io/storj/pkg/storj"
)
var (
//ErrECDSA indicates a key was not an ECDSA key
ErrECDSA = errs.New("Key is not ecdsa key")
//ErrSign indicates a failure during signing
ErrSign = errs.Class("Failed to sign message")
//ErrVerify indicates a failure during signature validation
ErrVerify = errs.Class("Failed to validate message signature")
//ErrSigLen indicates an invalid signature length
ErrSigLen = errs.Class("Invalid signature length")
//ErrSerial indicates an invalid serial number length
ErrSerial = errs.Class("Invalid SerialNumber")
//ErrExpired indicates the agreement is expired
ErrExpired = errs.Class("Agreement is expired")
//ErrSigner indicates a public key / node id mismatch
ErrSigner = errs.Class("Message public key did not match expected signer")
//ErrBadID indicates a public key / node id mismatch
ErrBadID = errs.Class("Node ID did not match expected id")
//ErrMarshal indicates a failure during serialization
ErrMarshal = errs.Class("Could not marshal item to bytes")
//ErrUnmarshal indicates a failure during deserialization
ErrUnmarshal = errs.Class("Could not unmarshal bytes to item")
//ErrMissing indicates missing or empty information
ErrMissing = errs.Class("Required field is empty")
)
//SignableMessage is a protocol buffer with a certs and a signature
//Note that we assume proto.Message is a pointer receiver
type SignableMessage interface {
proto.Message
GetCerts() [][]byte
GetSignature() []byte
SetCerts([][]byte)
SetSignature([]byte)
}
//SignMessage adds the crypto-related aspects of signed message
func SignMessage(msg SignableMessage, ID identity.FullIdentity) error {
if msg == nil {
return ErrMissing.New("message")
}
msg.SetSignature(nil)
msg.SetCerts(nil)
msgBytes, err := proto.Marshal(msg)
if err != nil {
return ErrMarshal.Wrap(err)
}
privECDSA, ok := ID.Key.(*ecdsa.PrivateKey)
if !ok {
return ErrECDSA
}
signature, err := cryptopasta.Sign(msgBytes, privECDSA)
if err != nil {
return ErrSign.Wrap(err)
}
msg.SetSignature(signature)
msg.SetCerts(ID.ChainRaw())
return nil
}
//VerifyMsg checks the crypto-related aspects of signed message
func VerifyMsg(msg SignableMessage, signer storj.NodeID) error {
//setup
if msg == nil {
return ErrMissing.New("message")
} else if msg.GetSignature() == nil {
return ErrMissing.New("message signature")
} else if msg.GetCerts() == nil {
return ErrMissing.New("message certificates")
}
signature := msg.GetSignature()
certs := msg.GetCerts()
msg.SetSignature(nil)
msg.SetCerts(nil)
msgBytes, err := proto.Marshal(msg)
if err != nil {
return ErrMarshal.Wrap(err)
}
//check certs
if len(certs) < 2 {
return ErrVerify.New("Expected at least leaf and CA public keys")
}
err = peertls.VerifyPeerFunc(peertls.VerifyPeerCertChains)(certs, nil)
if err != nil {
return ErrVerify.Wrap(err)
}
leafPubKey, err := parseECDSA(certs[0])
if err != nil {
return err
}
caPubKey, err := parseECDSA(certs[1])
if err != nil {
return err
}
// verify signature
signatureLength := leafPubKey.Curve.Params().P.BitLen() / 8
if len(signature) < signatureLength {
return ErrSigLen.New("%d vs %d", len(signature), signatureLength)
}
if id, err := identity.NodeIDFromECDSAKey(caPubKey); err != nil || id != signer {
return ErrSigner.New("%+v vs %+v", id, signer)
}
if ok := cryptopasta.Verify(msgBytes, signature, leafPubKey); !ok {
return ErrVerify.New("%+v", ok)
}
//cleanup
msg.SetSignature(signature)
msg.SetCerts(certs)
return nil
}
func parseECDSA(rawCert []byte) (*ecdsa.PublicKey, error) {
cert, err := pkcrypto.CertFromDER(rawCert)
if err != nil {
return nil, ErrVerify.Wrap(err)
}
ecdsa, ok := cert.PublicKey.(*ecdsa.PublicKey)
if !ok {
return nil, ErrECDSA
}
return ecdsa, nil
}