From 444182f5bbd635e1d80687e1ffcd333edfe25794 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Thu, 9 Apr 2020 22:11:12 +0100 Subject: [PATCH] Purge impurity --- cmd/ck-client/ck-client.go | 3 ++- cmd/ck-server/ck-server.go | 9 ++++---- internal/client/TLS.go | 7 ++---- internal/client/auth.go | 8 +++---- internal/client/connector.go | 2 +- internal/client/state.go | 13 ++++++----- internal/client/transport.go | 4 +++- internal/client/websocket.go | 7 ++---- internal/common/worldstate.go | 17 +++++++++++++++ internal/server/TLS.go | 23 ++++++++++++++++++-- internal/server/TLSAux.go | 41 +++++++++++------------------------ internal/server/auth.go | 5 ++--- internal/server/dispatcher.go | 8 +++---- internal/server/state.go | 16 +++++++------- internal/server/transport.go | 3 ++- internal/server/websocket.go | 5 +++-- internal/util/util.go | 7 +++++- 17 files changed, 101 insertions(+), 77 deletions(-) create mode 100644 internal/common/worldstate.go diff --git a/cmd/ck-client/ck-client.go b/cmd/ck-client/ck-client.go index 45f5800..dc83e40 100644 --- a/cmd/ck-client/ck-client.go +++ b/cmd/ck-client/ck-client.go @@ -6,6 +6,7 @@ import ( "encoding/base64" "flag" "fmt" + "github.com/cbeuw/Cloak/internal/common" "net" "os" @@ -126,7 +127,7 @@ func main() { } } - localConfig, remoteConfig, authInfo, err := rawConfig.SplitConfigs() + localConfig, remoteConfig, authInfo, err := rawConfig.SplitConfigs(common.RealWorldState) if err != nil { log.Fatal(err) } diff --git a/cmd/ck-server/ck-server.go b/cmd/ck-server/ck-server.go index 666cb63..38872b1 100644 --- a/cmd/ck-server/ck-server.go +++ b/cmd/ck-server/ck-server.go @@ -3,16 +3,15 @@ package main import ( "flag" "fmt" + "github.com/cbeuw/Cloak/internal/common" + "github.com/cbeuw/Cloak/internal/server" + log "github.com/sirupsen/logrus" "net" "net/http" _ "net/http/pprof" "os" "runtime" "strings" - "time" - - "github.com/cbeuw/Cloak/internal/server" - log "github.com/sirupsen/logrus" ) var version string @@ -145,7 +144,7 @@ func main() { } } - sta, err := server.InitState(raw, time.Now) + sta, err := server.InitState(raw, common.RealWorldState) if err != nil { log.Fatalf("unable to initialise server state: %v", err) } diff --git a/internal/client/TLS.go b/internal/client/TLS.go index 942bffa..bc89e83 100644 --- a/internal/client/TLS.go +++ b/internal/client/TLS.go @@ -1,14 +1,11 @@ package client import ( - "crypto/rand" "encoding/binary" "github.com/cbeuw/Cloak/internal/common" "github.com/cbeuw/Cloak/internal/util" - "net" - "time" - log "github.com/sirupsen/logrus" + "net" ) const appDataMaxLength = 16401 @@ -67,7 +64,7 @@ type DirectTLS struct { // NewClientTransport handles the TLS handshake for a given conn and returns the sessionKey // if the server proceed with Cloak authentication func (tls *DirectTLS) Handshake(rawConn net.Conn, authInfo authInfo) (sessionKey [32]byte, err error) { - payload, sharedSecret := makeAuthenticationPayload(authInfo, rand.Reader, time.Now()) + payload, sharedSecret := makeAuthenticationPayload(authInfo) chOnly := tls.browser.composeClientHello(genStegClientHello(payload, authInfo.MockDomain)) chWithRecordLayer := common.AddRecordLayer(chOnly, common.Handshake, common.VersionTLS11) _, err = rawConn.Write(chWithRecordLayer) diff --git a/internal/client/auth.go b/internal/client/auth.go index 6dffcfe..270f65b 100644 --- a/internal/client/auth.go +++ b/internal/client/auth.go @@ -4,8 +4,6 @@ import ( "encoding/binary" "github.com/cbeuw/Cloak/internal/ecdh" "github.com/cbeuw/Cloak/internal/util" - "io" - "time" ) const ( @@ -19,7 +17,7 @@ type authenticationPayload struct { // makeAuthenticationPayload generates the ephemeral key pair, calculates the shared secret, and then compose and // encrypt the authenticationPayload -func makeAuthenticationPayload(authInfo authInfo, randReader io.Reader, time time.Time) (ret authenticationPayload, sharedSecret [32]byte) { +func makeAuthenticationPayload(authInfo authInfo) (ret authenticationPayload, sharedSecret [32]byte) { /* Authentication data: +----------+----------------+---------------------+-------------+--------------+--------+------------+ @@ -28,14 +26,14 @@ func makeAuthenticationPayload(authInfo authInfo, randReader io.Reader, time tim | 16 bytes | 12 bytes | 1 byte | 8 bytes | 4 bytes | 1 byte | 6 bytes | +----------+----------------+---------------------+-------------+--------------+--------+------------+ */ - ephPv, ephPub, _ := ecdh.GenerateKey(randReader) + ephPv, ephPub, _ := ecdh.GenerateKey(authInfo.WorldState.Rand) copy(ret.randPubKey[:], ecdh.Marshal(ephPub)) plaintext := make([]byte, 48) copy(plaintext, authInfo.UID) copy(plaintext[16:28], authInfo.ProxyMethod) plaintext[28] = authInfo.EncryptionMethod - binary.BigEndian.PutUint64(plaintext[29:37], uint64(time.Unix())) + binary.BigEndian.PutUint64(plaintext[29:37], uint64(authInfo.WorldState.Now().Unix())) binary.BigEndian.PutUint32(plaintext[37:41], authInfo.SessionId) if authInfo.Unordered { diff --git a/internal/client/connector.go b/internal/client/connector.go index 420675e..b56b2e3 100644 --- a/internal/client/connector.go +++ b/internal/client/connector.go @@ -19,7 +19,7 @@ func MakeSession(connConfig remoteConnConfig, authInfo authInfo, dialer common.D // sessionID is usergenerated. There shouldn't be a security concern because the scope of // sessionID is limited to its UID. quad := make([]byte, 4) - util.CryptoRandRead(quad) + util.RandRead(authInfo.WorldState.Rand, quad) authInfo.SessionId = binary.BigEndian.Uint32(quad) } else { authInfo.SessionId = 0 diff --git a/internal/client/state.go b/internal/client/state.go index d8855dd..66dc330 100644 --- a/internal/client/state.go +++ b/internal/client/state.go @@ -4,6 +4,7 @@ import ( "crypto" "encoding/json" "fmt" + "github.com/cbeuw/Cloak/internal/common" "io/ioutil" "net" "strings" @@ -13,11 +14,11 @@ import ( mux "github.com/cbeuw/Cloak/internal/multiplex" ) -// rawConfig represents the fields in the config json file +// RawConfig represents the fields in the config json file // nullable means if it's empty, a default value will be chosen in SplitConfigs // jsonOptional means if the json's empty, its value will be set from environment variables or commandline args // but it mustn't be empty when SplitConfigs is called -type rawConfig struct { +type RawConfig struct { ServerName string ProxyMethod string EncryptionMethod string @@ -57,6 +58,7 @@ type authInfo struct { Unordered bool ServerPubKey crypto.PublicKey MockDomain string + WorldState common.WorldState } // semi-colon separated value. This is for Android plugin options @@ -98,7 +100,7 @@ func ssvToJson(ssv string) (ret []byte) { return ret } -func ParseConfig(conf string) (raw *rawConfig, err error) { +func ParseConfig(conf string) (raw *RawConfig, err error) { var content []byte // Checking if it's a path to json or a ssv string if strings.Contains(conf, ";") && strings.Contains(conf, "=") { @@ -110,7 +112,7 @@ func ParseConfig(conf string) (raw *rawConfig, err error) { } } - raw = new(rawConfig) + raw = new(RawConfig) err = json.Unmarshal(content, &raw) if err != nil { return @@ -118,7 +120,7 @@ func ParseConfig(conf string) (raw *rawConfig, err error) { return } -func (raw *rawConfig) SplitConfigs() (local localConnConfig, remote remoteConnConfig, auth authInfo, err error) { +func (raw *RawConfig) SplitConfigs(worldState common.WorldState) (local localConnConfig, remote remoteConnConfig, auth authInfo, err error) { nullErr := func(field string) (local localConnConfig, remote remoteConnConfig, auth authInfo, err error) { err = fmt.Errorf("%v cannot be empty", field) return @@ -148,6 +150,7 @@ func (raw *rawConfig) SplitConfigs() (local localConnConfig, remote remoteConnCo return } auth.ServerPubKey = pub + auth.WorldState = worldState // Encryption method switch strings.ToLower(raw.EncryptionMethod) { diff --git a/internal/client/transport.go b/internal/client/transport.go index 809abe2..510e4fd 100644 --- a/internal/client/transport.go +++ b/internal/client/transport.go @@ -1,6 +1,8 @@ package client -import "net" +import ( + "net" +) type Transport interface { Handshake(rawConn net.Conn, authInfo authInfo) (sessionKey [32]byte, err error) diff --git a/internal/client/websocket.go b/internal/client/websocket.go index c1d504c..ed037df 100644 --- a/internal/client/websocket.go +++ b/internal/client/websocket.go @@ -1,19 +1,16 @@ package client import ( - "crypto/rand" "encoding/base64" "errors" "fmt" "github.com/cbeuw/Cloak/internal/common" "github.com/cbeuw/Cloak/internal/util" "github.com/gorilla/websocket" + utls "github.com/refraction-networking/utls" "net" "net/http" "net/url" - "time" - - utls "github.com/refraction-networking/utls" ) type WSOverTLS struct { @@ -37,7 +34,7 @@ func (ws *WSOverTLS) Handshake(rawConn net.Conn, authInfo authInfo) (sessionKey return sessionKey, fmt.Errorf("failed to parse ws url: %v", err) } - payload, sharedSecret := makeAuthenticationPayload(authInfo, rand.Reader, time.Now()) + payload, sharedSecret := makeAuthenticationPayload(authInfo) header := http.Header{} header.Add("hidden", base64.StdEncoding.EncodeToString(append(payload.randPubKey[:], payload.ciphertextWithTag[:]...))) c, _, err := websocket.NewClient(uconn, u, header, 16480, 16480) diff --git a/internal/common/worldstate.go b/internal/common/worldstate.go new file mode 100644 index 0000000..6443472 --- /dev/null +++ b/internal/common/worldstate.go @@ -0,0 +1,17 @@ +package common + +import ( + "crypto/rand" + "io" + "time" +) + +var RealWorldState = WorldState{ + Rand: rand.Reader, + Now: time.Now, +} + +type WorldState struct { + Rand io.Reader + Now func() time.Time +} diff --git a/internal/server/TLS.go b/internal/server/TLS.go index c8d38ab..861c29c 100644 --- a/internal/server/TLS.go +++ b/internal/server/TLS.go @@ -6,6 +6,9 @@ import ( "fmt" "github.com/cbeuw/Cloak/internal/common" "github.com/cbeuw/Cloak/internal/ecdh" + "github.com/cbeuw/Cloak/internal/util" + "io" + "math/rand" "net" log "github.com/sirupsen/logrus" @@ -39,8 +42,24 @@ func (TLS) processFirstPacket(clientHello []byte, privateKey crypto.PrivateKey) } func (TLS) makeResponder(clientHelloSessionId []byte, sharedSecret [32]byte) Responder { - respond := func(originalConn net.Conn, sessionKey [32]byte) (preparedConn net.Conn, err error) { - reply, err := composeReply(clientHelloSessionId, sharedSecret, sessionKey) + respond := func(originalConn net.Conn, sessionKey [32]byte, randSource io.Reader) (preparedConn net.Conn, err error) { + // the cert length needs to be the same for all handshakes belonging to the same session + // we can use sessionKey as a seed here to ensure consistency + possibleCertLengths := []int{42, 27, 68, 59, 36, 44, 46} + rand.Seed(int64(sessionKey[0])) + cert := make([]byte, possibleCertLengths[rand.Intn(len(possibleCertLengths))]) + util.RandRead(randSource, cert) + + var nonce [12]byte + util.RandRead(randSource, nonce[:]) + encryptedSessionKey, err := util.AESGCMEncrypt(nonce[:], sharedSecret[:], sessionKey[:]) + if err != nil { + return + } + var encryptedSessionKeyArr [48]byte + copy(encryptedSessionKeyArr[:], encryptedSessionKey) + + reply, err := composeReply(clientHelloSessionId, nonce, encryptedSessionKeyArr, cert) if err != nil { err = fmt.Errorf("failed to compose TLS reply: %v", err) return diff --git a/internal/server/TLSAux.go b/internal/server/TLSAux.go index fb201da..331effa 100644 --- a/internal/server/TLSAux.go +++ b/internal/server/TLSAux.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "github.com/cbeuw/Cloak/internal/util" - "math/rand" ) // ClientHello contains every field in a ClientHello message @@ -162,29 +161,21 @@ func parseClientHello(data []byte) (ret *ClientHello, err error) { return } -func composeServerHello(sessionId []byte, sharedSecret [32]byte, sessionKey [32]byte) ([]byte, error) { - nonce := make([]byte, 12) - util.CryptoRandRead(nonce) - - encryptedKey, err := util.AESGCMEncrypt(nonce, sharedSecret[:], sessionKey[:]) // 32 + 16 = 48 bytes - if err != nil { - return nil, err - } - +func composeServerHello(sessionId []byte, nonce [12]byte, encryptedSessionKeyWithTag [48]byte) ([]byte, error) { var serverHello [11][]byte - serverHello[0] = []byte{0x02} // handshake type - serverHello[1] = []byte{0x00, 0x00, 0x76} // length 77 - serverHello[2] = []byte{0x03, 0x03} // server version - serverHello[3] = append(nonce[0:12], encryptedKey[0:20]...) // random 32 bytes - serverHello[4] = []byte{0x20} // session id length 32 - serverHello[5] = sessionId // session id - serverHello[6] = []byte{0xc0, 0x30} // cipher suite TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - serverHello[7] = []byte{0x00} // compression method null - serverHello[8] = []byte{0x00, 0x2e} // extensions length 46 + serverHello[0] = []byte{0x02} // handshake type + serverHello[1] = []byte{0x00, 0x00, 0x76} // length 77 + serverHello[2] = []byte{0x03, 0x03} // server version + serverHello[3] = append(nonce[0:12], encryptedSessionKeyWithTag[0:20]...) // random 32 bytes + serverHello[4] = []byte{0x20} // session id length 32 + serverHello[5] = sessionId // session id + serverHello[6] = []byte{0xc0, 0x30} // cipher suite TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + serverHello[7] = []byte{0x00} // compression method null + serverHello[8] = []byte{0x00, 0x2e} // extensions length 46 keyShare, _ := hex.DecodeString("00330024001d0020") keyExchange := make([]byte, 32) - copy(keyExchange, encryptedKey[20:48]) + copy(keyExchange, encryptedSessionKeyWithTag[20:48]) util.CryptoRandRead(keyExchange[28:32]) serverHello[9] = append(keyShare, keyExchange...) @@ -198,21 +189,15 @@ func composeServerHello(sessionId []byte, sharedSecret [32]byte, sessionKey [32] // composeReply composes the ServerHello, ChangeCipherSpec and an ApplicationData messages // together with their respective record layers into one byte slice. -func composeReply(clientHelloSessionId []byte, sharedSecret [32]byte, sessionKey [32]byte) ([]byte, error) { +func composeReply(clientHelloSessionId []byte, nonce [12]byte, encryptedSessionKeyWithTag [48]byte, cert []byte) ([]byte, error) { TLS12 := []byte{0x03, 0x03} - sh, err := composeServerHello(clientHelloSessionId, sharedSecret, sessionKey) + sh, err := composeServerHello(clientHelloSessionId, nonce, encryptedSessionKeyWithTag) if err != nil { return nil, err } shBytes := addRecordLayer(sh, []byte{0x16}, TLS12) ccsBytes := addRecordLayer([]byte{0x01}, []byte{0x14}, TLS12) - // the cert length needs to be the same for all handshakes belonging to the same session - // we can use sessionKey as a seed here to ensure consistency - possibleCertLengths := []int{42, 27, 68, 59, 36, 44, 46} - rand.Seed(int64(sessionKey[0])) - cert := make([]byte, possibleCertLengths[rand.Intn(len(possibleCertLengths))]) - util.CryptoRandRead(cert) encryptedCertBytes := addRecordLayer(cert, []byte{0x17}, TLS12) ret := append(shBytes, ccsBytes...) ret = append(ret, encryptedCertBytes...) diff --git a/internal/server/auth.go b/internal/server/auth.go index a1f3ff4..4de28e6 100644 --- a/internal/server/auth.go +++ b/internal/server/auth.go @@ -34,7 +34,7 @@ var ErrTimestampOutOfWindow = errors.New("timestamp is outside of the accepting var ErrUnreconisedProtocol = errors.New("unreconised protocol") // decryptClientInfo checks if a the authFragments are valid. It doesn't check if the UID is authorised -func decryptClientInfo(fragments authFragments, now func() time.Time) (info ClientInfo, err error) { +func decryptClientInfo(fragments authFragments, serverTime time.Time) (info ClientInfo, err error) { var plaintext []byte plaintext, err = util.AESGCMDecrypt(fragments.randPubKey[0:12], fragments.sharedSecret[:], fragments.ciphertextWithTag[:]) if err != nil { @@ -51,7 +51,6 @@ func decryptClientInfo(fragments authFragments, now func() time.Time) (info Clie timestamp := int64(binary.BigEndian.Uint64(plaintext[29:37])) clientTime := time.Unix(timestamp, 0) - serverTime := now() if !(clientTime.After(serverTime.Truncate(TIMESTAMP_TOLERANCE)) && clientTime.Before(serverTime.Add(TIMESTAMP_TOLERANCE))) { err = fmt.Errorf("%v: received timestamp %v", ErrTimestampOutOfWindow, timestamp) return @@ -89,7 +88,7 @@ func AuthFirstPacket(firstPacket []byte, sta *State) (info ClientInfo, finisher return } - info, err = decryptClientInfo(fragments, sta.Now) + info, err = decryptClientInfo(fragments, sta.WorldState.Now()) if err != nil { log.Debug(err) err = fmt.Errorf("transport %v in correct format but not Cloak: %v", transport, err) diff --git a/internal/server/dispatcher.go b/internal/server/dispatcher.go index 26e7f9d..989e750 100644 --- a/internal/server/dispatcher.go +++ b/internal/server/dispatcher.go @@ -65,7 +65,7 @@ func DispatchConnection(conn net.Conn, sta *State) { } var sessionKey [32]byte - util.CryptoRandRead(sessionKey[:]) + util.RandRead(sta.WorldState.Rand, sessionKey[:]) obfuscator, err := mux.MakeObfuscator(ci.EncryptionMethod, sessionKey) if err != nil { log.Error(err) @@ -84,7 +84,7 @@ func DispatchConnection(conn net.Conn, sta *State) { // added to the userinfo database. The distinction between going into the admin mode // and normal proxy mode is that sessionID needs == 0 for admin mode if bytes.Equal(ci.UID, sta.AdminUID) && ci.SessionId == 0 { - preparedConn, err := finishHandshake(conn, sessionKey) + preparedConn, err := finishHandshake(conn, sessionKey, sta.WorldState.Rand) if err != nil { log.Error(err) return @@ -125,7 +125,7 @@ func DispatchConnection(conn net.Conn, sta *State) { } if existing { - preparedConn, err := finishHandshake(conn, sesh.SessionKey) + preparedConn, err := finishHandshake(conn, sesh.SessionKey, sta.WorldState.Rand) if err != nil { log.Error(err) return @@ -135,7 +135,7 @@ func DispatchConnection(conn net.Conn, sta *State) { return } - preparedConn, err := finishHandshake(conn, sessionKey) + preparedConn, err := finishHandshake(conn, sessionKey, sta.WorldState.Rand) if err != nil { log.Error(err) return diff --git a/internal/server/state.go b/internal/server/state.go index 7327cda..77b5acc 100644 --- a/internal/server/state.go +++ b/internal/server/state.go @@ -34,9 +34,9 @@ type State struct { ProxyBook map[string]net.Addr ProxyDialer common.Dialer - Now func() time.Time - AdminUID []byte - Timeout time.Duration + WorldState common.WorldState + AdminUID []byte + Timeout time.Duration //KeepAlive time.Duration BypassUID map[[16]byte]struct{} @@ -144,13 +144,13 @@ func ParseConfig(conf string) (raw RawConfig, err error) { } // ParseConfig parses the config (either a path to json or the json itself as argument) into a State variable -func InitState(preParse RawConfig, nowFunc func() time.Time) (sta *State, err error) { +func InitState(preParse RawConfig, worldState common.WorldState) (sta *State, err error) { sta = &State{ - Now: nowFunc, BypassUID: make(map[[16]byte]struct{}), ProxyBook: map[string]net.Addr{}, usedRandom: map[[32]byte]int64{}, RedirDialer: &net.Dialer{}, + WorldState: worldState, } if preParse.CncMode { err = errors.New("command & control mode not implemented") @@ -220,10 +220,10 @@ const CACHE_CLEAN_INTERVAL = 12 * time.Hour func (sta *State) UsedRandomCleaner() { for { time.Sleep(CACHE_CLEAN_INTERVAL) - now := sta.Now() sta.usedRandomM.Lock() for key, t := range sta.usedRandom { - if time.Unix(t, 0).Before(now.Add(TIMESTAMP_TOLERANCE)) { + // todo: inpure time + if time.Unix(t, 0).Before(sta.WorldState.Now().Add(TIMESTAMP_TOLERANCE)) { delete(sta.usedRandom, key) } } @@ -234,7 +234,7 @@ func (sta *State) UsedRandomCleaner() { func (sta *State) registerRandom(r [32]byte) bool { sta.usedRandomM.Lock() _, used := sta.usedRandom[r] - sta.usedRandom[r] = sta.Now().Unix() + sta.usedRandom[r] = sta.WorldState.Now().Unix() sta.usedRandomM.Unlock() return used } diff --git a/internal/server/transport.go b/internal/server/transport.go index 0b2a369..986dc3d 100644 --- a/internal/server/transport.go +++ b/internal/server/transport.go @@ -3,10 +3,11 @@ package server import ( "crypto" "errors" + "io" "net" ) -type Responder = func(originalConn net.Conn, sessionKey [32]byte) (preparedConn net.Conn, err error) +type Responder = func(originalConn net.Conn, sessionKey [32]byte, randSource io.Reader) (preparedConn net.Conn, err error) type Transport interface { processFirstPacket(reqPacket []byte, privateKey crypto.PrivateKey) (authFragments, Responder, error) } diff --git a/internal/server/websocket.go b/internal/server/websocket.go index 0a910c5..60a2af0 100644 --- a/internal/server/websocket.go +++ b/internal/server/websocket.go @@ -9,6 +9,7 @@ import ( "fmt" "github.com/cbeuw/Cloak/internal/ecdh" "github.com/cbeuw/Cloak/internal/util" + "io" "net" "net/http" ) @@ -39,7 +40,7 @@ func (WebSocket) processFirstPacket(reqPacket []byte, privateKey crypto.PrivateK } func (WebSocket) makeResponder(reqPacket []byte, sharedSecret [32]byte) Responder { - respond := func(originalConn net.Conn, sessionKey [32]byte) (preparedConn net.Conn, err error) { + respond := func(originalConn net.Conn, sessionKey [32]byte, randSource io.Reader) (preparedConn net.Conn, err error) { handler := newWsHandshakeHandler() // For an explanation of the following 3 lines, see the comments in websocketAux.go @@ -48,7 +49,7 @@ func (WebSocket) makeResponder(reqPacket []byte, sharedSecret [32]byte) Responde <-handler.finished preparedConn = handler.conn nonce := make([]byte, 12) - util.CryptoRandRead(nonce) + util.RandRead(randSource, nonce) // reply: [12 bytes nonce][32 bytes encrypted session key][16 bytes authentication tag] encryptedKey, err := util.AESGCMEncrypt(nonce, sharedSecret[:], sessionKey[:]) // 32 + 16 = 48 bytes diff --git a/internal/util/util.go b/internal/util/util.go index 9a74c74..e8bd666 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -4,6 +4,7 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" + "io" "time" log "github.com/sirupsen/logrus" @@ -38,7 +39,11 @@ func AESGCMDecrypt(nonce []byte, key []byte, ciphertext []byte) ([]byte, error) } func CryptoRandRead(buf []byte) { - _, err := rand.Read(buf) + RandRead(rand.Reader, buf) +} + +func RandRead(randSource io.Reader, buf []byte) { + _, err := randSource.Read(buf) if err == nil { return }