मैं एक स्क्रिप्ट पर काम कर रहा हूं जो स्थानीय रूप से स्थापित प्रमाणपत्र का उपयोग करके माइक्रोसॉफ्ट ग्राफ़ तक पहुंच के लिए जेडब्ल्यूटी बना रहा है।

ऐसा लगता है कि प्रमाण पत्र से विशेषता PrivateKey का उपयोग करके एक आसान समाधान है, लेकिन कुछ मामलों में (मुख्य प्रदाता सीएनजी या कुछ और के आधार पर) यह दिखाई नहीं देता है। मुझे ओपनएसएसएल के साथ निजी कुंजी को मैन्युअल रूप से निकालना होगा और फिर हेडर + पेलोड पर हस्ताक्षर करने में सक्षम होने के लिए इसे RSACryptoServiceProvider में बदलना होगा।

अब मैं एक जेडब्ल्यूटी टोकन के साथ संघर्ष कर रहा हूं जो कि jwt.io के अनुसार मान्य है लेकिन माइक्रोसॉफ्ट के लिए अमान्य या विकृत है।

यहाँ त्रुटि कोड है:

Invoke-RestMethod : 
{
  "error": "invalid_request",
  "error_description": "AADSTS50027: JWT token is invalid or malformed.\r\nTrace ID:ed98bb58-8545-4667-acdd-8ce863303b00\r\nCorrelation ID: 722422ce-48d0-47ae-876d-3fb60a588e75\r\nTimestamp: 2021-02-12 13:53:47Z",
  "error_codes": [50027],
  "timestamp": "2021-02-12 13:53:47Z",
  "trace_id": "ed98bb58-8545-4667-acdd-8ce863303b00",
  "correlation_id": "722422ce-48d0-47ae-876d-3fb60a588e75",
  "error_uri": "https://login.microsoftonline.com/error?code=50027"
}

At D:\Scripts_Teams\QUAL\ReportingGraphAPI\Get-AuthTokenMSALDelegate.ps1:223 char:20
 + $accessToken = Invoke-RestMethod @PostSplat
 + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
 + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand ConvertFrom-Json : Cannot bind argument to parameter 'InputObject' because it is null.
 + 
At D:\Scripts_Teams\QUAL\ReportingGraphAPI\Get-AuthTokenMSALDelegate.ps1:225 char:41
 + $accessToken=$accessToken.content | ConvertFrom-Json
 + CategoryInfo          : InvalidData: (:) [ConvertFrom-Json], ParameterBindingValidationException
 + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.ConvertFromJsonCommand

यहाँ परिणाम है जो मेरे पास jwt.io से है: JWT.IO

और यहाँ कोड है:

