Decode Acrobat FRL License to Get Expiration Date

Working on an air-gapped network, Adobe Acrobat licensing is handled by the “Feature Restricted Licensing” option. This requires generating a license package from the Adobe tool and deploying alongside Acrobat. You will need to generate a new license file on a regular cadence, I have heard twice a year but I am not smart on the specifics.

When you generate your license file, you get no indication of when it will expire. Not in the Adobe tool, not in Acrobat itself, nowhere that I could find. Of course, I would not be writing this, nor you reading this, if the story ended there. I was able to find some code that Adobe was nice enough to share on GitHub, but it is overkill just to quickly find the license expiration date. Even so, the code did give me some pointers on where to pull the information. Now let’s use some PowerShell to make things happen.

Let’s start with where to find your installed license(s). In %ProgramData%\Adobe\OperatingConfigs you will find the installed license(s), which have an OperatingConfig file extension. Opening one of the files you will see if is in a json format but most of the data is not in a readable format. Let’s assume that we have the license file path in a $file variable and then import the json.

$data = Get-Content $file | ConvertFrom-Json

When we look at the imported file we can’t read much of it. The meaningful data is base64 encoded. If we decode the json payload section we find it is more json. Let’s decode and expand out the “inner json” to its own variable.

$j = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($data.payload)) | ConvertFrom-Json

When viewing the raw json, I noticed a value named “licenseExpiryTimestamp”. How to dot source it was not immediately obvious looking at the text of the json, but luckily the Adobe code on GitHub showed the path to get it. Let’s use that to pull the datetime out to a variable.

$datestring = $j.asnpData.adobeCertSignedValues.values.licenseExpiryTimestamp

The date string is a 13 digit string, an Epoch Time. To make the time more useful (to me), let’s convert it to a standard DateTime object.

[datetimeoffset]::FromUnixTimeMilliseconds($datestring).DateTime

I could just throw that all in a single code block and call it good. Maybe I’ll throw in a line to get the most recent license file. Something like this.

$file = Get-childitem "$ENV:ProgramData\Adobe\OperatingConfigs" -Filter *.OperatingConfig | sort -Property LastWriteTime -Descending | select -First 1 -ExpandProperty Fullname
$data = Get-Content $file | ConvertFrom-Json
$j = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($data.payload)) | ConvertFrom-Json
$datestring = $j.asnpData.adobeCertSignedValues.values.licenseExpiryTimestamp
[datetimeoffset]::FromUnixTimeMilliseconds($datestring).DateTime

Likely the least readable of the options is to turn it into a long oneliner.

[datetimeoffset]::FromUnixTimeMilliseconds(([System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String((Get-Content (Get-childitem "$ENV:ProgramData\Adobe\OperatingConfigs" -Filter *.OperatingConfig | sort -Property LastWriteTime -Descending | select -First 1 -ExpandProperty Fullname) | ConvertFrom-Json).payload)) | ConvertFrom-Json).asnpData.adobeCertSignedValues.values.licenseExpiryTimestamp).DateTime

While that does work, I’d rather make it into an actual function.

Function Get-AdobeLicenseDate{
Param (
[Parameter(Mandatory=$true)][string]$filepath
)
$data = Get-Content $filepath | ConvertFrom-Json
$j = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($data.payload)) | ConvertFrom-Json
$datestring = $j.asnpData.adobeCertSignedValues.values.licenseExpiryTimestamp
[datetimeoffset]::FromUnixTimeMilliseconds($datestring).DateTime
}

These methods get our license expiration date for the one system. If you know my background, you know I am going to be dealing with this at scale, not individual systems. Let’s take the function and make a slightly more rounded out script to use in a CI. There are a couple ways I could use the data in a CI, I could compare the date to say a license expiring within x months is non-compliant. If systems were renewing themselves in a rolling manner, that is how I would do it. In this case, an admin is creating a new license every 6-12 months and deploying to all systems. With this, all systems should have the same expiration date, those with something different don’t have the right license. Instead of a DateTime difference calculation, I will make the date a string with a yyyyMMdd format and then compare that string. Add in that I do not know who will take over this code in the future and I am going to comment the heck out of it.

Function Get-AdobeLicenseDate{
[CmdletBinding()]
Param (
    [Parameter(Mandatory=$false)][string]$filepath
)
    #Get license file content as JSON
    $data = Get-Content $filepath | ConvertFrom-Json
    #Get the payload data from outer json, convert to text from Base64 and convert to the "inner json"
    $j = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($data.payload)) | ConvertFrom-Json
    #Get date string from inner json
    $datestring = $j.asnpData.adobeCertSignedValues.values.licenseExpiryTimestamp
    #convert time to standard DateTime
    [datetimeoffset]::FromUnixTimeMilliseconds($datestring).DateTime
}

#verify the license directory exists
if(!(test-path "$ENV:ProgramData\Adobe\OperatingConfigs")){write-output "00000000"}
Else{
    #create empty array for the license dates
    $dates = @()
    #get license file paths
    $files = Get-childitem "$ENV:ProgramData\Adobe\OperatingConfigs" -Filter *.OperatingConfig | Select-object -ExpandProperty FullName
    #Get the expiration date from each license file and add to the array
    ForEach($file in $files){
        $dates += Get-AdobeLicenseDate $file
    }
}
#Return the latest date in the array to a variable
$expirationdate = $dates | sort-object -Descending | Select-Object -first 1
#return the expirationdate as a string
write-output $expirationdate.ToString("yyyyMMdd")

Now I can put that in a CI defining it as a string result and compare it against the known date. I could also use as a detection method for an application to deploy the license.