diff --git a/cryptojs/cryptojs.go b/cryptojs/cryptojs.go
new file mode 100644
index 0000000..b1af6bc
--- /dev/null
+++ b/cryptojs/cryptojs.go
@@ -0,0 +1,126 @@
+package cryptojs
+
+import (
+ "encoding/base64"
+ "crypto/md5"
+ "crypto/aes"
+ "crypto/cipher"
+ "math/rand"
+ "errors"
+
+ "git.meow.tf/tyler/go-pastee/evpkdf"
+ "bytes"
+)
+
+const (
+ keySize = 32
+)
+
+func Encrypt(data, passphrase string) (string, error) {
+ salt := make([]byte, 8)
+
+ if _, err := rand.Read(salt); err != nil {
+ return "", err
+ }
+
+ key := make([]byte, keySize)
+ iv := make([]byte, aes.BlockSize)
+
+ keymat := evpkdf.New(md5.New, []byte(passphrase), salt, keySize + aes.BlockSize, 1)
+ keymatbuf := bytes.NewReader(keymat)
+
+ n, err := keymatbuf.Read(key)
+
+ if n != keySize || err != nil {
+ return "", err
+ }
+
+ n, err = keymatbuf.Read(iv)
+
+ if n != aes.BlockSize || err != nil {
+ return "", err
+ }
+
+ block, err := aes.NewCipher(key)
+
+ if err != nil {
+ return "", err
+ }
+
+ padded, err := pkcs7Pad([]byte(data), block.BlockSize())
+
+ if err != nil {
+ return "", err
+ }
+
+ ciphertext := make([]byte, len(padded))
+
+ cbc := cipher.NewCBCEncrypter(block, iv)
+ cbc.CryptBlocks(ciphertext, padded)
+
+ return encode(ciphertext, salt), nil
+}
+
+func Decrypt(b64, passphrase string) ([]byte, error) {
+ salt, ct, err := decode(b64)
+
+ if err != nil {
+ return nil, err
+ }
+
+ key := make([]byte, keySize)
+ iv := make([]byte, aes.BlockSize)
+
+ keymat := evpkdf.New(md5.New, []byte(passphrase), salt, keySize + aes.BlockSize, 1)
+ keymatbuf := bytes.NewReader(keymat)
+
+ n, err := keymatbuf.Read(key)
+
+ if n != keySize || err != nil {
+ return nil, err
+ }
+
+ n, err = keymatbuf.Read(iv)
+
+ if n != aes.BlockSize || err != nil {
+ return nil, err
+ }
+
+ block, err := aes.NewCipher(key)
+
+ if err != nil {
+ return nil, err
+ }
+
+ cbc := cipher.NewCBCDecrypter(block, iv)
+ cbc.CryptBlocks(ct, ct)
+
+ plain, err := pkcs7Unpad(ct, block.BlockSize())
+
+ if err != nil {
+ return nil, err
+ }
+
+ return plain, nil
+}
+
+func encode(ct []byte, salt []byte) string {
+ b := []byte("Salted__")
+ b = append(b, salt...)
+ b = append(b, ct...)
+ return base64.StdEncoding.EncodeToString(b)
+}
+
+func decode(b64 string) ([]byte, []byte, error) {
+ decoded, err := base64.StdEncoding.DecodeString(b64)
+
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if string(decoded[0:8]) != "Salted__" {
+ return nil, nil, errors.New("invalid data")
+ }
+
+ return decoded[8:16], decoded[16:], nil
+}
\ No newline at end of file
diff --git a/cryptojs/cryptojs_test.go b/cryptojs/cryptojs_test.go
new file mode 100644
index 0000000..a5301cb
--- /dev/null
+++ b/cryptojs/cryptojs_test.go
@@ -0,0 +1,47 @@
+package cryptojs
+
+import "testing"
+
+func Test_EncryptDecrypt(t *testing.T) {
+ enc, err := Encrypt("testing123", "testing")
+
+ if err != nil {
+ t.Fatal("Unable to encrypt:", err)
+ }
+
+ b, err := Decrypt(enc, "testing")
+
+ if err != nil {
+ t.Fatal("Unable to decrypt:", err)
+ }
+
+ final := string(b)
+
+ if final != "testing123" {
+ t.Fatal("Final text does not match:", final)
+ }
+}
+
+func Test_WebsiteEncrypt(t *testing.T) {
+ enc, err := Encrypt("Testing Paste.ee", "wXBbztgFOsZtTvhK1vcZlR7izK84bmUW")
+
+ if err != nil {
+ t.Fatal("Error:", err)
+ }
+
+ if enc != "U2FsdGVkX183sL2jzqJiJdhIl0O7R9v46TIkKnORreGpUufYUsqjqYO9b++CSyvV" {
+ t.Fatal("Unexpected output:", enc)
+ }
+}
+
+func Test_WebsiteDecrypt(t *testing.T) {
+ b, err := Decrypt("U2FsdGVkX183sL2jzqJiJdhIl0O7R9v46TIkKnORreGpUufYUsqjqYO9b++CSyvV", "wXBbztgFOsZtTvhK1vcZlR7izK84bmUW")
+
+ if err != nil {
+ t.Fatal("Unable to decrypt:", err)
+ }
+
+ if string(b) != "Testing Paste.ee" {
+ t.Fatal("Unexpected output:", string(b))
+ }
+}
\ No newline at end of file
diff --git a/cryptojs/pkcs7.go b/cryptojs/pkcs7.go
new file mode 100644
index 0000000..ad978ac
--- /dev/null
+++ b/cryptojs/pkcs7.go
@@ -0,0 +1,48 @@
+package cryptojs
+
+import (
+ "fmt"
+ "bytes"
+)
+
+// Appends padding.
+func pkcs7Pad(data []byte, blocklen int) ([]byte, error) {
+ if blocklen <= 0 {
+ return nil, fmt.Errorf("invalid blocklen %d", blocklen)
+ }
+ padlen := uint8(1)
+ for ((len(data) + int(padlen)) % blocklen) != 0 {
+ padlen++
+ }
+
+ if int(padlen) > blocklen {
+ panic(fmt.Sprintf("generated invalid padding length %v for block length %v", padlen, blocklen))
+ }
+ pad := bytes.Repeat([]byte{byte(padlen)}, int(padlen))
+ return append(data, pad...), nil
+}
+
+// Returns slice of the original data without padding.
+func pkcs7Unpad(data []byte, blocklen int) ([]byte, error) {
+ if blocklen <= 0 {
+ return nil, fmt.Errorf("invalid blocklen %d", blocklen)
+ }
+ if len(data)%blocklen != 0 || len(data) == 0 {
+ return nil, fmt.Errorf("invalid data len %d", len(data))
+ }
+
+ padlen := int(data[len(data)-1])
+ if padlen > blocklen || padlen == 0 {
+ // Not padded
+ return data, nil
+ }
+ // check padding
+ pad := data[len(data)-padlen:]
+ for i := 0; i < padlen; i++ {
+ if pad[i] != byte(padlen) {
+ return data, nil
+ }
+ }
+
+ return data[:len(data)-padlen], nil
+}
diff --git a/evpkdf/evpkdf.go b/evpkdf/evpkdf.go
new file mode 100644
index 0000000..0a266ee
--- /dev/null
+++ b/evpkdf/evpkdf.go
@@ -0,0 +1,55 @@
+// Copyright (c) 2016 Tristan Colgate-McFarlane
+//
+// This file is part of evpkdf.
+//
+// radia is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// radia is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with radia. If not, see .
+
+// Package evpkdf implements OpenSSL EVP Key derivation function, aiming to
+// be compatible with crypto-js default behaviour.
+package evpkdf
+
+import (
+ "bytes"
+ "hash"
+ "io"
+)
+
+//New creates a key derivation function that should match the EVP kdf in crypto-js, which, in turn
+// should be compatible with openssl's EVP kdf
+func New(hash func() hash.Hash, password []byte, salt []byte, keysize int, iterations int) []byte {
+ hasher := hash()
+ derivedKey := []byte{}
+ block := []byte{}
+
+ // Generate key
+ for len(derivedKey) < keysize {
+ if len(block) != 0 {
+ io.Copy(hasher, bytes.NewBuffer(block))
+ }
+ io.Copy(hasher, bytes.NewBuffer(password))
+ io.Copy(hasher, bytes.NewBuffer(salt))
+ block = hasher.Sum(nil)
+ hasher.Reset()
+
+ // Iterations
+ for i := 1; i < iterations; i++ {
+ io.Copy(hasher, bytes.NewBuffer(block))
+ block = hasher.Sum(nil)
+ hasher.Reset()
+ }
+
+ derivedKey = append(derivedKey, block...)
+ }
+ return derivedKey[0:keysize]
+}
\ No newline at end of file
diff --git a/pastee.go b/pastee.go
index 54a487d..a109d60 100644
--- a/pastee.go
+++ b/pastee.go
@@ -1,17 +1,118 @@
-package pastee
+package GoPastee
import (
"net/http"
"io"
"encoding/json"
"bytes"
+ "strconv"
+ "time"
+ "net/url"
+ "math/rand"
+
+ "git.meow.tf/tyler/go-pastee/cryptojs"
)
func New(key string) *Pastee {
- return &Pastee{ApiKey: key, Base: "https://api.paste.ee/v1", Client: &http.Client{}}
+ return &Pastee{ApiKey: key, Base: "https://api.paste.ee/v1", Client: &http.Client{Timeout: time.Minute}}
+}
+
+func (p *Pastee) Authenticate(username, password string) (*AuthResponse, error) {
+ body, err := json.Marshal(&authRequest{username, password})
+
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := p.newRequest("POST", "users/authenticate", bytes.NewReader(body))
+
+ if err != nil {
+ return nil, err
+ }
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Content-Length", strconv.Itoa(len(body)))
+
+ res, err := p.Client.Do(req)
+
+ if err != nil {
+ return nil, err
+ }
+
+ defer res.Body.Close()
+
+ var response AuthResponse
+
+ if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
+ return nil, err
+ }
+
+ return &response, nil
+}
+
+func (p *Pastee) List() (*PasteListResponse, error) {
+ q := &url.Values{}
+ q.Set("perpage", "25")
+ q.Set("page", "1")
+
+ req, err := p.newRequest("GET", "pastes?" + q.Encode(), nil)
+
+ if err != nil {
+ return nil, err
+ }
+
+ res, err := p.Client.Do(req)
+
+ if err != nil {
+ return nil, err
+ }
+
+ defer res.Body.Close()
+
+ var response PasteListResponse
+
+ if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
+ return nil, err
+ }
+
+ return &response, nil
+}
+
+func (p *Pastee) Get(id string) (*Paste, error) {
+ req, err := p.newRequest("GET", "pastes/" + id, nil)
+
+ if err != nil {
+ return nil, err
+ }
+
+ res, err := p.Client.Do(req)
+
+ if err != nil {
+ return nil, err
+ }
+
+ defer res.Body.Close()
+
+ var response Paste
+
+ if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
+ return nil, err
+ }
+
+ return &response, nil
}
func (p *Pastee) Submit(paste *Paste) (*PasteResponse, error) {
+ var key string
+
+ if paste.Encrypted {
+ key = RandStringBytesMaskImprSrc(32)
+
+ for _, section := range paste.Sections {
+ section.Contents = cryptojs.Encrypt(section.Contents, key)
+ }
+ }
+
body, err := json.Marshal(paste)
if err != nil {
@@ -25,7 +126,7 @@ func (p *Pastee) Submit(paste *Paste) (*PasteResponse, error) {
}
req.Header.Set("Content-Type", "application/json")
- req.Header.Set("Content-Length", len(body))
+ req.Header.Set("Content-Length", strconv.Itoa(len(body)))
res, err := p.Client.Do(req)
@@ -41,11 +142,15 @@ func (p *Pastee) Submit(paste *Paste) (*PasteResponse, error) {
return nil, err
}
+ if key != "" {
+ response.Key = key
+ }
+
return &response, nil
}
func (p *Pastee) newRequest(method, path string, body io.Reader) (*http.Request, error) {
- req, err := http.NewRequest(method, p.Base + path, body)
+ req, err := http.NewRequest(method, p.Base + "/" + path, body)
if err != nil {
return nil, err
@@ -54,4 +159,31 @@ func (p *Pastee) newRequest(method, path string, body io.Reader) (*http.Request,
req.Header.Set("X-Auth-Token", p.ApiKey)
return req, nil
+}
+
+const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+const (
+ letterIdxBits = 6 // 6 bits to represent a letter index
+ letterIdxMask = 1<= 0; {
+ if remain == 0 {
+ cache, remain = src.Int63(), letterIdxMax
+ }
+ if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
+ b[i] = letterBytes[idx]
+ i--
+ }
+ cache >>= letterIdxBits
+ remain--
+ }
+
+ return string(b)
}
\ No newline at end of file
diff --git a/structs.go b/structs.go
index 0e66748..91e4cad 100644
--- a/structs.go
+++ b/structs.go
@@ -1,4 +1,4 @@
-package pastee
+package GoPastee
import "net/http"
@@ -9,6 +9,8 @@ type Pastee struct {
}
type Paste struct {
+ ID string `json:"id,omitempty"`
+ Views int `json:"views,omitempty"`
Encrypted bool `json:"encrypted,omitempty"`
Description string `json:"description,omitempty"`
Sections []*Section `json:"sections"`
@@ -29,6 +31,34 @@ type Error struct {
type PasteResponse struct {
Success bool `json:"success"`
Errors []*Error `json:"errors"`
+ Key string `json:"-"`
ID string `json:"id"`
Link string `json:"link"`
+}
+
+type authRequest struct {
+ username string `json:"username"`
+ password string `json:"password"`
+}
+
+type AuthResponse struct {
+ Success bool `json:"success"`
+ Key string `json:"key"`
+}
+
+type PaginationResponse struct {
+ Total int `json:"total"`
+ PerPage int `json:"per_page"`
+ CurrentPage int `json:"current_page"`
+ LastPage int `json:"last_page"`
+ NextPageURL string `json:"next_page_url"`
+ PreviousPageURL string `json:"prev_page_url"`
+ From int `json:"from"`
+ To int `json:"to"`
+}
+
+type PasteListResponse struct {
+ *PaginationResponse
+
+ Data []*Paste `json:"data"`
}
\ No newline at end of file