function Generate-JWT (
  [Parameter(Mandatory = $True)]
  [string]$Issuer = $null,
  [Parameter(Mandatory = $True)]
  [int]$ValidforMinutes = $null,
  [Parameter(Mandatory = $true)]
  $CertificateThumbprint,
  [Parameter(Mandatory = $true)]
  $TenantName
) {

  $Certificate = Get-Item "Cert:\LocalMachine\My\$CertificateThumbprint"
  # Create base64 hash of certificate
  $CertificateBase64Hash = [System.Convert]::ToBase64String($Certificate.GetCertHash())
  $tempfile = "C:\Temp\tempCert.pfx"
  Export-PfxCertificate -FilePath $tempfile -Cert $Certificate -Password (ConvertTo-SecureString -AsPlainText "MyP@ssw0rd!" -Force)
  D:\Tools\OpenSSL\Bin\openssl.exe pkcs12 -in $tempfile -nocerts -out "C:\Temp\key.pem" -nodes -password pass:MyP@ssw0rd!
  D:\Tools\OpenSSL\Bin\openssl.exe rsa -in "C:\Temp\key.pem" -out "C:\Temp\key.key"

  $Algorithm = 'RS256'
  $type = 'JWT'
  $x5t = $CertificateBase64Hash -replace '\+', '-' -replace '/', '_' -replace '='
  $aud = "https://login.microsoftonline.com/$TenantName/oauth2/token"
  $jti = [guid]::NewGuid()

  # Create JWT timestamp for expiration
  $StartDate = (Get-Date "1970-01-01T00:00:00Z" ).ToUniversalTime()
  $JWTExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End (Get-Date).ToUniversalTime().AddMinutes($ValidforMinutes)).TotalSeconds
  $JWTExpiration = [math]::Round($JWTExpirationTimeSpan, 0)
  # Create JWT validity start timestamp
  $NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds
  $NotBefore = [math]::Round($NotBeforeExpirationTimeSpan, 0)


  [hashtable]$JWTHeader = @{
    alg = $Algorithm
    typ = $type
    x5t = $x5t
  }
  [hashtable]$JWTPayLoad = @{
    aud = $aud
    exp = $JWTExpiration
    iss = $Issuer
    jti = $jti
    nbf = $NotBefore
    sub = $Issuer
  }

  $headerjson = $JWTHeader | ConvertTo-Json -Compress
  $payloadjson = $JWTPayLoad | ConvertTo-Json -Compress
    
  # Convert header and payload to base64
  $EncodedHeader = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($headerjson)).Split('=')[0].Replace('+', '-').Replace('/', '_')
  $EncodedPayload = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($payloadjson)).Split('=')[0].Replace('+', '-').Replace('/', '_')

  $ToSign = $EncodedHeader + "." + $EncodedPayload


  # Define RSA signature and hashing algorithm
  $RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
  $HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256
    
  $opensslkeysource = Get-Content "D:\Scripts_Teams\QUAL\ReportingGraphAPI\opensslkey.cs" -Raw
  try {
    Add-Type -TypeDefinition $opensslkeysource
  }
  catch {
    if ($_.Exception -match "already exists") {
      Write-Verbose "The JavaScience.Win32 assembly (i.e. opensslkey.cs) is already loaded. Continuing..."
    }
  }
    
  $PemText = [System.IO.File]::ReadAllText("C:\Temp\key.key")
  $PemPrivateKey = [javascience.opensslkey]::DecodeOpenSSLPrivateKey($PemText)
  [System.Security.Cryptography.RSACryptoServiceProvider]$RSA = [javascience.opensslkey]::DecodeRSAPrivateKey($PemPrivateKey)
    

  # Create a signature of the JWT
  $Signature = [Convert]::ToBase64String(
    $RSA.SignData([System.Text.Encoding]::UTF8.GetBytes($ToSign), $HashAlgorithm, $RSAPadding)
  ) -replace '\+', '-' -replace '/', '_' -replace '='


  $JWT = $ToSign + "." + $Signature
  return $JWT
}

और यहां बताया गया है कि इसे कैसे कहा जाता है:

$JWT = Generate-JWT  -Issuer $clientID -ValidforMinutes 60  -CertificateThumbprint $Thumbprint -TenantName $TenantName
  
$authBody = @{
  client_id             = $clientID
  client_assertion      = $JWT
  client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
  scope                 = "https://graph.microsoft.com/.default"
  grant_type            = "client_credentials"
    
}

# Use the self-generated JWT as Authorization
$Header = @{
  Authorization = "Bearer $JWT"
}


$uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
# Splat the parameters for Invoke-Restmethod for cleaner code
$PostSplat = @{
  ContentType = 'application/x-www-form-urlencoded'
  Method      = 'POST'
  Body        = $authBody
  Uri         = $Uri
  Headers     = $Header
}

$accessToken = Invoke-RestMethod @PostSplat

कोई विचार क्यों इस जेडब्ल्यूटी को खारिज कर दिया जाएगा?

0
Stéphen Wolf 12 फरवरी 2021, 19:03

1 उत्तर

सबसे बढ़िया उत्तर

इस मुद्दे के संबंध में, कृपया निम्नलिखित स्क्रिप्ट देखें

  1. अपने स्टोर से प्रमाणपत्र को PFX फ़ाइल के रूप में निर्यात करें
$mypwd = ConvertTo-SecureString -String "1234" -Force -AsPlainText
 $Certificate = Get-Item "Cert:\LocalMachine\My\$CertificateThumbprint"
 $Certificate | Export-PfxCertificate -FilePath C:\mypfx.pfx -Password $mypwd
  1. Azure AD में प्रमाणपत्र अपलोड करें
$password= ConvertTo-SecureString "Password0123!" -AsPlainText -Force
$Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$Cert.Import("E:\cert\my.pfx", $password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)

