<commit_before>package main

import (
	"bufio"
	"bytes"
	"code.google.com/p/go.crypto/pbkdf2"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/rpc"
	"net/url"
	"os"
	"os/signal"
	"strings"
	"syscall"
)

// Constants
const (
	BNOTIFY_PACKAGE_NAME = "cc.bran.bnotify"
	GCM_SEND_ADDRESS     = "https://android.googleapis.com/gcm/send"
	AES_KEY_SIZE         = 16
	PBKDF2_ITER_COUNT    = 4096
)

// Flags
var (
	socketFilename        = flag.String("socket_filename", "bnotify.sock", "path of UNIX socket to listen to RPCs on")
	apiKeyFilename        = flag.String("api_key", "api.key", "filename of API key file to use")
	encryptionKeyFilename = flag.String("encryption_key", "encryption.key", "filename of encryption key file to use")
	registrationFilename  = flag.String("registration_id", "registration.id", "filename of the registration ID file to use")
)

// Types
type NotificationService struct {
	httpClient         *http.Client
	apiKey             string
	registrationId     string
	encryptionPassword string
}

type NotificationRequest struct {
	Title string `json:"title"`
	Text  string `json:"text"`
}

type NotificationResponse struct{}

// Code
func (ns *NotificationService) Notify(req *NotificationRequest, resp *NotificationResponse) error {
	log.Printf("Got request: {title='%s', text='%s'}", req.Title, req.Text)

	// Marshal request into JSON.
	plaintextPayload, err := json.Marshal(req)
	if err != nil {
		log.Printf("Error while posting notification: %s", err)
		return err
	}

	// Encrypt (first deriving key from password + random salt).
	plaintextPayload = pkcs5Pad(plaintextPayload, aes.BlockSize)
	encryptedPayload := make([]byte, 2*aes.BlockSize+len(plaintextPayload))
	salt := encryptedPayload[:aes.BlockSize]
	iv := encryptedPayload[aes.BlockSize : 2*aes.BlockSize]
	_, err = rand.Read(encryptedPayload[:2*aes.BlockSize])
	if err != nil {
		log.Fatalf("Error while posting notification: %s", err)
	}
	key := pbkdf2.Key([]byte(ns.encryptionPassword), salt, PBKDF2_ITER_COUNT, AES_KEY_SIZE, sha1.New)

	aesBlock, err := aes.NewCipher(key)
	if err != nil {
		log.Printf("Error while posting notification: %s", err)
		return err
	}

	aesCbc := cipher.NewCBCEncrypter(aesBlock, iv)
	if err != nil {
		log.Printf("Error while posting notification: %s", err)
		return err
	}

	aesCbc.CryptBlocks(encryptedPayload[2*aes.BlockSize:], plaintextPayload)

	// Base64-encode the encrypted payload & IV.
	base64Payload := base64.StdEncoding.EncodeToString(encryptedPayload)

	err = postNotification(ns.httpClient, ns.apiKey, ns.registrationId, string(base64Payload))
	if err != nil {
		log.Printf("Error while posting notification: %s", err)
		return err
	}

	return nil
}

func pkcs5Pad(src []byte, blockSize int) []byte {
	padding := blockSize - len(src)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(src, padtext...)
}

func postNotification(httpClient *http.Client, apiKey string, registrationId string, payload string) error {
	// Set up request.
	values := url.Values{}
	values.Set("restricted_package_name", BNOTIFY_PACKAGE_NAME)
	values.Set("registration_id", registrationId)
	values.Set("data.payload", payload)

	req, err := http.NewRequest("POST", GCM_SEND_ADDRESS, strings.NewReader(values.Encode()))
	if err != nil {
		return err
	}
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
	req.Header.Add("Authorization", fmt.Sprintf("key=%s", apiKey))

	// Make request to GCM server.
	resp, err := httpClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// Check for HTTP error code.
	if resp.StatusCode != 200 {
		return fmt.Errorf("GCM HTTP error (%s)", resp.Status)
	}

	// Read the first line of the response and figure out if it indicates a GCM-level error.
	bodyReader := bufio.NewReader(resp.Body)
	lineBytes, _, err := bodyReader.ReadLine()
	if err != nil {
		return err
	}
	line := string(lineBytes)
	if strings.HasPrefix(line, "Error=") {
		return fmt.Errorf("GCM error (%s)", strings.TrimPrefix(line, "Error="))
	}

	return nil
}

func readFileContent(keyFilename string) (string, error) {
	content, err := ioutil.ReadFile(keyFilename)
	if err != nil {
		return "", err
	}

	return string(content), nil
}

