AES GCM decryption failed while decrypting data encrypted in flutter - flutter

I'm trying to decrypt the data in golang using the in-inbuild crypto library, the data is encrypted in flutter/dart using steel_crypt library.
The specific message that is thrown by the golang's crypto library is: panic: cipher: message authentication failed.
I'm running flutter app on Android amulator (localhost is: 10.0.2.2)
Golang version: go1.17.6 linux/amd64 |
Flutter version: Flutter 2.8.1 • channel stable |
Dart version: Dart 2.15.1
Golang Code
package main
import (
"bufio"
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io"
"net"
)
func Encode(data []byte) string {
hb := base64.StdEncoding.EncodeToString([]byte(data))
return hb
}
// Decoding the base string to array of bytes
func Decode(data string) []byte {
hb, _ := base64.StdEncoding.DecodeString(data)
return hb
}
// Generating RSA private key
func GenerateRsaPrivateKey(size int) (*rsa.PrivateKey, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, size)
if err != nil {
return nil, err
}
return privateKey, nil
}
// Generating RSA public key
func GenerateRsaPublicKey(privateKey *rsa.PrivateKey) rsa.PublicKey {
return privateKey.PublicKey
}
// This function can be use encrypt a plain text with rsa algorithm
func RsaEncrypt(publicKey rsa.PublicKey, data string) ([]byte, error) {
encryptedBytes, err := rsa.EncryptPKCS1v15(
rand.Reader,
&publicKey,
[]byte(data))
return encryptedBytes, err
// encryptedBytes, err := rsa.EncryptOAEP(
// sha256.New(),
// rand.Reader,
// &publicKey,
// []byte(data),
// nil)
// return encryptedBytes, err
}
// This function can be use decrypt a encrypted text with rsa algorithm
func RsaDecrypt(privateKey rsa.PrivateKey, data []byte) ([]byte, error) {
decryptedBytes, err := privateKey.Decrypt(
nil,
data,
&rsa.OAEPOptions{Hash: crypto.SHA256})
return decryptedBytes, err
}
// This fucntion is used to dump/serialize the rsa public key
func DumpKey(key *rsa.PublicKey) ([]byte, error) {
return x509.MarshalPKCS1PublicKey(key), nil
}
// This function is used to load the rsa public key
func LoadKey(byteKey []byte) (*rsa.PublicKey, error) {
key, err := x509.ParsePKCS1PublicKey(byteKey)
return key, err
}
// Generate fixed size byte array
func GenerateAesKey(size int) []byte {
token := make([]byte, size)
rand.Read(token)
return token
}
// This fucntion can be used for encrypting a plain text using AES-GCM algorithm
func AesEncryption(key []byte, data string) ([]byte, error) {
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
cipherText := gcm.Seal(nonce, nonce, []byte(data), nil)
return cipherText, nil
}
// This fucntion can be used for decrypting the ciphertext encrypted using AES-GCM algorithm
func AesDecryption(key []byte, cipherText []byte, nonce []byte) ([]byte, error) {
c, err := aes.NewCipher(key)
if err != nil {
fmt.Println("1")
return nil, err
}
gcm, err := cipher.NewGCM(c)
if err != nil {
fmt.Println("2")
return nil, err
}
noncesize := gcm.NonceSize()
if len(cipherText) < noncesize {
fmt.Println("3")
return nil, err
}
cipherText = cipherText[noncesize:]
// nonce, cipherText := cipherText[:noncesize], cipherText[noncesize:]
plainText, err := gcm.Open(nil, nonce, cipherText, nil)
if err != nil {
fmt.Println("4", err.Error())
return nil, err
}
return plainText, nil
}
func ParseRsaPublicKeyFromPemStr(pubPEM string) (*rsa.PublicKey, error) {
block, _ := pem.Decode([]byte(pubPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
switch pub := pub.(type) {
case *rsa.PublicKey:
return pub, nil
default:
break // fall through
}
return nil, errors.New("Key type is not RSA")
}
type trans struct {
Key string `json:"key"`
}
type creden struct {
Data []byte `json:"data"`
Nonce []byte `json:"nonce"`
}
func startServer() {
fmt.Println("Starting Server...")
l, err := net.Listen("tcp", "127.0.0.1:8080")
if err != nil {
panic(err)
}
defer l.Close()
c, err := l.Accept()
if err != nil {
panic(err)
}
fmt.Println("Client Connected: ", c)
data, err := bufio.NewReader(c).ReadBytes('\n')
if err != nil {
panic(err)
}
var t trans
err_ := json.Unmarshal(data, &t)
if err != nil {
panic(err_)
}
// fmt.Println("Key: ", t.Key)
publicKey, e := ParseRsaPublicKeyFromPemStr(t.Key)
if e != nil {
panic(e)
}
// fmt.Println("Success", publicKey)
var cre creden
cre.Data = GenerateAesKey(32)
cre.Nonce = GenerateAesKey(12)
jsonRes, err := json.Marshal(cre)
if err != nil {
panic(err)
}
cipherText, e_r := RsaEncrypt(*publicKey, string(jsonRes))
if e_r != nil {
panic(e_r)
}
fmt.Println("cipherText: ", len(cipherText), " | ", cipherText)
encodedCipherText := Encode(cipherText)
n, err := c.Write([]byte(encodedCipherText))
if err != nil {
panic(err)
}
fmt.Println("Written back the response. Written Bytes: ", n)
data2, err2 := bufio.NewReader(c).ReadBytes('\n')
if err2 != nil {
panic(err2)
}
var t2 trans
err_2 := json.Unmarshal(data2, &t2)
if err_2 != nil {
panic(err_2)
}
fmt.Println("recv data: ", t2.Key)
cipherText2 := Decode(t2.Key)
fmt.Println("cipherText2: ", cipherText2)
plainText2, err := AesDecryption(cre.Data, cipherText2, cre.Nonce)
if err != nil {
panic(err)
}
fmt.Println("plainText: ", plainText2)
}
func main() {
startServer()
}
Flutter Code
External Libraries:
steel_crypt flutter pub add steel_crypt.
crypton flutter pub add crypton.
Just add below function in your flutter project and call it just before returning the MaterialApp(). If you call this function below this level it will get executed every time windows refres.
void connectFunc() async {
Socket socket = await Socket.connect('10.0.2.2', 8080);
print(socket);
print("Connected...");
// listen to the received data event stream
RSAKeypair rsaKeypair = RSAKeypair.fromRandom();
socket.listen((List<int> event) {
String base64Response = utf8.decode(event);
// dynamic response = base64.decode(base64Response);
// print(response.length);
// print(response.runtimeType);
try {
String plainText = rsaKeypair.privateKey.decrypt(base64Response);
print(plainText);
dynamic jsonData = jsonDecode(plainText);
String Key = jsonData["data"];
String Nonce = jsonData["nonce"];
var aes = AesCrypt(key: Key, padding: PaddingAES.pkcs7);
print('AES Symmetric GCM:');
var crypted = aes.gcm.encrypt(inp: 'words', iv: Nonce); //encrypt
// print(crypted);
// print(aes.gcm.decrypt(enc: crypted, iv: Nonce)); //decrypt
// print('');
print("send data: " + crypted);
dynamic dictData = {
"key": crypted,
};
socket.add(utf8.encode(jsonEncode(dictData) + "\n"));
} catch (e) {
print(e);
}
// dynamic response = jsonDecode(jsonResponse);
// String key = response["data"];
// String nonce = response["nonce"];
});
// RSAKeypair rsaKeypair = RSAKeypair.fromRandom();
dynamic dictData = {
"key": rsaKeypair.publicKey.toFormattedPEM(),
};
socket.add(utf8.encode(jsonEncode(dictData) + "\n"));
// send hello
// socket.add(utf8.encode('hello from flutter/dart'));
// return socket;
}

I have found the solution of the problem.
The solution is that we have to use another library called cryptography to encrypt the data in flutter/dart.
There is no error at golang side ( except some modifications ).
AesDecrypt function at the golang side have been modified
func AesDecryption(key []byte, cipherText []byte, nonce []byte) ([]byte, error) {
c, err := aes.NewCipher(key)
if err != nil {
fmt.Println("1")
return nil, err
}
gcm, err := cipher.NewGCM(c)
if err != nil {
fmt.Println("2")
return nil, err
}
// noncesize := gcm.NonceSize()
// if len(cipherText) < noncesize {
// fmt.Println("3")
// return nil, err
// }
// cipherText = cipherText[noncesize:]
// nonce, cipherText := cipherText[:noncesize], cipherText[noncesize:]
plainText, err := gcm.Open(nil, nonce, cipherText, nil)
if err != nil {
fmt.Println("4", err.Error())
return nil, err
}
return plainText, nil
}
Now the whole revised startServer function in here:
func startServer(nonce []byte, key []byte) {
fmt.Println("Starting Server...")
l, err := net.Listen("tcp", "127.0.0.1:8080")
if err != nil {
panic(err)
}
defer l.Close()
c, err := l.Accept()
if err != nil {
panic(err)
}
fmt.Println("Client Connected: ", c)
data, err := bufio.NewReader(c).ReadBytes('\n')
if err != nil {
panic(err)
}
var t trans
err_ := json.Unmarshal(data, &t)
if err != nil {
panic(err_)
}
// fmt.Println("Key: ", t.Key)
publicKey, e := ParseRsaPublicKeyFromPemStr(t.Key)
if e != nil {
panic(e)
}
// fmt.Println("Success", publicKey)
var cre creden
cre.Data = Encode(key) //GenerateAesKey(32)
cre.Nonce = Encode(nonce) //GenerateAesKey(12)
jsonRes, err := json.Marshal(cre)
if err != nil {
panic(err)
}
cipherText, e_r := RsaEncrypt(*publicKey, string(jsonRes))
if e_r != nil {
panic(e_r)
}
// fmt.Println("cipherText: ", len(cipherText), " | ", cipherText)
encodedCipherText := Encode(cipherText)
n, err := c.Write([]byte(encodedCipherText))
if err != nil {
panic(err)
}
fmt.Println("Written back the response. Written Bytes: ", n)
data2, err2 := bufio.NewReader(c).ReadBytes('\n')
if err2 != nil {
panic(err2)
}
var t2 trans
err_2 := json.Unmarshal(data2, &t2)
if err_2 != nil {
panic(err_2)
}
fmt.Println("recv data: ", t2.Key)
cipherText2 := Decode(t2.Key)
fmt.Println("cipherText2: ", cipherText2)
plainText2, err := AesDecryption(key, cipherText2, nonce)
if err != nil {
panic(err)
}
fmt.Println("plainText: ", string(plainText2))
}
And now the flutter side of solution:
external denepencies: cryptography: ^2.0.5
void connectFunc() async {
Socket socket = await Socket.connect('10.0.2.2', 8080);
print(socket);
print("Connected...");
// listen to the received data event stream
RSAKeypair rsaKeypair = RSAKeypair.fromRandom();
socket.listen((List<int> event) async {
String base64Response = utf8.decode(event);
try {
String plainText = rsaKeypair.privateKey.decrypt(base64Response);
dynamic jsonData = jsonDecode(plainText);
String Key = jsonData["data"];
String Nonce = jsonData["nonce"];
String message = "Hello World";
final algorithm = AesGcm.with256bits();
final secretBox = await algorithm.encrypt(
message.codeUnits,
secretKey: SecretKey(base64.decode(jsonData["data"])),
nonce: base64.decode(jsonData["nonce"]),
);
print("key: ${base64.decode(jsonData["data"])}");
print("nonce: ${base64.decode(jsonData["nonce"])}");
print("secretBox: ${secretBox.concatenation(nonce: false)}");
dynamic dictData = {
"key": base64.encode(secretBox.concatenation(nonce: false)),
};
socket.add(utf8.encode(jsonEncode(dictData) + "\n"));
} catch (e) {
print(e);
}
});
dynamic dictData = {
"key": rsaKeypair.publicKey.toFormattedPEM(),
};
socket.add(utf8.encode(jsonEncode(dictData) + "\n"));
}

Related

Is there a way with GRPC to notify the stream of CRUD operations to give realtime updates to the client?

I am new to GRPC and I am trying to implement a basic CRUD + listing. I use unary rpc's for the CRUD and a server stream for the listing. What I would like to do however is update the client whenever someone changes a record in the database that you are listing.
So for example user A is listing 10 companies. And user B is updating one of those companies. I want user A's client to be updated once the update rpc is called.
This is what I have for now
func RegisterCompanyServer(l hclog.Logger, gs *grpc.Server) {
r := postgres.NewPostgresCompanyRepository()
cs := NewCompanyServer(l, r)
pb.RegisterCompanyServiceServer(gs, cs)
}
type CompanyServer struct {
logger hclog.Logger
repo repo.CompanyRepository
pb.UnimplementedCompanyServiceServer
}
func NewCompanyServer(l hclog.Logger, r repo.CompanyRepository) *CompanyServer {
return &CompanyServer{
logger: l,
repo: r,
}
}
func (c *CompanyServer) ListCompany(req *pb.CompanyListRequest, stream pb.CompanyService_ListCompanyServer) error {
//Somehow listen to CreateCompany() and update the client
companies, err := c.repo.List(req.Query)
if err != nil {
return err
}
for _, c := range companies {
bytes, err := json.Marshal(c)
if err != nil {
return err
}
out := &pb.Company{}
if err = jsonEnc.Unmarshal(bytes, out); err != nil {
return err
}
res := &pb.CompanyListResponse{
Company: out,
}
err = stream.Send(res)
if err != nil {
return err
}
}
return nil
}
func (c *CompanyServer) CreateCompany(context context.Context, req *pb.CompanyCreateRequest) (*pb.CompanyCreateResponse, error) {
input := req.GetCompany()
if input == nil {
return nil, errors.New("Parsing Error")
}
bytes, err := jsonEnc.Marshal(input)
if err != nil {
return nil, err
}
company := &myCompany.Company{}
if err = json.Unmarshal(bytes, company); err != nil {
return nil, err
}
result, err := c.repo.Create(company)
if err != nil {
return nil, err
}
res := &pb.CompanyCreateResponse{
Id: result,
}
//Somehow notify the stream that a company was created
return res, nil
}
Is this even feasable with GRPC? What techniques are out there to do this? I am currently working with a postgresql database.

Setting up standard Go net/smtp with Office 365 fails with "Error tls: first record does not look like a TLS handshake"

I'm trying to create a simple Go emailing service using the default Go packages net/smtp - I know there's gomailer, but i'd like to use the standard library
I need help with configuring the tls/server setting to work with Office365
I believe that I have the correct host:
smtp.office365.com:587
From copying the documentation for smtp that Microsoft provide, however, I get the following error in my console when running the below code:
Error: tls: first record does not look like a TLS handshake
panic: runtime error: invalid memory address or nil pointer dereference
package main
import (
"fmt"
"net"
mail "net/mail"
smtp "net/smtp"
)
func main() {
from := mail.Address{"", "example#example.com"}
to := mail.Address{"", "example#example.com"}
subject := "My test subject"
body := "Test email body"
// Setup email headers
headers := make(map[string]string)
headers["From"] = from.String()
headers["To"] = to.String()
headers["Subject"] = subject
message := ""
for k, v := range headers {
message += fmt.Sprintf("%s: %s\r\n", k, v)
}
message += "\r\n" + body
servername := "smtp.office365.com:587"
host, _, _ := net.SplitHostPort(servername)
auth := smtp.PlainAuth("", "example#example.com", "password", host)
tlsconfig := &tls.Config{
InsecureSkipVerify: true,
ServerName: host,
}
conn, err := tls.Dial("tcp", "smtp.office365.com:587", tlsconfig)
if err != nil {
fmt.Println("tls.Dial Error: %s", err)
}
c, err := smtp.NewClient(conn, host)
if err != nil {
fmt.Println("smtp.NewClient Error: %s", err)
}
if err = c.Auth(auth); err != nil {
fmt.Println("c.Auth Error: %s", err)
}
if err = c.Mail(from.Address); err != nil {
fmt.Println("c.Mail Error: %s", err)
}
if err = c.Rcpt(to.Address); err != nil {
fmt.Println("c.Rcpt Error: %s", err)
}
w, err := c.Data()
if err != nil {
fmt.Println("c.Data Error: %s", err)
}
_, err = w.Write([]byte(message))
if err != nil {
fmt.Println("Error: %s", err)
}
err = w.Close()
if err != nil {
fmt.Println("reader Error: %s", err)
}
c.Quit()
}
Any examples of an O365 client will be appreciated, or anything that anyone can spot that seems suspect will be great
Thanks
Outlook.com no longer supports AUTH PLAIN authentication since August 2017.
https://support.microsoft.com/en-us/office/outlook-com-no-longer-supports-auth-plain-authentication-07f7d5e9-1697-465f-84d2-4513d4ff0145?ui=en-us&rs=en-us&ad=us
Use AUTH LOGIN
The following codes implement AUTH LOGIN
type loginAuth struct {
username, password string
}
func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte(a.username), nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, errors.New("Unknown from server")
}
}
return nil, nil
}
remove "InsecureSkipVerify: true"
tlsconfig := &tls.Config {
ServerName: host,
}
don't use tsl.Dial(), use net.Dial()
conn, err := net.Dial("tcp", "smtp.office365.com:587")
if err != nil {
return err
}
call StartTLS() after smtp.NewClient()
c, err := smtp.NewClient(conn, host)
if err != nil {
return err
}
if err = c.StartTLS(tlsconfig); err != nil {
return err
}
use AUTH LOGIN
auth := LoginAuth(fromAddress, password)
if err = c.Auth(auth); err != nil {
return err
}
The error message Error: tls: first record does not look like a TLS handshake is telling you what the problem is :-). If you try connecting to the server, you will see that (as any SMTP servers) it uses plain text:
telnet smtp.office365.com 587
Trying 2603:1026:c0b:10::2...
Connected to zrh-efz.ms-acdc.office.com.
Escape character is '^]'.
220 ZRAP278CA0003.outlook.office365.com Microsoft ESMTP MAIL Service ready at Mon, 11 Nov 2019 17:13:50 +0000
...
You need to use the STARTTLS command, see https://en.wikipedia.org/wiki/Opportunistic_TLS (and the RFCs pointed by that wiki page).
In Go, it is https://golang.org/pkg/net/smtp/#Client.StartTLS.
In your code I noticed
tlsconfig := &tls.Config{
InsecureSkipVerify: true, <== REMOVE THIS
ServerName: host,
}
Please remove the InsecureSkipVerify, it is, as the name implies, insecure and has nothing to do with the error you are facing.
Below worked fine with me:
package main
import (
"bytes"
"crypto/tls"
"errors"
"fmt"
"net"
"net/smtp"
"text/template"
)
type loginAuth struct {
username, password string
}
func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte(a.username), nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, errors.New("Unknown from server")
}
}
return nil, nil
}
func main() {
// Sender data.
from := "O365 logging name"
password := "O365 logging pasword"
// Receiver email address.
to := []string{
"receiver email",
}
// smtp server configuration.
smtpHost := "smtp.office365.com"
smtpPort := "587"
conn, err := net.Dial("tcp", "smtp.office365.com:587")
if err != nil {
println(err)
}
c, err := smtp.NewClient(conn, smtpHost)
if err != nil {
println(err)
}
tlsconfig := &tls.Config{
ServerName: smtpHost,
}
if err = c.StartTLS(tlsconfig); err != nil {
println(err)
}
auth := LoginAuth(from, password)
if err = c.Auth(auth); err != nil {
println(err)
}
t, _ := template.ParseFiles("template.html")
var body bytes.Buffer
mimeHeaders := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
body.Write([]byte(fmt.Sprintf("Subject: This is a test subject \n%s\n\n", mimeHeaders)))
t.Execute(&body, struct {
Name string
Message string
}{
Name: "Hasan Yousef",
Message: "This is a test message in a HTML template",
})
// Sending email.
err = smtp.SendMail(smtpHost+":"+smtpPort, auth, from, to, body.Bytes())
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Email Sent!")
}
With the below template as bonus :)
<!-- template.html -->
<!DOCTYPE html>
<html>
<body>
<h3>Name:</h3><span>{{.Name}}</span><br/><br/>
<h3>Email:</h3><span>{{.Message}}</span><br/>
</body>
</html>
So the issue was all about authorisation. Firstly requiring that I use the StartTLS method on the client, and also that I write a function and methods to support LOGIN, something that the standard Go library doesn't support (for whatever reason)
See the functions and struct above the main()
Here's the full code, with the helper function, that can now successfully send an email through my O365 account:
package main
import (
"fmt"
"net"
"errors"
mail "net/mail"
smtp "net/smtp"
)
type loginAuth struct {
username, password string
}
func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte{}, nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, errors.New("Unknown fromServer")
}
}
return nil, nil
}
func main() {
from := mail.Address{"", "example#example.com"}
to := mail.Address{"", "example#example.com"}
subject := "My test subject"
body := "Test email body"
headers := make(map[string]string)
headers["From"] = from.String()
headers["To"] = to.String()
headers["Subject"] = subject
message := ""
for k, v := range headers {
message += fmt.Sprintf("%s: %s\r\n", k, v)
}
message += "\r\n" + body
tlsconfig := &tls.Config{
ServerName: host,
}
conn, err := tls.Dial("tcp", "smtp.office365.com:587", tlsconfig)
if err != nil {
fmt.Println("tls.Dial Error: ", err)
}
c, err := smtp.NewClient(conn, host)
if err != nil {
fmt.Println("smtp.NewClient Error: ", err)
}
if err = c.Auth(LoginAuth("example#example.com", "password")); err != nil {
fmt.Println("c.Auth Error: ", err)
return
}
if err = c.Mail(from.Address); err != nil {
fmt.Println("c.Mail Error: ", err)
}
if err = c.Rcpt(to.Address); err != nil {
fmt.Println("c.Rcpt Error: ", err)
}
w, err := c.Data()
if err != nil {
fmt.Println("c.Data Error: ", err)
}
_, err = w.Write([]byte(message))
if err != nil {
fmt.Println("Error: ", err)
}
err = w.Close()
if err != nil {
fmt.Println("reader Error: ", err)
}
c.Quit()
}

