I could use the Azure Key Vault provider for Secrets Store CSI driver (https://learn.microsoft.com/en-us/azure/aks/csi-secrets-store-driver)
but I need to have a bit more control over the certificates via a custom k8 management API am developing for my use case. I had some challenges with this.
I think I have a good enough solution but want to put it out in the world for comment from others that may have done this in the past.
here is what I did...
When one creates the certificates (or uploads them) in Azure vault the values are stored in secrets. I used the Azure sdk for Go to get them
I use base64 package to decode what Azure sends me (a PFX containing private and public keys
I use pkcs12 package pkcs12.toPEM function to break it apart into an array of pem.Block.
Each block has a value that contains headers in the value
I need to strip the headers out before I use them in the kubernetes secret
package secretcreator
import (
"context"
"encoding/base64"
"encoding/pem"
"log"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates"
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets"
"golang.org/x/crypto/pkcs12"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
type MyStruct struct {
kubeClientSet *kubernetes.Clientset
azureVaultSecrets *azsecrets.Client
azureVaultCertificates *azcertificates.Client
beNamespace *v1.Namespace
identitySecrests *v1.Secret
}
func (t *MyStruct) createSecrets() error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
//tlsSecret.value is a base64 encoded PEM/PFX containing PRIVATE KEY and CERTIFICATES blocks
tlsSecret, err := t.azureVaultSecrets.GetSecret(ctx, `myCertName`, ``, nil)
if err != nil {
log.Printf(`Failed to retrieve cert :%s`, err.Error())
return err
}
//pfxBytes is the Byte representation of the string stored in tlsSecret.Value
pfxBytes, err := base64.StdEncoding.Strict().DecodeString(*tlsSecret.Value)
if err != nil {
log.Printf(`Failed to decode cert PFX:%s`, err.Error())
return err
}
// pemBlocks is a array of *pem.Block - in my case I have 1 PRIVATE KEY Block and three CERTIFICATE Blocks
//the blocks have headers too that look something like this on the PRIVATE KEY, after encoding the encoded string looks like this
//
// -----BEGIN PRIVATE KEY-----
// Microsoft CSP Name: Microsoft Enhanced Cryptographic Provider v1.0
// friendlyName: {D06B3D5A-98D1-45F1-A8C2-DC091232D6A7}
// localKeyId: 01000000
//
// MIIEAI.......ymfjsQ==
// -----END PRIVATE KEY-----
pemBlocks, err := pkcs12.ToPEM(pfxBytes, "")
if err != nil {
log.Printf(`Failed to decode key or cert from PEM Blocks:%s`, err.Error())
return err
}
// k8 needs to store TLS in a secret with key value pairs in the data - one for tls.key and the other for tls.crt
// this code iterates through the blocks, looks at the type of data stored and breaks it up into tls.key and tls.crt
// k8.nginx does not like the headers for some reason - so i strip them out using base64.StdEncoding.Encode to string and
// then adding "-----BEGIN..." and "-----END..." strings"
var tlsKey []byte
var tlsCert []byte
for _, b := range pemBlocks {
if b.Type == "PRIVATE KEY" {
block, _ := pem.Decode(pem.EncodeToMemory(b))
tlsKey = append(tlsKey, "-----BEGIN PRIVATE KEY-----\n"...)
tlsKey = append(tlsKey, base64.StdEncoding.EncodeToString(block.Bytes)...)
tlsKey = append(tlsKey, "-----END PRIVATE KEY-----\n"...)
}
if b.Type == "CERTIFICATE" {
block, _ := pem.Decode(pem.EncodeToMemory(b))
tlsCert = append(tlsCert, "-----BEGIN CERTIFICATE-----\n"...)
tlsCert = append(tlsCert, base64.StdEncoding.EncodeToString(block.Bytes)...)
tlsCert = append(tlsCert, "-----END CERTIFICATE-----\n"...)
}
}
// this following code uses client-go package to create a TLS secret in kubernetes
tlsData := make(map[string][]byte)
tlsData[`tls.key`] = tlsKey
tlsData[`tls.crt`] = tlsCert
beTls := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: `mycertname`,
Namespace: t.beNamespace.Name,
},
Data: tlsData,
Type: v1.SecretTypeTLS,
}
_, err = t.kubeClientSet.CoreV1().Secrets(t.beNamespace.Name).Create(ctx, beTls, metav1.CreateOptions{})
if err != nil {
log.Printf(`Failed to create TLS secret :%s`, err.Error())
return err
}
return nil
}
,,,
Related
I am trying to transfer a public RSA key generated in swift into my Nodejs server. I generated the RSA key using the following code.
private var clientPriv: SecKey?
private var clientPub: SecKey?
private init(){
let params: [String: Any] = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecAttrKeySizeInBits): 4096
]
SecKeyGeneratePair(params as CFDictionary, &clientPub, &clientPriv)
}
I send the key to my server using this code
...
guard let clientPub = clientPub else { return }
let key = SecKeyCopyExternalRepresentation(clientPub, nil)! as Data
let pem = exportToPEM(data: key, withLabel: "PUBLIC KEY")
let data = ["clientPub": pem]
var urlRequest = URLRequest(url: url)
do {
try urlRequest.httpBody = JSONSerialization.data(withJSONObject: data)
urlRequest.httpMethod = "POST"
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}catch let err {
print(err)
}
let task = URLSession.shared.dataTask(with: urlRequest){ data, response, error in
guard let data = data, error == nil else {
return
}
...
The exportToPem helper looks like this.
public func exportToPEM(data: Data, withLabel label: String) -> String {
let key = data.base64EncodedString(options: [.lineLength64Characters])
var pem = "-----BEGIN \(label)-----\n"
pem += key
pem += "\n-----END \(label)-----\n"
return pem
}
On my Nodejs side, I am using express to handle my requests and body-parser to parse my json post data in requests. Here is what my Nodejs receiving code looks like.
app.post('/api/init', jsonParser, function (req, res) {
console.log(req.body.clientPub);
CLIENTPUB = crypto.createPublicKey({ key: req.body.clientPub, format: 'pem', type: 'pkcs1' });
console.log(CLIENTPUB);
res.write(JSON.stringify({'server-pub': SERVERPUB.toString()}));
res.end()
});
The problem is that the function crypto.createPublicKey keeps throwing an error, error:0D0680A8:asn1 encoding routines:asn1_check_tlen:wrong tag. I have tried many different ways to write the string of my key but no matter what it seems that the crypto createPublicKey just refuses to take it. I have tried keeping the format with \n every 64 bytes or without \n at all, removing the header/footer altogether, and many other different combinations. I can not figure out why it keeps refusing to accept any format I send it. I have also tried using just the der format but that also gets refused.
Can anyone please offer me any advice on how to get this function to accept my key format?
SecKeyCopyExternalRepresentation() exports the public key in PKCS#1 format, which is correctly specified on the NodeJS side in the createPublicKey() call with 'pkcs1' for the type parameter.
However, the header and footer texts of a PEM encoded key for PKCS#1 format are BEGIN RSA PUBLIC KEY and END RSA PUBLIC KEY, so when calling exportToPEM(), "RSA PUBLIC KEY" must be passed in the second parameter instead of "PUBLIC KEY":
let pem = exportToPEM(data: key, withLabel: "RSA PUBLIC KEY")
BEGIN PUBLIC KEY and END PUBLIC KEY are used for a PEM encoded public key in X.509/SPKI format. This is what the error message wrong tag means.
I have a JWT token generated in nodejs app. It is signed using HS256. I've written the code to validate it in golang. I get an error message of "signature is invalid" even though I verified it in the JWT.io site.
The code validates also Public/Private, but this works. Only the HS256 is not
I've also printed the token and the secret to make sure they are the right values.
Any help will be appreciated.
My golang code:
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Validate the alg is the expected algorithm:
if conf.JwtAlgorithm != token.Header["alg"] {
log.Printf("unexpected signing method: %s, conf algorithm: %s\n", token.Header["alg"], conf.JwtAlgorithm)
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
log.Printf("JWT algo is: %s, Public is %s, secret is %s", token.Header["alg"], publicKey, secret)
if secret != "" {
log.Printf("Returning secret %s", secret)
return []byte(secret), nil
}
if publicKey != "" {
pub, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(publicKey))
fmt.Println("pub is of type RSA:", pub)
return pub, nil
}
return nil, fmt.Errorf("PublicKey and secret are empty")
})
Since you only have a single HMAC key, you'll want something like this:
package main
import (
"log"
"github.com/golang-jwt/jwt/v4"
)
func main() {
const tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.drt_po6bHhDOF_FJEHTrK-KD8OGjseJZpHwHIgsnoTM"
var keyfunc jwt.Keyfunc = func(token *jwt.Token) (interface{}, error) {
return []byte("mysecret"), nil
}
parsed, err := jwt.Parse(tokenString, keyfunc)
if err != nil {
log.Fatalf("Failed to parse JWT.\nError: %s", err.Error())
}
if !parsed.Valid {
log.Fatalln("Token is not valid.")
}
log.Println("Token is valid.")
}
It's certainly confusing what the return type should be for a jwt.Keyfunc. For an HMAC key, the return type should be []byte.
Please note that HMAC keys do not use public key cryptography and therefore are only a private key that shouldn't be shared.
If the JWTs you need to parse and verify start to become more complex, check out this package: github.com/MicahParks/keyfunc. It has support for multiple given keys like HMAC and remote JWKS resources.
I am trying to do the modern authentication from azure ad with client certificate.
The process says.
Create an azure application.
Upload the certificate to azure application and get the thumbprint.
Generate the JWT token using that certificate(https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-certificate-credentials)
Then use that jwt-token to get the access-token.
To get the access-token, call an API (https://login.microsoftonline.com//oauth2/token)
The HTTP method is of type POST and post body data will be x-www-form-urlencoded
After doing all the steps when i try to get the token.
I get this error.
{
"error": "invalid_client",
"error_description": "AADSTS700023: Client assertion audience claim does not match Realm issuer. Review the documentation at https://learn.microsoft.com/azure/active-directory/develop/active-directory-certificate-credentials .\r\nTrace ID: 714b5009-b74d-46b5-bd0e-3bea76272a01\r\nCorrelation ID: 2cb703bb-f325-44b5-a669-605bc7a81ac0\r\nTimestamp: 2020-08-28 07:31:19Z",
"error_codes": [
700023
],
"timestamp": "2020-08-28 07:31:19Z",
"trace_id": "714b5009-b74d-46b5-bd0e-3bea76272a01",
"correlation_id": "2cb703bb-f325-44b5-a669-605bc7a81ac0"
}
I am generating the JWT token using go code and parsing the PFX file.
func getAuthJWTToken() (string, error) {
clientID := "**********************"
_tenantName := "******************"
pfxFilePath := `E:\abcd.pfx`
certPassword := `*********`
authToken := ""
pfxFile, err := os.Open(pfxFilePath)
if err != nil {
return authToken, err
}
pfxfileinfo, _ := pfxFile.Stat()
var size int64 = pfxfileinfo.Size()
pfxbytes := make([]byte, size)
buffer := bufio.NewReader(pfxFile)
_, err = buffer.Read(pfxbytes)
//PFX to PEM for computation of signature
var pembytes []byte
blocks, err := pkcs12.ToPEM(pfxbytes, certPassword)
for _, b := range blocks {
pembytes = append(pembytes, pem.EncodeToMemory(b)...)
}
//Decoding the certificate contents from pfxbytes
pk, cert, err := pkcs12.Decode(pfxbytes, certPassword)
if cert == nil {
fmt.Printf("Bye")
return authToken, nil
}
if pk == nil {
}
pfxFile.Close() // close file
notToBeUsedBefore := time.Now()
expirationTime := time.Now().Add(3000 * time.Minute)
URL := fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/token", _tenantName)
id := guid.New()
claims := &claims{
StandardClaims: jwt.StandardClaims{
// In JWT, the expiry time is expressed as unix milliseconds
ExpiresAt: expirationTime.Unix(),
Audience: URL,
Issuer: clientID, // consumer key of the connected app, hardcoded
NotBefore: notToBeUsedBefore.Unix(),
Subject: clientID,
Id: id.String(),
},
}
//token_header map[string]interface{}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
sha1Fingerprint := sha1.Sum(cert.Raw)
var slice []byte
slice = sha1Fingerprint[:]
b64FingerPrint := base64.StdEncoding.EncodeToString([]byte(slice))
token.Header["x5t"] = b64FingerPrint
signKey, err := jwt.ParseRSAPrivateKeyFromPEM(pembytes) // parse the RSA key
tokenString, err := token.SignedString(signKey) // sign the claims with private key
fmt.Printf(fmt.Sprintf("JWT token is %s", tokenString))
return tokenString, err
}
I need the help to resolve this issue.
I am creating an app using Go and I am trying to start a https server using the ListenAndServeTLS function. Here is my code:
func StartServer() {
defer config.CapturePanic()
c := config.GetInstance()
serverAddress := fmt.Sprintf(":%s", c.GetConfig().ServerPort)
server := http.Server{Addr: serverAddress}
log.Info("Starting local server")
http.HandleFunc("/", login.Handler)
http.HandleFunc("/login", login.Handler)
http.HandleFunc("/settings", settings.Handler)
//cert, _ := data.Asset("my-cert.pem")
//key, _ := data.Asset("my-key.pem")
err := server.ListenAndServeTLS("my-cert.crt", "my-cert.key")
if err != nil {
log.WithError(err).Fatal("Error stopping local server")
}
}
The thing is that I would like to embed my certificate and its key inside my executable file and then pass them to the the server.ListeAndServeTLS function as a string or a byte array. However this function does not take these types of arguments. Is there another way to do this?
Note: I am aware that it is a bad practice to embed a private key inside a client application, however what I am trying to do here is just to create a config webpage that will be hosted as https://localhost:8080.
You can build your own server object and still call ListenAndServeTLS. Since your tls config has certificates, it will ignore the passed-in filenames.
I'm omitting the return on error for conciseness, please do not:
// Generate a key pair from your pem-encoded cert and key ([]byte).
cert, err := tls.X509KeyPair(<cert contents>, <key contents>)
// Construct a tls.config
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert}
// Other options
}
// Build a server:
server := http.Server{
// Other options
TLSConfig: tlsConfig,
}
// Finally: serve.
err = server.ListenAndServeTLS("", "")
In my case, I was unable to load files from disk, but did have the certificates passed in to the environment as variables ([]byte).
Adding on to Marc's answer, I had to only change the top line.
// Append signed leaf certificate and intermediate to a single
certChain := append(leaf.PublicBytes, intermediate.PublicBytes...)
// Generate a key pair from your pem-encoded cert and key ([]byte).
cert, err := tls.X509KeyPair(certChain, leaf.PrivateBytes)
...
I have some experience using Go, but now I don't really understand the complexity in security of what I am doing, so I need to ask.
I am creating an RSA private key, converting to PEM and then encryping it with a passphrase.
So, how secure is to store it in a public place?
I'm not looking for answers like "it's ok, just change the passphrase over time", I really want to know which mechanism of cypher Golang is using to do it and if is safe to leave the encrypted PEM in, for example, a public blockchain and why I can do it or why I cannot.
I'm leaving here the code I am using right now:
func New(passphrase string)(*pem.Block, error){
pk, err := createPrivateKey(2048)
if err != nil {
return false, err
}
pem := getPemFromPK(pk)
block, err := EncryptPEMBlock(pem,passphrase)
if err != nil {
return false, err
}
return block,nil
}
func createPrivateKey(bits int) (*rsa.PrivateKey, error){
pk, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, err
}
return pk,nil
}
func getPemFromPK(pk *rsa.PrivateKey) (*pem.Block){
block := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(pk),
}
return block
}
func EncryptPEMBlock(block *pem.Block, passphrase string) (*pem.Block, error){
block, err := x509.EncryptPEMBlock(rand.Reader, block.Type, block.Bytes, []byte(passphrase), x509.PEMCipherAES256)
if err != nil {
return nil, err
}
return block,nil
}
Thank you very much.
Edit:
As an answer here and other forums, it is not recommended to publish in public any type of private key, even if encrypted.
This topic is answered.
You are making a mistake in your thinking about what a private key is and what a passphrase is. The passphrase is used to encrypt and unencrypt your private key - if you are storing a key file which needs a passphrase to be used, then that file contains your encrypted key.
If you store the "private key" as you say, it sounds like you wish to publicly store the unencrypted key. However, even if you publish an encrypted private key on a public online repository, there's many ways to crack a passphrase. If the passphrase is short or unsecure in other ways, the attacker now has your private key. If they target you and gain access to a machine of yours that has used this key in an application (i.e. bash), then they can just access bash history log to find the passphrase.
Sometimes actually, it's trivial to keylog someone in a targeted attack.
There are many many things that can go wrong if you store an unencrypted private key online.