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.
Related
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
}
,,,
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 authenticate to Azure service management / graph API using golang.
Using purely REST APIs.
No matter what I do, I always end up with error:
{"error":"invalid_request","error_description":"AADSTS900144: The request body must contain the following parameter: 'grant_type'.
Since I am not using SDK there is limited samples out there. Any help would be appreciated.
package main
import (
"bytes"
"encoding/json"
"io/ioutil"
"log"
"net/http"
)
func main() {
authendpoint := "https://login.microsoftonline.com/8xxxxx7-6372-4bcb-xxx-xxxxxx/oauth2/token"
jsonData := []byte(`{
"resource": "https://graph.microsoft.com",
"client_id": "xxxxxxxx-7549-4ea2-b00d-xxxxxxxxxxx",
"client_secret": "Q.xxxxxxxxxxxxxx-6_CgA4yOi_8sS-",
"grant_type": "client_credentials",
}`)
request, err := http.NewRequest("POST", authendpoint, bytes.NewBuffer(jsonData))
request.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(request)
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
var res map[string]interface{}
json.NewDecoder(resp.Body).Decode(&res)
log.Println(string(body))
}
The Microsoft request docs posted by Praveen Premaratne show the request needs to be formatted using Content-Type: application/x-www-form-urlencoded which is a requirement for the OAuth 2.0 standard.
Here's the Microsoft docs and example:
https://learn.microsoft.com/en-us/graph/auth/auth-concepts#register-your-app-with-the-microsoft-identity-platform
POST /common/oauth2/v2.0/token HTTP/1.1
Host: https://login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&scope=user.read%20mail.read
&code=OAAABAAAAiL9Kn2Z27UubvWFPbm0gLWQJVzCTE9UkP3pSx1aXxUjq3n8b2JRLk4OxVXr...
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&grant_type=authorization_code
&client_secret=JqQX2PNo9bpM0uEihUPzyrh
Here's how to accomplish this:
package main
import (
"fmt"
"net/http"
"net/url"
"strings"
)
func main() {
authendpoint := "https://login.microsoftonline.com/8xxxxx7-6372-4bcb-xxx-xxxxxx/oauth2/token"
body := url.Values(map[string][]string{
"resource": {"https://graph.microsoft.com"},
"client_id": {"xxxxxxxx-7549-4ea2-b00d-xxxxxxxxxxx"},
"client_secret": {"Q.xxxxxxxxxxxxxx-6_CgA4yOi_8sS-"},
"grant_type": {"client_credentials"}})
request, err := http.NewRequest(
http.MethodPost,
authendpoint,
strings.NewReader(body.Encode()))
if err != nil {
panic(err)
}
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
client := &http.Client{}
resp, err := client.Do(request)
if err != nil {
panic(err)
}
fmt.Println(resp.StatusCode)
}
for me, it worked when I removed resource from the request body and added scope as new parameter in body.
I am generating a JSON web signature in JavaScript using node-jws package (https://www.npmjs.com/package/jws). In Headers, I am giving crit: ["exp"] and exp: someTimeStamp. The snippet given below is used in generating the JWS:
let token = jws.sign({
header: { alg: 'HS256', crit: ["exp"], exp: Math.floor(Date.now() / 1000) + (60 * 60) },
payload: "somestring" ,
privateKey: 'supersecret',
});
I am verifying this token in Golang using the snippet given below:
import (
"github.com/square/go-jose"
)
func main() {
jsonWebSig, err := jose.ParseSigned(token)
if err != nil {
panic(err)
}
payload, err := jsonWebSig.Verify([]byte("supersecret"))
fmt.Println(string(payload))
fmt.Println(err)
}
The above code in GO works if I don't give the crit: ["exp"] in header while generating the token in JS. Otherwise, it gives me the error saying square/go-jose: error in cryptographic primitive.
I have to use crit: ["exp"] in headers at any cost. Is there any way to verify this?
Here I have a function in which I'm sending a POST request which is used to save customer in the squareup with data and also set the headers with Authentication using variable_name.Header.Set()
But in body response it will always give me error of the:-
"errors":[
{"category":"AUTHENTICATION_ERROR",
"code":"UNAUTHORIZED",
"detail":"Your request did not include an `Authorization` http header with an access token. }]}
But In the function I'm setting the authentication token.
Code:-
func CreateCustomer(c *gin.Context) {
customer := models.Customer{}
bearer := strings.Split(c.Request.Header["Authorization"][0], "Bearer")// token pass in the postman.
bearerToken := strings.TrimSpace(bearer[1])
customerErr := json.NewDecoder(c.Request.Body).Decode(&customer)
if customerErr != nil {
fmt.Println(customerErr)
return
}
fmt.Println(customer)
bindData, err := json.Marshal(customer)
if err != nil {
panic(err)
}
var jsonStr = []byte(string(bindData))
url :="https://connect.squareup.com/v2/customers"
fmt.Println(url)
req, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
// I used this one too.
// req.Header.Set("Authorization", "Bearer "+bearerToken)
// req.Header.Set("Accept", "application/json")
req.Header.Add("Authorization", "Bearer "+bearerToken)
req.Header.Add("Accept", "application/json")
fmt.Println(req.Header)
if err != nil {
panic(err)
}
defer req.Body.Close()
body, _ := ioutil.ReadAll(req.Body)
fmt.Println("response Body:", string(body))
}
type Customer struct {
GivenName string `json:"given_name" bson:"given_name"`
FamilyName string `json:"family_name" bson:"family_name"`
CompanyName string `json:"company_name" bson:"company_name"`
Nickname string `json:"nickname" bson:"nickname"`
EmailAddress string `json:"email_address" bson:"email_address"`
Address Addresss `json:"address" bson:"address"`
PhoneNumber string `json:"phone_number" bson:"phone_number"`
ReferenceId string `json:"reference_id" bson:"reference_id"`
Note string `json:"note" bson:"note"`
}
The req.Header result is:-
map[X-Xss-Protection:[1; mode=block]
Keep-Alive:[timeout=60]
Accept:[application/json]
X-Permitted-Cross-Domain-Policies:[none]
Content-Type:[application/json]
Vary:[Origin, Accept-Encoding]
X-Content-Type-Options:[nosniff]
X-Download-Options:[noopen]
X-Frame-Options:[SAMEORIGIN]
Date:[Wed, 12 Dec 2018 03:41:16 GMT]
Strict-Transport-Security:[max-age=631152000]
Authorization:[Bearer YOUR_TOKEN HERE]]
Can anyone tell me that what error should I'm doing or where I do correction that it will able to save customer in the Squareup?
Your code sends POST request and after request is processed it adds headers to response struct:
response, err := http.Post(url, "application/json", bytes.NewBuffer(jsonStr))
You should set headers first and send request after that:
// create request
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
if err != nil {
panic(err)
}
// set headers
req.Header.Add("Authorization", "Bearer "+bearerToken)
req.Header.Add("Accept", "application/json")
// send request with headers
client := &http.Client{}
response, err := client.Do(req)
#Dmitry Harnitski this is the correct approach. Only reminder is that, try not confused between "authentication" and "authorization", which was kind of roadblock I encountered. It's not about literally meaning, everyone knows the difference. However, even in the syntax, a lot material used these two words interchangeably, which should not be encouraged, at least to my mind. The topic above is a good example: began with "authentication" as the question, then ended with "authorization" as the solution.