mirror of
https://github.com/Egida/EndGame0.git
synced 2025-08-04 20:34:27 -04:00
235 lines
9.1 KiB
Go
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
|
|
}
|