func ExampleCipher() { key, err := hex.DecodeString("60143a3d7c7137c3622d490e7dbb85859138d198d9c648960e186412a6250722") if err != nil { panic(err) } // A nonce should only be used once. Generate it randomly. nonce, err := hex.DecodeString("308c92676fa95973") if err != nil { panic(err) } c, err := chacha20.New(key, nonce) if err != nil { panic(err) } src := []byte("hello I am a secret message") dst := make([]byte, len(src)) c.XORKeyStream(dst, src) fmt.Printf("%x\n", dst) // Output: // a05452ebd981422dcdab2c9cde0d20a03f769e87d3e976ee6d6a11 }
func TestBadNonceSize(t *testing.T) { key := make([]byte, chacha20.KeySize) nonce := make([]byte, 3) _, err := chacha20.New(key, nonce) if err != chacha20.ErrInvalidNonce { t.Error("Should have rejected an invalid nonce") } }
func TestChaCha20(t *testing.T) { for i, vector := range testVectors { t.Logf("Running test vector %d", i) key, err := hex.DecodeString(vector[0]) if err != nil { t.Error(err) } nonce, err := hex.DecodeString(vector[1]) if err != nil { t.Error(err) } c, err := chacha20.New(key, nonce) if err != nil { t.Error(err) } expected, err := hex.DecodeString(vector[2]) if err != nil { t.Error(err) } src := make([]byte, len(expected)) dst := make([]byte, len(expected)) c.XORKeyStream(dst, src) if !bytes.Equal(expected, dst) { t.Errorf("Bad keystream: expected %x, was %x", expected, dst) for i, v := range expected { if dst[i] != v { t.Logf("Mismatch at offset %d: %x vs %x", i, v, dst[i]) break } } } } }
func (m *Mailer) HandleDelivery(next func(conn *smtpd.Connection)) func(conn *smtpd.Connection) { return func(conn *smtpd.Connection) { // Context variables var ( isSpam = false ctxID = uniuri.NewLen(uniuri.UUIDLen) ) // Check for spam spamReply, err := m.Spam.Report(string(conn.Envelope.Data)) if err != nil { m.Log.WithFields(logrus.Fields{ "ctx_id": ctxID, "err": err, }).Error("Unable to check an email in spamd") } if spamReply != nil && spamReply.Code == spamc.EX_OK { if spam, ok := spamReply.Vars["isSpam"]; ok && spam.(bool) { isSpam = true } } // Trim the spaces from the input conn.Envelope.Data = bytes.TrimSpace(conn.Envelope.Data) // First run the analysis algorithm to generate an email description node := &models.EmailNode{} if err := analyzeEmail(node, conn.Envelope.Data); err != nil { m.Error(conn, err) return } // Calculate message ID messageID := node.Headers.Get("Message-ID") x1i := strings.Index(messageID, "<") if x1i != -1 { x2i := strings.Index(messageID[x1i+1:], ">") if x2i != -1 { messageID = messageID[x1i+1 : x1i+x2i+1] } } if messageID == "" { messageID = uniuri.NewLen(uniuri.UUIDLen) + "@invalid-incoming.pgp.st" } // Generate the members field fromHeader, err := mail.ParseAddress(node.Headers.Get("From")) if err != nil { m.Error(conn, err) return } members := []string{fromHeader.Address} toHeader, err := node.Headers.AddressList("From") if err != nil { m.Error(conn, err) return } for _, to := range toHeader { members = append(members, to.Address) } if x := node.Headers.Get("CC"); x != "" { ccHeader, err := node.Headers.AddressList("CC") if err != nil { m.Error(conn, err) return } for _, cc := range ccHeader { members = append(members, cc.Address) } } // Get the recipients list from the scope recipients := conn.Environment["recipients"].([]recipient) for _, recipient := range recipients { // Parse the found key keyring, err := openpgp.ReadKeyRing(bytes.NewReader(recipient.Key.Body)) if err != nil { m.Error(conn, err) return } // Prepare a new email object email := &models.Email{ ID: uniuri.NewLen(uniuri.UUIDLen), DateCreated: time.Now(), DateModified: time.Now(), Owner: recipient.Account.ID, MessageID: messageID, Status: "received", } // Generate a new key key := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, key); err != nil { m.Error(conn, err) return } akey := [32]byte{} copy(akey[:], key) // Generate a new nonce nonce := make([]byte, 8) if _, err = rand.Read(nonce); err != nil { m.Error(conn, err) return } // Set up a new stream stream, err := chacha20.New(key, nonce) if err != nil { m.Error(conn, err) return } // Prepare output ciphertext := make( []byte, len(conn.Envelope.Data)+ chacha20.NonceSize+ (len(conn.Envelope.Data)/1024+1)*16, ) // First write the nonce copy(ciphertext[:chacha20.NonceSize], nonce) // Then write the authenticated encrypted stream oi := chacha20.NonceSize for i := 0; i < len(conn.Envelope.Data); i += 1024 { // Get the chunk max := i + 1024 if max > len(conn.Envelope.Data) { max = len(conn.Envelope.Data) } chunk := conn.Envelope.Data[i:max] // Encrypt the block stream.XORKeyStream(ciphertext[oi:oi+len(chunk)], chunk) // Authenticate it var out [16]byte poly1305.Sum(&out, ciphertext[oi:oi+len(chunk)], &akey) // Write it into the result copy(ciphertext[oi+len(chunk):oi+len(chunk)+poly1305.TagSize], out[:]) // Increase the counter oi += 1024 + poly1305.TagSize } // Save it to the body email.Body = ciphertext // Create a manifest and encrypt it manifest, err := json.Marshal(models.Manifest{ Key: key, Nonce: nonce, Description: node, }) if err != nil { m.Error(conn, err) return } encryptedManifest, err := utils.PGPEncrypt(manifest, keyring) if err != nil { m.Error(conn, err) return } email.Manifest = encryptedManifest // Match the thread var thread *models.Thread // Get the References header var references string if x := node.Headers.Get("In-Reply-To"); x != "" { references = x } else if x := node.Headers.Get("References"); x != "" { references = x } // Match by Message-ID if references != "" { // As specified in http://www.jwz.org/doc/threading.html, first thing <> is the msg id // We support both <message-id> and message-id format. x1i := strings.Index(references, "<") if x1i != -1 { x2i := strings.Index(references[x1i+1:], ">") if x2i != -1 { references = references[x1i+1 : x1i+x2i+1] } } // Look up the message ID in the database cursor, err := r.Table("emails").GetAllByIndex("messageIDOwner", []interface{}{ references, recipient.Account.ID, }).CoerceTo("array").Do(func(emails r.Term) r.Term { return r.Branch( emails.Count().Eq(1), r.Table("threads").Get(emails.Nth(0).Field("thread")).Default(map[string]interface{}{}), map[string]interface{}{}, ) }).Run(m.Rethink) if err != nil { m.Error(conn, err) return } defer cursor.Close() if err := cursor.One(&thread); err != nil { m.Error(conn, err) return } // Check if we've found it, clear it if it's invalid if thread.ID == "" { thread = nil } } // We can't match it by subject, so proceed to create a new thread if thread == nil { var secure string if findEncrypted(node) { secure = "all" } else { secure = "none" } labels := []string{recipient.Labels.Inbox} if isSpam { labels = append(labels, recipient.Labels.Spam) } thread = &models.Thread{ ID: uniuri.NewLen(uniuri.UUIDLen), DateCreated: time.Now(), DateModified: time.Now(), Owner: recipient.Account.ID, Labels: labels, Members: members, Secure: secure, } if err := r.Table("threads").Insert(thread).Exec(m.Rethink); err != nil { m.Error(conn, err) return } } else { // Modify the existing thread foundInbox := false foundSpam := false for _, label := range thread.Labels { if label == recipient.Labels.Inbox { foundInbox = true } if isSpam { if label == recipient.Labels.Spam { foundSpam = true } if foundInbox && foundSpam { break } } else { if foundInbox { break } } } // Append to thread.Labels if !foundInbox { thread.Labels = append(thread.Labels, recipient.Labels.Inbox) } if !foundSpam && isSpam { thread.Labels = append(thread.Labels, recipient.Labels.Spam) } // Members update membersHash := map[string]struct{}{} for _, member := range thread.Members { membersHash[member] = struct{}{} } for _, member := range members { if _, ok := membersHash[member]; !ok { membersHash[member] = struct{}{} } } thread.Members = []string{} for member := range membersHash { thread.Members = append(thread.Members, member) } update := map[string]interface{}{ "date_modified": time.Now(), "is_read": false, "labels": thread.Labels, "members": thread.Members, } secure := findEncrypted(node) if (thread.Secure == "all" && !secure) || (thread.Secure == "none" && secure) { thread.Secure = "some" } if err := r.Table("threads").Get(thread.ID).Update(update).Exec(m.Rethink); err != nil { m.Error(conn, err) return } } email.Thread = thread.ID if err := r.Table("emails").Insert(email).Exec(m.Rethink); err != nil { m.Error(conn, err) return } m.Log.WithFields(logrus.Fields{ "address": recipient.Address.ID, "account": recipient.Account.MainAddress, }).Info("Email received") } next(conn) } }
func BenchmarkChaCha20(b *testing.B) { key := make([]byte, chacha20.KeySize) nonce := make([]byte, chacha20.NonceSize) c, _ := chacha20.New(key, nonce) benchmarkStream(b, c) }