Connect-AzureAD

 $certKeyId = [Guid]::NewGuid()
   $certBase64Value = [System.Convert]::ToBase64String($Cert.GetRawCertData())
   $certBase64Thumbprint = [System.Convert]::ToBase64String($Cert.GetCertHash())

   # Add a Azure Key Credentials from the certificate for the daemon application
   $clientKeyCredentials = New-AzureADApplicationKeyCredential -ObjectId <> `
                                                                    -CustomKeyIdentifier "" `
                                                                    -Type AsymmetricX509Cert `
                                                                    -Usage Verify `
                                                                    -Value $certBase64Value `
                                                                    -StartDate $Cert.NotBefore `
                                                                    -EndDate $Cert.NotAfter
  1. ग्राहक अभिकथन बनाएँ
$password= ConvertTo-SecureString "!" -AsPlainText -Force
$Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$Cert.Import("E:\cert\my.pfx", $password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)

$appEndPoint = "https://login.microsoftonline.com/<tenantId>/v2.0"
$appClientID = "
<clientid>"
$jwtStartTimeUnix = ([DateTimeOffset](Get-Date).ToUniversalTime()).ToUnixTimeSeconds()
$jwtEndTimeUnix = ([DateTimeOffset](Get-Date).AddHours(1).ToUniversalTime()).ToUnixTimeSeconds()
$jwtID = [guid]::NewGuid().Guid

$headerjson = @{
    alg="RS256";
    typ="JWT";
    x5t=[System.Convert]::ToBase64String($Cert.GetCertHash())
} | ConvertTo-Json -Compress

$payloadjson = @{
    aud = $appEndPoint;
    exp = $jwtEndTimeUnix;
    iss = $appClientID;
    jti = $jwtID;
    nbf = $jwtStartTimeUnix;
    sub = $appClientID
} | ConvertTo-Json -Compress

$EncodedHeader = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($headerjson)).Split('=')[0].Replace('+', '-').Replace('/', '_')
$EncodedPayload = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($payloadjson)).Split('=')[0].Replace('+', '-').Replace('/', '_')

$ToSign = $EncodedHeader + "." + $EncodedPayload
$RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
$HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256

$rsa=[System.Security.Cryptography.RSACryptoServiceProvider]::new()
$rsa.FromXmlString($Cert.PrivateKey.ToXmlString($true))
$Signature=$rsa.SignData([System.Text.Encoding]::UTF8.GetBytes($ToSign), $HashAlgorithm, $RSAPadding)
$Signature = [Convert]::ToBase64String($Signature).Split('=')[0].Replace('+', '-').Replace('/', '_')

$JWT = $ToSign + "." + $Signature
  1. टोकन प्राप्त करें
$authBody = @{
  client_id             = $appClientID 
  client_assertion      = $JWT
  client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
  scope                 = "https://graph.microsoft.com/.default"
  grant_type            = "client_credentials"
    
}



$uri = "https://login.microsoftonline.com/<>/oauth2/v2.0/token"
# Splat the parameters for Invoke-Restmethod for cleaner code
$PostSplat = @{
  ContentType = 'application/x-www-form-urlencoded'
  Method      = 'POST'
  Body        = $authBody
  Uri         = $Uri

}

$accessToken = Invoke-RestMethod @PostSplat
$accessToken 

enter image description here

अधिक जानकारी के लिए, कृपया देखें

https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-certificate-credentials

https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-net-client-assertions#signed-assertions


अद्यतन

मेरे परीक्षण के अनुसार, C# कोड सही है। हो सकता है कि आपकी स्क्रिप्ट में कुछ समस्याएँ हों। यह मेरी स्क्रिप्ट है

$cert = Get-Item "Cert:\LocalMachine\My\<>"
Export-PfxCertificate -FilePath $tempfile -Cert $Certificate -Password (ConvertTo-SecureString -AsPlainText "MyP@ssw0rd!" -Force)
$tempfile=""
D:\Tools\OpenSSL\Bin\openssl.exe pkcs12 -in $tempfile -nocerts -out "E:\cert\key.pem" -nodes -password pass:MyP@ssw0rd!
D:\Tools\OpenSSL\Bin\openssl.exe rsa -in "E:\cert\key.pem" -out "E:\cert\key.key"



$code= Get-Content -Path "D:\opensslkey.cs" -Raw

Add-Type -TypeDefinition $code -Language CSharp 

$pemString =Get-Content -Path E:\cert\key.key -Raw
$key=[JavaScience.opensslkey]::DecodeOpenSSLPrivateKey($pemString)

$rsa=[JavaScience.opensslkey]::DecodeRSAPrivateKey($key)


