Check licenses of composer dependencies – Rob Allen

With some commercial projects, it can be useful to know that all your dependencies have licences that your organisation deems acceptable.

I had this requirement for a few clients now and came up with this script that we ran as part of our CI which would then fail if a dependency used a license that wasn’t allowed.

This proved to be reasonably easy as composer licenses will provide a list of all packages with their license, and more usefully, the -f json switch will output the list as JSON. With a machine-readable format, the script just came together!

At some point, we discovered that we needed to allow exceptions for specifically authorised packages, so I added that and haven’t changed it since.

check-licenses.php

<?php $allowedLicenses = ['Apache-2.0', 'BSD-2-Clause', 'BSD-3-Clause', 'ISC', 'MIT', 'MPL-2.0', 'OSL-3.0'];
$allowedExceptions = [ 'some-provider/some-provider-php', // Proprietary license used by SMS provider
]; $licences = shell_exec('composer licenses -f json');
if ($licences === null || $licences === false) { echo "Failed to retrieve licenses\n"; exit(1);
} try { $data = json_decode($licences, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) { echo "Failed to decode licenses JSON: " . $e->getMessage() . "\n"; exit(1);
} // Filter out all dependencies that have an allowed license or exception
$disallowed = array_filter( $data['dependencies'], fn(array $info, $name) => ! in_array($name, $allowedExceptions) && count(array_diff($info['license'], $allowedLicenses)) === 1, ARRAY_FILTER_USE_BOTH
);
if (count($disallowed)) { $disallowedList = array_map( fn(string $k, array $info) => sprintf("$k (%s)", implode(',', $info['license'])), array_keys($disallowed), $disallowed ); printf("Disallowed licenses found in PHP dependencies: %s\n", implode(', ', $disallowedList)); exit(1);
} exit(0);

Running check-licenses.php

If all dependencies are allowed, then check-licenses will output nothing and exit with status code 0:

$ php bin/check-licenses.php
$ echo $?
0

If at least one dependency is not allowed, then check-licenses will list the packages that have licenses that ar not allowed and exit with status code 1:

$ php bin/check-licenses.php
Disallowed licenses found in PHP dependencies: foo/bar (GPL-3.0)
$ echo $?
1

Maybe it’s useful to others too. If you use it, put it in your CI system.

Creating JWKS.json file in PHP – Rob Allen

In order to verify a JWT created with an asymmetric key, the verifier needs to get the correct public key. One way to do is described in RFC7517 which describes the JSON Web Key format.

Within the header of the JWT there is a kid property which is the key ID which is then used to find the correct key within a list provided at the /.well-known/jwks.json endpoint.

The JWT header therefore looks something like this:

{ "alg" : "RS256", "kid" : "6eaf334518784ff392c3123b41ae49f5", "typ" : "JWT"
}

And the jwks.json is structured something like this:

{ "keys": [ { "alg": "RS256", "kty": "RSA", "use": "sig", "kid": "6eaf334518784ff392c3123b41ae49f5", "n": "sj6R1AYPKISqYKFxmQMMFJSm583Jfn6ef51SpQPCe17SM10Ljp2YIte924U ...", "e": "AQAB" } ]
}

This is an interesting format as it doesn’t use the standard PEM format for the key, but rather stores it as a modulus (“n”) and exponent (“e”) as per RFC 7518 section 6.3:

6.3.1.1. “n” (Modulus) Parameter
The “n” (modulus) parameter contains the modulus value for the RSA
public key. It is represented as a Base64urlUInt-encoded value.
Note that implementers have found that some cryptographic libraries
prefix an extra zero-valued octet to the modulus representations they
return, for instance, returning 257 octets for a 2048-bit key, rather
than 256. Implementations using such libraries will need to take
care to omit the extra octet from the base64url-encoded
representation.
6.3.1.2. “e” (Exponent) Parameter
The “e” (exponent) parameter contains the exponent value for the RSA
public key. It is represented as a Base64urlUInt-encoded value.

Fortunately, we can use openssl to sort this all out for us:

// $keyString is a PEM encoded public key
$key = openssl_get_publickey($keyString);
$details = openssl_pkey_get_details($key);

Assuming $key is an instance of OpenSSLAsymmetricKey and $details is an array, then:

$modulus = $details['rsa']['n'];
$exponent = $details['rsa']['e'];

Putting this into a PSR-15 request handler that is passed an array of public keys, we can put together a jwks.json response:

<?php declare(strict_types=1); namespace App\Handler; use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Webmozart\Assert\Assert; class JwksHandler implements RequestHandlerInterface
{ /** * @param string[] $publicKeys */ public function __construct(private readonly array $publicKeys) { } public function handle(ServerRequestInterface $request): ResponseInterface { $keys = []; foreach ($this->publicKeys as $keyString) { $key = openssl_get_publickey($keyString); Assert::isInstanceOf(\OpenSSLAsymmetricKey::class, 'Public key is not valid.'); $details = openssl_pkey_get_details($key); Assert::isArray($details, 'Public key details are not valid.'); $keys[] = [ 'kty' => 'RSA', 'alg' => 'RS256', 'use' => 'sig', 'kid' => sha1($keyString), 'n' => strtr(rtrim(base64_encode($details['rsa']['n']), '='), '+/', '-_'), 'e' => strtr(rtrim(base64_encode($details['rsa']['e']), '='), '+/', '-_'), ]; } return new JsonResponse(['keys' => $keys]); }
}

Note that we remove the base64 padding (= at the end) and also use the Base64Url modification where “+” is replaced with “-” and “/” with “_“.

The other properties in the JSON object are:

  • kty: Key Type – the cryptographic algorithm family used with the key
  • alg: Algorithm – the specific cryptographic algorithm used.
  • use: Use – the intended use of the public key. “sig” for signature, “enc” for encryption.
  • kid: Key ID – used to match a specific key

The verifier reads the jwks.json file and iterates over the list to find the one that matches the kid in the JWT header that they are trying to verify. When they find it, the can then convert the modulus exponent back into a public key and verify the JWT as per usual.

A quick guide to JWTs in PHP – Rob Allen

The most common use of JWTs is as an authentication token, usually within an OAuth2 workflow. Creating these tokens is part and parcel of the authentication library that you use.

I recently had a requirement to use a JWT independent of authentication and these are some notes on what I learned when researching with Lcobucci\JWT.

Make up of a JWT

To really understand JWTs, read RFC7519. For a more readable introduction, read the one on jwt.io.

Also, JWT is pronounced “jot“.

The most important thing about a JWT is that it contains data and a signature. The signature allows you to verify that the JWT’s data hasn’t been tampered with. This works because the signature is signed with a secret. This can be a shared secret (a symmetrical algorithm) or a public/private key pair (asymmetric algorithm).

In my case, I’m only interested in signing a JWT using a public/private key as my client cannot securely hold a shared secret.

Creating a public/private key

OpenSSL is your friend here. Create a keys directory and then use the command line.

To create a private key:

openssl genpkey -algorithm RSA -out keys/private.key -pkeyopt rsa_keygen_bits:2048

To create the public key from the private key:

openssl rsa -pubout -in keys/private.key -out keys/public.key

You will now have two files in the keys directory: private.key and public.key. Keep private.key safe and make public.key available to your clients.

Creating a token

Using Lcobucci\JWT, we create a Configuration:

use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory; $configuration = Configuration::forAsymmetricSigner( new Sha256(), InMemory::file(__DIR__ . '/keys/private.key'), InMemory::file(__DIR__ . '/keys/public.key')
);

From our configuration, we can obtain a builder and specify our token:

$keyId = '40597EB1-5E20-49B5-BDDF-B24D1B3B05B5';
$subject = '851828E8-C376-4026-9FD8-D2CCE406CD1C';
$now = new \DateTimeImmutable(); $builder = $configuration->builder() ->identifiedBy($keyId) ->relatedTo($subject) ->issuedBy('https://app.example.com') ->issuedAt($now) ->expiresAt($now->modify('+2 weeks')) ->withClaim('foo', 'bar');

The data elements in a JWT are called claims. There are a number of registered claims that are not mandatory, but you should set if they are relevant as all clients will recognise them and their purpose. In particular JWT ID, issuer, subject, issued at and expiration time are particularly useful. There are also a set of public claims that provide a standardised set of key names for common information which help avoid clashes.

Then there is the data specific to your application which are known as private claims. These can be whatever you like and in the example above, we have created a claim called “foo”.

Finally we create the token itself:

$token = $builder->getToken($configuration->signer(), $configuration->signingKey());
$tokenString = $token->toString();

We can then send the token string in response to an API request, etc.

Validating a token

When we receive a token, we need to validate it. This means that we check that it hasn’t been tampered with and we can also check that the data within it is as expected.

Firstly, we parse the token string back into a token object:

use Lcobucci\Clock\SystemClock;
use Lcobucci\JWT\Encoding\JoseEncoder;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Token\Parser; $parser = new Parser(new JoseEncoder());
$token = $parser->parse($tokenString);

To validate it, we need a validator and a set of constraints that the validator will test the token for:

$validator = new Validator(); $clock = new SystemClock();
$constraints = [ new SignedWith(new Sha256(), InMemory::file(__DIR__ . '/keys/public.key')), new LooseValidAt($clock), new IssuedBy('https://app.example.com'),
];

The three constraints here check that the JWT:

  • has been signed with the private key (by using the public key to verify)
  • is current and has not expired
  • was issued by the expected issuer

There are other constraints too – check the docs.

Note that the time based constraints use the PSR-20:Clock interface, so we use the original (another 719 bytes)

python-oracledb 2.1 and node-oracledb 6.4 have been released – Christopher Jones

I’m still on a long sabbatical so this will be a brief post. In my absence our Oracle Database driver team has been busy and are proud to announce that python-oracledb 2.1 and node-oracledb 6.4 have been released. Also our C Oracle Database Programming Interface for Drivers and Applications ODPI-C 5.2 is available from GitHub.

Photo by Jingda Chen on Unsplash

You can read about node-oracledb 6.4 for Node.js in Sharad Chandran’s post Node-oracledb 6.4 offers improved LOB and OSON support.

ODPI-C 5.2 release notes are here.

To see what’s new in the python-oracledb 2.1 release for Python, review the release notes and check out the considerable number of enhancements and fixes. Some highlights are:

  • asyncio support is out of pre-release status.
  • Some of the great behind-the-scenes work to reduce connection establishment timea for Oracle Database can now be used in the python-oracledb’s default Thin mode. (Thick mode will automatically get these benefits when compatible Instant Clients or Oracle Client libraries are used). In addition to the benefits for all Oracle Database 23c users, there is a new, extra option to use “TCP fast open” support when connecting to Oracle Autonomous Database Serverless (ADB-S).
  • Ongoing work exposes more of Oracle Database’s advanced JSON support, in particular to let you take advantage of Oracle’s efficient internal storage format (“OSON”) with its extended data types.

Installing or Upgrading python-oracledb

You can install or upgrade python-oracledb by running:

python -m pip install oracledb --upgrade

The pip options–proxy and –user may be useful in some environments. See python-oracledb Installation for details.

python-oracledb References

Home page: oracle.github.io/python-oracledb/index.html

Installation instructions: python-oracledb.readthedocs.io/en/latest/installation.html

Documentation: python-oracledb.readthedocs.io/en/latest/index.html

Release Notes: python-oracledb.readthedocs.io/en/latest/release_notes.html

Discussions: github.com/oracle/python-oracledb/discussions

Issues: github.com/oracle/python-oracledb/issues

Source Code Repository: github.com/oracle/python-oracledb