commit a72416672ac3892b33f2b7eb777d8b360ee87a08 Author: fangdingjun Date: Wed Jun 27 12:25:00 2018 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b25c15b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..341c30b --- /dev/null +++ b/LICENSE @@ -0,0 +1,166 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..b0b4489 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +gnutls +===== + +This is a gnutls binding for golang. + +This library is just work, a lot things need to be done. + diff --git a/_gnutls.h b/_gnutls.h new file mode 100644 index 0000000..8c419e2 --- /dev/null +++ b/_gnutls.h @@ -0,0 +1,47 @@ +#ifndef _GNUTLS_H +#define _GNUTLS_H +#include +#include +#include +#include +#include + +struct session +{ + gnutls_session_t session; + gnutls_certificate_credentials_t xcred; + int handshake; + void *data; +}; + +extern int DataRead(void *, char *, int); +extern int DataWrite(void *, char *, int); +extern int DataTimeoutPull(void *, int); + +struct session *init_client_session(); +struct session *init_server_session(); + +int pull_timeout_function(gnutls_transport_ptr_t ptr, unsigned int ms); +ssize_t pull_function(gnutls_transport_ptr_t ptr, void *data, size_t len); +ssize_t push_function(gnutls_transport_ptr_t ptr, const void *data, size_t len); + +void set_data(struct session *sess, size_t data); +void set_servername(struct session *sess, char *servername, int namelen); +int handshake(struct session *sess); +int set_callback(struct session *sess); +int set_keyfile(struct session *, char *, char *); + +int write_application_data(struct session *sess, char *data, int datalen); +int read_application_data(struct session *sess, char *data, int buflen); + +void session_destroy(struct session *); + +gnutls_cipher_hd_t new_cipher(int cipher_type, char *key, int keylen, char *iv, int ivlen); + +gnutls_hash_hd_t new_hash(int t); + +int get_hash_len(int); +int cipher_get_block_size(int); +int cipher_get_iv_size(int); + +#endif \ No newline at end of file diff --git a/cipher.go b/cipher.go new file mode 100644 index 0000000..2e2c3e9 --- /dev/null +++ b/cipher.go @@ -0,0 +1,113 @@ +package gnutls + +/* +#include "_gnutls.h" +#include +*/ +import "C" +import ( + "fmt" + "log" +) + +const ( + GNUTLS_CIPHER_AES_128_CBC = 4 + GNUTLS_CIPHER_AES_256_CBC = 5 + GNUTLS_CIPHER_ARCFOUR_40 = 6 + GNUTLS_CIPHER_CAMELLIA_128_CBC = 7 + GNUTLS_CIPHER_CAMELLIA_256_CBC = 8 + GNUTLS_CIPHER_AES_192_CBC = 9 + GNUTLS_CIPHER_AES_128_GCM = 10 + GNUTLS_CIPHER_AES_256_GCM = 11 + GNUTLS_CIPHER_CAMELLIA_192_CBC = 12 + GNUTLS_CIPHER_SALSA20_256 = 13 + GNUTLS_CIPHER_ESTREAM_SALSA20_256 = 14 + GNUTLS_CIPHER_CAMELLIA_128_GCM = 15 + GNUTLS_CIPHER_CAMELLIA_256_GCM = 16 + GNUTLS_CIPHER_RC2_40_CBC = 17 + GNUTLS_CIPHER_DES_CBC = 18 + GNUTLS_CIPHER_AES_128_CCM = 19 + GNUTLS_CIPHER_AES_256_CCM = 20 + GNUTLS_CIPHER_AES_128_CCM_8 = 21 + GNUTLS_CIPHER_AES_256_CCM_8 = 22 + GNUTLS_CIPHER_CHACHA20_POLY1305 = 23 +) + +// Cipher cipher +type Cipher struct { + cipher C.gnutls_cipher_hd_t + t int +} + +// NewCipher create cipher +func NewCipher(t int, key []byte, iv []byte) (*Cipher, error) { + ivSize := C.cipher_get_block_size(C.int(t)) + blockSize := C.cipher_get_iv_size(C.int(t)) + if len(key) != int(blockSize) || len(iv) != int(ivSize) { + + return nil, fmt.Errorf("wrong block/iv size") + } + + ckey := C.CBytes(key) + civ := C.CBytes(iv) + + defer C.free(ckey) + defer C.free(civ) + + c := C.new_cipher(C.int(t), (*C.char)(ckey), C.int(len(key)), (*C.char)(civ), C.int(len(iv))) + if c == nil { + log.Println("new cipher return nil") + return nil, nil + } + return &Cipher{c, t}, nil +} + +// Encrypt encrypt +func (c *Cipher) Encrypt(buf []byte) ([]byte, error) { + blockSize := C.cipher_get_iv_size(C.int(c.t)) + if len(buf)%int(blockSize) != 0 { + return nil, fmt.Errorf("wrong block size") + } + + cbuf := C.CBytes(buf) + defer C.free(cbuf) + + bufLen := C.size_t(len(buf)) + dstBuf := C.malloc(bufLen) + + defer C.free(dstBuf) + + ret := C.gnutls_cipher_encrypt2(c.cipher, cbuf, bufLen, dstBuf, bufLen) + if int(ret) < 0 { + return nil, fmt.Errorf("encrypt error: %s", C.GoString(C.gnutls_strerror(ret))) + } + return C.GoBytes(dstBuf, C.int(bufLen)), nil +} + +// Decrypt decrypt +func (c *Cipher) Decrypt(buf []byte) ([]byte, error) { + blockSize := C.cipher_get_iv_size(C.int(c.t)) + if len(buf)%int(blockSize) != 0 { + return nil, fmt.Errorf("wrong block size") + } + + cbuf := C.CBytes(buf) + defer C.free(cbuf) + + bufLen := C.size_t(len(buf)) + dstBuf := C.malloc(C.size_t(len(buf))) + + defer C.free(dstBuf) + + ret := C.gnutls_cipher_decrypt2(c.cipher, cbuf, bufLen, dstBuf, bufLen) + if int(ret) < 0 { + return nil, fmt.Errorf("decrypt error: %s", C.GoString(C.gnutls_strerror(ret))) + } + return C.GoBytes(dstBuf, C.int(bufLen)), nil +} + +// Close destroy the cipher +func (c *Cipher) Close() error { + C.gnutls_cipher_deinit(c.cipher) + return nil +} diff --git a/cipher_test.go b/cipher_test.go new file mode 100644 index 0000000..b16e4a1 --- /dev/null +++ b/cipher_test.go @@ -0,0 +1,47 @@ +package gnutls + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "testing" +) + +func TestEncryptDecrypt(t *testing.T) { + key := []byte("0123456789abcdef") + iv := []byte("abcdefg123456789") + c, err := NewCipher(GNUTLS_CIPHER_AES_128_CBC, key, iv) + if err != nil { + t.Fatal(err) + } + defer c.Close() + + c1, err := NewCipher(GNUTLS_CIPHER_AES_128_CBC, key, iv) + if err != nil { + t.Fatal(err) + } + defer c1.Close() + + data := []byte("1234012121212121") + if c == nil { + t.Fatal("new ciphoer failed") + } + cdata, err := c.Encrypt(data) + if err != nil { + t.Fatal("encrypt failed", err) + } + data1, err := c1.Decrypt(cdata) + if err != nil { + t.Fatal("decrypt failed", err) + } + if !bytes.Equal(data, data1) { + t.Fatal("encrypt/decrypt failed", string(data), string(data1)) + } + block, _ := aes.NewCipher(key) + mode := cipher.NewCBCEncrypter(block, iv) + dst := make([]byte, len(data)) + mode.CryptBlocks(dst, data) + if !bytes.Equal(dst, cdata) { + t.Fatal("cipher text not equal to cypto/aes") + } +} diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..6557f59 --- /dev/null +++ b/doc.go @@ -0,0 +1,103 @@ +/*Package gnutls is a gnutls binding for golang + +a limit set of api is supported. + +TLS api is very similar to crypto/tls on standard library. + +TLS client example: + + addr := "127.0.0.1:9443" + c, err := gnutls.Dial("tcp", addr, &gnutls.Config{ServerName: "localhost"}) + if err != nil { + t.Fatal("gnutls dial ", err) + } + defer c.Close() + + data := "hello, world" + if _, err = c.Write([]byte(data)); err != nil { + t.Fatal("gnutls write ", err) + } + buf := make([]byte, 100) + n, err := c.Read(buf) + if err != nil { + t.Fatal("gnutls read ", err) + } + +TLS Server example: + + l, err := gnults.Listen("tcp", "127.0.0.1:9443", &gnutls.Config{ + CrtFile: "testdata/server.crt", KeyFile: "testdata/server.key"}) + if err != nil { + t.Fatal("gnutls listen ", err) + } + defer l.Close() + for { + c, err := l.Accept() + if err != nil { + log.Println("gnutls accept ", err) + break + } + log.Println("accept connection from ", c.RemoteAddr()) + go func(c net.Conn) { + defer c.Close() + + buf := make([]byte, 4096) + for { + n, err := c.Read(buf[0:]) + if err != nil { + log.Println("gnutls read ", err) + break + } + if _, err := c.Write(buf[:n]); err != nil { + log.Println("gnutls write ", err) + break + } + } + }(c) + } + +AES encrypt/decrypt example: + + key := []byte("0123456789abcdef") + iv := []byte("abcdefg123456789") + c, err := gnutls.NewCipher(gnutls.GNUTLS_CIPHER_AES_128_CBC, key, iv) + if err != nil { + t.Fatal(err) + } + defer c.Close() + + c1, err := gnutls.NewCipher(gnutls.GNUTLS_CIPHER_AES_128_CBC, key, iv) + if err != nil { + t.Fatal(err) + } + defer c1.Close() + + data := []byte("1234012121212121") + if c == nil { + t.Fatal("new ciphoer failed") + } + + // encrypt + cdata, err := c.Encrypt(data) + if err != nil { + t.Fatal("encrypt failed", err) + } + + // decrypt + data1, err := c1.Decrypt(cdata) + if err != nil { + t.Fatal("decrypt failed", err) + } + + +Hash example: + + h := gnutls.NewHash(gnutls.GNUTLS_SHA512) + defer h.Close() + + data := []byte("1234") + + h1 := h.Sum(data) + +*/ +package gnutls diff --git a/gnutls.c b/gnutls.c new file mode 100644 index 0000000..cc2694b --- /dev/null +++ b/gnutls.c @@ -0,0 +1,175 @@ +#include "_gnutls.h" + +#define MAX_BUF 1024 +char buffer[MAX_BUF + 1], *desc; +gnutls_datum_t out; +int status; +int type; + +struct session *init_client_session() +{ + struct session *sess = malloc(sizeof(struct session)); + memset(sess, sizeof(struct session), 0); + gnutls_init(&sess->session, GNUTLS_CLIENT); + gnutls_certificate_allocate_credentials(&sess->xcred); + gnutls_certificate_set_x509_system_trust(sess->xcred); + gnutls_set_default_priority(sess->session); + gnutls_credentials_set(sess->session, GNUTLS_CRD_CERTIFICATE, sess->xcred); + return sess; +} + +struct session *init_server_session() +{ + struct session *sess = malloc(sizeof(struct session)); + memset(sess, sizeof(struct session), 0); + gnutls_init(&sess->session, GNUTLS_SERVER); + gnutls_certificate_allocate_credentials(&sess->xcred); + gnutls_certificate_set_x509_system_trust(sess->xcred); + gnutls_set_default_priority(sess->session); + gnutls_credentials_set(sess->session, GNUTLS_CRD_CERTIFICATE, sess->xcred); + gnutls_certificate_server_set_request(sess->session, GNUTLS_CERT_IGNORE); + return sess; +} + +int set_keyfile(struct session *sess, char *crtfile, char *keyfile) +{ + return gnutls_certificate_set_x509_key_file( + sess->xcred, crtfile, keyfile, GNUTLS_X509_FMT_PEM); +} + +void session_destroy(struct session *sess) +{ + gnutls_bye(sess->session, GNUTLS_SHUT_WR); + gnutls_deinit(sess->session); + gnutls_certificate_free_credentials(sess->xcred); + free(sess); +} + +ssize_t pull_function(gnutls_transport_ptr_t ptr, void *data, size_t len) +{ + return DataRead(ptr, data, len); +} + +int pull_timeout_function(gnutls_transport_ptr_t ptr, unsigned int ms) +{ + return DataTimeoutPull(ptr, ms); +} + +ssize_t push_function(gnutls_transport_ptr_t ptr, const void *data, size_t len) +{ + return DataWrite(ptr, (char *)data, len); +} + +void set_data(struct session *sess, size_t data) +{ + sess->data = (void *)((int *)data); +} + +void set_servername(struct session *sess, char *servername, int namelen) +{ + gnutls_server_name_set(sess->session, GNUTLS_NAME_DNS, servername, namelen); + gnutls_session_set_verify_cert(sess->session, NULL, 0); +} + +int handshake(struct session *sess) +{ + if (sess->handshake > 0) + { + return 0; + } + + int ret; + do + { + ret = gnutls_handshake(sess->session); + } while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + + if (ret < 0) + { + if (ret == GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR) + { + // check certificate verification status + type = gnutls_certificate_type_get(sess->session); + status = gnutls_session_get_verify_cert_status(sess->session); + gnutls_certificate_verification_status_print(status, + type, &out, 0); + printf("cert verify output: %s\n", out.data); + gnutls_free(out.data); + } + fprintf(stderr, "*** Handshake failed: %s\n", gnutls_strerror(ret)); + } /*else{ + desc = gnutls_session_get_desc(sess->session); + printf("- Session info: %s\n", desc); + gnutls_free(desc); + }*/ + return ret; +} + +int read_application_data(struct session *sess, char *data, int buflen) +{ + int ret = gnutls_record_recv(sess->session, data, buflen); + return ret; +} + +int write_application_data(struct session *sess, char *data, int datalen) +{ + int ret = gnutls_record_send(sess->session, data, datalen); + return ret; +} + +int set_callback(struct session *sess) +{ + if (sess->data == NULL) + { + return -1; + } + gnutls_transport_set_ptr(sess->session, sess->data); + gnutls_transport_set_pull_function(sess->session, pull_function); + gnutls_transport_set_push_function(sess->session, push_function); + gnutls_transport_set_pull_timeout_function(sess->session, pull_timeout_function); + return 0; +} + +gnutls_cipher_hd_t new_cipher(int cipher_type, char *key, int keylen, char *iv, int ivlen) +{ + gnutls_cipher_hd_t handle; + gnutls_datum_t _key; + gnutls_datum_t _iv; + + _key.data = key; + _key.size = keylen; + _iv.data = iv; + _iv.size = ivlen; + + int ret = gnutls_cipher_init(&handle, cipher_type, &_key, &_iv); + if (ret < 0) + { + printf("new cipher: %s\n", gnutls_strerror(ret)); + return NULL; + } + //printf("new cipher done\n"); + //cipher->handle = handle; + return handle; +} + +int cipher_get_block_size(int t) +{ + return gnutls_cipher_get_block_size(t); +} + +int cipher_get_iv_size(int t) +{ + return gnutls_cipher_get_iv_size(t); +} + +gnutls_hash_hd_t new_hash(int t) +{ + gnutls_hash_hd_t hash; + gnutls_hash_init(&hash, t); + return hash; +} + +int get_hash_len(int t) +{ + return gnutls_hash_get_len(t); +} \ No newline at end of file diff --git a/hash.go b/hash.go new file mode 100644 index 0000000..0452593 --- /dev/null +++ b/hash.go @@ -0,0 +1,70 @@ +package gnutls + +/* +#include +#include "_gnutls.h" +*/ +import "C" +import ( + "fmt" +) + +const ( + GNUTLS_MD5 = 2 + GNUTLS_SHA1 = 3 + GNUTLS_MD2 = 5 + GNUTLS_SHA256 = 6 + GNUTLS_SHA384 = 7 + GNUTLS_SHA512 = 8 + GNUTLS_SHA224 = 9 +) + +// Hash hash struct +type Hash struct { + hash C.gnutls_hash_hd_t + t int +} + +// NewHash new hash struct +func NewHash(t int) *Hash { + h := C.new_hash(C.int(t)) + return &Hash{h, t} +} + +// Write write data to hash context +func (h *Hash) Write(buf []byte) error { + dataLen := len(buf) + + cbuf := C.CBytes(buf) + defer C.free(cbuf) + + ret := C.gnutls_hash(h.hash, cbuf, C.size_t(dataLen)) + if int(ret) < 0 { + return fmt.Errorf("hash failed: %s", C.GoString(C.gnutls_strerror(ret))) + } + return nil +} + +// Sum get hash result +func (h *Hash) Sum(buf []byte) []byte { + if buf != nil { + h.Write(buf) + } + + hashOutLen := C.get_hash_len(C.int(h.t)) + + dstBuf := C.malloc(C.size_t(hashOutLen)) + defer C.free(dstBuf) + + C.gnutls_hash_output(h.hash, dstBuf) + + gobuf := C.GoBytes(dstBuf, hashOutLen) + + return gobuf +} + +// Close destroy hash context +func (h *Hash) Close() error { + C.gnutls_hash_deinit(h.hash, nil) + return nil +} diff --git a/hash_test.go b/hash_test.go new file mode 100644 index 0000000..d44c889 --- /dev/null +++ b/hash_test.go @@ -0,0 +1,24 @@ +package gnutls + +import ( + "bytes" + "crypto/sha512" + "encoding/hex" + "log" + "testing" +) + +func TestHashSHA(t *testing.T) { + h := NewHash(GNUTLS_SHA512) + defer h.Close() + + data := []byte("1234") + + h1 := h.Sum(data) + + h3 := sha512.Sum512(data) + if !bytes.Equal(h3[:], h1) { + log.Printf("\n%s\n%s", hex.EncodeToString(h3[:]), hex.EncodeToString(h1)) + t.Fatal("hash not equal") + } +} diff --git a/testdata/server.crt b/testdata/server.crt new file mode 100644 index 0000000..ba731b0 --- /dev/null +++ b/testdata/server.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDYDCCAkigAwIBAgIJAJ92ThBK0H0ZMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTgwNjI3MDMwMjUwWhcNMjgwNjI0MDMwMjUwWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAufMjhIyoWzqqRKbglkfCWqSbU1N6SPaSjBmVQCfFGzUZx3DHWvemJ8Ip +2Nb9Gx9fI/GiRHs1K6noWZjJzsFZNdsoAADg+vXhS4MEwsItcRnKFgbmBtjKPbQy +jHnndAy8Wwe73NXTy7oBSbd5CZogvblLjfndSUIhCXVv7+PFHjfG78LEL7Dp1i1A +96SV2YuVYHr6FIS6C5FA0FGtGpXUC265jUN+sI88ONXMc/7zQ+4VWggB+Kq3B7uR +4DjTtxAC0X58AdWJm3yH6nTJLcsfOVQs1u7Mg97aUpRo9osw7ZMcEEchOw85L0Rl +K4vlZDnB2xD+8S8RRQi+Y/4GKPAihwIDAQABo1MwUTAdBgNVHQ4EFgQUGY2IxTxn +3tKlRsUnko97fF4X8WYwHwYDVR0jBBgwFoAUGY2IxTxn3tKlRsUnko97fF4X8WYw +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAHpvWRFV+Cen2VQdX +OO1YYufqcGPmTxo0XmksOUQsd+vwR0HSjCK1oC0PlloviFvCXn9p4ZvwVK1NdK19 +RUkiXPAIw5QmXeNQgCJRv7jhObIuqfKVGfZuH8PdPMcPzU+PxgaZ/gnaW62AAFJA +frZXwMhr/Ar+CvH4NSZfOxBF4LOWM0eVYfxUOFq+qvYptdSxXyK1kuBxFXOVNEIw +CWp75uzvZ1gNaUrdfCNoYMV2qeyuL8gNfRhExrDfjn4ouOZq0Lh8FHCnGdoZo15D +ZHkLUqdKnOXtHKdnLcxStF6orKF7I3f3fp5kyRM19uZzLV07zdawVJSDbaO2cJz1 +Qff5gg== +-----END CERTIFICATE----- diff --git a/testdata/server.key b/testdata/server.key new file mode 100644 index 0000000..28d543c --- /dev/null +++ b/testdata/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAufMjhIyoWzqqRKbglkfCWqSbU1N6SPaSjBmVQCfFGzUZx3DH +WvemJ8Ip2Nb9Gx9fI/GiRHs1K6noWZjJzsFZNdsoAADg+vXhS4MEwsItcRnKFgbm +BtjKPbQyjHnndAy8Wwe73NXTy7oBSbd5CZogvblLjfndSUIhCXVv7+PFHjfG78LE +L7Dp1i1A96SV2YuVYHr6FIS6C5FA0FGtGpXUC265jUN+sI88ONXMc/7zQ+4VWggB ++Kq3B7uR4DjTtxAC0X58AdWJm3yH6nTJLcsfOVQs1u7Mg97aUpRo9osw7ZMcEEch +Ow85L0RlK4vlZDnB2xD+8S8RRQi+Y/4GKPAihwIDAQABAoIBAFUuooin/r+8Ah/s +6lktikUHvvfO9+fQvJVdate24D50dti8Ozba18zCz3S8F7qDBoxqttD0OAlGjl/s +9BW40Osw+AP4YxfT5182J8ooNbToAYFRq7JbQVo+4CEl/vdUljyFMHQbqChdjxV7 +7QCPEIyZA5mIauySVJwGpj6YcsZCMemwEVjD3tFEeXF6qu0hD9R4SVDiJEosxta7 +evzfENVUgfUMceJQzRUQ/3HfbgBJybPfNQ8fH2ORF2eOgjcmlmh+EwXGNcuCCpXd +EOndM30YYBRJBCAuvCdPrg9/2H2KL9jsIuLG1PyO9/IiLGMh5nTP/2TYjKOxNcYh +1g8S4+ECgYEA8lW1Q9msH1wWM+eZwHDg1rZ3BLMCguRHh2CN/q1KAOgdRsEBsP3A +u45MjwwBWzy56H6w69RC4AXQcMdGjZT20095RpYD8Rv1hmaB/1n9mL6i8V2p+X5D +pk1EgDDTWy8VbT3jn/Qt8hxINGvKYbZaRQgAtAXPx71mNGgxYLm3+18CgYEAxG93 ++on9SmhTXZNke+BRrgX0FTpkGtdCwfNnGMGyX7qlNR3kUls17JYE7ejLED7NWxOT +uSBCib8Lhh7qhCl+N9rvxpZkeESoZazhoBIigv9osOLOxZtrct1sg7MHDTWgIzT+ ++OvLzveHPuliVKZVJdM69dz2QUplC9buf0JtUdkCgYAHaA0xNK7pCnR3Q6XUVt7Y +UR1UHHCANZ/mCFJurTcszetPJUj68tZ4JQI8AP7tne6Ep5KaspMUq7jSKZUDcMEW +dkBbouwd61/Wqr1gY4y3pWPvgpBWWsCQjZ4BWPystcSu4Qxa8CiTVL/0MjMuR1d8 +8qCq396Y2TYNdf3EWgjAewKBgHUNJyU3zKLl/6cnCR130bQtAAEWRkhoNPN1ot1x +rmS0x3UbVs5sY3mS+2T47ufDRIMc603JF10VZjyJd51BTGDkKTTgsQWpg97yYZAM +vlvo7e1ZeXTu49wSbXMc3vrUFZRlI/oYJ94wSXsHfvyKEPr1H5EaFfNZ7VRcwsk6 +QAIhAoGBAOX5gnUHiQe9n8Vg5AZS6JoNf11+ZHemDNczQAZ4o7L0nHvorqeKBZvG +U7jo1XDTyrdbs8BPibVdArSpD23auLBL8WZ2wORvFLWXl5po/GSGqoOVS9KoBGMs +6E2UkiVR00zYSXTXwsiin3DrLuZcjG7InV45I+ws5DerYFGoNer/ +-----END RSA PRIVATE KEY----- diff --git a/tls.go b/tls.go new file mode 100644 index 0000000..562fb62 --- /dev/null +++ b/tls.go @@ -0,0 +1,271 @@ +package gnutls + +/* +#include "_gnutls.h" +#cgo pkg-config: gnutls +*/ +import "C" +import ( + "fmt" + "log" + "net" + "os" + "time" + "unsafe" +) + +// Conn tls connection for client +type Conn struct { + c net.Conn + sess *C.struct_session + handshake bool +} + +// Config tls configure +type Config struct { + ServerName string + CrtFile string + KeyFile string +} +type listener struct { + l net.Listener + c *Config +} + +// Accept +func (l *listener) Accept() (net.Conn, error) { + c, err := l.l.Accept() + if err != nil { + return nil, err + } + return NewServerConn(c, l.c) +} + +// Close +func (l *listener) Close() error { + return l.l.Close() +} + +// Addr +func (l *listener) Addr() net.Addr { + return l.l.Addr() +} + +// Dial create a new connection +func Dial(network, addr string, cfg *Config) (*Conn, error) { + c, err := net.Dial(network, addr) + if err != nil { + return nil, err + } + return NewClientConn(c, cfg) +} + +// Listen create a listener +func Listen(network, addr string, cfg *Config) (net.Listener, error) { + if cfg == nil { + return nil, fmt.Errorf("config is need") + } + if cfg.CrtFile == "" || cfg.KeyFile == "" { + return nil, fmt.Errorf("keyfile is needed") + } + if _, err := os.Stat(cfg.CrtFile); err != nil { + return nil, err + } + if _, err := os.Stat(cfg.KeyFile); err != nil { + return nil, err + } + l, err := net.Listen(network, addr) + if err != nil { + return nil, err + } + return &listener{l, cfg}, nil +} + +// NewServerConn create a server Conn +func NewServerConn(c net.Conn, cfg *Config) (*Conn, error) { + var sess = C.init_server_session() + conn := &Conn{c: c, sess: sess} + n := C.size_t(uintptr(unsafe.Pointer(conn))) + //log.Println("conn addr ", int(n)) + C.set_data(sess, n) + C.set_callback(sess) + crtfile := C.CString(cfg.CrtFile) + keyfile := C.CString(cfg.KeyFile) + defer C.free(unsafe.Pointer(crtfile)) + defer C.free(unsafe.Pointer(keyfile)) + ret := C.set_keyfile(sess, crtfile, keyfile) + if int(ret) < 0 { + cerrstr := C.gnutls_strerror(ret) + return nil, fmt.Errorf("set keyfile failed: %s", C.GoString(cerrstr)) + } + return conn, nil +} + +// NewClientConn create a new gnutls connection +func NewClientConn(c net.Conn, cfg *Config) (*Conn, error) { + var sess = C.init_client_session() + conn := &Conn{c: c, sess: sess} + n := C.size_t(uintptr(unsafe.Pointer(conn))) + //log.Println("conn addr ", int(n)) + C.set_data(sess, n) + C.set_callback(sess) + if cfg != nil { + if cfg.ServerName != "" { + srvname := C.CString(cfg.ServerName) + defer C.free(unsafe.Pointer(srvname)) + C.set_servername(sess, srvname, C.int(len(cfg.ServerName))) + } + + if cfg.CrtFile != "" && cfg.KeyFile != "" { + crtfile := C.CString(cfg.CrtFile) + keyfile := C.CString(cfg.KeyFile) + defer C.free(unsafe.Pointer(crtfile)) + defer C.free(unsafe.Pointer(keyfile)) + ret := C.set_keyfile(sess, crtfile, keyfile) + if int(ret) < 0 { + return nil, fmt.Errorf("set keyfile failed: %s", C.GoString(C.gnutls_strerror(ret))) + } + } + } + return conn, nil +} + +// Handshake handshake tls +func (c *Conn) Handshake() error { + if c.handshake { + return nil + } + ret := C.handshake(c.sess) + if int(ret) < 0 { + return fmt.Errorf("handshake error") + } + c.handshake = true + //log.Println("handshake done") + return nil +} + +// Read +func (c *Conn) Read(buf []byte) (n int, err error) { + if !c.handshake { + err = c.Handshake() + if err != nil { + return + } + c.handshake = true + } + + bufLen := len(buf) + cbuf := C.malloc(C.size_t(bufLen)) + defer C.free(cbuf) + + ret := C.read_application_data(c.sess, (*C.char)(cbuf), C.int(bufLen)) + if int(ret) < 0 { + return 0, fmt.Errorf("read error: %s", C.GoString(C.gnutls_strerror(ret))) + } + + if int(ret) == 0 { + return 0, fmt.Errorf("connection closed") + } + + n = int(ret) + gobuf2 := C.GoBytes(cbuf, ret) + copy(buf, gobuf2) + return n, nil +} + +// Write +func (c *Conn) Write(buf []byte) (n int, err error) { + if !c.handshake { + err = c.Handshake() + if err != nil { + return + } + c.handshake = true + } + cbuf := C.CBytes(buf) + defer C.free(cbuf) + + ret := C.write_application_data(c.sess, (*C.char)(cbuf), C.int(len(buf))) + n = int(ret) + + if n < 0 { + return 0, fmt.Errorf("write error: %s", C.GoString(C.gnutls_strerror(ret))) + } + + if int(ret) == 0 { + return 0, fmt.Errorf("connection closed") + } + + return n, nil +} + +// Close close the conn +func (c *Conn) Close() error { + C.session_destroy(c.sess) + c.c.Close() + return nil +} + +// SetWriteDeadline implements net.Conn +func (c *Conn) SetWriteDeadline(t time.Time) error { + return c.c.SetWriteDeadline(t) +} + +// SetReadDeadline implements net.Conn +func (c *Conn) SetReadDeadline(t time.Time) error { + return c.c.SetReadDeadline(t) +} + +// RemoteAddr implements net.Conn +func (c *Conn) RemoteAddr() net.Addr { + return c.c.RemoteAddr() +} + +// LocalAddr implements net.Conn +func (c *Conn) LocalAddr() net.Addr { + return c.c.LocalAddr() +} + +// SetDeadline implements net.Conn +func (c *Conn) SetDeadline(t time.Time) error { + return c.c.SetDeadline(t) +} + +// DataRead c callback function for data read +//export DataRead +func DataRead(d unsafe.Pointer, cbuf *C.char, bufLen C.int) C.int { + //log.Println("read addr ", uintptr(d)) + conn := (*Conn)(unsafe.Pointer((uintptr(d)))) + buf := make([]byte, int(bufLen)) + n, err := conn.c.Read(buf) + if err != nil { + log.Println(err) + return -1 + } + cbuf2 := C.CBytes(buf[:n]) + // d := C.CString(string(buf[:n])) + defer C.free(cbuf2) + C.memcpy(unsafe.Pointer(cbuf), unsafe.Pointer(cbuf2), C.size_t(n)) + return C.int(n) +} + +// DataWrite c callback function for data write +//export DataWrite +func DataWrite(d unsafe.Pointer, cbuf *C.char, bufLen C.int) C.int { + //log.Println("write addr ", uintptr(d), int(_l)) + conn := (*Conn)(unsafe.Pointer((uintptr(d)))) + gobuf := C.GoBytes(unsafe.Pointer(cbuf), bufLen) + n, err := conn.c.Write(gobuf) + if err != nil { + log.Println(err) + return -1 + } + return C.int(n) +} + +// DataTimeoutPull c callback function for timeout read +//export DataTimeoutPull +func DataTimeoutPull(d unsafe.Pointer, delay C.int) C.int { + log.Println("timeout pull function") + return 0 +} diff --git a/tls_test.go b/tls_test.go new file mode 100644 index 0000000..2c481b0 --- /dev/null +++ b/tls_test.go @@ -0,0 +1,122 @@ +package gnutls + +import ( + "crypto/tls" + "log" + "net" + "testing" +) + +func TestTLSClient(t *testing.T) { + cert, err := tls.LoadX509KeyPair("testdata/server.crt", "testdata/server.key") + if err != nil { + t.Fatal("load certificate failed") + } + l, err := tls.Listen("tcp", "127.0.0.1:0", &tls.Config{ + Certificates: []tls.Certificate{cert}, + }) + if err != nil { + t.Fatal("listen failed") + } + defer l.Close() + addr := l.Addr().String() + log.Println("test server listen on ", addr) + go func() { + for { + c, err := l.Accept() + if err != nil { + break + } + log.Printf("accept connection from %s", c.RemoteAddr()) + go func(c net.Conn) { + defer c.Close() + for { + buf := make([]byte, 4096) + n, err := c.Read(buf) + if err != nil { + log.Println("connection closed") + break + } + if _, err = c.Write(buf[:n]); err != nil { + break + } + } + }(c) + } + }() + + c, err := Dial("tcp", addr, &Config{}) + if err != nil { + t.Fatal("gnutls dial ", err) + } + defer c.Close() + + data := "hello, world" + if _, err = c.Write([]byte(data)); err != nil { + t.Fatal("gnutls write ", err) + } + buf := make([]byte, 100) + n, err := c.Read(buf) + if err != nil { + t.Fatal("gnutls read ", err) + } + if string(buf[:n]) != data { + t.Errorf("need: %s, got: %s", data, string(buf[:n])) + } +} + +func TestTLSServer(t *testing.T) { + l, err := Listen("tcp", "127.0.0.1:0", &Config{ + CrtFile: "testdata/server.crt", KeyFile: "testdata/server.key"}) + if err != nil { + t.Fatal("gnutls listen ", err) + } + addr := l.Addr().String() + log.Println("test server listen on ", addr) + defer l.Close() + go func() { + for { + c, err := l.Accept() + if err != nil { + log.Println("gnutls accept ", err) + break + } + log.Println("accept connection from ", c.RemoteAddr()) + go func(c net.Conn) { + defer c.Close() + + buf := make([]byte, 4096) + for { + n, err := c.Read(buf[0:]) + if err != nil { + log.Println("gnutls read ", err) + break + } + if _, err := c.Write(buf[:n]); err != nil { + log.Println("gnutls write ", err) + break + } + } + }(c) + } + }() + + c, err := tls.Dial("tcp", addr, &tls.Config{InsecureSkipVerify: true}) + if err != nil { + t.Fatal("dial ", err) + } + defer c.Close() + + data := "hello, world" + if _, err := c.Write([]byte(data)); err != nil { + t.Fatal("write ", err) + } + buf := make([]byte, 100) + n, err := c.Read(buf) + if err != nil { + t.Fatal("read ", err) + } + if string(buf[:n]) != data { + t.Errorf("need: %s, got: %s", data, string(buf[:n])) + } +}