JWT (JSON Web Token) Encoding and creating a signature

Tagged: , , , ,

This topic contains 13 replies, has 4 voices, and was last updated by Profile photo of i255d i255d 5 months, 2 weeks ago.

  • Author
    Posts
  • #60574
    Profile photo of i255d
    i255d
    Participant

    I am going through the instructions given by Box on how to connect to their API using a JWT.
    https://docs.box.com/docs/app-auth
    I have created a public and private pem key.
    I have the Box pieces setup.

    I am at the point where I trying to construct the JWT.
    It states there are three parts, the Header, Claims, and Signature.
    I believe I have the Header and claims JSON created properly.

    $rawheader = [Ordered]@{
        alg = "RS256"
        typ = "JWT"
        kid = "NumberFromBox"  #Box
    } | ConvertTo-Json -Compress
    
    $client_id = "Generated by box"   #Box
    $enterprise_id = "FromBox"    #Box
    $unixDatePlus60 = [int][double]::Parse((Get-Date -Date (Get-Date).AddSeconds(59) -UFormat %s))
    $rawclaims = [Ordered]@{
        iss = $client_id
        sub = $enterprise_id
        box_sub_type = "enterprise"
        aud = "https://api.box.com/oauth2/token"  #Box
        jti = "$rdmHex"
        exp = $unixDatePlus60
    } | ConvertTo-Json -Compress
  • #60577
    Profile photo of Don Jones
    Don Jones
    Keymaster

    Is there a question?

  • #60586
    Profile photo of i255d
    i255d
    Participant

    4. Constructing the JWT Assertion
    Once you have created the RSA keypair and submitted the public key to Box, you can request Enterprise and User OAuth2.0 Access tokens using the JWT grant.

    Every JWT assertion is composed of three components, the header, the claims, and the signature.

    The header specifies the algorithm used for the JWT signature.
    The claims contain the information necessary to authenticate and provide the correct token.
    The signature is used to verify the identify of the application and is verified using the public key.
    To construct the JWT assertion, these three components must be base64 encoded and concatenated using a “.” separator:

    Format for JWT Assertion
    ..
    Once encoded and concatenated, the JWT assertion will look like this:

    Example Encoded JWT Assertion
    eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9eyJpc3MiOiJ2Z3.
    B2bWFvaDJjZ2ZjNGRuMzFnMWx0cmlhbmdlZCIsInN1YiI.
    6IjE2ODczOTQzIiwiZXhwIjoxNDI5MDM3ODYwLCJqdGkiOiJ

    To do this part, the only thing I could figure out, following their instructions is to use the Nuget module for encoding. So I used Visual Studio and created a project and added under Tool – NuGet Package Manager – NuGet Solutions:
    Microsoft.Owin.Security 3.0.0
    Microsoft.Owin 3.0.0
    OWIN 1.0.0

    Box gives this site as a general reference to help with this process, rather than just telling how it is actually done.
    https://jwt.io/#libraries

    If someone can help me figure out how to begin to use this site, I would be greatful.

  • #60589
    Profile photo of i255d
    i255d
    Participant

    Because I don't know how to use the reference they supplied, I found this blog post I thought would be helpful.
    http://www.thingsthatmademeangry.com/2014/11/google-apps-oauth2-service-account.html

    This is the site that showed how to use the Nuget pieces.

    #MS Owin for base64url encoding
    $Owin = 'C:\EEDevOps\TeamFoundation\DevOps\Box\BoxConnect\packages\Owin.1.0\lib\net40\Owin.dll'
    $msOwin = 'C:\EEDevOps\TeamFoundation\DevOps\Box\BoxConnect\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll'
    $msOwinSec = 'C:\EEDevOps\TeamFoundation\DevOps\Box\BoxConnect\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll'
    Add-Type -Path $Owin 
    Add-Type -Path $msOwin
    Add-Type -Path $msOwinSec
    
    # get a base64url encoder object
    $encoder = New-Object Microsoft.Owin.Security.DataHandler.Encoder.Base64UrlTextEncoder
    # we then serialize to UTF-8 bytes, then base64url encode the claims
    $header = $encoder.Encode([System.Text.Encoding]::UTF8.GetBytes($rawheader))
    $claims = $encoder.Encode([System.Text.Encoding]::UTF8.GetBytes($rawclaims))

    This gives me this much:
    PS C:\EEDevOps> $header
    eyJhbGciOiJSUzI1NtIsInR5cCI6IkpXVCvsImtpZCI6Iml0MmgxZXVrIn0

    PS C:\EEDevOps> $claims
    eyJpc3MiOiJ6NbliMmkzbWZ3Z3ZnazBiNzFzNzNlNXM4Y3hhdzF0bSIsInN1YiI6IjE1NjM1OCIsImJveF9zdrrfdHlwZSI6ImVudGVycHJpc2UiLCJhdWQiOiJodHRwczovL2FwaS5ib3guY29tL29hdXRoMi90b2tlbiIsImp0aSI6IzNCNjAxMUJFNERBMTZGMT
    Y0NzAwIiwiZXhwIjoxNDgyMjUwMDY3fQ

    Now I need to generate the signature and pull it all together to get it in the right format.

  • #60591
    Profile photo of i255d
    i255d
    Participant

    The Box employee gives as an example his code:
    https://github.com/cghil/cmd-line-app-users

    And the blog post I found does it this way:

    $googleCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("[path to your p12 private key]", "notasecret",[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable )
        
     
    # get just the private key
    $rsaPrivate = $googleCert.PrivateKey
     
    # get a new RSA provider
    $rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider
     
    # copy the parameters from the private key into our new rsa provider
    $rsa.ImportParameters($rsaPrivate.ExportParameters($true))

    I don't know what I am missing, but this doesn't work. I am not sure what I am suppose to put in this spot above:
    "[path to your p12 private key]"

    I need help trying to pull all of this together using the private key to generate the final JWT.

  • #60594
    Profile photo of i255d
    i255d
    Participant

    Hi Don, Yup there is a question...lol

  • #60597
    Profile photo of Don Jones
    Don Jones
    Keymaster

    It's probably expecting the private key to be in a key file on disk. However... honestly, you should ask the blogger. I'm a PowerShell guy, not a Box guy ;).

    Based on https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509certificate2(v=vs.110).aspx, there are several constructors for that class; the one with only one String seems to be looking for a file path. See https://msdn.microsoft.com/en-us/library/ms148423(v=vs.110).aspx. If you've got the private key in another form, another constructor might be appropriate, but I'm also not a .NET guy ;).

  • #60601
    Profile photo of i255d
    i255d
    Participant

    Thanks Don, I will look through these references and see what I can pull together.
    In my opinion, if I am a PowerShell Guy, which I am, I am the glue, the string or whatever else is needed because I can traverse any piece of any environment because PowerShell is just that good!!!

    Always taking it to the next level. You should see all the functions I have created for the Box API, now I want to make it so no one has to use them, Jams just runs them once a month as needed.

  • #60621
    Profile photo of Max Kozlov
    Max Kozlov
    Participant

    may be you can use .net libraries from https://jwt.io/#libraries ?
    'Jose JWT' use examples seem to me easy to translate into powershell from c#

  • #61191
    Profile photo of i255d
    i255d
    Participant

    Ok, I have created a project in Visual Studio and imported Jose JWT:
    PS C:\EEDevOps> dir C:\EEDevOps\TeamFoundation\DevOps\Box\BoxJWT\BoxJWT1

    Directory: C:\EEDevOps\TeamFoundation\DevOps\Box\BoxJWT\BoxJWT1

    Mode LastWriteTime Length Name
    —- ————- —— —-
    d—– 1/3/2017 4:18 PM bin
    d—– 1/3/2017 4:18 PM obj
    d—– 1/3/2017 4:20 PM packages
    d—– 1/3/2017 4:18 PM Properties
    -a—- 1/3/2017 4:18 PM 2581 .gitattributes
    -a—- 1/3/2017 4:18 PM 4077 .gitignore
    -a—- 1/3/2017 4:18 PM 189 App.config
    -a—- 1/3/2017 4:20 PM 2987 BoxJWT1.csproj
    -a—- 1/3/2017 4:18 PM 980 BoxJWT1.sln
    -a—- 1/3/2017 4:20 PM 136 packages.config
    -a—- 1/3/2017 4:18 PM 247 Program.cs

    PS C:\EEDevOps> dir C:\EEDevOps\TeamFoundation\DevOps\Box\BoxJWT\BoxJWT1\packages

    Directory: C:\EEDevOps\TeamFoundation\DevOps\Box\BoxJWT\BoxJWT1\packages

    Mode LastWriteTime Length Name
    —- ————- —— —-
    d—– 1/3/2017 4:20 PM jose-jwt.2.1.0

    A representative from Box is recommending one of the examples given by the creator of this Nuget package to help guide doing this in PowerShell:

    var payload = new Dictionary()
    {
        { "sub", "mr.x@contoso.com" },
        { "exp", 1300819380 }
    };
    var privateKey=new X509Certificate2("my-key.p12", "password", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet).GetRSAPrivateKey();
    string token=Jose.JWT.Encode(payload, privateKey, JwsAlgorithm.RS256);

    The question is, did I need to create the package above to be utilized in PowerShell, or is there a better way. And, How do I put the Nuget package to use and create the JWT signature in PowerShell? Is any of what I have done above useful?

  • #61327
    Profile photo of i255d
    i255d
    Participant

    Thanks @beefarino,

    Import-Module 'C:\EEDevOps\TeamFoundation\DevOps\Box\BoxJWT\BoxJWT1\bin\Debug\jose-jwt.dll'
    
    $payload = New-Object 'System.Collections.Generic.Dictionary[[string].[System.Collections.Generic.List[string]]]'
    
    $payload.add( "sub", "mr.x@contoso.com" );
    $payload.add( "exp", 1300819380 );
    
    $x509Flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable + [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet;
    $certificate = new-object 'System.Security.Cryptography.X509Certificates.X509Certificate2' "C:\01Servers\pem\private_key.pem", "password", $x509Flags
    $privateKey = $certificate.GetRSAPrivateKey();
    $token = [Jose.JWT]::Encode($payload, $privateKey, [Jose.JwsAlgorithm]::RS256);

    I am not sure about:

    $payload = New-Object 'System.Collections.Generic.Dictionary[[string].[System.Collections.Generic.List[string]]]'
    New-Object : Cannot find type [System.Collections.Generic.Dictionary[[string].[System.Collections.Generic.List[string]]]]: verify that the assembly containing this type is loaded.
    At line:1 char:12
    + $payload = New-Object 'System.Collections.Generic.Dictionary[[string] ...
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidType: (:) [New-Object], PSArgumentException
    + FullyQualifiedErrorId : TypeNotFound,Microsoft.PowerShell.Commands.NewObjectCommand

    $x509Flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable + [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet;
    $certificate = new-object 'System.Security.Cryptography.X509Certificates.X509Certificate2' "C:\01Servers\pem\private_key.pem", "password", $x509Flags
    new-object : Exception calling ".ctor" with "3" argument(s): "Cannot find the requested object.
    "
    At line:2 char:16
    + ... rtificate = new-object 'System.Security.Cryptography.X509Certificates ...
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvocationException
    + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand

  • #61342
    Profile photo of i255d
    i255d
    Participant

    Right now, this seem to be the part that has me fooled. I can't seem to get this method to work. I have tried creating this PEM cert with password and without. I have tried putting the path in quotes in the bracket, setting the type string and system.string to a variable, I tried putting the raw data in a variable, and the method doesn't' seem to work.
    I used a Linux server and these commands one time to create the certs:
    openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
    openssl rsa -pubout -in private_key.pem -out public_key.pem

    PS Cert:\CurrentUser\My> [string]$path = 'C:\01Servers\pem\private_key.pem'

    PS Cert:\CurrentUser\My> $pfx = new-object System.Security.Cryptography.X509Certificates.X509Certificate2

    PS Cert:\CurrentUser\My> $pfx.import($Path)
    Exception calling "Import" with "1" argument(s): "Cannot find the requested object.
    "
    At line:1 char:3
    + $pfx.import($Path)
    + ~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : CryptographicException

    PS Cert:\CurrentUser\My> $PublicCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($path)
    New-Object : Exception calling ".ctor" with "1" argument(s): "Cannot find the requested object.
    "
    At line:1 char:17
    + ... ublicCert = New-Object System.Security.Cryptography.X509Certificates. ...
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvocationException
    + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand

  • #62487
    Profile photo of Todd Ferris
    Todd Ferris
    Participant

    Did you find a solution to this?

    I just started on a powershell Box.com upload script that needs to use JWT. I was originally going to go down the same path as you and try to construct the JWT assertion using power shell, but then realized it was fairly complex to generate. I then was looking at just installing the the .Net SDK and attempting to call that from Powershell. If I followed your post correctly, it looks like you pulled from the jwt.io libraries and compiled a dll.

    Is there a reason you didn't use the Box .Net sdk?

    Did you figure out the issue with reading the private key in?

  • #63670
    Profile photo of i255d
    i255d
    Participant

    Sorry for the slow response. I have been pulled away from this for a bit, but I still need to work this out. Bruce Payette reached out and said that it was a formatting issue, for the reason why I am unable to read the cert. He gave this as a reference: http://stackoverflow.com/questions/7400500/how-to-get-private-key-from-pem-file

    I have gone in a couple of different directions, with which pull the dll libraries, I guess I chose the jwt.io because that is what one of Box's employees had used in his none PowerShell script. I am hoping to be able to spend some time on this over the next 3 to 5 days. I will need to get my head back into it, which will probably take 4 hours.

    Box has also reached out to me and says they are going to work out the code in .NET soon.

You must be logged in to reply to this topic.