golang net/smtp getting smtp server response DSN

I am using golang net/smtp to send mails
Whenever I send to my smtp server I need to capture the response from the server
Especially the DSN
For example my local smtp server gives a "ok queued as " at the end of the mail
I need to capture this and print in the logs
How can I do this
package main
import (
"log"
"net/smtp"
)
func sendEmail(msg []byte) {
c, err := smtp.Dial("localhost:25")
if err != nil {
log.Fatal(err)
}
if err := c.Mail("sender#example.org"); err != nil {
log.Fatal(err)
}
if err := c.Rcpt("recipient#example.net"); err != nil {
log.Fatal(err)
}
wc, err := c.Data()
if err != nil {
log.Fatal(err)
}
_, err = wc.Write(msg)
if err != nil {
log.Fatal(err)
}
//How do I get the response here ??
err = wc.Close()
if err != nil {
log.Fatal(err)
}
err = c.Quit()
if err != nil {
log.Fatal(err)
}
}
As mentioned in the comments you can use c.Text.ReadResponse():
package main
import (
"net/smtp"
)
func sendEmail(msg []byte) (code int, message string, err error) {
c, err := smtp.Dial("localhost:25")
if err != nil {
return
}
defer c.Quit() // make sure to quit the Client
if err = c.Mail("sender#example.org"); err != nil {
return
}
if err = c.Rcpt("recipient#example.net"); err != nil {
return
}
wc, err := c.Data()
if err != nil {
return
}
defer wc.Close() // make sure WriterCloser gets closed
_, err = wc.Write(msg)
if err != nil {
return
}
code, message, err = c.Text.ReadResponse(0)
return
}
The code, message and any err are now passed to the caller, don't use log.Fatal throughout your code, handle the error on the calling side.
package main
import (
"net/smtp"
)
func sendEmail(msg []byte) (code int, message string, err error) {
c, err := smtp.Dial("localhost:25")
if err != nil {
return
}
defer c.Quit() // make sure to quit the Client
if err = c.Mail("sender#example.org"); err != nil {
return
}
if err = c.Rcpt("recipient#example.net"); err != nil {
return
}
wc, err := c.Data()
if err != nil {
return
}
_, err = wc.Write(msg)
if err != nil {
return
}
code, message, err = closeData(c)
if err != nil {
return 0, "", err
}
return code, message, err
}
func closeData(client *smtp.Client) error {
d := &dataCloser{
c: client,
WriteCloser: client.Text.DotWriter(),
}
return d.Close()
}
type dataCloser struct {
c *smtp.Client
io.WriteCloser
}
func (d *dataCloser) Close() (int, string, error) {
d.WriteCloser.Close() // make sure WriterCloser gets closed
code, message, err := d.c.Text.ReadResponse(250)
fmt.Printf("Message %v, Error %v\n", message, err)
return code, message, err
}

