141 lines
4.0 KiB
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
|
|
}
|