Fetch certificates and private keys bundle from Azure Keyvault in Go via Azure SDK

Published: by

  • Categories:

As this required a little bit of digging around for a seemingly simple task, felt like sharing is a great idea. This code supports fetching certificates from Azure Keyvault

Azure Keyvault supports storing the certificate bundle in 2 formats, PEM and PKCS#12.

Screenshot of supported types while creating a new certificate

AZ

Go code, self explanatory

package main

import (
	"context"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"flag"
	"fmt"
	"os"
	"time"

	"github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault"
	"github.com/Azure/azure-sdk-for-go/services/keyvault/auth"
	"github.com/Azure/go-autorest/autorest/azure"
	"golang.org/x/crypto/pkcs12"
)

type azureKVClient struct {
	ctx   context.Context
	vault string

	client keyvault.BaseClient

	authenticated bool
	vaultBaseURL  string
}

// initKVClient initializes and authenticates the client to interact with Azure Key Vault
func (akv *azureKVClient) initKVClient(ctx context.Context, vaultName string) (err error) {
	akv.ctx = ctx
	akv.vault = vaultName
	akv.client = keyvault.New()

	authorizer, err := auth.NewAuthorizerFromEnvironment()
	// authorizer, err := auth.NewAuthorizerFromCLI()
	if err != nil {
		return err
	}
	akv.client.Authorizer = authorizer
	akv.authenticated = true

	akv.vaultBaseURL = fmt.Sprintf("https://%s.%s", akv.vault, azure.PublicCloud.KeyVaultDNSSuffix)

	return nil
}

// getBundle returns the bundle
func (akv *azureKVClient) getBundle(certificateName string) (bundle string, err error) {
	// Error if there's no authenticated client yet
	if !akv.authenticated {
		return
	}

	// Last param is empty so it fetches the latest version
	ctx, cancel := context.WithTimeout(akv.ctx, 5*time.Second)
	defer cancel()

	secret, err := akv.client.GetSecret(ctx, akv.vaultBaseURL, certificateName, "")
	if err != nil {
		return
	}
	// Azure currently supports only PKCS#12 or PEM
	switch *secret.ContentType {
	case "application/x-pem-file":
		bundle = *secret.Value

	case "application/x-pkcs12":
		pfx, err := base64.StdEncoding.DecodeString(*secret.Value)
		if err != nil {
			return bundle, err
		}
		blocks, _ := pkcs12.ToPEM(pfx, "")
		for _, block := range blocks {
			// No headers, we don't wait it
			if block.Type == "PRIVATE KEY" {
				pkey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
				if err != nil {
					panic(err)
				}
				derStream := x509.MarshalPKCS1PrivateKey(pkey)
				block = &pem.Block{
					Type:  "RSA PRIVATE KEY",
					Bytes: derStream,
				}
			}
			block.Headers = nil
			bundle = bundle + string(pem.EncodeToMemory(block))
		}
		return bundle, nil

	default:
		return bundle, fmt.Errorf("unknown ContentType, %s", *secret.ContentType)

	}
	return
}

func main() {

	var vaultName string
	var certName string

	flag.StringVar(&vaultName, "V", "", "Vault name")
	flag.StringVar(&certName, "C", "", "Certificate name")
	flag.Parse()

	ctx := context.Background()

	// Authenticate
	client := azureKVClient{}
	if err := client.initKVClient(ctx, vaultName); err != nil {
		fmt.Println("Error", err)
		os.Exit(1)
	}

	// Fetch the client and key as PEM
	bundle, err := client.getBundle(certName)
	if err != nil {
		fmt.Println("Error: ", err)
		os.Exit(1)
	}
	fmt.Println(bundle)
}

To Run:

$ go run . -V <AZ-Keyvault-Name> -C <cert-name-in-azure-keyvault>