adding skeleton functionality

This commit is contained in:
2016-09-17 22:53:50 -04:00
parent 94c601d5ea
commit 80edad1ed3
9 changed files with 460 additions and 2 deletions

2
.gitignore vendored
View File

@ -22,3 +22,5 @@ _testmain.go
*.exe
*.test
*.prof
/go-syndie

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2016 Keith Petkus
Copyright (c) 2016 Keith Petkus <keith@keithp.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1 +1,6 @@
# go-syndie
# go-syndie
Major WIP, not much to see here. Expect major refactoring and breaking changes.
Dependencies:
* http://github.com/Sirupsen/logrus
* http://github.com/jackpal/bencode-go

37
header.go Normal file
View File

@ -0,0 +1,37 @@
package main
// SyndieHeader holds a Syndie header that contains version and pairs fields:
/*
Author AuthenticationMask TargetChannel PostURI References Tags OverwriteURI ForceNewThread
RefuseReplies Cancel Subject BodyKey BodyKeyPromptSalt BodyKeyPrompt Identity EncryptKey Name
Description Edition PublicPosting PublicReplies AuthorizedKeys ManagerKeys Archives ChannelReadKeys Expiration
*/
type SyndieHeader struct {
Version string
Author string
AuthenticationMask string
TargetChannel string
PostURI URI
References []URI
Tags []string
OverwriteURI URI
ForceNewThread bool
RefuseReplies bool
Cancel []URI
Subject string
BodyKey string
BodyKeyPromptSalt string
BodyKeyPrompt string
Identity string
EncryptKey string
Name string
Description string
Edition int
PublicPosting bool
PublicReplies bool
AuthorizedKeys []string
ManagerKeys []string
Archives []URI
ChannelReadKeys []string
Expiration string
}

13
main.go Normal file
View File

@ -0,0 +1,13 @@
package main
import (
"os"
log "github.com/Sirupsen/logrus"
)
func main() {
log.Printf("go-syndie: startup.")
payload := SyndiePayload{}
payload.OpenFile(os.Args[1])
}

173
payload.go Normal file
View File

@ -0,0 +1,173 @@
package main
import (
"bufio"
"bytes"
"os"
"strconv"
"strings"
log "github.com/Sirupsen/logrus"
)
// SyndiePayload holds a Syndie Payload that contains a header and trailer field
type SyndiePayload struct {
Header []byte
Trailer []byte
}
// Message holds the reference to a Syndie header and trailer
type Message struct {
Head *SyndieHeader
Body *SyndieTrailer
}
// MarshallTrailer marshalls a SyndiePayload into a SyndieTrailer with various struct fields
func (payload *SyndiePayload) MarshallTrailer() *SyndieTrailer {
bs := payload.Trailer
trailer := SyndieTrailer{}
if bytes.HasPrefix(bs, []byte("Size=")) {
line := strings.Split(string(bs)[len("Size="):], "\n")
size, err := strconv.Atoi(line[0])
rest := strings.Join(line[1:], "")
if err != nil {
log.WithFields(log.Fields{
"at": "(payload) MarshallTrailer",
"size": size,
"line": line,
"reason": "parsing error",
}).Fatalf("%s", err)
}
trailer.size = size
trailer.body = []byte(rest)
} else {
panic("Invalid trailer marshalling attempted")
}
return &trailer
}
// MarshallHeader marshalls a SyndiePayload into a SyndieHeader with various struct fields
func (payload *SyndiePayload) MarshallHeader() *SyndieHeader {
header := SyndieHeader{}
str := strings.SplitAfter(string(payload.Header), "\n")
header.Version = str[0]
for _, h := range str {
switch strings.Split(h, "=")[0] {
case "Author":
header.Author = strings.Split(h, "=")[1]
case "AuthenticationMask":
header.AuthenticationMask = strings.Split(h, "=")[1]
case "TargetChannel":
header.TargetChannel = strings.Split(h, "=")[1]
case "PostURI":
// header.PostURI = strings.Split(h, "=")[1:]
case "References":
// header.References = strings.Split(h, "=")[1:]
case "Tags":
header.Tags = strings.Split(h, "=")[1:]
case "OverwriteURI":
// header.OverwriteURI = strings.Split(h, "=")[1:]
case "ForceNewThread":
if strings.Contains(strings.Split(h, "=")[1], "true") {
header.ForceNewThread = true
}
case "RefuseReplies":
if strings.Contains(strings.Split(h, "=")[1], "true") {
header.RefuseReplies = true
}
case "Cancel":
// header.Cancel = strings.Split(h, "=")[1:]
case "Subject":
header.Subject = strings.Split(h, "=")[1]
case "BodyKey":
header.BodyKey = strings.Split(h, "=")[1]
case "BodyKeyPromptSalt":
header.BodyKeyPromptSalt = strings.Split(h, "=")[1]
case "BodyKeyPrompt":
header.BodyKeyPrompt = strings.Split(h, "=")[1]
case "Identity":
header.Identity = strings.Split(h, "=")[1]
case "EncryptKey":
header.EncryptKey = strings.Split(h, "=")[1]
case "Name":
header.Name = strings.Split(h, "=")[1]
case "Description":
header.Description = strings.Split(h, "=")[1]
case "Edition":
i, err := strconv.Atoi(strings.TrimRight(strings.Split(h, "=")[1], "\n"))
if err != nil {
log.WithFields(log.Fields{
"at": "(payload) MarshallHeader strconv",
"i": i,
"reason": "conversion error",
}).Fatalf("%s", err)
}
header.Edition = i
case "PublicPosting":
if strings.Contains(strings.Split(h, "=")[1], "true") {
header.PublicPosting = true
}
case "PublicReplies":
if strings.Contains(strings.Split(h, "=")[1], "true") {
header.PublicReplies = true
}
case "AuthorizedKeys":
header.AuthorizedKeys = strings.Split(h, "=")[1:]
case "ManagerKeys":
header.ManagerKeys = strings.Split(h, "=")[1:]
case "Archives":
// header.Archives = strings.Split(h, "=")[1:]
case "ChannelReadKeys":
header.ChannelReadKeys = strings.Split(h, "=")[1:]
case "Expiration":
header.Expiration = strings.Split(h, "=")[1]
}
}
return &header
}
// Parse marshalls a raw []byte representation of a Syndie Message into header and trailer fields
func (payload *SyndiePayload) Parse(bs []byte) {
if bytes.HasPrefix(bs, []byte("Syndie.Message.1.0")) {
payload.Header = bs
} else {
rest := append(payload.Trailer[:], bs...)
payload.Trailer = rest
}
}
//OpenFile tries to open a SyndiePayload
func (payload *SyndiePayload) OpenFile(s string) {
file, err := os.Open(s)
if err != nil {
log.WithFields(log.Fields{
"at": "(main)",
"file": s,
"reason": "failed to open file",
}).Fatalf("%s", err)
}
defer file.Close()
message := Message{}
scanner := bufio.NewScanner(file)
buf := make([]byte, 0, 64*1024)
scanner.Buffer(buf, 1024*1024)
scanner.Split(newlineDelimiter)
for scanner.Scan() {
payload.Parse(scanner.Bytes())
}
if err := scanner.Err(); err != nil {
log.WithFields(log.Fields{
"at": "(main) scanner.Scan",
"payload_parsed": scanner.Bytes(),
"reason": "invalid input scanned",
}).Fatalf("%s", err)
}
message.Head = payload.MarshallHeader()
message.Body = payload.MarshallTrailer()
// TODO: Lookup the channel key from the URI and attempt to decrypt
log.Printf("Dumping contents: %s", message.Body.DecryptAES(""))
}

