mirror of
https://github.com/Egida/EndGame0.git
synced 2025-08-13 16:45:46 -04:00
EndGame v3
This commit is contained in:
commit
9e36ba54ee
646 changed files with 271674 additions and 0 deletions
832
sourcecode/gobalance/pkg/stem/descriptor/hidden_service.go
Normal file
832
sourcecode/gobalance/pkg/stem/descriptor/hidden_service.go
Normal file
|
@ -0,0 +1,832 @@
|
|||
package descriptor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ed25519"
|
||||
"crypto/x509"
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gobalance/pkg/brand"
|
||||
"gobalance/pkg/btime"
|
||||
"gobalance/pkg/gobpk"
|
||||
"gobalance/pkg/stem/util"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"maze.io/x/crypto/x25519"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Descriptor common parent for all types of descriptors.
|
||||
// https://github.com/torproject/torspec/blob/4da63977b86f4c17d0e8cf87ed492c72a4c9b2d9/rend-spec-v3.txt#L1057
|
||||
type Descriptor struct {
|
||||
HsDescriptorVersion int64
|
||||
descriptorLifetime int64
|
||||
DescriptorSigningKeyCert string
|
||||
revisionCounter int64
|
||||
superencrypted string
|
||||
signature string
|
||||
}
|
||||
|
||||
func (d *Descriptor) FromStr(content string) {
|
||||
*d = *descFromStr(content)
|
||||
}
|
||||
|
||||
func descFromStr(content string) *Descriptor {
|
||||
d := &Descriptor{}
|
||||
lines := strings.Split(content, "\n")
|
||||
startCert := false
|
||||
startSuperencrypted := false
|
||||
for idx, line := range lines {
|
||||
if idx == 0 {
|
||||
d.HsDescriptorVersion, _ = strconv.ParseInt(strings.TrimPrefix(line, "hs-descriptor "), 10, 64)
|
||||
continue
|
||||
} else if idx == 1 {
|
||||
d.descriptorLifetime, _ = strconv.ParseInt(strings.TrimPrefix(line, "descriptor-lifetime "), 10, 64)
|
||||
continue
|
||||
} else if line == "descriptor-signing-key-cert" {
|
||||
startCert = true
|
||||
continue
|
||||
} else if line == "superencrypted" {
|
||||
startSuperencrypted = true
|
||||
continue
|
||||
} else if strings.HasPrefix(line, "revision-counter ") {
|
||||
d.revisionCounter, _ = strconv.ParseInt(strings.TrimPrefix(line, "revision-counter "), 10, 64)
|
||||
continue
|
||||
} else if strings.HasPrefix(line, "signature ") {
|
||||
d.signature = strings.TrimPrefix(line, "signature ")
|
||||
continue
|
||||
}
|
||||
if startCert {
|
||||
d.DescriptorSigningKeyCert += line + "\n"
|
||||
if line == "-----END ED25519 CERT-----" {
|
||||
startCert = false
|
||||
d.DescriptorSigningKeyCert = strings.TrimSpace(d.DescriptorSigningKeyCert)
|
||||
}
|
||||
} else if startSuperencrypted {
|
||||
d.superencrypted += line + "\n"
|
||||
if line == "-----END MESSAGE-----" {
|
||||
startSuperencrypted = false
|
||||
d.superencrypted = strings.TrimSpace(d.superencrypted)
|
||||
}
|
||||
}
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// BaseHiddenServiceDescriptor hidden service descriptor.
|
||||
type BaseHiddenServiceDescriptor struct {
|
||||
Descriptor
|
||||
}
|
||||
|
||||
const (
|
||||
// ExtensionType
|
||||
HasSigningKey = 4
|
||||
)
|
||||
|
||||
// HiddenServiceDescriptorV3 version 3 hidden service descriptor.
|
||||
type HiddenServiceDescriptorV3 struct {
|
||||
BaseHiddenServiceDescriptor
|
||||
SigningCert Ed25519CertificateV1
|
||||
InnerLayer *InnerLayer
|
||||
rawContents string
|
||||
}
|
||||
|
||||
func (d HiddenServiceDescriptorV3) String() string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("hs-descriptor 3\n")
|
||||
sb.WriteString("descriptor-lifetime ")
|
||||
sb.WriteString(strconv.FormatInt(d.descriptorLifetime, 10))
|
||||
sb.WriteByte('\n')
|
||||
sb.WriteString("descriptor-signing-key-cert\n")
|
||||
sb.WriteString(d.DescriptorSigningKeyCert)
|
||||
sb.WriteByte('\n')
|
||||
sb.WriteString("revision-counter ")
|
||||
sb.WriteString(strconv.FormatInt(d.revisionCounter, 10))
|
||||
sb.WriteByte('\n')
|
||||
sb.WriteString("superencrypted\n")
|
||||
sb.WriteString(d.superencrypted)
|
||||
sb.WriteByte('\n')
|
||||
sb.WriteString("signature ")
|
||||
sb.WriteString(d.signature)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func blindedPubkey(identityKey gobpk.PrivateKey, blindingNonce []byte) ed25519.PublicKey {
|
||||
return util.BlindedPubkey(identityKey.Public(), blindingNonce)
|
||||
}
|
||||
|
||||
func blindedSign(msg []byte, identityKey gobpk.PrivateKey, blindedKey, blindingNonce []byte) []byte {
|
||||
if identityKey.IsPrivKeyInTorFormat() {
|
||||
return util.BlindedSignWithTorKey(msg, identityKey.Seed(), blindedKey, blindingNonce)
|
||||
} else {
|
||||
return util.BlindedSign(msg, identityKey.Seed(), blindedKey, blindingNonce)
|
||||
}
|
||||
}
|
||||
|
||||
func HiddenServiceDescriptorV3Content(blindingNonce []byte, identityKey gobpk.PrivateKey,
|
||||
descSigningKey ed25519.PrivateKey, innerLayer *InnerLayer, revCounter *int64) string {
|
||||
if innerLayer == nil {
|
||||
tmp := InnerLayerCreate(nil)
|
||||
innerLayer = &tmp
|
||||
}
|
||||
if descSigningKey == nil {
|
||||
_, descSigningKey, _ = ed25519.GenerateKey(brand.Reader())
|
||||
}
|
||||
if revCounter == nil {
|
||||
tmp := btime.Clock.Now().Unix()
|
||||
revCounter = &tmp
|
||||
}
|
||||
blindedKey := blindedPubkey(identityKey, blindingNonce)
|
||||
//if blinding_nonce != nil {
|
||||
// blindedKey = onionbalance.BlindedPubkey(identityKey, blinding_nonce)
|
||||
//}
|
||||
pub := identityKey.Public()
|
||||
subcredential := subcredential(pub, blindedKey)
|
||||
|
||||
//if outerLayer == nil {
|
||||
outerLayer := OuterLayerCreate(innerLayer, revCounter, subcredential, blindedKey)
|
||||
//}
|
||||
|
||||
// if {
|
||||
signingCert := getSigningCert(blindedKey, descSigningKey, identityKey, blindingNonce)
|
||||
// }
|
||||
|
||||
descContent := "hs-descriptor 3\n"
|
||||
descContent += fmt.Sprintf("descriptor-lifetime %d\n", 180)
|
||||
descContent += "descriptor-signing-key-cert\n"
|
||||
descContent += signingCert.ToBase64() + "\n"
|
||||
descContent += fmt.Sprintf("revision-counter %d\n", *revCounter)
|
||||
descContent += "superencrypted\n"
|
||||
descContent += outerLayer.encrypt(*revCounter, subcredential, blindedKey) + "\n"
|
||||
|
||||
sigContent := SigPrefixHsV3 + descContent
|
||||
sig := ed25519.Sign(descSigningKey, []byte(sigContent))
|
||||
descContent += fmt.Sprintf("signature %s", strings.TrimRight(base64.StdEncoding.EncodeToString(sig), "="))
|
||||
|
||||
return descContent
|
||||
}
|
||||
|
||||
func priv2Pem(pk ed25519.PrivateKey) string {
|
||||
var identityKeyPem bytes.Buffer
|
||||
identityKeyBytes, _ := x509.MarshalPKCS8PrivateKey(pk)
|
||||
block := &pem.Block{Type: "PRIVATE KEY", Bytes: identityKeyBytes}
|
||||
_ = pem.Encode(&identityKeyPem, block)
|
||||
return identityKeyPem.String()
|
||||
}
|
||||
|
||||
func getSigningCert(blindedKey ed25519.PublicKey, descSigningKey ed25519.PrivateKey, identityKey gobpk.PrivateKey, blindingNonce []byte) Ed25519CertificateV1 {
|
||||
extensions := []Ed25519Extension{NewEd25519Extension(HasSigningKey, 0, blindedKey)}
|
||||
signingCert := NewEd25519CertificateV1(HsV3DescSigning, nil, 1, descSigningKey.Public().(ed25519.PublicKey), extensions, nil, nil)
|
||||
signingCert.Signature = blindedSign(signingCert.pack(), identityKey, blindedKey, blindingNonce)
|
||||
return signingCert
|
||||
}
|
||||
|
||||
const SigPrefixHsV3 = "Tor onion service descriptor sig v3"
|
||||
|
||||
func HiddenServiceDescriptorV3Create(blindingNonce []byte, identityPrivKey gobpk.PrivateKey, descSigningKey ed25519.PrivateKey, v3DescInnerLayer InnerLayer, revCounter int64) *HiddenServiceDescriptorV3 {
|
||||
return NewHiddenServiceDescriptorV3(HiddenServiceDescriptorV3Content(blindingNonce, identityPrivKey, descSigningKey, &v3DescInnerLayer, &revCounter))
|
||||
}
|
||||
|
||||
func NewHiddenServiceDescriptorV3(rawContents string) *HiddenServiceDescriptorV3 {
|
||||
d := &HiddenServiceDescriptorV3{}
|
||||
d.rawContents = rawContents
|
||||
d.Descriptor.FromStr(rawContents)
|
||||
d.SigningCert = Ed25519CertificateFromBase64(d.DescriptorSigningKeyCert)
|
||||
|
||||
//lines := strings.Split(rawContents, "\n")
|
||||
//startCert := false
|
||||
//startSuperencrypted := false
|
||||
//for idx, line := range lines {
|
||||
// if idx == 0 {
|
||||
// d.HsDescriptorVersion, _ = strconv.ParseInt(strings.TrimPrefix(line, "hs-descriptor "), 10, 64)
|
||||
// continue
|
||||
// } else if idx == 1 {
|
||||
// d.descriptorLifetime, _ = strconv.ParseInt(strings.TrimPrefix(line, "descriptor-lifetime "), 10, 64)
|
||||
// continue
|
||||
// } else if line == "descriptor-signing-key-cert" {
|
||||
// startCert = true
|
||||
// continue
|
||||
// } else if line == "superencrypted" {
|
||||
// startSuperencrypted = true
|
||||
// continue
|
||||
// } else if strings.HasPrefix(line, "revision-counter ") {
|
||||
// d.revisionCounter, _ = strconv.ParseInt(strings.TrimPrefix(line, "revision-counter "), 10, 64)
|
||||
// continue
|
||||
// } else if strings.HasPrefix(line, "signature ") {
|
||||
// d.signature = strings.TrimPrefix(line, "signature ")
|
||||
// continue
|
||||
// }
|
||||
// if startCert {
|
||||
// d.DescriptorSigningKeyCert += line + "\n"
|
||||
// if line == "-----END ED25519 CERT-----" {
|
||||
// startCert = false
|
||||
// d.DescriptorSigningKeyCert = strings.TrimSpace(d.DescriptorSigningKeyCert)
|
||||
// }
|
||||
// } else if startSuperencrypted {
|
||||
// d.superencrypted += line + "\n"
|
||||
// if line == "-----END MESSAGE-----" {
|
||||
// startSuperencrypted = false
|
||||
// d.superencrypted = strings.TrimSpace(d.superencrypted)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
// TODO - n0tr1v
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *HiddenServiceDescriptorV3) Decrypt(onionAddress string) (i *InnerLayer, err error) {
|
||||
if d.InnerLayer == nil {
|
||||
descriptorSigningKeyCert := d.DescriptorSigningKeyCert
|
||||
cert := Ed25519CertificateFromBase64(descriptorSigningKeyCert)
|
||||
blindedKey := cert.SigningKey()
|
||||
if blindedKey == nil {
|
||||
return d.InnerLayer, errors.New("no signing key is present")
|
||||
}
|
||||
identityPublicKey := IdentityKeyFromAddress(onionAddress)
|
||||
subcredential := subcredential(identityPublicKey, blindedKey)
|
||||
outerLayer := outerLayerDecrypt(d.superencrypted, d.revisionCounter, subcredential, blindedKey)
|
||||
tmp := innerLayerDecrypt(outerLayer, d.revisionCounter, subcredential, blindedKey)
|
||||
d.InnerLayer = &tmp
|
||||
}
|
||||
return d.InnerLayer, nil
|
||||
}
|
||||
|
||||
type InnerLayer struct {
|
||||
outer OuterLayer
|
||||
IntroductionPoints []IntroductionPointV3
|
||||
unparsedIntroductionPoints string
|
||||
rawContents string
|
||||
}
|
||||
|
||||
func (l InnerLayer) encrypt(revisionCounter int64, subcredential, blindedKey []byte) string {
|
||||
// encrypt back into an outer layer's 'encrypted' field
|
||||
return encryptLayer(l.getBytes(), "hsdir-encrypted-data", revisionCounter, subcredential, blindedKey)
|
||||
}
|
||||
|
||||
func (l InnerLayer) getBytes() []byte {
|
||||
return []byte(l.rawContents)
|
||||
}
|
||||
|
||||
func InnerLayerContent(introductionPoints []IntroductionPointV3) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("create2-formats 2")
|
||||
if introductionPoints != nil {
|
||||
for _, ip := range introductionPoints {
|
||||
sb.WriteByte('\n')
|
||||
sb.WriteString(ip.encode())
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func InnerLayerCreate(introductionPoints []IntroductionPointV3) InnerLayer {
|
||||
return NewInnerLayer(InnerLayerContent(introductionPoints), OuterLayer{})
|
||||
}
|
||||
|
||||
func NewInnerLayer(content string, outerLayer OuterLayer) InnerLayer {
|
||||
l := InnerLayer{}
|
||||
l.rawContents = content
|
||||
l.outer = outerLayer
|
||||
div := strings.Index(content, "\nintroduction-point ")
|
||||
if div != -1 {
|
||||
l.unparsedIntroductionPoints = content[div+1:]
|
||||
content = content[:div]
|
||||
} else {
|
||||
l.unparsedIntroductionPoints = ""
|
||||
}
|
||||
//entries := descriptor_components(content, validate)
|
||||
l.parseV3IntroductionPoints()
|
||||
return l
|
||||
}
|
||||
|
||||
type IntroductionPointV3 struct {
|
||||
LinkSpecifiers []LinkSpecifier
|
||||
OnionKey string
|
||||
EncKey string
|
||||
AuthKeyCertRaw string
|
||||
EncKeyCertRaw string
|
||||
AuthKeyCert Ed25519CertificateV1
|
||||
EncKeyCert Ed25519CertificateV1
|
||||
LegacyKeyRaw any
|
||||
}
|
||||
|
||||
func (i IntroductionPointV3) Equals(other IntroductionPointV3) bool {
|
||||
return i.encode() == other.encode()
|
||||
}
|
||||
|
||||
// Descriptor representation of this introduction point.
|
||||
func (i IntroductionPointV3) encode() string {
|
||||
var sb strings.Builder
|
||||
linkCount := uint8(len(i.LinkSpecifiers))
|
||||
linkSpecifiers := []byte{linkCount}
|
||||
for _, ls := range i.LinkSpecifiers {
|
||||
linkSpecifiers = append(linkSpecifiers, ls.pack()...)
|
||||
}
|
||||
sb.WriteString("introduction-point ")
|
||||
sb.WriteString(base64.StdEncoding.EncodeToString(linkSpecifiers))
|
||||
sb.WriteString("\n")
|
||||
|
||||
sb.WriteString("onion-key ntor ")
|
||||
sb.WriteString(i.OnionKey)
|
||||
sb.WriteString("\n")
|
||||
|
||||
sb.WriteString("auth-key\n")
|
||||
sb.WriteString(i.AuthKeyCertRaw)
|
||||
sb.WriteString("\n")
|
||||
|
||||
if i.EncKey != "" {
|
||||
sb.WriteString("enc-key ntor ")
|
||||
sb.WriteString(i.EncKey)
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
sb.WriteString("enc-key-cert\n")
|
||||
sb.WriteString(i.EncKeyCertRaw)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
/**
|
||||
// Descriptor representation of this introduction point.
|
||||
func (i IntroductionPointV3) encode() string {
|
||||
out := strings.Builder{}
|
||||
linkCount := uint8(len(i.LinkSpecifiers))
|
||||
linkSpecifiers := []byte{linkCount}
|
||||
for _, ls := range i.LinkSpecifiers {
|
||||
linkSpecifiers = append(linkSpecifiers, ls.pack()...)
|
||||
}
|
||||
out.WriteString(fmt.Sprintf("introduction-point %s\n", base64.StdEncoding.EncodeToString(linkSpecifiers)))
|
||||
out.WriteString(fmt.Sprintf("onion-key ntor %s\n", i.OnionKey))
|
||||
out.WriteString(fmt.Sprintf("auth-key\n%s\n", i.AuthKeyCertRaw))
|
||||
if i.EncKey != "" {
|
||||
out.WriteString(fmt.Sprintf("enc-key ntor %s\n", i.EncKey))
|
||||
}
|
||||
out.WriteString(fmt.Sprintf("enc-key-cert\n%s", i.EncKeyCertRaw))
|
||||
return out.String()
|
||||
}
|
||||
*/
|
||||
|
||||
func parseLinkSpecifier(content string) []LinkSpecifier {
|
||||
decoded, err := base64.StdEncoding.DecodeString(content)
|
||||
if err != nil {
|
||||
logrus.Panicf("Unable to base64 decode introduction point (%v): %s", err, content)
|
||||
}
|
||||
content = string(decoded)
|
||||
linkSpecifiers := make([]LinkSpecifier, 0)
|
||||
count, content := content[0], content[1:]
|
||||
for i := 0; i < int(count); i++ {
|
||||
var linkSpecifier LinkSpecifier
|
||||
linkSpecifier, content = linkSpecifierPop(content)
|
||||
linkSpecifiers = append(linkSpecifiers, linkSpecifier)
|
||||
}
|
||||
if len(content) > 0 {
|
||||
logrus.Panicf("Introduction point had excessive data (%s)", content)
|
||||
}
|
||||
return linkSpecifiers
|
||||
}
|
||||
|
||||
type LinkSpecifier struct {
|
||||
Typ uint8
|
||||
Value []byte
|
||||
}
|
||||
|
||||
func (l LinkSpecifier) String() string {
|
||||
return fmt.Sprintf("T:%d,V:%x", l.Typ, l.Value)
|
||||
}
|
||||
|
||||
func (l LinkSpecifier) pack() (out []byte) {
|
||||
out = append(out, l.Typ)
|
||||
out = append(out, uint8(len(l.Value)))
|
||||
out = append(out, l.Value...)
|
||||
return
|
||||
}
|
||||
|
||||
func linkSpecifierPop(packed string) (LinkSpecifier, string) {
|
||||
linkType, packed := packed[0], packed[1:]
|
||||
valueSize, packed := packed[0], packed[1:]
|
||||
if int(valueSize) > len(packed) {
|
||||
logrus.Panicf("Link specifier should have %d bytes, but only had %d remaining", valueSize, len(packed))
|
||||
}
|
||||
value, packed := packed[:valueSize], packed[valueSize:]
|
||||
if linkType == 0 {
|
||||
return LinkByIPv4Unpack(value).LinkSpecifier, packed
|
||||
} else if linkType == 1 {
|
||||
return LinkByIPv6Unpack(value).LinkSpecifier, packed
|
||||
} else if linkType == 2 {
|
||||
return NewLinkByFingerprint([]byte(value)).LinkSpecifier, packed
|
||||
} else if linkType == 3 {
|
||||
return NewLinkByEd25519([]byte(value)).LinkSpecifier, packed
|
||||
}
|
||||
return LinkSpecifier{Typ: linkType, Value: []byte(value)}, packed // unrecognized type
|
||||
}
|
||||
|
||||
type LinkByIPv4 struct {
|
||||
LinkSpecifier
|
||||
Address string
|
||||
Port uint16
|
||||
}
|
||||
|
||||
func NewLinkByIPv4(address string, port uint16) LinkByIPv4 {
|
||||
portBytes := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(portBytes, port)
|
||||
l := LinkByIPv4{}
|
||||
l.Typ = 0
|
||||
l.Value = append(packIPV4Address(address), portBytes...)
|
||||
l.Address = address
|
||||
l.Port = port
|
||||
return l
|
||||
}
|
||||
|
||||
func LinkByIPv4Unpack(value string) LinkByIPv4 {
|
||||
if len(value) != 6 {
|
||||
logrus.Panicf("IPv4 link specifiers should be six bytes, but was %d instead: %x", len(value), value)
|
||||
}
|
||||
addr, portRaw := value[:4], value[4:]
|
||||
port := binary.BigEndian.Uint16([]byte(portRaw))
|
||||
return NewLinkByIPv4(unpackIPV4Address([]byte(addr)), port)
|
||||
}
|
||||
|
||||
func NewLinkByIPv6(address string, port uint16) LinkByIPv6 {
|
||||
portBytes := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(portBytes, port)
|
||||
l := LinkByIPv6{}
|
||||
l.Typ = 1
|
||||
l.Value = append(packIPV6Address(address), portBytes...)
|
||||
l.Address = address
|
||||
l.Port = port
|
||||
return l
|
||||
}
|
||||
|
||||
func LinkByIPv6Unpack(value string) LinkByIPv6 {
|
||||
if len(value) != 18 {
|
||||
logrus.Panicf("IPv6 link specifiers should be eighteen bytes, but was %d instead: %x", len(value), value)
|
||||
}
|
||||
addr, portRaw := value[:16], value[16:]
|
||||
port := binary.BigEndian.Uint16([]byte(portRaw))
|
||||
return NewLinkByIPv6(unpackIPV6Address([]byte(addr)), port)
|
||||
}
|
||||
|
||||
func packIPV4Address(address string) (out []byte) {
|
||||
parts := strings.Split(address, ".")
|
||||
for _, part := range parts {
|
||||
tmp, _ := strconv.ParseUint(part, 10, 8)
|
||||
out = append(out, uint8(tmp))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func unpackIPV4Address(value []byte) string {
|
||||
strs := make([]string, 0)
|
||||
for i := 0; i < 4; i++ {
|
||||
strs = append(strs, fmt.Sprintf("%d", value[i]))
|
||||
}
|
||||
return strings.Join(strs, ".")
|
||||
}
|
||||
|
||||
func packIPV6Address(address string) (out []byte) {
|
||||
parts := strings.Split(address, ":")
|
||||
for _, part := range parts {
|
||||
tmp, _ := hex.DecodeString(part)
|
||||
out = append(out, tmp...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func unpackIPV6Address(value []byte) string {
|
||||
strs := make([]string, 0)
|
||||
for i := 0; i < 8; i++ {
|
||||
strs = append(strs, fmt.Sprintf("%04x", value[i*2:(i+1)*2]))
|
||||
}
|
||||
return strings.Join(strs, ":")
|
||||
}
|
||||
|
||||
type LinkByIPv6 struct {
|
||||
LinkSpecifier
|
||||
Address string
|
||||
Port uint16
|
||||
}
|
||||
|
||||
type LinkByFingerprint struct {
|
||||
LinkSpecifier
|
||||
Fingerprint []byte
|
||||
}
|
||||
|
||||
type LinkByEd25519 struct {
|
||||
LinkSpecifier
|
||||
Fingerprint []byte
|
||||
}
|
||||
|
||||
func NewLinkByFingerprint(value []byte) LinkByFingerprint {
|
||||
if len(value) != 20 {
|
||||
logrus.Panicf("Fingerprint link specifiers should be twenty bytes, but was %d instead: %x", len(value), value)
|
||||
}
|
||||
l := LinkByFingerprint{}
|
||||
l.Typ = 2
|
||||
l.Value = value
|
||||
l.Fingerprint = value
|
||||
return l
|
||||
}
|
||||
|
||||
func NewLinkByEd25519(value []byte) LinkByEd25519 {
|
||||
if len(value) != 32 {
|
||||
logrus.Panicf("Fingerprint link specifiers should be thirty two bytes, but was %d instead: %x", len(value), value)
|
||||
}
|
||||
l := LinkByEd25519{}
|
||||
l.Typ = 3
|
||||
l.Value = value
|
||||
l.Fingerprint = value
|
||||
return l
|
||||
}
|
||||
|
||||
func introductionPointV3Parse(content string) IntroductionPointV3 {
|
||||
ip := IntroductionPointV3{}
|
||||
authKeyCertContent := ""
|
||||
encKeyCertContent := ""
|
||||
lines := strings.Split(content, "\n")
|
||||
startAuthKey := false
|
||||
startEncKeyCert := false
|
||||
for _, line := range lines {
|
||||
if line == "auth-key" {
|
||||
startAuthKey = true
|
||||
continue
|
||||
} else if strings.HasPrefix(line, "introduction-point ") {
|
||||
ip.LinkSpecifiers = parseLinkSpecifier(strings.TrimPrefix(line, "introduction-point "))
|
||||
continue
|
||||
} else if strings.HasPrefix(line, "onion-key ntor ") {
|
||||
ip.OnionKey = strings.TrimPrefix(line, "onion-key ntor ")
|
||||
continue
|
||||
} else if strings.HasPrefix(line, "enc-key ntor ") {
|
||||
ip.EncKey = strings.TrimPrefix(line, "enc-key ntor ")
|
||||
continue
|
||||
} else if line == "enc-key-cert" {
|
||||
startEncKeyCert = true
|
||||
continue
|
||||
}
|
||||
if startAuthKey {
|
||||
authKeyCertContent += line + "\n"
|
||||
if line == "-----END ED25519 CERT-----" {
|
||||
startAuthKey = false
|
||||
authKeyCertContent = strings.TrimSpace(authKeyCertContent)
|
||||
}
|
||||
}
|
||||
if startEncKeyCert {
|
||||
encKeyCertContent += line + "\n"
|
||||
if line == "-----END ED25519 CERT-----" {
|
||||
startEncKeyCert = false
|
||||
encKeyCertContent = strings.TrimSpace(encKeyCertContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
ip.AuthKeyCertRaw = authKeyCertContent
|
||||
ip.EncKeyCertRaw = encKeyCertContent
|
||||
ip.AuthKeyCert = Ed25519CertificateFromBase64(authKeyCertContent)
|
||||
ip.EncKeyCert = Ed25519CertificateFromBase64(encKeyCertContent)
|
||||
return ip
|
||||
}
|
||||
|
||||
func (l *InnerLayer) parseV3IntroductionPoints() {
|
||||
introductionPoints := make([]IntroductionPointV3, 0)
|
||||
remaining := l.unparsedIntroductionPoints
|
||||
for remaining != "" {
|
||||
div := strings.Index(remaining, "\nintroduction-point ")
|
||||
var content string
|
||||
if div != -1 {
|
||||
content = remaining[:div]
|
||||
remaining = remaining[div+1:]
|
||||
} else {
|
||||
content = remaining
|
||||
remaining = ""
|
||||
}
|
||||
introductionPoints = append(introductionPoints, introductionPointV3Parse(content))
|
||||
}
|
||||
l.IntroductionPoints = introductionPoints
|
||||
}
|
||||
|
||||
func innerLayerDecrypt(outerLayer OuterLayer, revisionCounter int64, subcredential, blindedKey ed25519.PublicKey) InnerLayer {
|
||||
plaintext := decryptLayer(outerLayer.encrypted, "hsdir-encrypted-data", revisionCounter, subcredential, blindedKey)
|
||||
return NewInnerLayer(plaintext, outerLayer)
|
||||
}
|
||||
|
||||
type OuterLayer struct {
|
||||
encrypted string
|
||||
rawContent string
|
||||
}
|
||||
|
||||
func (l OuterLayer) encrypt(revisionCounter int64, subcredential, blindedKey []byte) string {
|
||||
// Spec mandated padding: "Before encryption the plaintext is padded with
|
||||
// NUL bytes to the nearest multiple of 10k bytes."
|
||||
content := append(l.getBytes(), bytes.Repeat([]byte("\x00"), len(l.getBytes())%10000)...)
|
||||
// encrypt back into a hidden service descriptor's 'superencrypted' field
|
||||
return encryptLayer(content, "hsdir-superencrypted-data", revisionCounter, subcredential, blindedKey)
|
||||
}
|
||||
|
||||
func encryptLayer(plaintext []byte, constant string, revisionCounter int64, subcredential, blindedKey []byte) string {
|
||||
salt := make([]byte, 16)
|
||||
_, _ = brand.Read(salt)
|
||||
return encryptLayerDet(plaintext, constant, revisionCounter, subcredential, blindedKey, salt)
|
||||
}
|
||||
|
||||
// Deterministic code for tests
|
||||
func encryptLayerDet(plaintext []byte, constant string, revisionCounter int64, subcredential, blindedKey, salt []byte) string {
|
||||
ciphr, macFor := layerCipher(constant, revisionCounter, subcredential, blindedKey, salt)
|
||||
ciphertext := make([]byte, len(plaintext))
|
||||
ciphr.XORKeyStream(ciphertext, plaintext)
|
||||
encoded := base64.StdEncoding.EncodeToString([]byte(string(salt) + string(ciphertext) + string(macFor(ciphertext))))
|
||||
splits := splitByLength(encoded, 64)
|
||||
joined := strings.Join(splits, "\n")
|
||||
return fmt.Sprintf("-----BEGIN MESSAGE-----\n%s\n-----END MESSAGE-----", joined)
|
||||
}
|
||||
|
||||
func (l OuterLayer) getBytes() []byte {
|
||||
return []byte(l.rawContent)
|
||||
}
|
||||
|
||||
func OuterLayerCreate(innerLayer *InnerLayer, revisionCounter *int64, subcredential, blindedKey []byte) OuterLayer {
|
||||
return NewOuterLayer(OuterLayerContent(innerLayer, revisionCounter, subcredential, blindedKey))
|
||||
}
|
||||
|
||||
// AuthorizedClient Client authorized to use a v3 hidden service.
|
||||
// id: base64 encoded client id
|
||||
// iv: base64 encoded randomized initialization vector
|
||||
// cookie: base64 encoded authentication cookie
|
||||
type AuthorizedClient struct {
|
||||
id string
|
||||
iv string
|
||||
cookie string
|
||||
}
|
||||
|
||||
func NewAuthorizedClient() AuthorizedClient {
|
||||
a := AuthorizedClient{}
|
||||
idBytes := make([]byte, 8)
|
||||
_, _ = brand.Read(idBytes)
|
||||
a.id = strings.TrimRight(base64.StdEncoding.EncodeToString(idBytes), "=")
|
||||
ivBytes := make([]byte, 16)
|
||||
_, _ = brand.Read(ivBytes)
|
||||
a.iv = strings.TrimRight(base64.StdEncoding.EncodeToString(ivBytes), "=")
|
||||
cookieBytes := make([]byte, 16)
|
||||
_, _ = brand.Read(cookieBytes)
|
||||
a.cookie = strings.TrimRight(base64.StdEncoding.EncodeToString(cookieBytes), "=")
|
||||
return a
|
||||
}
|
||||
|
||||
func OuterLayerContent(innerLayer *InnerLayer, revisionCounter *int64, subcredential, blindedKey []byte) string {
|
||||
if innerLayer == nil {
|
||||
tmp := InnerLayerCreate(nil)
|
||||
innerLayer = &tmp
|
||||
}
|
||||
|
||||
authorizedClients := make([]AuthorizedClient, 0)
|
||||
for i := 0; i < 16; i++ {
|
||||
authorizedClients = append(authorizedClients, NewAuthorizedClient())
|
||||
}
|
||||
|
||||
pk, _ := x25519.GenerateKey(brand.Reader())
|
||||
|
||||
out := "desc-auth-type x25519\n"
|
||||
out += "desc-auth-ephemeral-key " + base64.StdEncoding.EncodeToString(pk.PublicKey.Bytes()) + "\n"
|
||||
for _, c := range authorizedClients {
|
||||
out += fmt.Sprintf("auth-client %s %s %s\n", c.id, c.iv, c.cookie)
|
||||
}
|
||||
out += "encrypted\n"
|
||||
out += innerLayer.encrypt(*revisionCounter, subcredential, blindedKey)
|
||||
return out
|
||||
}
|
||||
|
||||
func NewOuterLayer(content string) OuterLayer {
|
||||
l := OuterLayer{}
|
||||
l.rawContent = content
|
||||
encrypted := parseOuterLayer(content)
|
||||
l.encrypted = encrypted
|
||||
return l
|
||||
}
|
||||
|
||||
func parseOuterLayer(content string) string {
|
||||
out := ""
|
||||
lines := strings.Split(content, "\n")
|
||||
startEncrypted := false
|
||||
for _, line := range lines {
|
||||
if line == "encrypted" {
|
||||
startEncrypted = true
|
||||
continue
|
||||
}
|
||||
if startEncrypted {
|
||||
out += line + "\n"
|
||||
if line == "-----END MESSAGE-----" {
|
||||
startEncrypted = false
|
||||
out = strings.TrimSpace(out)
|
||||
}
|
||||
}
|
||||
}
|
||||
out = strings.ReplaceAll(out, "\r", "")
|
||||
out = strings.ReplaceAll(out, "\x00", "")
|
||||
return strings.TrimSpace(out)
|
||||
}
|
||||
|
||||
func outerLayerDecrypt(encrypted string, revisionCounter int64, subcredential, blindedKey ed25519.PublicKey) OuterLayer {
|
||||
plaintext := decryptLayer(encrypted, "hsdir-superencrypted-data", revisionCounter, subcredential, blindedKey)
|
||||
return NewOuterLayer(plaintext)
|
||||
}
|
||||
|
||||
func decryptLayer(encryptedBlock, constant string, revisionCounter int64, subcredential, blindedKey ed25519.PublicKey) string {
|
||||
if strings.HasPrefix(encryptedBlock, "-----BEGIN MESSAGE-----\n") &&
|
||||
strings.HasSuffix(encryptedBlock, "\n-----END MESSAGE-----") {
|
||||
encryptedBlock = strings.TrimPrefix(encryptedBlock, "-----BEGIN MESSAGE-----\n")
|
||||
encryptedBlock = strings.TrimSuffix(encryptedBlock, "\n-----END MESSAGE-----")
|
||||
}
|
||||
encrypted, err := base64.StdEncoding.DecodeString(encryptedBlock)
|
||||
if err != nil {
|
||||
panic("Unable to decode encrypted block as base64")
|
||||
}
|
||||
if len(encrypted) < SALT_LEN+MAC_LEN {
|
||||
logrus.Panicf("Encrypted block malformed (only %d bytes)", len(encrypted))
|
||||
}
|
||||
salt := encrypted[:SALT_LEN]
|
||||
ciphertext := encrypted[SALT_LEN : len(encrypted)-MAC_LEN]
|
||||
expectedMac := encrypted[len(encrypted)-MAC_LEN:]
|
||||
ciphr, macFor := layerCipher(constant, revisionCounter, subcredential, blindedKey, salt)
|
||||
|
||||
if !bytes.Equal(expectedMac, macFor(ciphertext)) {
|
||||
logrus.Panicf("Malformed mac (expected %x, but was %x)", expectedMac, macFor(ciphertext))
|
||||
}
|
||||
|
||||
plaintext := make([]byte, len(ciphertext))
|
||||
ciphr.XORKeyStream(plaintext, ciphertext)
|
||||
return string(plaintext)
|
||||
}
|
||||
|
||||
func layerCipher(constant string, revisionCounter int64, subcredential []byte, blindedKey ed25519.PublicKey, salt []byte) (cipher.Stream, func([]byte) []byte) {
|
||||
keys := make([]byte, S_KEY_LEN+S_IV_LEN+MAC_LEN)
|
||||
data1 := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(data1, uint64(revisionCounter))
|
||||
data := []byte(string(blindedKey) + string(subcredential) + string(data1) + string(salt) + constant)
|
||||
sha3.ShakeSum256(keys, data)
|
||||
|
||||
secretKey := keys[:S_KEY_LEN]
|
||||
secretIv := keys[S_KEY_LEN : S_KEY_LEN+S_IV_LEN]
|
||||
macKey := keys[S_KEY_LEN+S_IV_LEN:]
|
||||
|
||||
block, _ := aes.NewCipher(secretKey)
|
||||
ciphr := cipher.NewCTR(block, secretIv)
|
||||
//cipher = Cipher(algorithms.AES(secret_key), modes.CTR(secret_iv), default_backend())
|
||||
data2 := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(data2, uint64(len(macKey)))
|
||||
data3 := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(data3, uint64(len(salt)))
|
||||
macPrefix := string(data2) + string(macKey) + string(data3) + string(salt)
|
||||
fn := func(ciphertext []byte) []byte {
|
||||
tmp := sha3.Sum256([]byte(macPrefix + string(ciphertext)))
|
||||
return tmp[:]
|
||||
}
|
||||
return ciphr, fn
|
||||
}
|
||||
|
||||
const S_KEY_LEN = 32
|
||||
const S_IV_LEN = 16
|
||||
const SALT_LEN = 16
|
||||
const MAC_LEN = 32
|
||||
|
||||
// IdentityKeyFromAddress converts a hidden service address into its public identity key.
|
||||
func IdentityKeyFromAddress(onionAddress string) ed25519.PublicKey {
|
||||
if strings.HasSuffix(onionAddress, ".onion") {
|
||||
onionAddress = strings.TrimSuffix(onionAddress, ".onion")
|
||||
}
|
||||
decodedAddress, _ := base32.StdEncoding.DecodeString(strings.ToUpper(onionAddress))
|
||||
pubKey := decodedAddress[:32]
|
||||
expectedChecksum := decodedAddress[32:34]
|
||||
version := decodedAddress[34:35]
|
||||
checksumTmp := sha3.Sum256([]byte(".onion checksum" + string(pubKey) + string(version)))
|
||||
checksum := checksumTmp[:2]
|
||||
if !bytes.Equal(expectedChecksum, checksum) {
|
||||
logrus.Panicf("Bad checksum (expected %x but was %x)", expectedChecksum, checksum)
|
||||
}
|
||||
return pubKey
|
||||
}
|
||||
|
||||
func AddressFromIdentityKey(pub ed25519.PublicKey) string {
|
||||
var checksumBytes bytes.Buffer
|
||||
checksumBytes.Write([]byte(".onion checksum"))
|
||||
checksumBytes.Write(pub)
|
||||
checksumBytes.Write([]byte{0x03})
|
||||
checksum := sha3.Sum256(checksumBytes.Bytes())
|
||||
var onionAddressBytes bytes.Buffer
|
||||
onionAddressBytes.Write(pub)
|
||||
onionAddressBytes.Write(checksum[:2])
|
||||
onionAddressBytes.Write([]byte{0x03})
|
||||
addr := strings.ToLower(base32.StdEncoding.EncodeToString(onionAddressBytes.Bytes()))
|
||||
return addr + ".onion"
|
||||
}
|
||||
|
||||
func subcredential(identityKey, blindedKey ed25519.PublicKey) []byte {
|
||||
// credential = H('credential' | public - identity - key)
|
||||
// subcredential = H('subcredential' | credential | blinded - public - key)
|
||||
credential := sha3.Sum256([]byte("credential" + string(identityKey)))
|
||||
sub := sha3.Sum256([]byte("subcredential" + string(credential[:]) + string(blindedKey)))
|
||||
return sub[:]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue