diff --git a/api/chisel/tunnel.go b/api/chisel/tunnel.go index 45e023da2..7a38ba359 100644 --- a/api/chisel/tunnel.go +++ b/api/chisel/tunnel.go @@ -8,9 +8,9 @@ import ( "strings" "time" - "github.com/portainer/libcrypto" portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/internal/edge/cache" + "github.com/portainer/portainer/pkg/libcrypto" "github.com/dchest/uniuri" ) diff --git a/api/crypto/ecdsa.go b/api/crypto/ecdsa.go index bbf839324..1279c06e1 100644 --- a/api/crypto/ecdsa.go +++ b/api/crypto/ecdsa.go @@ -8,7 +8,7 @@ import ( "encoding/base64" "encoding/hex" - "github.com/portainer/libcrypto" + "github.com/portainer/portainer/pkg/libcrypto" ) const ( diff --git a/api/http/handler/motd/motd.go b/api/http/handler/motd/motd.go index 84e5d030b..db93d3342 100644 --- a/api/http/handler/motd/motd.go +++ b/api/http/handler/motd/motd.go @@ -5,10 +5,10 @@ import ( "net/http" "strings" - "github.com/portainer/libcrypto" "github.com/portainer/libhttp/response" portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/http/client" + "github.com/portainer/portainer/pkg/libcrypto" ) type motdResponse struct { diff --git a/api/internal/ssl/ssl.go b/api/internal/ssl/ssl.go index f52b6bec5..f3c9641ca 100644 --- a/api/internal/ssl/ssl.go +++ b/api/internal/ssl/ssl.go @@ -6,9 +6,9 @@ import ( "os" "time" - "github.com/portainer/libcrypto" portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/dataservices" + "github.com/portainer/portainer/pkg/libcrypto" "github.com/pkg/errors" "github.com/rs/zerolog/log" diff --git a/go.mod b/go.mod index 7ae265de1..f8d4831c0 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,6 @@ require ( github.com/orcaman/concurrent-map v1.0.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 - github.com/portainer/libcrypto v0.0.0-20220506221303-1f4fb3b30f9a github.com/portainer/libhttp v0.0.0-20230615144939-a999f666d9a9 github.com/robfig/cron/v3 v3.0.1 github.com/rs/zerolog v1.29.0 diff --git a/go.sum b/go.sum index 2abba4e87..2356076f0 100644 --- a/go.sum +++ b/go.sum @@ -311,8 +311,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/portainer/libcrypto v0.0.0-20220506221303-1f4fb3b30f9a h1:B0z3skIMT+OwVNJPQhKp52X+9OWW6A9n5UWig3lHBJk= -github.com/portainer/libcrypto v0.0.0-20220506221303-1f4fb3b30f9a/go.mod h1:n54EEIq+MM0NNtqLeCby8ljL+l275VpolXO0ibHegLE= github.com/portainer/libhttp v0.0.0-20230615144939-a999f666d9a9 h1:Jq8g/pDcFL1Z/DnZgn6DyaWu29y9+RiB5aOJ/Xw4960= github.com/portainer/libhttp v0.0.0-20230615144939-a999f666d9a9/go.mod h1:H49JLiywwLt2rrJVroafEWy8fIs0i7mThAThK40sbb8= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= diff --git a/pkg/libcrypto/LICENSE b/pkg/libcrypto/LICENSE new file mode 100644 index 000000000..8696d60fa --- /dev/null +++ b/pkg/libcrypto/LICENSE @@ -0,0 +1,17 @@ +Copyright (c) 2018-2020 Portainer.io + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. diff --git a/pkg/libcrypto/README.md b/pkg/libcrypto/README.md new file mode 100644 index 000000000..3058c3c1e --- /dev/null +++ b/pkg/libcrypto/README.md @@ -0,0 +1,3 @@ +# libcrypto + +A small library providing encryption and decryption functions. diff --git a/pkg/libcrypto/decrypt.go b/pkg/libcrypto/decrypt.go new file mode 100644 index 000000000..ef0eb9d41 --- /dev/null +++ b/pkg/libcrypto/decrypt.go @@ -0,0 +1,35 @@ +package libcrypto + +import ( + "crypto/aes" + "crypto/cipher" + "errors" +) + +// Decrypt decrypts data using 256-bit AES-GCM. This both hides the content of +// the data and provides a check that it hasn't been altered. Expects input +// form nonce|ciphertext|tag where '|' indicates concatenation. +// Creates a 32bit hash of the key before decrypting the data. +func Decrypt(data []byte, key []byte) ([]byte, error) { + hashKey := Hash32Bit(key) + + block, err := aes.NewCipher(hashKey) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + if len(data) < gcm.NonceSize() { + return nil, errors.New("malformed ciphertext") + } + + return gcm.Open(nil, + data[:gcm.NonceSize()], + data[gcm.NonceSize():], + nil, + ) +} diff --git a/pkg/libcrypto/encrypt.go b/pkg/libcrypto/encrypt.go new file mode 100644 index 000000000..c397155cb --- /dev/null +++ b/pkg/libcrypto/encrypt.go @@ -0,0 +1,34 @@ +package libcrypto + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "io" +) + +// Encrypt encrypts data using 256-bit AES-GCM. This both hides the content of +// the data and provides a check that it hasn't been altered. Output takes the +// form nonce|ciphertext|tag where '|' indicates concatenation. +// Creates a 32bit hash of the key before encrypting the data. +func Encrypt(data, key []byte) ([]byte, error) { + hashKey := Hash32Bit(key) + + block, err := aes.NewCipher(hashKey) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + nonce := make([]byte, gcm.NonceSize()) + _, err = io.ReadFull(rand.Reader, nonce) + if err != nil { + return nil, err + } + + return gcm.Seal(nonce, nonce, data, nil), nil +} diff --git a/pkg/libcrypto/hash.go b/pkg/libcrypto/hash.go new file mode 100644 index 000000000..6047e2dc7 --- /dev/null +++ b/pkg/libcrypto/hash.go @@ -0,0 +1,19 @@ +package libcrypto + +import ( + "crypto/md5" + "encoding/hex" +) + +// HashFromBytes returns the hash of the specified data +func HashFromBytes(data []byte) []byte { + digest := md5.New() + digest.Write(data) + return digest.Sum(nil) +} + +// Hash32Bit returns a hexadecimal encoded hash +func Hash32Bit(data []byte) []byte { + hash := HashFromBytes(data) + return []byte(hex.EncodeToString(hash)) +} diff --git a/pkg/libcrypto/ssl.go b/pkg/libcrypto/ssl.go new file mode 100644 index 000000000..c08499317 --- /dev/null +++ b/pkg/libcrypto/ssl.go @@ -0,0 +1,82 @@ +package libcrypto + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/pem" + "errors" + "math/big" + "net" + "os" + "time" +) + +// GenerateCertsForHost generates a self-signed certificate for host and saves them at certPath and keyPath +func GenerateCertsForHost(hostname, ip, certPath, keyPath string, expiry time.Time) error { + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return err + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + NotAfter: expiry, + NotBefore: time.Now(), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + parsedIP := net.ParseIP(ip) + if parsedIP == nil { + return errors.New("Failed parsing host ip") + } + + template.DNSNames = append(template.DNSNames, hostname) + template.IPAddresses = append(template.IPAddresses, parsedIP) + + keyPair, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return err + } + + encodedCert, err := x509.CreateCertificate(rand.Reader, &template, &template, &keyPair.PublicKey, keyPair) + if err != nil { + return err + } + + err = createPEMEncodedFile(certPath, "CERTIFICATE", encodedCert) + if err != nil { + return err + } + + key, err := x509.MarshalECPrivateKey(keyPair) + if err != nil { + return err + } + + err = createPEMEncodedFile(keyPath, "EC PRIVATE KEY", key) + if err != nil { + return err + } + + return nil +} + +func createPEMEncodedFile(path, header string, data []byte) error { + file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return err + } + defer file.Close() + + err = pem.Encode(file, &pem.Block{Type: header, Bytes: data}) + if err != nil { + return err + } + + return nil +}