72
trailer.go Normal file
View File

@ -0,0 +1,72 @@
package main
import (
"crypto/aes"
"crypto/cipher"
"encoding/binary"
log "github.com/Sirupsen/logrus"
)
// SyndieTrailer contains a Syndie trailer that contains version and pairs fields
type SyndieTrailer struct {
size int
body []byte
authorizationSig string
authenticationSig string
}
// MessagePayload holds the following: rand(nonzero) padding + 0 + internalSize + totalSize + data + rand
type MessagePayload struct {
internalSize int
totalSize int
decrypted []byte
}
// DecryptAES decrypts a SyndieTrailer into a messagePayload
func (trailer *SyndieTrailer) DecryptAES(key string) MessagePayload {
var found bool
inner := MessagePayload{}
k, err := I2PEncoding.DecodeString(key)
if err != nil {
log.WithFields(log.Fields{
"at": "(trailer) DecryptAES, DecodeString",
"key": key,
"reason": "unable to convert key into b64",
}).Fatalf("%s", err)
}
block, err := aes.NewCipher([]byte(k))
if err != nil {
log.WithFields(log.Fields{
"at": "(trailer) DecryptAES, NewCipher",
"key": key,
"block": block,
"reason": "invalid block AES cipher",
}).Fatalf("%s", err)
}
decrypter := cipher.NewCBCDecrypter(block, trailer.body[:16])
decrypted := make([]byte, len(trailer.body[:trailer.size]))
decrypter.CryptBlocks(decrypted, trailer.body[:trailer.size])
for i := range decrypted {
if !found && decrypted[i] == 0x0 {
is := int(binary.BigEndian.Uint32(decrypted[i+1 : +i+5]))
ts := int(binary.BigEndian.Uint32(decrypted[i+5 : +i+9]))
if trailer.size != ts+16 {
log.WithFields(log.Fields{
"at": "(trailer) DecryptAES",
"trailer_size": trailer.size,
"is": is,
"ts": ts + 16,
"reason": "payload size did not match envelope size",
}).Fatalf("%s", err)
}
inner.internalSize = is
inner.totalSize = ts
inner.decrypted = decrypted[i+10 : is]
found = true
}
}
return inner
}