func main() {
	// Parse flags.
	flag.Parse()

	// Read API key, registration ID, and encryption password.
	apiKey, err := readFileContent(*apiKeyFilename)
	if err != nil {
		log.Fatalf("Error reading API key: %s", err)
	}

	registrationId, err := readFileContent(*registrationFilename)
	if err != nil {
		log.Fatalf("Error reading registration ID: %s", err)
	}

	encryptionPassword, err := readFileContent(*encryptionKeyFilename)
	if err != nil {
		log.Fatalf("Error reading encryption key: %s", err)
	}

	// Create service object & socket.
	notificationService := NotificationService{
		httpClient:         new(http.Client),
		apiKey:             apiKey,
		registrationId:     registrationId,
		encryptionPassword: encryptionPassword,
	}

	rpc.Register(&notificationService)<commit_msg>bnotifyd: derive key on startup instead of per-notification.<commit_after>package main

import (
	"bufio"
	"bytes"
	"code.google.com/p/go.crypto/pbkdf2"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/rpc"
	"net/url"
	"os"
	"os/signal"
	"strings"
	"syscall"
)

// Constants
const (
	BNOTIFY_PACKAGE_NAME = "cc.bran.bnotify"
	GCM_SEND_ADDRESS     = "https://android.googleapis.com/gcm/send"
	AES_KEY_SIZE         = 16
	PBKDF2_ITER_COUNT    = 4096
)

// Flags
var (
	socketFilename        = flag.String("socket_filename", "bnotify.sock", "path of UNIX socket to listen to RPCs on")
	apiKeyFilename        = flag.String("api_key", "api.key", "filename of API key file to use")
	encryptionKeyFilename = flag.String("encryption_key", "encryption.key", "filename of encryption key file to use")
	registrationFilename  = flag.String("registration_id", "registration.id", "filename of the registration ID file to use")
)

// Types
type NotificationService struct {
	httpClient         *http.Client
	apiKey             string
	registrationId     string
	salt               []byte
	blockCipher          cipher.Block
}

type NotificationRequest struct {
	Title string `json:"title"`
	Text  string `json:"text"`
}

type NotificationResponse struct{}

// Code
func (ns *NotificationService) Notify(req *NotificationRequest, resp *NotificationResponse) error {
	log.Printf("Got request: {title='%s', text='%s'}", req.Title, req.Text)

	// Marshal request into JSON.
	plaintextPayload, err := json.Marshal(req)
	if err != nil {
		log.Printf("Error while posting notification: %s", err)
		return err
	}

	// Encrypt.
	blockSize := ns.blockCipher.BlockSize()
	plaintextPayload = pkcs5Pad(plaintextPayload, blockSize)
	encryptedPayload := make([]byte, 2*blockSize+len(plaintextPayload))
	copy(encryptedPayload[:blockSize], ns.salt)
	iv := encryptedPayload[blockSize : 2*blockSize]
	_, err = rand.Read(iv)
	if err != nil {
		log.Printf("Error while posting notification: %s", err)
		return err
	}
	blockModeCipher := cipher.NewCBCEncrypter(ns.blockCipher, iv)
	blockModeCipher.CryptBlocks(encryptedPayload[2*blockSize:], plaintextPayload)

	// Base64-encode the encrypted payload & IV.
	base64Payload := base64.StdEncoding.EncodeToString(encryptedPayload)

	err = postNotification(ns.httpClient, ns.apiKey, ns.registrationId, string(base64Payload))
	if err != nil {
		log.Printf("Error while posting notification: %s", err)
		return err
	}

	return nil
}

func pkcs5Pad(src []byte, blockSize int) []byte {
	padding := blockSize - len(src)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(src, padtext...)
}

func postNotification(httpClient *http.Client, apiKey string, registrationId string, payload string) error {
	// Set up request.
	values := url.Values{}
	values.Set("restricted_package_name", BNOTIFY_PACKAGE_NAME)
	values.Set("registration_id", registrationId)
	values.Set("data.payload", payload)

	req, err := http.NewRequest("POST", GCM_SEND_ADDRESS, strings.NewReader(values.Encode()))
	if err != nil {
		return err
	}
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
	req.Header.Add("Authorization", fmt.Sprintf("key=%s", apiKey))

	// Make request to GCM server.
	resp, err := httpClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// Check for HTTP error code.
	if resp.StatusCode != 200 {
		return fmt.Errorf("GCM HTTP error (%s)", resp.Status)
	}

	// Read the first line of the response and figure out if it indicates a GCM-level error.
	bodyReader := bufio.NewReader(resp.Body)
	lineBytes, _, err := bodyReader.ReadLine()
	if err != nil {
		return err
	}
	line := string(lineBytes)
	if strings.HasPrefix(line, "Error=") {
		return fmt.Errorf("GCM error (%s)", strings.TrimPrefix(line, "Error="))
	}

	return nil
}

func readFileContent(keyFilename string) (string, error) {
	content, err := ioutil.ReadFile(keyFilename)
	if err != nil {
		return "", err
	}

	return string(content), nil
}

func main() {
	// Parse flags.
	flag.Parse()

	// Read API key, registration ID, and encryption password.
	apiKey, err := readFileContent(*apiKeyFilename)
	if err != nil {
		log.Fatalf("Error reading API key: %s", err)
	}

	registrationId, err := readFileContent(*registrationFilename)
	if err != nil {
		log.Fatalf("Error reading registration ID: %s", err)
	}

	encryptionPassword, err := readFileContent(*encryptionKeyFilename)
	if err != nil {
		log.Fatalf("Error reading encryption key: %s", err)
	}

	// Generate salt & derive key from password + salt.
	salt := make([]byte, aes.BlockSize)
	_, err = rand.Read(salt)
	if err != nil {
		log.Fatalf("Error generating salt: %s", err)
	}
	key := pbkdf2.Key([]byte(encryptionPassword), salt, PBKDF2_ITER_COUNT, AES_KEY_SIZE, sha1.New)

	// Initialize cipher based on key.
	blockCipher, err := aes.NewCipher(key)
	if err != nil {
		log.Fatalf("Error initializing block cipher: %s", err)
	}

	// Create service object & socket.
	notificationService := NotificationService{
		httpClient:         new(http.Client),
		apiKey:             apiKey,
		registrationId:     registrationId,
		salt: salt,
		blockCipher: blockCipher,
	}

	rpc.Register(&notificationService)
{'fraction_non_alphanumeric': 0.1342223927851866, 'fraction_numerical': 0.006044325050369375, 'mean_word_length': 4.307535641547862, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6095620', 'n_tokens_mistral': 3634, 'n_tokens_neox': 3458, 'n_words': 871}