Golang Client for Bitfinex API V2.0: How to Post Authenticated Requests

I'm trying to use the Bitfinex REST API V2.0 and here there is a description of how a request should be authenticated. Here below is my golang implementation:
func (c *Client) newAuthenticatedRequest(method string, refURL string, data map[string]interface{}) (*http.Request, error) {
path := "auth/r/" + refURL
rel, err := url.Parse(path)
if err != nil {
return nil, err
}
payload := "{}"
if data != nil {
p, err := json.Marshal(data)
if err != nil {
return nil, err
}
payload = string(p)
}
url := c.BaseURL.ResolveReference(rel)
req, err := http.NewRequest(method, url.String(), strings.NewReader(payload))
if err != nil {
return nil, err
}
nonce := utils.GetNonce()
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
req.Header.Add("bfx-nonce", nonce)
req.Header.Add("bfx-apikey", c.APIKey)
req.Header.Add("bfx-signature", c.signPayload("/api/v2/" + path + nonce + payload))
return req, nil
}
func (c *Client) signPayload(payload string) string {
sig := hmac.New(sha512.New384, []byte(c.APISecret))
sig.Write([]byte(payload))
return hex.EncodeToString(sig.Sum(nil))
}
func (c *Client) do(req *http.Request, v interface{}) (*Response, error) {
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
response, err := newResponse(resp)
if err != nil {
return nil, err
}
if v != nil {
err = json.Unmarshal(response.Body, v)
if err != nil {
return nil, err
}
}
return response, nil
}
func (s *WalletsService) All() ([]Wallet, error) {
req, err := s.client.newAuthenticatedRequest("POST", "wallets", nil)
if err != nil {
return nil, err
}
var w []Wallet
_, err = s.client.do(req, &w)
if err != nil {
return nil, err
}
return w, nil
}
The problem is that when i POST a request, I always get back error [10100] apikey: invalid. Is it correct to set the payload to {} when empty? I've tried any possible combination but no way.