129
uri.go Normal file
View File

@ -0,0 +1,129 @@
package main
import (
"bytes"
"errors"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/jackpal/bencode-go"
)
/*
URI defines the URIs safely passable within syndie, capable of referencing specific resources.
They contain one of four reference types, plus a bencoded set of attributes:
*/
type URI struct {
RefType string
Name string `bencode:",omitempty"`
Desc string `bencode:",omitempty"`
Tag []string `bencode:",omitempty"`
Author string `bencode:",omitempty"`
Net string `bencode:",omitempty"`
ReadKeyType string `bencode:",omitempty"`
ReadKeyData string `bencode:",omitempty"`
PostKeyType string `bencode:",omitempty"`
PostKeyData string `bencode:",omitempty"`
URL string `bencode:",omitempty"`
Channel string `bencode:",omitempty"`
MessageID int `bencode:",omitempty"`
Page int `bencode:",omitempty"`
Attachment int `bencode:",omitempty"`
Scope []string `bencode:",omitempty"`
PostByScope []string `bencode:",omitempty"`
Age int `bencode:",omitempty"`
AgeLocal int `bencode:",omitempty"`
UnreadOnly bool `bencode:",omitempty"`
TagInclude []string `bencode:",omitempty"`
TagRequire []string `bencode:",omitempty"`
TagExclude []string `bencode:",omitempty"`
TagMessages bool `bencode:",omitempty"`
PageMin int `bencode:",omitempty"`
PageMax int `bencode:",omitempty"`
AttachMin int `bencode:",omitempty"`
AttachMax int `bencode:",omitempty"`
RefMin int `bencode:",omitempty"`
RefMax int `bencode:",omitempty"`
KeyMin int `bencode:",omitempty"`
KeyMax int `bencode:",omitempty"`
Encrypted bool `bencode:",omitempty"`
PBE bool `bencode:",omitempty"`
Private bool `bencode:",omitempty"`
Public bool `bencode:",omitempty"`
Authorized bool `bencode:",omitempty"`
Threaded bool `bencode:",omitempty"`
Keyword string `bencode:",omitempty"`
Body string `bencode:",omitempty"`
}
// colonDelimiter returns data for a scanner delimited by a colon
func colonDelimiter(data []byte, atEOF bool) (advance int, token []byte, err error) {
log.Printf("data was %s", data)
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.Index(data, []byte{58}); i >= 0 {
return i + 1, data[0:i], nil
}
if atEOF {
return len(data), data, nil
}
return 0, nil, nil
}
// trimSyndieURI trims "urn:", "urn:syndie", and "syndie:" from the left of a string
func trimSyndieURI(in string) string {
if strings.HasPrefix(in, "urn:syndie:") {
in = strings.Join(strings.Split(in, "urn:syndie:")[1:], "")
}
if strings.HasPrefix(in, "urn:") {
in = strings.Join(strings.Split(in, "urn:")[1:], "")
}
if strings.HasPrefix(in, "syndie:") {
in = strings.Join(strings.Split(in, "syndie:")[1:], "")
}
return in
}
// prepareURI checks a string for an invalid URI and ommits the refType before bencoding the rest.
func prepareURI(in string) (out string, err error) {
if len(in) < 3 {
return in, errors.New("invalid URI")
}
in = trimSyndieURI(in)
switch strings.Split(strings.ToLower(in), ":")[0] {
case "url", "channel", "search", "archive", "text":
// Drop the RefType to prepare for bencode
return strings.Join(strings.Split(in, ":")[1:], ":"), nil
default:
return in, errors.New("invalid URI refType: " + in)
}
}
// Marshall takes a URI as string and returns a populated URI
func (u *URI) Marshall(s string) *URI {
if len(s) < 3 {
log.WithFields(log.Fields{
"at": "(uri) Marshall",
"reason": "URI was too short to process",
}).Fatalf("URI was too short to process")
return &URI{}
}
s = trimSyndieURI(s)
u.RefType = strings.Split(s, ":")[0]
prepared, err := prepareURI(s)
if err != nil {
panic(err)
}
r := bytes.NewReader([]byte(prepared))
berr := bencode.Unmarshal(r, &u)
if berr != nil {
log.WithFields(log.Fields{
"at": "(uri) Marshall",
"reason": "error while parsing bencode",
}).Fatalf("%s", berr)
panic(err)
}
return u
}

27
util.go Normal file
View File

@ -0,0 +1,27 @@
package main
import (
"bytes"
"encoding/base64"
)
// TODO: depend on go-i2p/common
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~"
// I2PEncoding returns an I2P compatible base64 encoding based on a custom alphabet
var I2PEncoding *base64.Encoding = base64.NewEncoding(alphabet)
// newlineDelimiter returns data for a scanner delimited by \n\n
func newlineDelimiter(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.Index(data, []byte{'\n', '\n'}); i >= 0 {
return i + 2, data[0:i], nil
}
if atEOF {
return len(data), data, nil
}
return 0, nil, nil
}