Adding encryption decryption feature

Signed-off-by: Tarun1 Gupta <gupttaru@deshaw.com>
This commit is contained in:
gupttaru 2022-08-18 20:26:48 +05:30 committed by Tarun1 Gupta
parent f50ce4aea4
commit 3bb9ed4f09
19 changed files with 391 additions and 8 deletions

View file

@ -571,9 +571,18 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
createFlags.StringVar(&cf.PasswdEntry, passwdEntryName, "", "Entry to write to /etc/passwd")
_ = cmd.RegisterFlagCompletionFunc(passwdEntryName, completion.AutocompleteNone)
decryptionKeysFlagName := "decryption-key"
createFlags.StringSliceVar(
&cf.DecryptionKeys,
decryptionKeysFlagName, []string{},
"Key needed to decrypt the image (e.g. /path/to/key.pem)",
)
_ = cmd.RegisterFlagCompletionFunc(decryptionKeysFlagName, completion.AutocompleteNone)
if registry.IsRemote() {
_ = createFlags.MarkHidden("env-host")
_ = createFlags.MarkHidden("http-proxy")
_ = createFlags.MarkHidden(decryptionKeysFlagName)
} else {
createFlags.StringVar(
&cf.SignaturePolicy,

View file

@ -334,15 +334,21 @@ func PullImage(imageName string, cliVals *entities.ContainerCreateOptions) (stri
skipTLSVerify = types.NewOptionalBool(!cliVals.TLSVerify.Value())
}
decConfig, err := util.DecryptConfig(cliVals.DecryptionKeys)
if err != nil {
return "unable to obtain decryption config", err
}
pullReport, pullErr := registry.ImageEngine().Pull(registry.GetContext(), imageName, entities.ImagePullOptions{
Authfile: cliVals.Authfile,
Quiet: cliVals.Quiet,
Arch: cliVals.Arch,
OS: cliVals.OS,
Variant: cliVals.Variant,
SignaturePolicy: cliVals.SignaturePolicy,
PullPolicy: pullPolicy,
SkipTLSVerify: skipTLSVerify,
Authfile: cliVals.Authfile,
Quiet: cliVals.Quiet,
Arch: cliVals.Arch,
OS: cliVals.OS,
Variant: cliVals.Variant,
SignaturePolicy: cliVals.SignaturePolicy,
PullPolicy: pullPolicy,
SkipTLSVerify: skipTLSVerify,
OciDecryptConfig: decConfig,
})
if pullErr != nil {
return "", pullErr

View file

@ -23,6 +23,7 @@ type pullOptionsWrapper struct {
entities.ImagePullOptions
TLSVerifyCLI bool // CLI only
CredentialsCLI string
DecryptionKeys []string
}
var (
@ -107,6 +108,13 @@ func pullFlags(cmd *cobra.Command) {
flags.StringVar(&pullOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
_ = cmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault)
decryptionKeysFlagName := "decryption-key"
flags.StringSliceVar(&pullOptions.DecryptionKeys, decryptionKeysFlagName, nil, "Key needed to decrypt the image (e.g. /path/to/key.pem)")
_ = cmd.RegisterFlagCompletionFunc(decryptionKeysFlagName, completion.AutocompleteDefault)
if registry.IsRemote() {
_ = flags.MarkHidden(decryptionKeysFlagName)
}
if !registry.IsRemote() {
certDirFlagName := "cert-dir"
flags.StringVar(&pullOptions.CertDir, certDirFlagName, "", "`Pathname` of a directory containing TLS certificates and keys")
@ -156,6 +164,12 @@ func imagePull(cmd *cobra.Command, args []string) error {
pullOptions.Password = creds.Password
}
decConfig, err := util.DecryptConfig(pullOptions.DecryptionKeys)
if err != nil {
return fmt.Errorf("unable to obtain decryption config: %w", err)
}
pullOptions.OciDecryptConfig = decConfig
if !pullOptions.Quiet {
pullOptions.Writer = os.Stderr
}

View file

@ -1,6 +1,7 @@
package images
import (
"fmt"
"os"
"github.com/containers/common/pkg/auth"
@ -20,6 +21,8 @@ type pushOptionsWrapper struct {
TLSVerifyCLI bool // CLI only
CredentialsCLI string
SignPassphraseFileCLI string
EncryptionKeys []string
EncryptLayers []int
}
var (
@ -121,6 +124,14 @@ func pushFlags(cmd *cobra.Command) {
flags.StringVar(&pushOptions.CompressionFormat, compressionFormat, "", "compression format to use")
_ = cmd.RegisterFlagCompletionFunc(compressionFormat, common.AutocompleteCompressionFormat)
encryptionKeysFlagName := "encryption-key"
flags.StringSliceVar(&pushOptions.EncryptionKeys, encryptionKeysFlagName, nil, "Key with the encryption protocol to use to encrypt the image (e.g. jwe:/path/to/key.pem)")
_ = cmd.RegisterFlagCompletionFunc(encryptionKeysFlagName, completion.AutocompleteDefault)
encryptLayersFlagName := "encrypt-layer"
flags.IntSliceVar(&pushOptions.EncryptLayers, encryptLayersFlagName, nil, "Layers to encrypt, 0-indexed layer indices with support for negative indexing (e.g. 0 is the first layer, -1 is the last layer). If not defined, will encrypt all layers if encryption-key flag is specified")
_ = cmd.RegisterFlagCompletionFunc(encryptLayersFlagName, completion.AutocompleteDefault)
if registry.IsRemote() {
_ = flags.MarkHidden("cert-dir")
_ = flags.MarkHidden("compress")
@ -129,6 +140,8 @@ func pushFlags(cmd *cobra.Command) {
_ = flags.MarkHidden(signByFlagName)
_ = flags.MarkHidden(signBySigstorePrivateKeyFlagName)
_ = flags.MarkHidden(signPassphraseFileFlagName)
_ = flags.MarkHidden(encryptionKeysFlagName)
_ = flags.MarkHidden(encryptLayersFlagName)
}
if !registry.IsRemote() {
flags.StringVar(&pushOptions.SignaturePolicy, "signature-policy", "", "Path to a signature-policy file")
@ -172,6 +185,13 @@ func imagePush(cmd *cobra.Command, args []string) error {
return err
}
encConfig, encLayers, err := util.EncryptConfig(pushOptions.EncryptionKeys, pushOptions.EncryptLayers)
if err != nil {
return fmt.Errorf("unable to obtain encryption config: %w", err)
}
pushOptions.OciEncryptConfig = encConfig
pushOptions.OciEncryptLayers = encLayers
// Let's do all the remaining Yoga in the API to prevent us from scattering
// logic across (too) many parts of the code.
return registry.ImageEngine().Push(registry.GetContext(), source, destination, pushOptions.ImagePushOptions)

View file

@ -0,0 +1,7 @@
####> This option file is used in:
####> podman create, pull, run
####> If you edit this file, make sure your changes
####> are applicable to all of those.
#### **--decryption-key**=*key[:passphrase]*
The [key[:passphrase]] to be used for decryption of images. Key can point to keys and/or certificates. Decryption will be tried with all keys. If the key is protected by a passphrase, it is required to be passed in the argument and omitted otherwise.

View file

@ -114,6 +114,8 @@ and specified with a _tag_.
@@option cpuset-mems
@@option decryption-key
@@option device
Note: if the user only has access rights via a group, accessing the device

View file

@ -57,6 +57,8 @@ All tagged images in the repository will be pulled.
@@option creds
@@option decryption-key
@@option disable-content-trust
#### **--help**, **-h**

View file

@ -64,6 +64,14 @@ Note: This flag can only be set when using the **dir** transport
@@option disable-content-trust
#### **--encrypt-layer**=*layer(s)*
Layer(s) to encrypt: 0-indexed layer indices with support for negative indexing (e.g. 0 is the first layer, -1 is the last layer). If not defined, will encrypt all layers if encryption-key flag is specified.
#### **--encryption-key**=*key*
The [protocol:keyfile] specifies the encryption protocol, which can be JWE (RFC7516), PGP (RFC4880), and PKCS7 (RFC2315) and the key material required for image encryption. For instance, jwe:/path/to/key.pem or pgp:admin@example.com or pkcs7:/path/to/x509-file.
#### **--format**, **-f**=*format*
Manifest Type (oci, v2s2, or v2s1) to use when pushing an image.

View file

@ -131,6 +131,8 @@ and specified with a _tag_.
@@option cpuset-mems
@@option decryption-key
#### **--detach**, **-d**
Detached mode: run the container in the background and print the new container ID. The default is *false*.

View file

@ -8,6 +8,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/types"
encconfig "github.com/containers/ocicrypt/config"
"github.com/containers/podman/v4/pkg/inspect"
"github.com/containers/podman/v4/pkg/trust"
"github.com/docker/docker/api/types/container"
@ -158,6 +159,9 @@ type ImagePullOptions struct {
PullPolicy config.PullPolicy
// Writer is used to display copy information including progress bars.
Writer io.Writer
// OciDecryptConfig contains the config that can be used to decrypt an image if it is
// encrypted if non-nil. If nil, it does not attempt to decrypt an image.
OciDecryptConfig *encconfig.DecryptConfig
}
// ImagePullReport is the response from pulling one or more images.
@ -227,6 +231,15 @@ type ImagePushOptions struct {
CompressionFormat string
// Writer is used to display copy information including progress bars.
Writer io.Writer
// OciEncryptConfig when non-nil indicates that an image should be encrypted.
// The encryption options is derived from the construction of EncryptConfig object.
OciEncryptConfig *encconfig.EncryptConfig
// OciEncryptLayers represents the list of layers to encrypt.
// If nil, don't encrypt any layers.
// If non-nil and len==0, denotes encrypt all layers.
// integers in the slice represent 0-indexed layer indices, with support for negative
// indexing. i.e. 0 is the first layer, -1 is the last (top-most) layer.
OciEncryptLayers *[]int
}
// ImagePushReport is the response from pushing an image.

View file

@ -290,6 +290,7 @@ type ContainerCreateOptions struct {
ChrootDirs []string
IsInfra bool
IsClone bool
DecryptionKeys []string
Net *NetOptions `json:"net,omitempty"`

View file

@ -236,6 +236,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti
pullOptions.SignaturePolicyPath = options.SignaturePolicy
pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify
pullOptions.Writer = options.Writer
pullOptions.OciDecryptConfig = options.OciDecryptConfig
if !options.Quiet && pullOptions.Writer == nil {
pullOptions.Writer = os.Stderr
@ -309,6 +310,8 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
pushOptions.SignSigstorePrivateKeyPassphrase = options.SignSigstorePrivateKeyPassphrase
pushOptions.InsecureSkipTLSVerify = options.SkipTLSVerify
pushOptions.Writer = options.Writer
pushOptions.OciEncryptConfig = options.OciEncryptConfig
pushOptions.OciEncryptLayers = options.OciEncryptLayers
compressionFormat := options.CompressionFormat
if compressionFormat == "" {

View file

@ -105,6 +105,10 @@ func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOption
}
func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, opts entities.ImagePullOptions) (*entities.ImagePullReport, error) {
if opts.OciDecryptConfig != nil {
return nil, fmt.Errorf("decryption is not supported for remote clients")
}
options := new(images.PullOptions)
options.WithAllTags(opts.AllTags).WithAuthfile(opts.Authfile).WithArch(opts.Arch).WithOS(opts.OS)
options.WithVariant(opts.Variant).WithPassword(opts.Password)
@ -240,6 +244,10 @@ func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOpti
}
func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, opts entities.ImagePushOptions) error {
if opts.OciEncryptConfig != nil {
return fmt.Errorf("encryption is not supported for remote clients")
}
options := new(images.PushOptions)
options.WithAll(opts.All).WithCompress(opts.Compress).WithUsername(opts.Username).WithPassword(opts.Password).WithAuthfile(opts.Authfile).WithFormat(opts.Format).WithRemoveSignatures(opts.RemoveSignatures).WithQuiet(opts.Quiet).WithCompressionFormat(opts.CompressionFormat).WithProgressWriter(opts.Writer)

View file

@ -20,6 +20,8 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/util"
"github.com/containers/image/v5/types"
encconfig "github.com/containers/ocicrypt/config"
enchelpers "github.com/containers/ocicrypt/helpers"
"github.com/containers/podman/v4/pkg/errorhandling"
"github.com/containers/podman/v4/pkg/namespaces"
"github.com/containers/podman/v4/pkg/rootless"
@ -756,3 +758,37 @@ func SizeOfPath(path string) (uint64, error) {
})
return size, err
}
// EncryptConfig translates encryptionKeys into a EncriptionsConfig structure
func EncryptConfig(encryptionKeys []string, encryptLayers []int) (*encconfig.EncryptConfig, *[]int, error) {
var encLayers *[]int
var encConfig *encconfig.EncryptConfig
if len(encryptionKeys) > 0 {
// encryption
encLayers = &encryptLayers
ecc, err := enchelpers.CreateCryptoConfig(encryptionKeys, []string{})
if err != nil {
return nil, nil, fmt.Errorf("invalid encryption keys: %w", err)
}
cc := encconfig.CombineCryptoConfigs([]encconfig.CryptoConfig{ecc})
encConfig = cc.EncryptConfig
}
return encConfig, encLayers, nil
}
// DecryptConfig translates decryptionKeys into a DescriptionConfig structure
func DecryptConfig(decryptionKeys []string) (*encconfig.DecryptConfig, error) {
var decryptConfig *encconfig.DecryptConfig
if len(decryptionKeys) > 0 {
// decryption
dcc, err := enchelpers.CreateCryptoConfig([]string{}, decryptionKeys)
if err != nil {
return nil, fmt.Errorf("invalid decryption keys: %w", err)
}
cc := encconfig.CombineCryptoConfigs([]encconfig.CryptoConfig{dcc})
decryptConfig = cc.DecryptConfig
}
return decryptConfig, nil
}

View file

@ -6,6 +6,7 @@ import (
"path/filepath"
"runtime"
"github.com/containers/podman/v4/pkg/rootless"
. "github.com/containers/podman/v4/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@ -559,4 +560,89 @@ var _ = Describe("Podman pull", func() {
Expect(session).Should(Exit(0))
Expect(session.ErrorToString()).To(BeEmpty())
})
Describe("podman pull and decrypt", func() {
decryptionTestHelper := func(imgPath string) *PodmanSessionIntegration {
bitSize := 1024
keyFileName := filepath.Join(podmanTest.TempDir, "key")
publicKeyFileName, privateKeyFileName, err := WriteRSAKeyPair(keyFileName, bitSize)
Expect(err).To(BeNil())
wrongKeyFileName := filepath.Join(podmanTest.TempDir, "wrong_key")
_, wrongPrivateKeyFileName, err := WriteRSAKeyPair(wrongKeyFileName, bitSize)
Expect(err).To(BeNil())
session := podmanTest.Podman([]string{"push", "--encryption-key", "jwe:" + publicKeyFileName, "--tls-verify=false", "--remove-signatures", ALPINE, imgPath})
session.WaitWithDefaultTimeout()
session = podmanTest.Podman([]string{"rmi", ALPINE})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
// Pulling encrypted image without key should fail
session = podmanTest.Podman([]string{"pull", imgPath})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(125))
// Pulling encrypted image with wrong key should fail
session = podmanTest.Podman([]string{"pull", "--decryption-key", wrongPrivateKeyFileName, imgPath})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(125))
// Pulling encrypted image with correct key should pass
session = podmanTest.Podman([]string{"pull", "--decryption-key", privateKeyFileName, imgPath})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
session = podmanTest.Podman([]string{"images"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
return session
}
It("From oci", func() {
SkipIfRemote("Remote pull neither supports oci transport, nor decryption")
podmanTest.AddImageToRWStore(ALPINE)
bbdir := filepath.Join(podmanTest.TempDir, "busybox-oci")
imgPath := fmt.Sprintf("oci:%s", bbdir)
session := decryptionTestHelper(imgPath)
Expect(session.LineInOutputContainsTag(filepath.Join("localhost", bbdir), "latest")).To(BeTrue())
})
It("From local registry", func() {
SkipIfRemote("Remote pull does not support decryption")
if podmanTest.Host.Arch == "ppc64le" {
Skip("No registry image for ppc64le")
}
podmanTest.AddImageToRWStore(ALPINE)
if rootless.IsRootless() {
err := podmanTest.RestoreArtifact(REGISTRY_IMAGE)
Expect(err).ToNot(HaveOccurred())
}
lock := GetPortLock("5000")
defer lock.Unlock()
session := podmanTest.Podman([]string{"run", "-d", "--name", "registry", "-p", "5000:5000", REGISTRY_IMAGE, "/entrypoint.sh", "/etc/docker/registry/config.yml"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
if !WaitContainerReady(podmanTest, "registry", "listening on", 20, 1) {
Skip("Cannot start docker registry.")
}
imgPath := "localhost:5000/my-alpine"
session = decryptionTestHelper(imgPath)
Expect(session.LineInOutputContainsTag(imgPath, "latest")).To(BeTrue())
})
})
})

View file

@ -127,6 +127,17 @@ var _ = Describe("Podman push", func() {
Expect(output).To(ContainSubstring("Writing manifest to image destination"))
Expect(output).To(ContainSubstring("Storing signatures"))
bitSize := 1024
keyFileName := filepath.Join(podmanTest.TempDir, "key")
publicKeyFileName, _, err := WriteRSAKeyPair(keyFileName, bitSize)
Expect(err).To(BeNil())
if !IsRemote() { // Remote does not support --encryption-key
push = podmanTest.Podman([]string{"push", "--encryption-key", "jwe:" + publicKeyFileName, "--tls-verify=false", "--remove-signatures", ALPINE, "localhost:5000/my-alpine"})
push.WaitWithDefaultTimeout()
Expect(push).Should(Exit(0))
}
if !IsRemote() { // Remote does not support --digestfile
// Test --digestfile option
push2 := podmanTest.Podman([]string{"push", "--tls-verify=false", "--digestfile=/tmp/digestfile.txt", "--remove-signatures", ALPINE, "localhost:5000/my-alpine"})
@ -261,6 +272,25 @@ var _ = Describe("Podman push", func() {
Expect(push).Should(Exit(0))
})
It("podman push and encrypt to oci", func() {
SkipIfRemote("Remote push neither supports oci transport, nor encryption")
bbdir := filepath.Join(podmanTest.TempDir, "busybox-oci")
bitSize := 1024
keyFileName := filepath.Join(podmanTest.TempDir, "key")
publicKeyFileName, _, err := WriteRSAKeyPair(keyFileName, bitSize)
Expect(err).To(BeNil())
session := podmanTest.Podman([]string{"push", "--encryption-key", "jwe:" + publicKeyFileName, ALPINE, fmt.Sprintf("oci:%s", bbdir)})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
session = podmanTest.Podman([]string{"rmi", ALPINE})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
})
It("podman push to docker-archive", func() {
SkipIfRemote("Remote push does not support docker-archive transport")
tarfn := filepath.Join(podmanTest.TempDir, "alp.tar")

View file

@ -1996,4 +1996,46 @@ WORKDIR /madethis`, BB)
Expect(output).To(ContainSubstring("noexec"))
Expect(output).To(ContainSubstring("nodev"))
})
It("podman run and decrypt from local registry", func() {
SkipIfRemote("Remote run does not support decryption")
if podmanTest.Host.Arch == "ppc64le" {
Skip("No registry image for ppc64le")
}
podmanTest.AddImageToRWStore(ALPINE)
if rootless.IsRootless() {
err := podmanTest.RestoreArtifact(REGISTRY_IMAGE)
Expect(err).ToNot(HaveOccurred())
}
lock := GetPortLock("5000")
defer lock.Unlock()
session := podmanTest.Podman([]string{"run", "-d", "--name", "registry", "-p", "5000:5000", REGISTRY_IMAGE, "/entrypoint.sh", "/etc/docker/registry/config.yml"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
if !WaitContainerReady(podmanTest, "registry", "listening on", 20, 1) {
Skip("Cannot start docker registry.")
}
bitSize := 1024
keyFileName := filepath.Join(podmanTest.TempDir, "key")
publicKeyFileName, privateKeyFileName, err := WriteRSAKeyPair(keyFileName, bitSize)
Expect(err).To(BeNil())
imgPath := "localhost:5000/my-alpine"
session = podmanTest.Podman([]string{"push", "--encryption-key", "jwe:" + publicKeyFileName, "--tls-verify=false", "--remove-signatures", ALPINE, imgPath})
session.WaitWithDefaultTimeout()
session = podmanTest.Podman([]string{"rmi", ALPINE})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
session = podmanTest.Podman([]string{"run", "--decryption-key", privateKeyFileName, imgPath})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.ErrorToString()).To(ContainSubstring("Trying to pull"))
})
})

View file

@ -151,4 +151,20 @@ var _ = Describe("Common functions test", func() {
Entry("Docker not in cgroup file", "/tmp/cgroup.test", false, true, false),
)
It("Test WriteRSAKeyPair", func() {
fileName := "/tmp/test_key"
bitSize := 1024
publicKeyFileName, privateKeyFileName, err := WriteRSAKeyPair(fileName, bitSize)
Expect(err).To(BeNil(), "Failed to write RSA key pair to files.")
read, err := os.Open(publicKeyFileName)
Expect(err).To(BeNil(), "Cannot find the public key file after we write it.")
defer read.Close()
read, err = os.Open(privateKeyFileName)
Expect(err).To(BeNil(), "Cannot find the private key file after we write it.")
defer read.Close()
})
})

View file

@ -11,6 +11,11 @@ import (
"strings"
"time"
crypto_rand "crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"github.com/sirupsen/logrus"
"github.com/containers/storage/pkg/parsers/kernel"
@ -523,3 +528,76 @@ func RandomString(n int) string {
}
return string(b)
}
// Encode *rsa.PublicKey and store it in a file.
// Adds appropriate extension to the fileName, and returns the complete fileName of
// the file storing the public key.
func savePublicKey(fileName string, publicKey *rsa.PublicKey) (string, error) {
// Encode public key to PKIX, ASN.1 DER form
pubBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return "", err
}
pubPEM := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: pubBytes,
},
)
// Write public key to file
publicKeyFileName := fileName + ".rsa.pub"
if err := os.WriteFile(publicKeyFileName, pubPEM, 0600); err != nil {
return "", err
}
return publicKeyFileName, nil
}
// Encode *rsa.PrivateKey and store it in a file.
// Adds appropriate extension to the fileName, and returns the complete fileName of
// the file storing the private key.
func savePrivateKey(fileName string, privateKey *rsa.PrivateKey) (string, error) {
// Encode private key to PKCS#1, ASN.1 DER form
privBytes := x509.MarshalPKCS1PrivateKey(privateKey)
keyPEM := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privBytes,
},
)
// Write private key to file
privateKeyFileName := fileName + ".rsa"
if err := os.WriteFile(privateKeyFileName, keyPEM, 0600); err != nil {
return "", err
}
return privateKeyFileName, nil
}
// Generate RSA key pair of specified bit size and write them to files.
// Adds appropriate extension to the fileName, and returns the complete fileName of
// the files storing the public and private key respectively.
func WriteRSAKeyPair(fileName string, bitSize int) (string, string, error) {
// Generate RSA key
privateKey, err := rsa.GenerateKey(crypto_rand.Reader, bitSize)
if err != nil {
return "", "", err
}
publicKey := privateKey.Public().(*rsa.PublicKey)
publicKeyFileName, err := savePublicKey(fileName, publicKey)
if err != nil {
return "", "", err
}
privateKeyFileName, err := savePrivateKey(fileName, privateKey)
if err != nil {
return "", "", err
}
return publicKeyFileName, privateKeyFileName, nil
}