Skip to content

Commit

Permalink
feat: add crypto utils as template functions (#865)
Browse files Browse the repository at this point in the history
Signed-off-by: Mehant Kammakomati <kmehant@gmail.com>
  • Loading branch information
kmehant authored Sep 16, 2022
1 parent 5064d5f commit c21c835
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 1 deletion.
133 changes: 133 additions & 0 deletions common/cryptoutils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* Copyright IBM Corporation 2021
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package common

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"fmt"

"github.com/sirupsen/logrus"
"golang.org/x/crypto/pbkdf2"
)

// EncryptRsaCertWrapper can be used to encrypt the data using RSA PKCS1v15 algorithm with certificate as key
func EncryptRsaCertWrapper(certificate string, data string) string {

rsaPublicKey, err := getRSAPublicKeyFromCertificate([]byte(certificate))
if err != nil {
logrus.Errorf("failed to parse the certificate and get the RSA public key from it. Error: %q", err)
}

out, err := rsaEncrypt(rsaPublicKey, []byte(data))
if err != nil {
logrus.Errorf("failed to encrypt the data with RSA PKCS1v15 algorithm with certificate as key : %q", err)
}

return string(out)
}

// EncryptAesCbcWithPbkdfWrapper can be used to encrypt the data using AES 256 CBC mode with Pbkdf key derivation
func EncryptAesCbcWithPbkdfWrapper(key string, data string) string {
salt := make([]byte, 16)
_, err := rand.Read(salt)
if err != nil {
logrus.Errorf("failed to prepare the salt : %s", err)
}
out, err := aesCbcEncryptWithPbkdf([]byte(key), salt, []byte(data))
if err != nil {
logrus.Errorf("failed to encrypt the data using AES 256 CBC mode with Pbkdf key derivation : %q", err)
}
opensslOut := toOpenSSLFormat(salt, out)
return string(opensslOut)
}

// getRSAPublicKeyFromCertificate gets a RSA public key from a PEM-encoded certificate.crt file.
func getRSAPublicKeyFromCertificate(certificateInPemFormat []byte) (*rsa.PublicKey, error) {
block, _ := pem.Decode(certificateInPemFormat)
if block == nil {
return nil, fmt.Errorf("invalid certificate. Expected a PEM encoded certificate")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse the x509 certificate. Error: %q", err)
}
pubKey, ok := cert.PublicKey.(*rsa.PublicKey)
if !ok {
return nil, fmt.Errorf("failed to type cast the public key from the x509 certificate. Actual type: %T and value: %+v", cert.PublicKey, cert.PublicKey)
}
return pubKey, nil
}

// rsaEncrypt encrypts the plain text using the RSA public key.
func rsaEncrypt(publicKey *rsa.PublicKey, plainText []byte) ([]byte, error) {
cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plainText)
if err != nil {
return cipherText, fmt.Errorf("failed to RSA encrypt the plain text. Error: %q", err)
}
return cipherText, nil
}

// deriveAesKeyAndIv returns an AES key and IV derived from the password and salt using PBKDF2 function.
func deriveAesKeyAndIv(password, salt []byte) ([]byte, []byte) {
aesKeyAndIv := pbkdf2.Key(password, salt, 10000, 32+16, sha256.New)
return aesKeyAndIv[:32], aesKeyAndIv[32:]
}

// aesCbcEncryptWithPbkdf derives an AES key and IV using the given password and salt and then encrypts the plain text.
func aesCbcEncryptWithPbkdf(password, salt, plainText []byte) ([]byte, error) {

// derive an AES key and IV using the password and the salt

aesKey, iv := deriveAesKeyAndIv(password, salt)

// encrypt the workload using the AES key

aesCipher, err := aes.NewCipher(aesKey)
if err != nil {
return nil, fmt.Errorf("failed to create a new AES cipher using the key. Error: %q", err)
}

// pad the plain text as per PKCS#5 https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS#5_and_PKCS#7

paddingRequired := aes.BlockSize - len(plainText)%aes.BlockSize
if paddingRequired == 0 {
paddingRequired = aes.BlockSize
}
padding := make([]byte, paddingRequired)
for i := 0; i < paddingRequired; i++ {
padding[i] = byte(paddingRequired)
}

paddedPlainText := append(plainText, padding...)
cipherText := make([]byte, len(paddedPlainText))

aesCbcEncrypter := cipher.NewCBCEncrypter(aesCipher, iv)
aesCbcEncrypter.CryptBlocks(cipherText, paddedPlainText)
return cipherText, nil
}

// toOpenSSLFormat converts the cipher text to OpenSSL encrypted with password and salt format.
// http://justsolve.archiveteam.org/wiki/OpenSSL_salted_format
func toOpenSSLFormat(salt, cipherText []byte) []byte {
return append(append([]byte("Salted__"), salt...), cipherText...)
}
19 changes: 18 additions & 1 deletion filesystem/templatecopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"text/template"

"github.com/Masterminds/sprig"
Expand Down Expand Up @@ -145,6 +146,15 @@ func templateCopyDeletionCallBack(source, destination string, addOnConfigAsIface
return nil
}

// execTemplate executes the template and returns the filled template
func execTemplate(t *template.Template) func(string, interface{}) (string, error) {
return func(name string, v interface{}) (string, error) {
var buf strings.Builder
err := t.ExecuteTemplate(&buf, name, v)
return buf.String(), err
}
}

// writeTemplateToFile writes a templated string to a file
func writeTemplateToFile(tpl string, config interface{}, writepath string,
filemode os.FileMode, openingDelimiter string, closingDelimiter string) error {
Expand All @@ -153,7 +163,14 @@ func writeTemplateToFile(tpl string, config interface{}, writepath string,
openingDelimiter = "{{"
closingDelimiter = "}}"
}
packageTemplate, err := template.New("").Delims(openingDelimiter, closingDelimiter).Funcs(sprig.TxtFuncMap()).Parse(tpl)
packageTemplate := template.New("")
var err error
methodMap := template.FuncMap{
"execTemplate": execTemplate(packageTemplate),
"encAesCbcPbkdf": common.EncryptAesCbcWithPbkdfWrapper,
"encRsaCert": common.EncryptRsaCertWrapper,
}
template.Must(packageTemplate.Delims(openingDelimiter, closingDelimiter).Funcs(sprig.TxtFuncMap()).Funcs(methodMap).Parse(tpl))
if err != nil {
logrus.Errorf("Unable to parse the template : %s", err)
return err
Expand Down

0 comments on commit c21c835

Please sign in to comment.