diff --git a/.gitignore b/.gitignore index daeae1a..6baf2e5 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,8 @@ poc-cb-net/cmd/controller/controller poc-cb-net/cmd/admin-web/admin-web poc-cb-net/cmd/service/cladnet-service poc-cb-net/cmd/test-client/demo-client -poc-cb-net/cmd/test-client/check-response-time-of-cb-tumblebug-api/check-response-time-of-cb-tumblebug-api \ No newline at end of file +poc-cb-net/cmd/test-client/check-response-time-of-cb-tumblebug-api/check-response-time-of-cb-tumblebug-api + +# Ignore secrets +*.pem +*.pub diff --git a/poc-cb-net/cmd/agent/agent.go b/poc-cb-net/cmd/agent/agent.go index 9cdc667..c892c34 100644 --- a/poc-cb-net/cmd/agent/agent.go +++ b/poc-cb-net/cmd/agent/agent.go @@ -112,13 +112,24 @@ func handleCommand(command string, commandOption string, etcdClient *clientv3.Cl // Watch the networking rule to update dynamically go watchNetworkingRule(etcdClient) + // Watch the other agents' secrets (RSA public keys) + if CBNet.IsEncryptionEnabled() { + go watchSecret(etcdClient) + } + // Start the cb-network go CBNet.Startup() // Sleep until the all routines are ready - time.Sleep(3 * time.Second) + time.Sleep(2 * time.Second) + + // Try Compare-And-Swap (CAS) an agent's secret (RSA public keys) + if CBNet.IsEncryptionEnabled() { + compareAndSwapSecret(etcdClient) + } + //time.Sleep(2 * time.Second) - // Try Compare-And-Swap (CAS) host-network-information by cladnetID and hostId + // Try Compare-And-Swap (CAS) a host-network-information by cladnetID and hostId compareAndSwapHostNetworkInformation(etcdClient) case "check-connectivity": @@ -197,6 +208,70 @@ func compareAndSwapHostNetworkInformation(etcdClient *clientv3.Client) { CBLogger.Debug("End.........") } +func watchSecret(etcdClient *clientv3.Client) { + CBLogger.Debug("Start.........") + + // Watch "/registry/cloud-adaptive-network/secret/{cladnet-id}" + keySecretGroup := fmt.Sprint(etcdkey.Secret + "/" + CBNet.ID) + CBLogger.Tracef("Watch \"%v\"", keySecretGroup) + watchChan1 := etcdClient.Watch(context.TODO(), keySecretGroup, clientv3.WithPrefix()) + for watchResponse := range watchChan1 { + for _, event := range watchResponse.Events { + CBLogger.Tracef("Watch - %s %q : %q", event.Type, event.Kv.Key, event.Kv.Value) + slicedKeys := strings.Split(string(event.Kv.Key), "/") + parsedHostID := slicedKeys[len(slicedKeys)-1] + CBLogger.Tracef("ParsedHostID: %v", parsedHostID) + + // Update keyring (including add) + CBNet.UpdateKeyring(parsedHostID, string(event.Kv.Value)) + } + } + CBLogger.Debug("End.........") +} + +func compareAndSwapSecret(etcdClient *clientv3.Client) { + CBLogger.Debug("Start.........") + + CBLogger.Debug("Compare-And-Swap (CAS) an agent's secret") + // Watch "/registry/cloud-adaptive-network/secret/{cladnet-id}" + KeySecretGroup := fmt.Sprint(etcdkey.Secret + "/" + CBNet.ID) + keySecretHost := fmt.Sprint(etcdkey.Secret + "/" + CBNet.ID + "/" + CBNet.HostID) + + base64PublicKey, _ := CBNet.GetPublicKeyBase64() + CBLogger.Tracef("Base64PublicKey: %+v", base64PublicKey) + + // NOTICE: "!=" doesn't work..... It might be a temporal issue. + txnResp, err := etcdClient.Txn(context.Background()). + If(clientv3.Compare(clientv3.Value(keySecretHost), "=", base64PublicKey)). + Then(clientv3.OpGet(KeySecretGroup, clientv3.WithPrefix())). + Else(clientv3.OpPut(keySecretHost, base64PublicKey)). + Commit() + + if err != nil { + CBLogger.Error(err) + } + CBLogger.Tracef("Transaction Response: %#v", txnResp) + + // The CAS would be succeeded if the prev host network information and current host network information are same. + // Then the networking rule will be returned. (The above "watch" will not be performed.) + // If not, the host tries to put the current host network information. + + if txnResp.Succeeded { + // Set the networking rule to the host + for _, kv := range txnResp.Responses[0].GetResponseRange().Kvs { + respKey := kv.Key + slicedKeys := strings.Split(string(respKey), "/") + parsedHostID := slicedKeys[len(slicedKeys)-1] + CBLogger.Tracef("ParsedHostID: %v", parsedHostID) + + // Update keyring (including add) + CBNet.UpdateKeyring(parsedHostID, string(kv.Value)) + } + } + + CBLogger.Debug("End.........") +} + func checkConnectivity(data string, etcdClient *clientv3.Client) { CBLogger.Debug("Start.........") @@ -329,6 +404,9 @@ func main() { CBNet.ID = cladnetID CBNet.HostID = hostID + // Enable encryption or not + CBNet.EnableEncryption(config.CBNetwork.IsEncrypted) + // Wait for multiple goroutines to complete var wg sync.WaitGroup diff --git a/poc-cb-net/cmd/test-client/config/template-config.yaml b/poc-cb-net/cmd/test-client/config/template-config.yaml index d322d81..8cbd29b 100644 --- a/poc-cb-net/cmd/test-client/config/template-config.yaml +++ b/poc-cb-net/cmd/test-client/config/template-config.yaml @@ -11,6 +11,7 @@ admin_web: cb_network: cladnet_id: "xxxx" host_id: "" # if host_id is "" (empty string), the cb-network agent will use hostname. + is_encrypted: false # false is default. # A config for the grpc as follows: grpc: diff --git a/poc-cb-net/config/template-config.yaml b/poc-cb-net/config/template-config.yaml index d322d81..8cbd29b 100644 --- a/poc-cb-net/config/template-config.yaml +++ b/poc-cb-net/config/template-config.yaml @@ -11,6 +11,7 @@ admin_web: cb_network: cladnet_id: "xxxx" host_id: "" # if host_id is "" (empty string), the cb-network agent will use hostname. + is_encrypted: false # false is default. # A config for the grpc as follows: grpc: diff --git a/poc-cb-net/internal/cb-network/cb-network.go b/poc-cb-net/internal/cb-network/cb-network.go index 1bfa668..a38438c 100644 --- a/poc-cb-net/internal/cb-network/cb-network.go +++ b/poc-cb-net/internal/cb-network/cb-network.go @@ -1,6 +1,8 @@ package cbnet import ( + "crypto/rand" + "crypto/rsa" "encoding/json" "errors" "fmt" @@ -18,6 +20,7 @@ import ( model "github.com/cloud-barista/cb-larva/poc-cb-net/internal/cb-network/model" "github.com/cloud-barista/cb-larva/poc-cb-net/internal/file" + secutil "github.com/cloud-barista/cb-larva/poc-cb-net/internal/secret-util" cblog "github.com/cloud-barista/cb-log" "github.com/sirupsen/logrus" "golang.org/x/net/ipv4" @@ -74,21 +77,25 @@ type ifReq struct { // CBNetwork represents a network for the multi-cloud type CBNetwork struct { // Variables for the cb-network - NetworkingRules model.NetworkingRule // Networking rule for a network interface and tunneling - ID string // ID for a cloud adaptive network + NetworkingRules model.NetworkingRule // Networking rule for a network interface and tunneling + ID string // ID for a cloud adaptive network + isEncryptionEnabled bool // Status if encryption is applied or not. // Variables for the cb-network controller // TBD // Variables for the cb-network agents - HostID string // HostID in a cloud adaptive network - HostPublicIP string // Inquired public IP of VM/Host - HostPrivateIPv4Networks []string // Inquired private IPv4 networks of VM/Host (e.g. ["192.168.10.4/24", ...]) - Interface *os.File // Assigned cbnet0 IP from the controller - name string // Name of a network interface, e.g., cbnet0 - port int // Port used for tunneling - isInterfaceConfigured bool // Status if a network interface is configured or not - notificationChannel chan bool // Channel to notify the status of a network interface + HostID string // HostID in a cloud adaptive network + HostPublicIP string // Inquired public IP of VM/Host + HostPrivateIPv4Networks []string // Inquired private IPv4 networks of VM/Host (e.g. ["192.168.10.4/24", ...]) + Interface *os.File // Assigned cbnet0 IP from the controller + name string // Name of a network interface, e.g., cbnet0 + port int // Port used for tunneling + isInterfaceConfigured bool // Status if a network interface is configured or not + notificationChannel chan bool // Channel to notify the status of a network interface + privateKey *rsa.PrivateKey // Private key + keyring map[string]*rsa.PublicKey // Keyring for secrets + keyringMutex *sync.Mutex // Mutext for keyring //listenConnection *net.UDPConn // Connection for encapsulation and decapsulation //NetworkInterfaces []model.NetworkInterface // Deprecated @@ -101,6 +108,7 @@ func New(name string, port int) *CBNetwork { temp := &CBNetwork{ name: name, port: port, + isEncryptionEnabled: false, isInterfaceConfigured: false, notificationChannel: make(chan bool), } @@ -133,7 +141,7 @@ func (cbnetwork *CBNetwork) inquireVMPublicIP() { for _, url := range urls { // Try to inquire public IP address - CBLogger.Debug("Try to inuire public IP address") + CBLogger.Debug("Try to inquire public IP address") CBLogger.Tracef("by %s", url) resp, err := http.Get(url) @@ -247,6 +255,7 @@ func (cbnetwork CBNetwork) GetHostNetworkInformation() model.HostNetworkInformat CBLogger.Debug("Start.........") temp := model.HostNetworkInformation{ + IsEncrypted: cbnetwork.isEncryptionEnabled, PublicIP: cbnetwork.HostPublicIP, PrivateIPv4Networks: cbnetwork.HostPrivateIPv4Networks, } @@ -405,43 +414,27 @@ func (cbnetwork *CBNetwork) runTunneling() { } }() - // Decapsulation - go func() { - CBLogger.Debug("Start decapsulation") - buf := make([]byte, BUFFERSIZE) - for { - // ReadFromUDP acts like ReadFrom but returns a UDPAddr. - n, _, err := lstnConn.ReadFromUDP(buf) - if err != nil { - CBLogger.Error("Error in cbnetwork.listenConnection.ReadFromUDP(buf): ", err) - return - } + var wg sync.WaitGroup - // Parse header - header, _ := ipv4.ParseHeader(buf[:n]) - CBLogger.Tracef("[Decapsulation] Header: %+v", header) + // Decapsulation + wg.Add(1) + go cbnetwork.decapsulate(lstnConn, &wg) - //fmt.Printf("Received %d bytes from %v: %+v", n, addr, header) + // Encapsulation + wg.Add(1) + go cbnetwork.encapsulate(lstnConn, &wg) - // It might be necessary to handle or route packets to the specific destination - // based on the NetworkingRule table - // To be determined. + wg.Wait() + CBLogger.Debug("End.........") +} - // Write to TUN interface - nWrite, errWrite := cbnetwork.Interface.Write(buf[:n]) - if errWrite != nil || nWrite == 0 { - CBLogger.Errorf("Error(%d len): %s", nWrite, errWrite) - } - } - }() +func (cbnetwork *CBNetwork) encapsulate(lstnConn *net.UDPConn, wg *sync.WaitGroup) { + CBLogger.Debug("Start.........") + defer wg.Done() - // Encapsulation - CBLogger.Debug("Start encapsulation") packet := make([]byte, BUFFERSIZE) for { - // Read packet from HostIPv4Network interface "cbnet0" - //fmt.Println("=== *cbnetwork.HostIPv4Network: ", *cbnetwork.HostIPv4Network) - //fmt.Println("=== cbnetwork.HostIPv4Network: ",cbnetwork.HostIPv4Network) + // Read packet from the interface "cbnet0" plen, err := cbnetwork.Interface.Read(packet[:]) if err != nil { CBLogger.Error("Error Read() in encapsulation:", err) @@ -450,14 +443,16 @@ func (cbnetwork *CBNetwork) runTunneling() { // Parse header header, _ := ipv4.ParseHeader(packet[:plen]) + CBLogger.Tracef("[Encapsulation] Received %d bytes from %v", plen, header.Src.String()) CBLogger.Tracef("[Encapsulation] Header: %+v", header) // Search and change destination (Public IP of target VM) idx := cbnetwork.NetworkingRules.GetIndexOfCBNetIP(header.Dst.String()) - var remoteIP string if idx != -1 { - remoteIP = cbnetwork.NetworkingRules.PublicIPAddress[idx] + + // Get the corresponding host's IP address + remoteIP := cbnetwork.NetworkingRules.PublicIPAddress[idx] // Resolve remote addr remoteAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%v", remoteIP, cbnetwork.port)) @@ -466,15 +461,88 @@ func (cbnetwork *CBNetwork) runTunneling() { CBLogger.Fatal("Unable to resolve remote addr:", err) } + bufToWrite := packet[:plen] + + if cbnetwork.isEncryptionEnabled { + + // Get the corresponding host's public key + HostID := cbnetwork.NetworkingRules.HostID[idx] + CBLogger.Tracef("HostID: %+v", HostID) + publicKey := cbnetwork.GetKey(HostID) + + // Encrypt plaintext by corresponidng public key + ciphertext, err := rsa.EncryptPKCS1v15( + rand.Reader, + publicKey, + []byte(packet[:plen]), + ) + CBLogger.Tracef("[Encapsulation] Ciphertext (encrypted) %d bytes", len(ciphertext)) + + if err != nil { + CBLogger.Error("could not encrypt plaintext") + continue + } + + bufToWrite = ciphertext + } + // Send packet - nWriteToUDP, errWriteToUDP := lstnConn.WriteToUDP(packet[:plen], remoteAddr) + nWriteToUDP, errWriteToUDP := lstnConn.WriteToUDP(bufToWrite, remoteAddr) if errWriteToUDP != nil || nWriteToUDP == 0 { CBLogger.Errorf("Error(%d len): %s", nWriteToUDP, errWriteToUDP) } } } + // CBLogger.Debug("End.........") +} - // Unreachable +func (cbnetwork *CBNetwork) decapsulate(lstnConn *net.UDPConn, wg *sync.WaitGroup) { + CBLogger.Debug("Start.........") + defer wg.Done() + + // Decapsulation + buf := make([]byte, BUFFERSIZE) + for { + // ReadFromUDP acts like ReadFrom but returns a UDPAddr. + n, addr, err := lstnConn.ReadFromUDP(buf) + if err != nil { + CBLogger.Error("Error in cbnetwork.listenConnection.ReadFromUDP(buf): ", err) + return + } + CBLogger.Tracef("[Decapsulation] Received %d bytes from %v", n, addr) + + bufToWrite := buf[:n] + + if cbnetwork.isEncryptionEnabled { + // Decrypt ciphertext by private key + plaintext, err := rsa.DecryptPKCS1v15( + rand.Reader, + cbnetwork.privateKey, + buf[:n], + ) + CBLogger.Tracef("[Decapsulation] Plaintext (decrypted) %d bytes", len(plaintext)) + + if err != nil { + CBLogger.Error("could not decrypt ciphertext") + continue + } + bufToWrite = plaintext + } + + // Parse header + header, _ := ipv4.ParseHeader(bufToWrite) + CBLogger.Tracef("[Decapsulation] Header: %+v", header) + + // It might be necessary to handle or route packets to the specific destination + // based on the NetworkingRule table + // To be determined. + + // Write to TUN interface + nWrite, errWrite := cbnetwork.Interface.Write(bufToWrite) + if errWrite != nil || nWrite == 0 { + CBLogger.Errorf("Error(%d len): %s", nWrite, errWrite) + } + } // CBLogger.Debug("End.........") } @@ -490,3 +558,147 @@ func (cbnetwork *CBNetwork) Shutdown() { CBLogger.Debug("End.........") } + +// EnableEncryption represents a function to set a status for message encryption. +func (cbnetwork *CBNetwork) EnableEncryption(b bool) { + if b == true { + err := cbnetwork.configureRSAKey() + if err != nil { + CBLogger.Error(err) + } + cbnetwork.keyring = make(map[string]*rsa.PublicKey) + cbnetwork.keyringMutex = new(sync.Mutex) + cbnetwork.isEncryptionEnabled = b + } +} + +// IsEncryptionEnabled represents a function to check if a message is encrypted or not. +func (cbnetwork CBNetwork) IsEncryptionEnabled() bool { + return cbnetwork.isEncryptionEnabled +} + +// GetPublicKeyBase64 represents a function to get a public key. +func (cbnetwork CBNetwork) GetPublicKeyBase64() (string, error) { + return secutil.PublicKeyToBase64(&cbnetwork.privateKey.PublicKey) +} + +// GenerateRSAKey represents a function to generate RSA key +func (cbnetwork *CBNetwork) configureRSAKey() error { + CBLogger.Debug("Start.........") + + // Set directory + ex, err := os.Executable() + if err != nil { + CBLogger.Error(err) + } + exePath := filepath.Dir(ex) + CBLogger.Tracef("exePath: %v\n", exePath) + + // Set secret path + secretPath := filepath.Join(exePath, "secret") + + // Set file and path for private key + privateKeyFile := cbnetwork.HostID + ".pem" + privateKeyPath := filepath.Join(secretPath, privateKeyFile) + CBLogger.Tracef("privateKeyPath: %+v", privateKeyPath) + + // Set file and path for public key + publicKeyFile := cbnetwork.HostID + ".pub" + publicKeyPath := filepath.Join(secretPath, publicKeyFile) + CBLogger.Tracef("publicKeyPath: %+v", publicKeyPath) + + if !file.Exists(privateKeyPath) { + CBLogger.Debug("Generage and save RSA key to files") + // Create directory or folder if not exist + _, err := os.Stat(secretPath) + + if os.IsNotExist(err) { + errDir := os.MkdirAll(secretPath, 600) + if errDir != nil { + log.Fatal(err) + } + + } + + // Generate RSA key + privateKey, publicKey, err := secutil.GenerateRSAKey() + if err != nil { + return err + } + + // Set member data in CBNetwork + cbnetwork.privateKey = privateKey + + // To bytes + privateKeyBytes, err := secutil.PrivateKeyToBytes(privateKey) + if err != nil { + return err + } + + // Save private key + err = secutil.SavePrivateKeyToFile(privateKeyBytes, privateKeyPath) + if err != nil { + return err + } + + // To bytes + publicKeyBytes, err := secutil.PublicKeyToBytes(publicKey) + if err != nil { + return err + } + + // Save public key + err = secutil.SavePublicKeyToFile(publicKeyBytes, publicKeyPath) + if err != nil { + return err + } + + } else { + CBLogger.Debug("Load RSA key from files") + privateKey, err := secutil.LoadPrivateKeyFromFile(privateKeyPath) + if err != nil { + return err + } + + publicKey, err := secutil.LoadPublicKeyFromFile(publicKeyPath) + if err != nil { + return err + } + + privateKey.PublicKey = *publicKey + + // Set member data in CBNetwork + cbnetwork.privateKey = privateKey + } + + CBLogger.Debug("End.........") + + return nil +} + +// UpdateKeyring updates a public key with a host ID +func (cbnetwork *CBNetwork) UpdateKeyring(hostID string, base64PublicKey string) error { + CBLogger.Debug("Start.........") + publicKey, err := secutil.PublicKeyFromBase64(base64PublicKey) + if err != nil { + return err + } + + cbnetwork.keyringMutex.Lock() + cbnetwork.keyring[hostID] = publicKey + cbnetwork.keyringMutex.Unlock() + CBLogger.Debug("End.........") + + return nil +} + +// GetKey returns a public key by a host ID +func (cbnetwork CBNetwork) GetKey(hostID string) *rsa.PublicKey { + CBLogger.Debug("Start.........") + cbnetwork.keyringMutex.Lock() + key := cbnetwork.keyring[hostID] + cbnetwork.keyringMutex.Unlock() + CBLogger.Debug("End.........") + + return key +} diff --git a/poc-cb-net/internal/cb-network/model/config.go b/poc-cb-net/internal/cb-network/model/config.go index 2906537..6aa070e 100644 --- a/poc-cb-net/internal/cb-network/model/config.go +++ b/poc-cb-net/internal/cb-network/model/config.go @@ -26,8 +26,9 @@ type AdminWebConfig struct { // CBNetworkConfig represents the configuration information for a cloud adaptive network type CBNetworkConfig struct { - CLADNetID string `yaml:"cladnet_id"` - HostID string `yaml:"host_id"` + CLADNetID string `yaml:"cladnet_id"` + HostID string `yaml:"host_id"` + IsEncrypted bool `yaml:"is_encrypted"` } // A config for the grpc as follows: diff --git a/poc-cb-net/internal/cb-network/model/host-network-information.go b/poc-cb-net/internal/cb-network/model/host-network-information.go index 509f949..4ddfb28 100644 --- a/poc-cb-net/internal/cb-network/model/host-network-information.go +++ b/poc-cb-net/internal/cb-network/model/host-network-information.go @@ -2,6 +2,7 @@ package cbnet // HostNetworkInformation represents the network information of VM, such as public IP and private networks type HostNetworkInformation struct { + IsEncrypted bool `json:"isEncrypted"` PublicIP string `json:"publicIPAddress"` PrivateIPv4Networks []string `json:"privateIPv4Networks"` } diff --git a/poc-cb-net/internal/etcd-key/etcd-key.go b/poc-cb-net/internal/etcd-key/etcd-key.go index 885d447..cd42ebb 100644 --- a/poc-cb-net/internal/etcd-key/etcd-key.go +++ b/poc-cb-net/internal/etcd-key/etcd-key.go @@ -3,18 +3,28 @@ package etcdkey const ( // CloudAdaptiveNetwork is a constant variable of "/registry/cloud-adaptive-network" key CloudAdaptiveNetwork = "/registry/cloud-adaptive-network" + // CLADNetSpecification is a constant variable of "/registry/cloud-adaptive-network/cladnet-specification" key CLADNetSpecification = CloudAdaptiveNetwork + "/cladnet-specification" + // HostNetworkInformation is a constant variable of "/registry/cloud-adaptive-network/host-network-information" key HostNetworkInformation = CloudAdaptiveNetwork + "/host-network-information" + // NetworkingRule is a constant variable of "/registry/cloud-adaptive-network/networking-rule" key NetworkingRule = CloudAdaptiveNetwork + "/networking-rule" + // ControlCommand is a constant variable of "/registry/cloud-adaptive-network/control-command" key ControlCommand = CloudAdaptiveNetwork + "/control-command" + // Status is a constant variable of "/registry/cloud-adaptive-network/status" key Status = CloudAdaptiveNetwork + "/status" + // StatusTestSpecification is a constant variable of "/registry/cloud-adaptive-network/status/test-specification" key StatusTestSpecification = Status + "/test-specification" + // StatusInformation is a constant variable of "/registry/cloud-adaptive-network/status/information" key StatusInformation = Status + "/information" + + // Secret is a constant variable of "/registry/cloud-adaptive-network/secret" key + Secret = CloudAdaptiveNetwork + "/secret" ) diff --git a/poc-cb-net/internal/secret-util/secret-util.go b/poc-cb-net/internal/secret-util/secret-util.go new file mode 100644 index 0000000..3f731c1 --- /dev/null +++ b/poc-cb-net/internal/secret-util/secret-util.go @@ -0,0 +1,283 @@ +package secutil + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/cloud-barista/cb-larva/poc-cb-net/internal/file" + cblog "github.com/cloud-barista/cb-log" + "github.com/sirupsen/logrus" +) + +// CBLogger represents a logger to show execution processes according to the logging level. +var CBLogger *logrus.Logger + +func init() { + fmt.Println("Start......... init() of secret-util.go") + ex, err := os.Executable() + if err != nil { + panic(err) + } + exePath := filepath.Dir(ex) + fmt.Printf("exePath: %v\n", exePath) + + // Load cb-log config from the current directory (usually for the production) + logConfPath := filepath.Join(exePath, "config", "log_conf.yaml") + fmt.Printf("logConfPath: %v\n", logConfPath) + if !file.Exists(logConfPath) { + // Load cb-log config from the project directory (usually for development) + path, err := exec.Command("git", "rev-parse", "--show-toplevel").Output() + if err != nil { + panic(err) + } + projectPath := strings.TrimSpace(string(path)) + logConfPath = filepath.Join(projectPath, "poc-cb-net", "config", "log_conf.yaml") + } + CBLogger = cblog.GetLoggerWithConfigPath("cb-network", logConfPath) + CBLogger.Debugf("Load %v", logConfPath) + fmt.Println("End......... init() of secret-util.go") +} + +const ( + rsaKeySize = 2048 +) + +// GenerateRSAKey generates a pair of RSA private and public keys. +func GenerateRSAKey() (*rsa.PrivateKey, *rsa.PublicKey, error) { + CBLogger.Debug("Start.........") + + // Generate RSA key + privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize) + if err != nil { + return nil, nil, err + } + + CBLogger.Debug("End.........") + return privateKey, &privateKey.PublicKey, nil +} + +// RSAKeyToBytes converts a pair of RSA private and public keys to []byte. +func RSAKeyToBytes(privateKey *rsa.PrivateKey, publicKey *rsa.PublicKey) ([]byte, []byte, error) { + CBLogger.Debug("Start.........") + privateKeyBytes, err := PrivateKeyToBytes(privateKey) + if err != nil { + return nil, nil, err + } + + publicKeyBytes, err := PublicKeyToBytes(publicKey) + if err != nil { + return nil, nil, err + } + + CBLogger.Debug("End.........") + return privateKeyBytes, publicKeyBytes, nil +} + +// PrivateKeyToBytes converts a pair of RSA private key to []byte. +func PrivateKeyToBytes(privateKey *rsa.PrivateKey) ([]byte, error) { + CBLogger.Debug("Start.........") + + privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + return nil, err + } + CBLogger.Tracef("privateKeyBytes: %+v", privateKeyBytes) + + CBLogger.Debug("End.........") + return privateKeyBytes, nil +} + +// PublicKeyToBytes converts a pair of RSA public key to []byte. +func PublicKeyToBytes(publicKey *rsa.PublicKey) ([]byte, error) { + CBLogger.Debug("Start.........") + + publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return nil, err + } + CBLogger.Tracef("publicKeyBytes: %+v", publicKeyBytes) + + CBLogger.Debug("End.........") + return publicKeyBytes, nil +} + +// SaveRSAKeyToFile saves a pair of RSA private and public keys to each key file. +func SaveRSAKeyToFile(privateKeyBytes []byte, pemPath string, publicKeyBytes []byte, pubPath string) error { + CBLogger.Debug("Start.........") + + if err := SavePrivateKeyToFile(privateKeyBytes, pemPath); err != nil { + return err + } + + if err := SavePublicKeyToFile(publicKeyBytes, pubPath); err != nil { + return err + } + + CBLogger.Debug("End.........") + return nil +} + +// SavePrivateKeyToFile saves a RSA private key to a key file. +func SavePrivateKeyToFile(privateKeyBytes []byte, pemPath string) error { + CBLogger.Debug("Start.........") + + // Save private key to file + privateKeyBlock := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: privateKeyBytes, + } + privatePem, err := os.Create(pemPath) + if err != nil { + return err + } + err = pem.Encode(privatePem, privateKeyBlock) + if err != nil { + return err + } + + CBLogger.Debug("End.........") + return nil +} + +// SavePublicKeyToFile saves a RSA public key to a key file. +func SavePublicKeyToFile(publicKeyBytes []byte, pubPath string) error { + CBLogger.Debug("Start.........") + + // Save public key to file + publicKeyBlock := &pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: publicKeyBytes, + } + publicPem, err := os.Create(pubPath) + if err != nil { + return err + } + + err = pem.Encode(publicPem, publicKeyBlock) + if err != nil { + return err + } + + CBLogger.Debug("End.........") + return nil +} + +// LoadPrivateKeyFromFile loads a RSA private key from a key file. +func LoadPrivateKeyFromFile(pemPath string) (*rsa.PrivateKey, error) { + CBLogger.Debug("Start.........") + + // Load private key from file + privateKeyBytes, err := ioutil.ReadFile(pemPath) + if err != nil { + return nil, err + } + + privateKeyPem, _ := pem.Decode(privateKeyBytes) + if privateKeyPem == nil || privateKeyPem.Type != "RSA PRIVATE KEY" { + return nil, errors.New("failed to decode PEM block containing private key") + } + + // Currently no need of password + + // var privPemBytes []byte + + // rsaPrivateKeyPassword := "" // Currently no need of password + // if rsaPrivateKeyPassword != "" { + // privPemBytes, err = x509.DecryptPEMBlock(privateKeyPem, []byte(rsaPrivateKeyPassword)) + // } else { + // privPemBytes = privateKeyPem.Bytes + // } + + CBLogger.Debug("End.........") + return PrivateKeyFromBytes(privateKeyPem.Bytes) +} + +// LoadPublicKeyFromFile loads a RSA public key from a key file. +func LoadPublicKeyFromFile(pubPath string) (*rsa.PublicKey, error) { + CBLogger.Debug("Start.........") + + // Load public key from file + publicKeyBytes, err := ioutil.ReadFile(pubPath) + if err != nil { + return nil, err + } + publicKeyPem, _ := pem.Decode(publicKeyBytes) + if publicKeyPem == nil || publicKeyPem.Type != "RSA PUBLIC KEY" { + return nil, errors.New("failed to decode PEM block containing public key") + } + + CBLogger.Debug("End.........") + return PublicKeyFromBytes(publicKeyPem.Bytes) +} + +// PublicKeyToBase64 convert a RSA public key to a base64 string. +func PublicKeyToBase64(publicKey *rsa.PublicKey) (string, error) { + CBLogger.Debug("Start.........") + publicKeyBytes, err := PublicKeyToBytes(publicKey) + if err != nil { + return "", err + } + + CBLogger.Debug("End.........") + return base64.StdEncoding.EncodeToString(publicKeyBytes), nil +} + +// PublicKeyFromBase64 convert a base64 string to a RSA public key. +func PublicKeyFromBase64(key string) (*rsa.PublicKey, error) { + CBLogger.Debug("Start.........") + + publicKeyBytes, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return nil, err + } + + CBLogger.Debug("End.........") + return PublicKeyFromBytes(publicKeyBytes) +} + +// PublicKeyFromBytes convert a base64 bytes to a RSA public key. +func PublicKeyFromBytes(publicKeyBytes []byte) (*rsa.PublicKey, error) { + CBLogger.Debug("Start.........") + + publicKeyInterface, err := x509.ParsePKIXPublicKey(publicKeyBytes) + if err != nil { + return nil, err + } + publicKey, ok := publicKeyInterface.(*rsa.PublicKey) + if !ok { + return nil, errors.New("invalid public key") + } + + CBLogger.Debug("End.........") + return publicKey, nil +} + +// PrivateKeyFromBytes convert a base64 bytes to a RSA private key. +func PrivateKeyFromBytes(privateKeyBytes []byte) (*rsa.PrivateKey, error) { + CBLogger.Debug("Start.........") + + var privateKeyInterface interface{} + var err error + if privateKeyInterface, err = x509.ParsePKCS1PrivateKey(privateKeyBytes); err != nil { + if privateKeyInterface, err = x509.ParsePKCS8PrivateKey(privateKeyBytes); err != nil { + return nil, err + } + } + privateKey, ok := privateKeyInterface.(*rsa.PrivateKey) + if !ok { + return nil, errors.New("invalid private key") + } + + CBLogger.Debug("End.........") + return privateKey, nil +}