diff --git a/keyvaultcertdownloader/go.mod b/keyvaultcertdownloader/go.mod index 62b1d5a..77f0f1d 100644 --- a/keyvaultcertdownloader/go.mod +++ b/keyvaultcertdownloader/go.mod @@ -1,39 +1,50 @@ -//module github.com/Azure/keyvaultcertdownloader/keyvaultcertdownloader module keyvaultcertdownloader go 1.18 -require ( - github.com/Azure/azure-sdk-for-go v67.0.0+incompatible - github.com/Azure/go-autorest/autorest v0.11.28 - github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 - software.sslmate.com/src/go-pkcs12 v0.2.0 -) +require github.com/Azure/azure-sdk-for-go v67.1.0+incompatible require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.28 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect + github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect - github.com/golang-jwt/jwt/v4 v4.2.0 // indirect + github.com/golang-jwt/jwt/v4 v4.4.2 // indirect + github.com/google/uuid v1.1.1 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect + golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect + golang.org/x/text v0.3.7 // indirect + software.sslmate.com/src/go-pkcs12 v0.2.0 // indirect ) - require ( - internal/utils v1.0.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0 + github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates v0.8.0 + github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0 + internal/corehelper v1.0.0 internal/iam v1.0.0 internal/models v1.0.0 + internal/utils v1.0.0 ) replace ( - internal/utils => ./internal/utils + internal/corehelper => ./internal/corehelper internal/iam => ./internal/iam internal/models => ./internal/models -) \ No newline at end of file + internal/utils => ./internal/utils +) diff --git a/keyvaultcertdownloader/go.sum b/keyvaultcertdownloader/go.sum index b7990f4..9a8219d 100644 --- a/keyvaultcertdownloader/go.sum +++ b/keyvaultcertdownloader/go.sum @@ -1,5 +1,17 @@ -github.com/Azure/azure-sdk-for-go v67.0.0+incompatible h1:SVBwznSETB0Sipd0uyGJr7khLhJOFRUEUb+0JgkCvDo= -github.com/Azure/azure-sdk-for-go v67.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v67.1.0+incompatible h1:oziYcaopbnIKfM69DL05wXdypiqfrUKdxUKrKpynJTw= +github.com/Azure/azure-sdk-for-go v67.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 h1:tz19qLF65vuu2ibfTqGVJxG/zZAI27NEIIbvAOQwYbw= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0 h1:t/W5MYAuQy81cvM8VUNfRLzhtKpXhVUAN7Cd7KVbTyc= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0/go.mod h1:NBanQUfSWiWn3QEpWDTCU0IjBECKOYvl2R8xdRtMtiM= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 h1:jp0dGvZ7ZK0mgqnTSClMxa5xuRL7NZgHameVYF6BurY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates v0.8.0 h1:edn/e2qs1fEkPHlZqbESJWhFai9Pk/UA5eiwFUA1nwI= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates v0.8.0/go.mod h1:8eUJPoEz7doIqSwW2pAvLGhEy3mDC9o/ToCa8OZy7go= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0 h1:82w8tzLcOwDP/Q35j/wEBPt0n0kVC3cjtPdD62G8UAk= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0/go.mod h1:S78i9yTr4o/nXlH76bKjGUye9Z2wSxO5Tz7GoDr4vfI= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 h1:Lg6BW0VPmCwcMlvOviL3ruHFO+H9tZNqscK0AeuFjGM= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= @@ -24,6 +36,8 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0 h1:VgSJlZH5u0k2qxSpqyghcFQKmvYckj46uymKK5XzkBM= +github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0/go.mod h1:BDJ5qMFKx9DugEg3+uQSDCdbYPr5s9vBTrL9P8TpqOU= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= @@ -31,8 +45,16 @@ github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/ github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -45,12 +67,18 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE= software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ= diff --git a/keyvaultcertdownloader/internal/corehelper/corehelper.go b/keyvaultcertdownloader/internal/corehelper/corehelper.go new file mode 100644 index 0000000..adccbf0 --- /dev/null +++ b/keyvaultcertdownloader/internal/corehelper/corehelper.go @@ -0,0 +1,162 @@ +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +// Package that provides some core functionality functions. + +package corehelper + +import ( + "context" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" + "net/url" + "os" + "strings" + + "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates" + "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets" + gpkcs12 "software.sslmate.com/src/go-pkcs12" +) + +// GetBlocksFromPEM - Gets decoded data block from PEM +func GetBlocksFromPEM(data []byte, blocks []*pem.Block) []*pem.Block { + block, rest := pem.Decode(data) + if block == nil { + return blocks + } + + blocks = append(blocks, block) + return GetBlocksFromPEM(rest, blocks) +} + +// GetBlocksFromPCKS12 - Gets decoded data block from PKCS12 +func GetBlocksFromPCKS12(certString string) (blocks []*pem.Block, err error) { + decodedData, _ := base64.StdEncoding.DecodeString(certString) + + // Decoding PKCS12 blob + privateKey, firstCert, certList, err := gpkcs12.DecodeChain(decodedData, "") + if err != nil { + return nil, err + } + + // Extracting private key and creating private key pem.block + privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + return nil, err + } + blocks = append(blocks, &pem.Block{Type: "PRIVATE KEY", Bytes: privateKeyBytes}) + + // Checking if first certificate is non-CA cert and converting to pem.block if true + if !firstCert.IsCA { + blocks = append(blocks, &pem.Block{Type: "CERTIFICATE", Bytes: firstCert.Raw}) + return blocks, nil + } + + // Iterating over the caCerts list since we cannot assume that cert returned by pkcs12.DecodeChain + // is the leaf certificate + for _, cert := range certList { + if !cert.IsCA { + blocks = append(blocks, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) + break + } + } + + return blocks, nil +} + +// WritePEMfile - Writes the output PEM file +func WritePEMfile(filename string, certificate, privateKey interface{}) error { + f, err := os.Create(filename) + if err != nil { + return fmt.Errorf("unable to create pem file %v: %v", filename, err) + } + defer f.Close() + + err = pem.Encode(f, &pem.Block{Type: certificate.(*pem.Block).Type, Bytes: certificate.(*pem.Block).Bytes}) + if err != nil { + return fmt.Errorf("an error ocurred writting certificate to pem file %v", err) + } + + err = pem.Encode(f, &pem.Block{Type: privateKey.(*pem.Block).Type, Bytes: privateKey.(*pem.Block).Bytes}) + if err != nil { + return fmt.Errorf("an error ocurred writting private key to pem file %v", err) + } + + return nil +} + +// GetCertificateFromPEMBLocks - Gets a certificate from PEM Blocks +func GetCertificateFromPEMBLocks(blocks interface{}) (certificate interface{}, err error) { + for _, b := range blocks.([]*pem.Block) { + if strings.Contains(b.Type, "CERTIFICATE") { + x509cert, err := x509.ParseCertificate(b.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse certificate: %v", err) + } + if !x509cert.IsCA { + certificate = b + break + } + } + } + if certificate == nil { + return nil, fmt.Errorf("unable to find non-CA certificate") + } + + return certificate, nil +} + +// GetPrivateKeyFromPEMBlocks - Gets private key from PEM Blocks +func GetPrivateKeyFromPEMBlocks(blocks interface{}) (privateKey interface{}, err error) { + for _, b := range blocks.([]*pem.Block) { + if strings.Contains(b.Type, "PRIVATE KEY") { + privateKey = b + break + } + } + if privateKey == nil { + return nil, fmt.Errorf("unable to find private key") + } + + return privateKey, nil +} + +// +//!SECTION - SDK dependent functions +// + +//!SECTION - Internal functions + +func getAKVCertificateBundle(cntx context.Context, client azcertificates.Client, certURL url.URL) (azcertificates.CertificateBundle, error) { + cert, err := client.GetCertificate(cntx, certURL.Path, "", nil) + if err != nil { + return azcertificates.CertificateBundle{}, err + } + + return cert, nil +} + +//!SECTION - Public functions + +// GetAKVCertificate - Gets a certificate from AKV +func GetAKVCertificate(cntx context.Context, client azsecrets.Client, certURL url.URL) (azsecrets.GetSecretRespose, error) { + certSecret, err := client.GetSecret(cntx, certURL.Path, "", nil) + if err != nil { + return azsecrets.SecretBundle{}, err + } + return certSecret, nil +} + +// GetAKVCertThumbprint - Gets thumbprint from bundle +func GetAKVCertThumbprint(cntx context.Context, client azcertificates.Client, certURL url.URL) (thumbprint string, err error) { + certBundle, err := getAKVCertificateBundle(cntx, client, certURL) + if err != nil { + return "", fmt.Errorf("unable to get certificate bundle: %v", err) + } + + return *certBundle.X509Thumbprint, nil +} diff --git a/keyvaultcertdownloader/internal/corehelper/go.mod b/keyvaultcertdownloader/internal/corehelper/go.mod new file mode 100644 index 0000000..b217cf5 --- /dev/null +++ b/keyvaultcertdownloader/internal/corehelper/go.mod @@ -0,0 +1,18 @@ +module github.com/Azure/keyvaultcertdownloader/keyvaultcertdownloader/internal/corehelper + +go 1.18 + +require ( + github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates v0.8.0 + github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0 + software.sslmate.com/src/go-pkcs12 v0.2.0 +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 // indirect + golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect + golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect + golang.org/x/text v0.3.7 // indirect +) diff --git a/keyvaultcertdownloader/internal/corehelper/go.sum b/keyvaultcertdownloader/internal/corehelper/go.sum new file mode 100644 index 0000000..5b8e367 --- /dev/null +++ b/keyvaultcertdownloader/internal/corehelper/go.sum @@ -0,0 +1,39 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 h1:tz19qLF65vuu2ibfTqGVJxG/zZAI27NEIIbvAOQwYbw= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 h1:QkAcEIAKbNL4KoFr4SathZPhDhF4mVwpBMFlYjyAqy8= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 h1:jp0dGvZ7ZK0mgqnTSClMxa5xuRL7NZgHameVYF6BurY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates v0.8.0 h1:edn/e2qs1fEkPHlZqbESJWhFai9Pk/UA5eiwFUA1nwI= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates v0.8.0/go.mod h1:8eUJPoEz7doIqSwW2pAvLGhEy3mDC9o/ToCa8OZy7go= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0 h1:82w8tzLcOwDP/Q35j/wEBPt0n0kVC3cjtPdD62G8UAk= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.11.0/go.mod h1:S78i9yTr4o/nXlH76bKjGUye9Z2wSxO5Tz7GoDr4vfI= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 h1:Lg6BW0VPmCwcMlvOviL3ruHFO+H9tZNqscK0AeuFjGM= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= +github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 h1:BWe8a+f/t+7KY7zH2mqygeUD0t8hNFXe08p1Pb3/jKE= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= +github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE= +software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ= diff --git a/keyvaultcertdownloader/keyvaultcertdownloader.go b/keyvaultcertdownloader/keyvaultcertdownloader.go index c1aab4f..718e202 100644 --- a/keyvaultcertdownloader/keyvaultcertdownloader.go +++ b/keyvaultcertdownloader/keyvaultcertdownloader.go @@ -11,10 +11,7 @@ package main import ( "context" - "crypto/x509" - "encoding/base64" "encoding/hex" - "encoding/pem" "flag" "fmt" "log" @@ -22,11 +19,12 @@ import ( "os" "strings" + "internal/corehelper" "internal/utils" - "github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault" - kvauth "github.com/Azure/azure-sdk-for-go/services/keyvault/auth" - "software.sslmate.com/src/go-pkcs12" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates" + "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets" ) const ( @@ -40,11 +38,13 @@ const ( ERR_X509_THUMBPRINT = 9 ERR_OUTPUTFOLDER_NOT_FOUND = 10 ERR_INVALID_AZURE_ENVIRONMENT = 11 + ERR_CREDENTIALS = 12 ) var ( validEnvironments = []string{"AZUREPUBLICCLOUD", "AZUREUSGOVERNMENTCLOUD", "AZUREGERMANCLOUD", "AZURECHINACLOUD"} certURL = flag.String("certurl", "", "certificate URL, e.g. \"https://mykeyvault.vault.azure.net/mycertificate\"") + keyVaultUrl = "" outputFolder = flag.String("outputfolder", "", "folder where PEM file with certificate and private key will be saved") environment = flag.String("environment", "AZUREPUBLICCLOUD", fmt.Sprintf("valid azure cloud environments: %v", validEnvironments)) cmdlineversion = flag.Bool("version", false, "shows current tool version") @@ -97,6 +97,7 @@ func main() { exitCode = ERR_INVALID_URL return } + keyVaultUrl := fmt.Sprintf("%v://%v/", u.Scheme, u.Hostname()) utils.PrintHeader(fmt.Sprintf("keyvaultcertdownloader - Downloads a certificate from Azure KeyVault saving as PEM file - v%v", version)) @@ -104,21 +105,48 @@ func main() { utils.ConsoleOutput(fmt.Sprintf("Using Certificate URL: %v", *certURL), stdout) utils.ConsoleOutput(fmt.Sprintf("Environment: %v", *environment), stdout) - utils.ConsoleOutput("Getting authorizer", stdout) - os.Setenv("AZURE_ENVIRONMENT", *environment) - authorizer, err := kvauth.NewAuthorizerFromEnvironment() + //utils.ConsoleOutput("Checking if this session needs to rely on AD Workload Identity webhook", stdout) + // client := keyvault.New() + // var authorizer autorest.Authorizer + + tokenFilePath := os.Getenv("AZURE_FEDERATED_TOKEN_FILE") + if tokenFilePath == "" { + // utils.ConsoleOutput("Getting authorizer", stdout) + // os.Setenv("AZURE_ENVIRONMENT", *environment) + // authorizer, err = kvauth.NewAuthorizerFromEnvironment() + // if err != nil { + // utils.ConsoleOutput(fmt.Sprintf(" unable to create vault authorizer: %v\n", err), stderr) + // exitCode = ERR_AUTHORIZER + // return + // } + + // utils.ConsoleOutput("Creating KeyVault base client", stdout) + + } else { + + } + + // client.Authorizer = authorizer + utils.ConsoleOutput("Obtaining credentials", stdout) + cred, err := azidentity.NewDefaultAzureCredential(nil) if err != nil { - utils.ConsoleOutput(fmt.Sprintf(" unable to create vault authorizer: %v\n", err), stderr) - exitCode = ERR_AUTHORIZER + utils.ConsoleOutput(fmt.Sprintf(" %v\n", err), stderr) + exitCode = ERR_CREDENTIALS return } - utils.ConsoleOutput("Creating KeyVault base client", stdout) - client := keyvault.New() - client.Authorizer = authorizer + utils.ConsoleOutput("Creating clients", stdout) + azsecretsClient, err := azsecrets.NewClient(keyVaultUrl, cred, nil) + if err != nil { + log.Fatalf("failed to create azsecrets client: %v", err) + } + azcertsClient, err := azcertificates.NewClient(keyVaultUrl, cred, nil) + if err != nil { + log.Fatalf("failed to create azcertificates client: %v", err) + } utils.ConsoleOutput("Getting certificate thumbprint", stdout) - x509Thumbprint, err := getAKVCertThumbprint(cntx, client, *u) + x509Thumbprint, err := corehelper.GetAKVCertThumbprint(cntx, azcertsClient, *u) if err != nil { utils.ConsoleOutput(fmt.Sprintf(" %v\n", err), stderr) exitCode = ERR_X509_THUMBPRINT @@ -139,7 +167,7 @@ func main() { // Get cert as secret utils.ConsoleOutput("Getting certificate as secret", stdout) - certAKV, err := getAKVCertificate(cntx, client, *u) + certAKV, err := corehelper.GetAKVCertificate(cntx, azsecretsClient, *u) if err != nil { utils.ConsoleOutput(fmt.Sprintf(" unable to get certificate: %v\n", err), stderr) exitCode = ERR_GET_AKV_CERT_SECRET @@ -151,26 +179,26 @@ func main() { // Getting PEM Blocks var blocks interface{} if *certAKV.ContentType == "application/x-pkcs12" { - blocks, _ = getBlocksFromPCKS12(*certAKV.Value) + blocks, _ = corehelper.GetBlocksFromPCKS12(*certAKV.Value) } else if *certAKV.ContentType == "application/x-pem-file" { - blocks = getBlocksFromPEM([]byte(*certAKV.Value), nil) + blocks = corehelper.GetBlocksFromPEM([]byte(*certAKV.Value), nil) } - privateKey, err := getPrivateKeyFromPEMBlocks(blocks) + privateKey, err := corehelper.GetPrivateKeyFromPEMBlocks(blocks) if err != nil { utils.ConsoleOutput(fmt.Sprintf(" %v\n", err), stderr) exitCode = ERR_GET_PEM_PRIVATE_KEY return } - certificate, err := getCertificateFromPEMBLocks(blocks) + certificate, err := corehelper.GetCertificateFromPEMBLocks(blocks) if err != nil { utils.ConsoleOutput(fmt.Sprintf(" %v\n", err), stderr) exitCode = ERR_GET_PEM_CERTIFICATE return } - err = writePEMfile(pemFileName, certificate, privateKey) + err = corehelper.WritePEMfile(pemFileName, certificate, privateKey) if err != nil { utils.ConsoleOutput(fmt.Sprintf(" %v\n", err), stderr) exitCode = ERR_CREATE_PEM_FILE @@ -185,128 +213,3 @@ func exit(cntx context.Context, exitCode int) { } } } - -func getBlocksFromPEM(data []byte, blocks []*pem.Block) []*pem.Block { - block, rest := pem.Decode(data) - if block == nil { - return blocks - } - - blocks = append(blocks, block) - return getBlocksFromPEM(rest, blocks) -} - -func getBlocksFromPCKS12(certString string) (blocks []*pem.Block, err error) { - decodedData, _ := base64.StdEncoding.DecodeString(certString) - - // Decoding PKCS12 blob - privateKey, firstCert, certList, err := pkcs12.DecodeChain(decodedData, "") - if err != nil { - return nil, err - } - - // Extracting private key and creating private key pem.block - privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) - if err != nil { - return nil, err - } - blocks = append(blocks, &pem.Block{Type: "PRIVATE KEY", Bytes: privateKeyBytes}) - - // Checking if first certificate is non-CA cert and converting to pem.block if true - if !firstCert.IsCA { - blocks = append(blocks, &pem.Block{Type: "CERTIFICATE", Bytes: firstCert.Raw}) - return blocks, nil - } - - // Iterating over the caCerts list since we cannot assume that cert returned by pkcs12.DecodeChain - // is the leaf certificate - for _, cert := range certList { - if !cert.IsCA { - blocks = append(blocks, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) - break - } - } - - return blocks, nil -} - -func getAKVCertificateBundle(cntx context.Context, client keyvault.BaseClient, certURL url.URL) (keyvault.CertificateBundle, error) { - cert, err := client.GetCertificate(cntx, fmt.Sprintf("https://%v", certURL.Host), strings.Replace(certURL.Path, "/", "", 1), "") - if err != nil { - return keyvault.CertificateBundle{}, err - } - - return cert, nil -} - -func getAKVCertificate(cntx context.Context, client keyvault.BaseClient, certURL url.URL) (keyvault.SecretBundle, error) { - certSecret, err := client.GetSecret(cntx, fmt.Sprintf("https://%v", certURL.Host), strings.Replace(certURL.Path, "/", "", 1), "") - if err != nil { - return keyvault.SecretBundle{}, err - } - - return certSecret, nil -} - -func writePEMfile(filename string, certificate, privateKey interface{}) error { - f, err := os.Create(filename) - if err != nil { - return fmt.Errorf("unable to create pem file %v: %v", filename, err) - } - defer f.Close() - - err = pem.Encode(f, &pem.Block{Type: certificate.(*pem.Block).Type, Bytes: certificate.(*pem.Block).Bytes}) - if err != nil { - return fmt.Errorf("an error ocurred writting certificate to pem file %v:", err) - } - - err = pem.Encode(f, &pem.Block{Type: privateKey.(*pem.Block).Type, Bytes: privateKey.(*pem.Block).Bytes}) - if err != nil { - return fmt.Errorf("an error ocurred writting private key to pem file %v:", err) - } - - return nil -} - -func getCertificateFromPEMBLocks(blocks interface{}) (certificate interface{}, err error) { - for _, b := range blocks.([]*pem.Block) { - if strings.Contains(b.Type, "CERTIFICATE") { - x509cert, err := x509.ParseCertificate(b.Bytes) - if err != nil { - return nil, fmt.Errorf("failed to parse certificate: %v", err) - } - if x509cert.IsCA == false { - certificate = b - break - } - } - } - if certificate == nil { - return nil, fmt.Errorf("unable to find non-CA certificate") - } - - return certificate, nil -} - -func getPrivateKeyFromPEMBlocks(blocks interface{}) (privateKey interface{}, err error) { - for _, b := range blocks.([]*pem.Block) { - if strings.Contains(b.Type, "PRIVATE KEY") { - privateKey = b - break - } - } - if privateKey == nil { - return nil, fmt.Errorf("unable to find private key") - } - - return privateKey, nil -} - -func getAKVCertThumbprint(cntx context.Context, client keyvault.BaseClient, certURL url.URL) (thumbprint string, err error) { - certBundle, err := getAKVCertificateBundle(cntx, client, certURL) - if err != nil { - return "", fmt.Errorf("unable to get certificate bundle: %v", err) - } - - return *certBundle.X509Thumbprint, nil -}