EndGame0/sourcecode/gobalance/pkg/onionbalance/descriptor.go
2024-10-23 20:50:14 +05:30

235 lines
9.1 KiB
Go

package onionbalance
import (
"crypto/aes"
"crypto/cipher"
"crypto/ed25519"
"github.com/sirupsen/logrus"
"gobalance/pkg/brand"
"gobalance/pkg/btime"
"gobalance/pkg/gobpk"
"gobalance/pkg/stem/descriptor"
"golang.org/x/crypto/sha3"
"time"
)
// V3Descriptor a generic v3 descriptor.
// Serves as the base class for OBDescriptor and ReceivedDescriptor which
// implement more specific functionalities.
type V3Descriptor struct {
onionAddress string
v3Desc *descriptor.HiddenServiceDescriptorV3
introSet *IntroductionPointSetV3
}
// GetIntroPoints get the raw intro points for this descriptor.
func (d *V3Descriptor) GetIntroPoints() []descriptor.IntroductionPointV3 {
return d.introSet.getIntroPointsFlat()
}
// Extract and return the blinded key from the descriptor
func (d *V3Descriptor) getBlindedKey() ed25519.PublicKey {
// The descriptor signing cert, signs the descriptor signing key using
// the blinded key. So the signing key should be the one we want here.
return d.v3Desc.SigningCert.SigningKey()
}
// ReceivedDescriptor an instance v3 descriptor received from the network.
// This class supports parsing descriptors.
type ReceivedDescriptor struct {
V3Descriptor
receivedTs *time.Time
}
// NewReceivedDescriptor parse a descriptor in 'desc_text' and return an ReceivedDescriptor object.
// Raises BadDescriptor if the descriptor cannot be used.
func NewReceivedDescriptor(descText, onionAddress string) (*ReceivedDescriptor, error) {
d := &ReceivedDescriptor{}
v3Desc := &descriptor.HiddenServiceDescriptorV3{}
v3Desc.FromStr(descText)
if _, err := v3Desc.Decrypt(onionAddress); err != nil {
logrus.Warnf("Descriptor is corrupted (%s).", onionAddress)
return nil, ErrBadDescriptor
}
tmp := btime.Clock.Now().UTC()
d.receivedTs = &tmp
logrus.Debugf("Successfuly decrypted descriptor for %s!", onionAddress)
d.onionAddress = onionAddress
d.v3Desc = v3Desc
p := Params()
nIntroduction := p.NIntroduction() + int64(len(d.v3Desc.InnerLayer.IntroductionPoints))
p.SetNIntroduction(nIntroduction)
// An IntroductionPointSetV3 object with the intros of this descriptor
logrus.Debugf("New Descriptor Received for %s (%d introduction points)", onionAddress, len(d.v3Desc.InnerLayer.IntroductionPoints))
d.introSet = NewIntroductionPointSetV3([][]descriptor.IntroductionPointV3{d.v3Desc.InnerLayer.IntroductionPoints})
logrus.Debugf("Introduction count, %d", p.NIntroduction())
return d, nil
}
// IsOld returns True if this received descriptor is old. If so, we should consider the
// instance as offline.
func (d *ReceivedDescriptor) IsOld() bool {
p := Params()
receivedAge := btime.Clock.Now().UTC().Sub(*d.receivedTs).Nanoseconds()
if receivedAge > p.InstanceDescriptorTooOld() {
return true
}
return false
}
type OBDescriptor struct {
V3Descriptor
lastPublishAttemptTs *time.Time
lastUploadTs *time.Time
responsibleHsdirs []string
consensus *ConsensusDoc
}
// NewOBDescriptor A v3 descriptor created by OnionBalance and meant to be published to the
// network.
// This class supports generating descriptors.
// Can raise BadDescriptor if we can't or should not generate a valid descriptor
func NewOBDescriptor(onionAddress string, identityPrivKey gobpk.PrivateKey, blindingParam []byte, introPoints []descriptor.IntroductionPointV3, isFirstDesc bool, consensus *ConsensusDoc) (*OBDescriptor, error) {
d := &OBDescriptor{}
d.consensus = consensus
// Timestamp of the last attempt to assemble this descriptor
d.lastPublishAttemptTs = nil
// Timestamp we last uploaded this descriptor
d.lastUploadTs = nil
// Set of responsible HSDirs for last time we uploaded this descriptor
d.responsibleHsdirs = nil
// Start generating descriptor
_, descSigningKey, _ := ed25519.GenerateKey(brand.Reader())
// Get the intro points for this descriptor and recertify them!
recertifiedIntroPoints := make([]descriptor.IntroductionPointV3, 0)
for _, ip := range introPoints {
rec := d.recertifyIntroPoint(ip, descSigningKey)
recertifiedIntroPoints = append(recertifiedIntroPoints, rec)
}
revCounter := d.getRevisionCounter(identityPrivKey, isFirstDesc)
v3DescInnerLayer := descriptor.InnerLayerCreate(recertifiedIntroPoints)
v3Desc := descriptor.HiddenServiceDescriptorV3Create(blindingParam, identityPrivKey, descSigningKey, v3DescInnerLayer, revCounter)
// TODO stem should probably initialize it itself so that it has balance
// between descriptor creation (where this is not inted) and descriptor
// parsing (where this is inited)
v3Desc.InnerLayer = &v3DescInnerLayer
// Check max size is within range
if len(v3Desc.String()) > MaxDescriptorSize {
logrus.Errorf("Created descriptor is too big (%d bytes [max 50000]). Consider relaxing the number of introduction points included in a descriptor (see NIntrosWanted)", len(v3Desc.String()))
return nil, ErrBadDescriptor
}
d.onionAddress = onionAddress
d.v3Desc = v3Desc
d.introSet = NewIntroductionPointSetV3([][]descriptor.IntroductionPointV3{d.v3Desc.InnerLayer.IntroductionPoints})
return d, nil
}
// MaxDescriptorSize Max descriptor size (in bytes) (see hs_cache_get_max_descriptor_size() in
// little-t-tor)
const MaxDescriptorSize = 50000
func (d *OBDescriptor) setLastPublishAttemptTs(lastPublishAttemptTs time.Time) {
d.lastPublishAttemptTs = &lastPublishAttemptTs
}
func (d *OBDescriptor) setLastUploadTs(lastUploadTs time.Time) {
d.lastUploadTs = &lastUploadTs
}
func (d *OBDescriptor) setResponsibleHsdirs(responsibleHsdirs []string) {
d.responsibleHsdirs = responsibleHsdirs
}
// Get the revision counter using the order-preserving-encryption scheme from
// rend-spec-v3.txt section F.2.
func (d *OBDescriptor) getRevisionCounter(identityPrivKey gobpk.PrivateKey, isFirstDesc bool) int64 {
now := btime.Clock.Now().Unix()
// TODO: Mention that this is done with the private key instead of the blinded priv key
// this means that this won't cooperate with normal tor
privkeyBytes := identityPrivKey.Seed()
var srvStart int64
if isFirstDesc {
srvStart = d.consensus.GetStartTimeOfPreviousSrvRun()
} else {
srvStart = d.consensus.GetStartTimeOfCurrentSrvRun()
}
opeResult, secondsSinceSrvStart := getRevisionCounterDet(privkeyBytes, now, srvStart)
logrus.Debugf("Rev counter for descriptor (FirstDesc %t) (SRV secs %d, OPE %d)", isFirstDesc, secondsSinceSrvStart, opeResult)
return opeResult
}
func getRevisionCounterDet(privkeyBytes []byte, now, srvStart int64) (opeResult int64, secondsSinceSrvStart int64) {
cipherKeyTmp := sha3.Sum256([]byte("rev-counter-generation" + string(privkeyBytes))) // good
cipherKey := cipherKeyTmp[:]
secondsSinceSrvStart = now - srvStart
// This must be strictly positive
secondsSinceSrvStart += 1
iv := make([]byte, 16)
block, _ := aes.NewCipher(cipherKey)
stream := cipher.NewCTR(block, iv)
getOpeSchemeWords := func() int64 {
v := make([]byte, 16)
stream.XORKeyStream(v, []byte("\x00\x00"))
return int64(v[0]) + 256*int64(v[1]) + 1
}
for i := int64(0); i < secondsSinceSrvStart; i++ {
opeResult += getOpeSchemeWords()
}
return opeResult, secondsSinceSrvStart
}
func (d *OBDescriptor) recertifyIntroPoint(introPoint descriptor.IntroductionPointV3, descriptorSigningKey ed25519.PrivateKey) descriptor.IntroductionPointV3 {
originalAuthKeyCert := introPoint.AuthKeyCert
originalEncKeyCert := introPoint.EncKeyCert
// We have already removed all the intros with legacy keys. Make sure that
// no legacy intros sneaks up on us, becausey they would result in
// unparseable descriptors if we don't recertify them (and we won't).
// assert(not intro_point.legacy_key_cert)
// Get all the certs we need to recertify
// [we need to use the _replace method of namedtuples because there is no
// setter for those attributes due to the way stem sets those fields. If we
// attempt to normally replace the attributes we get the following
// exception: AttributeError: can't set attribute]
introPoint.AuthKeyCert = d.recertifyEdCertificate(originalAuthKeyCert, descriptorSigningKey)
introPoint.EncKeyCert = d.recertifyEdCertificate(originalEncKeyCert, descriptorSigningKey)
introPoint.AuthKeyCertRaw = introPoint.AuthKeyCert.ToBase64()
introPoint.EncKeyCertRaw = introPoint.EncKeyCert.ToBase64()
recertifiedIntroPoint := introPoint
return recertifiedIntroPoint
}
// Recertify an HSv3 intro point certificate using the new descriptor signing
// key so that it can be accepted as part of a new descriptor.
// "Recertifying" means taking the certified key and signing it with a new
// key.
// Return the new certificate.
func (d *OBDescriptor) recertifyEdCertificate(edCert descriptor.Ed25519CertificateV1, descriptorSigningKey ed25519.PrivateKey) descriptor.Ed25519CertificateV1 {
return recertifyEdCertificate(edCert, descriptorSigningKey)
}
func recertifyEdCertificate(edCert descriptor.Ed25519CertificateV1, descriptorSigningKey ed25519.PrivateKey) descriptor.Ed25519CertificateV1 {
extensions := []descriptor.Ed25519Extension{descriptor.NewEd25519Extension(descriptor.HasSigningKey, 0, descriptorSigningKey.Public().(ed25519.PublicKey))}
newCert := descriptor.NewEd25519CertificateV1(edCert.Typ, &edCert.Expiration, edCert.KeyType, edCert.Key, extensions, descriptorSigningKey, nil)
return newCert
}