How to connect and send emails from exchange server in Go using starttls?

I'm working on porting some existing Python code to Go. One bit is in charge of sending emails through Exchange server (SMTP + STARTTLS). Existing (simplified) code looks like this:
import smtplib
client = smtplib.SMTP("exchangeserver.com")
client.starttls()
client.login('user', 'password')
client.sendmail('user#exchangeserver.com',
['otheruser1#exchangeserver.com', 'otheruser2#exchangeserver.com'],
'..message..')
I'd like to do the same thing with Go, help is appreciated - thanks.
Edited:
Here is an example using the tls connection setup. I don't have one to test against, so it fails on my end connecting, but I suspect this is what you need to make it work
package main
import (
"crypto/tls"
"fmt"
"log"
"net/smtp"
)
func main() {
var (
host = "smtp.google.com"
port = 587
from = "foo#bar.com"
password = "baz"
to = []string{"bin#bar.com"}
msg = []byte("This is my message")
auth = smtp.PlainAuth("", from, password, "smtp.gmail.com")
)
serverAddr := fmt.Sprintf("%s:%d", host, port)
conn, err := tls.Dial("tcp", serverAddr, nil)
if err != nil {
log.Printf("Error Dialing %s\n", err)
return
}
client, err := smtp.NewClient(conn, host)
if err != nil {
log.Printf("Error SMTP connection: %s\n", err)
return
}
if ok, _ := client.Extension("AUTH"); ok {
if err := client.Auth(auth); err != nil {
log.Printf("Error during AUTH %s\n", err)
return
}
}
if err := client.Mail(from); err != nil {
log.Printf("Error: %s\n", err)
return
}
for _, addr := range to {
if err := client.Rcpt(addr); err != nil {
log.Printf("Error: %s\n", err)
return
}
}
w, err := client.Data()
if err != nil {
log.Printf("Error: %s\n", err)
return
}
_, err = w.Write(msg)
if err != nil {
log.Printf("Error: %s\n", err)
return
}
err = w.Close()
if err != nil {
log.Printf("Error: %s\n", err)
return
}
client.Quit()
}
I think something like this might work. You'll have to make the appropriate changes for server, user/password, etc. Let me know if you need more help.
package main
import (
"fmt"
"log"
"net/smtp"
)
func main() {
to := "foo#foo.com"
from := "bin#baz.com"
password := "myPassword"
subject := "subject line of email"
msg := "a one-line email message"
emailTemplate := `To: %s
Subject: %s
%s
`
body := fmt.Sprintf(emailTemplate, to, subject, msg)
auth := smtp.PlainAuth("", from, password, "smtp.gmail.com")
err := smtp.SendMail(
"smtp.gmail.com:587",
auth,
from,
[]string{to},
[]byte(body),
)
if err != nil {
log.Fatal(err)
}
}
To expand on Cory LaNou's answer, you can use StartTLS on a normal net.Conn. You need to define a tls.Config and then use that to upgrade the connection to use TLS.
The example below is based on Cory's answer and also steals some code from the crypto/tls test files for creating the RSA cert and private key. In production you'd obviously replace these with real certs and keys.
It may require some customization of the variables, as well as some changes to the tls.Config to suit your environment.
package main
import (
"crypto/rsa"
"crypto/tls"
"encoding/hex"
"fmt"
"log"
"math/big"
"net"
"net/smtp"
)
func main() {
var (
host = "smtp.myexchange.com"
port = 587
from = "sender#example.org"
password = "password"
to = []string{"recipient#example.org"}
msg = []byte("This is my message")
auth = smtp.PlainAuth("", from, password, "smtp.myexchange.com")
)
conf := new(tls.Config)
conf.Certificates = make([]tls.Certificate, 1)
conf.Certificates[0].Certificate = [][]byte{testRSACertificate}
conf.Certificates[0].PrivateKey = testRSAPrivateKey
conf.CipherSuites = []uint16{tls.TLS_RSA_WITH_RC4_128_SHA}
conf.InsecureSkipVerify = true
conf.MinVersion = tls.VersionSSL30
conf.MaxVersion = tls.VersionTLS10
serverAddr := fmt.Sprintf("%s:%d", host, port)
conn, err := net.Dial("tcp", serverAddr)
if err != nil {
log.Printf("Error Dialing %s\n", err)
return
}
client, err := smtp.NewClient(conn, host)
if err != nil {
log.Printf("Error SMTP connection: %s\n", err)
return
}
if err = client.StartTLS(conf); err != nil {
log.Printf("Error performing StartTLS: %s\n", err)
return
}
if ok, _ := client.Extension("AUTH"); ok {
if err := client.Auth(auth); err != nil {
log.Printf("Error during AUTH %s\n", err)
return
}
}
if err := client.Mail(from); err != nil {
log.Printf("Error: %s\n", err)
return
}
for _, addr := range to {
if err := client.Rcpt(addr); err != nil {
log.Printf("Error: %s\n", err)
return
}
}
w, err := client.Data()
if err != nil {
log.Printf("Error: %s\n", err)
return
}
_, err = w.Write(msg)
if err != nil {
log.Printf("Error: %s\n", err)
return
}
err = w.Close()
if err != nil {
log.Printf("Error: %s\n", err)
return
}
client.Quit()
}
// Code below from http://golang.org/src/pkg/crypto/tls/handshake_server_test.go
func bigFromString(s string) *big.Int {
ret := new(big.Int)
ret.SetString(s, 10)
return ret
}
func fromHex(s string) []byte {
b, _ := hex.DecodeString(s)
return b
}
var testRSACertificate = fromHex("308202b030820219a00302010202090085b0bba48a7fb8ca300d06092a864886f70d01010505003045310b3009060355040613024155311330110603550408130a536f6d652d53746174653121301f060355040a1318496e7465726e6574205769646769747320507479204c7464301e170d3130303432343039303933385a170d3131303432343039303933385a3045310b3009060355040613024155311330110603550408130a536f6d652d53746174653121301f060355040a1318496e7465726e6574205769646769747320507479204c746430819f300d06092a864886f70d010101050003818d0030818902818100bb79d6f517b5e5bf4610d0dc69bee62b07435ad0032d8a7a4385b71452e7a5654c2c78b8238cb5b482e5de1f953b7e62a52ca533d6fe125c7a56fcf506bffa587b263fb5cd04d3d0c921964ac7f4549f5abfef427100fe1899077f7e887d7df10439c4a22edb51c97ce3c04c3b326601cfafb11db8719a1ddbdb896baeda2d790203010001a381a73081a4301d0603551d0e04160414b1ade2855acfcb28db69ce2369ded3268e18883930750603551d23046e306c8014b1ade2855acfcb28db69ce2369ded3268e188839a149a4473045310b3009060355040613024155311330110603550408130a536f6d652d53746174653121301f060355040a1318496e7465726e6574205769646769747320507479204c746482090085b0bba48a7fb8ca300c0603551d13040530030101ff300d06092a864886f70d010105050003818100086c4524c76bb159ab0c52ccf2b014d7879d7a6475b55a9566e4c52b8eae12661feb4f38b36e60d392fdf74108b52513b1187a24fb301dbaed98b917ece7d73159db95d31d78ea50565cd5825a2d5a5f33c4b6d8c97590968c0f5298b5cd981f89205ff2a01ca31b9694dda9fd57e970e8266d71999b266e3850296c90a7bdd9")
var testRSAPrivateKey = &rsa.PrivateKey{
PublicKey: rsa.PublicKey{
N: bigFromString("131650079503776001033793877885499001334664249354723305978524647182322416328664556247316495448366990052837680518067798333412266673813370895702118944398081598789828837447552603077848001020611640547221687072142537202428102790818451901395596882588063427854225330436740647715202971973145151161964464812406232198521"),
E: 65537,
},
D: bigFromString("29354450337804273969007277378287027274721892607543397931919078829901848876371746653677097639302788129485893852488285045793268732234230875671682624082413996177431586734171663258657462237320300610850244186316880055243099640544518318093544057213190320837094958164973959123058337475052510833916491060913053867729"),
Primes: []*big.Int{
bigFromString("11969277782311800166562047708379380720136961987713178380670422671426759650127150688426177829077494755200794297055316163155755835813760102405344560929062149"),
bigFromString("10998999429884441391899182616418192492905073053684657075974935218461686523870125521822756579792315215543092255516093840728890783887287417039645833477273829"),
},
}
Playground