$appEndPoint = "https://login.microsoftonline.com/<tenantId>/oauth2/token"
$appClientID = "232a1406-b27b-4667-b8c2-3a865c42b79c"
$jwtStartTimeUnix = ([DateTimeOffset](Get-Date).ToUniversalTime()).ToUnixTimeSeconds()
$jwtEndTimeUnix = ([DateTimeOffset](Get-Date).AddHours(1).ToUniversalTime()).ToUnixTimeSeconds()
$jwtID = [guid]::NewGuid().Guid

$headerjson = @{
    alg="RS256";
    typ="JWT";
    x5t=[System.Convert]::ToBase64String($Cert.GetCertHash()) -replace '\+', '-' -replace '/', '_' -replace '='
} | ConvertTo-Json -Compress

$payloadjson = @{
    aud = $appEndPoint;
    exp = $jwtEndTimeUnix;
    iss = $appClientID;
    jti = $jwtID;
    nbf = $jwtStartTimeUnix;
    sub = $appClientID
} | ConvertTo-Json -Compress

$EncodedHeader = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($headerjson)).Split('=')[0].Replace('+', '-').Replace('/', '_')
$EncodedPayload = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($payloadjson)).Split('=')[0].Replace('+', '-').Replace('/', '_')

$ToSign = $EncodedHeader + "." + $EncodedPayload
$RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
$HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256

$Signature=$rsa.SignData([System.Text.Encoding]::UTF8.GetBytes($ToSign), $HashAlgorithm, $RSAPadding)
$Signature = [Convert]::ToBase64String($Signature).Split('=')[0].Replace('+', '-').Replace('/', '_')

$JWT = $ToSign + "." + $Signature

$authBody = @{
  client_id             = $appClientID 
  client_assertion      = $JWT
  client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
  scope                 = "https://graph.microsoft.com/.default"
  grant_type            = "client_credentials"
    
}



$uri = "https://login.microsoftonline.com/<tenantId>/oauth2/v2.0/token"
# Splat the parameters for Invoke-Restmethod for cleaner code
$PostSplat = @{
  ContentType = 'application/x-www-form-urlencoded'
  Method      = 'POST'
  Body        = $authBody
  Uri         = $Uri

}

$accessToken = Invoke-RestMethod @PostSplat
$accessToken.access_token 

enter image description here

enter image description here

0
Jim Xu 18 फरवरी 2021, 04:55
हैलो जिम जू! धन्यवाद, मैंने इस संस्करण को PrivateKey विशेषता का उपयोग करते हुए देखा। मुद्दा यह है कि मैं दूसरा प्रमाणपत्र नहीं बना सकता क्योंकि मुझे अपने ग्राहक से आंतरिक पीकेआई का उपयोग करना है। लेकिन आपका नमूना काफी समान दिखता है। क्या आपको इस बात का अंदाजा है कि समस्या का कारण क्या हो सकता है?
 – 
Stéphen Wolf
16 फरवरी 2021, 12:08
जब आप अपनी कुंजी फ़ाइल से सामग्री पढ़ते हैं, तो कृपया pemprivheader और pemprivfooter को निकालने का प्रयास करें।
 – 
Jim Xu
16 फरवरी 2021, 12:33
इसके अलावा जब से आपने प्रमाण पत्र को pfx के रूप में निर्यात करने के लिए Export-PfxCertificate का उपयोग किया है, तो निजी कुंजी प्राप्त करने के लिए pfx फ़ाइल को पढ़ने के लिए सीधे X509Certificate2 का उपयोग क्यों न करें।
 – 
Jim Xu
16 फरवरी 2021, 12:37
मैंने अभी कोशिश की है, लेकिन फिर जेडब्ल्यूटी jwt.io पर अमान्य के रूप में प्रकट होता है और वही त्रुटि ग्राफ एपीआई द्वारा वापस कर दी जाती है।
 – 
Stéphen Wolf
16 फरवरी 2021, 12:53
मैंने वह भी कोशिश की, PS D:\ReportingGraphAPI> $Cert | fl * EnhancedKeyUsageList : {Client Authentication (1.3.6.1.5.5.7.3.2), Server Authentication (1.3.6.1.5.5.7.3.1)} DnsNameList : {serverXXX} [...] HasPrivateKey : True **PrivateKey : ** PublicKey : System.Security.Cryptography.X509Certificates.PublicKey [...] निजी कुंजी खाली है
 – 
Stéphen Wolf
16 फरवरी 2021, 12:55