Under Construction

Verifying the digital signature of a fileset

We’ll demonstrate the process for verifying a fileset in detail. As an example, we’ll use the fileset update bos.rte.libc version 7.3.2.1, which we’ve stored in the temporary directory /tmp/lpps:

# ls -l /tmp/lpps
total 25352
-rw-r--r--    1 root     system         4122 Jul 23 14:04 .toc
-rw-r--r--    1 root     system     12970240 Jul 23 14:04 bos.rte.libc.7.3.2.1.U
#

When installing a fileset, the shell script /usr/sbin/pkgverify is called to verify the digital signature. The absolute path of the fileset’s BFF file is specified as an argument:

/usr/sbin/pkgverify /tmp/lpps/bos.rte.libc.7.3.2.1.U

However, the shell script can also be called as root and the verification for a fileset can be started manually. Below, we have only described the essential steps of the shell script for the above case.

First, the package name and build date are determined:

  +128  # determine package name / date of creation
  +129  package=$($awk -v lfn=${infile##*/} '{ if ($1 == lfn) {print $(NF-1); exit} }' "${infile%/*}/.toc")
  +130  timestamp=$($restore -Tvqf $infile 2>&1 | $awk -F": " '/backup/ {print $NF; exit}' | $awk '{ gsub(/CDT |CST /,""); print}' | $awk '{ gsub(/ /,"_"); print}' | $awk '{ gsub(/:/,"-"); print}')

Note: The absolute path of the BFF file is stored in the variable infile.

The two variables package and timestamp then have the following values for our case:

# echo $package
bos
# echo $timestamp
Thu_Mar_21_11-13-28_2024
#

Then a list of the package’s associated filesets is extracted:

  +132  # build list of supported filesets
  +133  FilesetList=$($installp -Ld $tmpstorage 2>/dev/null |$awk -v lfn=$package -F: '{ if ($1 == lfn) print $1":"$2":"$3":"$5}')

In our case this results in:

# echo $FilesetList
bos:bos.rte.libc:7.3.2.1:S
#

Note: The 4 fields are the package name, the fileset name, the version and the fileset type (here “S” for Single Update).

If multiple filesets are present, the first one is used. Fileset name, version, and type are extracted into separate variables:

  +139          match=$(echo $FilesetList | $awk '{print $1}')
  +140          fileset=$(echo $match | $awk -F: '{print $2}')
  +141          vrmf=$(echo $match | $awk -F: '{print $3}')
  +142          ftype=$(echo $match | $awk -F: '{print $4}')

In our case, we get the following values for the variables fileset, vrmf and ftype:

# echo $fileset
bos.rte.libc
# echo $vrmf
7.3.2.1
# echo $ftype
S
#

The version is broken down into its 4 components:

  +157  # build odmadd stanza for catalog record search
  +158  ver=$(echo $vrmf | $cut -f1 -d".")
  +159  rel=$(echo $vrmf | $cut -f2 -d".")
  +160  mod=$(echo $vrmf | $cut -f3 -d".")
  +161  fix=$(echo $vrmf | $cut -f4 -d".")

Which in our case results in the following values:

# echo $ver
7
# echo $rel
3
# echo $mod
2
# echo $fix
1
#

This provides all the information needed to search the DSC (ODM dsc_inventory) for the entry for the specific version of the bos.rte.libc fileset:

  +163  # obtain the key & signature from dsc (NOTE: empty ftype is allowed)
  +164  key_id=$(load_key_from_catalog $package $fileset $timestamp $ver $rel $mod $fix $ftype)
  +165  signature=$(load_sig_from_catalog $package $fileset $timestamp $ver $rel $mod $fix $ftype)

The two functions load_key_from_catalog and load_sig_from_catalog ultimately just start the following odmget command:

# ODMDIR=/usr/lib/objrepos /usr/bin/odmget -q “pkg_name=bos AND lpp_name=bos.rte.libc AND ver=7 AND rel=3 AND mod=2 AND fix=1 AND timestamp=Thu_Mar_21_11-13-28_2024 AND ftype=S” dsc_inventory

dsc_inventory:
        pkg_name = "bos"
        lpp_name = "bos.rte.libc"
        ver = 7
        rel = 3
        mod = 2
        fix = 1
        ftype = "S"
        signature = "lRjpNE9gD+6nWyvPQJH0RoZgISpJrXxYUwlJoZuqfjCNfUWy73WgduuZRnkGeEeGHrC/LGy1VBH+NgXDfqKN+NxKZQmS7IA+wo4G0LsqcidzDIKE4ONbSOhQeA9k8izFxeFrLqFLmkntq6S3vcvku+5OF7ahoy6CuCmdczg580bs/SuQpEjp46XdDHwb6S8YlYBLYWvxunOlXVLneJBaOzCY/KGrKPbnHEwUhKwxamv3xoPWdqI7nOSjCHYoysNVUsIbukYId/XdmVeSIrC8/6EWmuxvZG/aHM0GDAoQdLy6zSNQ8zMlCBM2rfJcVxkgKNHFkzuNPDTW7aFwXVJ4XA=="
        timestamp = "Thu_Mar_21_11-13-28_2024"
        key = "3"
#

The function load_key_from_catalog extracts the key field and the function load_sig_from_catalog extracts the signature field:

# echo $key_id
3
# echo $signature
lRjpNE9gD+6nWyvPQJH0RoZgISpJrXxYUwlJoZuqfjCNfUWy73WgduuZRnkGeEeGHrC/LGy1VBH+NgXDfqKN+NxKZQmS7IA+wo4G0LsqcidzDIKE4ONbSOhQeA9k8izFxeFrLqFLmkntq6S3vcvku+5OF7ahoy6CuCmdczg580bs/SuQpEjp46XdDHwb6S8YlYBLYWvxunOlXVLneJBaOzCY/KGrKPbnHEwUhKwxamv3xoPWdqI7nOSjCHYoysNVUsIbukYId/XdmVeSIrC8/6EWmuxvZG/aHM0GDAoQdLy6zSNQ8zMlCBM2rfJcVxkgKNHFkzuNPDTW7aFwXVJ4XA==
#

Note: The found key_id is the ID of the associated certificate, not the ID of the associated public key. The certificate and public key are usually both contained in the ODM dsc_key.

Next, the ODM entry of the certificate with the determined ID (key_id) is searched:

  +170  # check if key needs extracting from keystore
  +171  set_global_record $key_id KEY
  +172  record_exists=$?

The function set_global_record also starts odmget to find the certificate:

# ODMDIR=/usr/lib/objrepos odmget -q id=3 dsc_key

dsc_key:
        id = 3
        type = "certificate"
        alias = "aixpublic_73"
        location = "/etc/security/pkgverify/certfile/aixpublic_73.pem"
        modulus = "b19c33e5eb0b4e2fbdcff3b2eeec31d5"
        hash = "sha256"
        keystore = "03"
#

If an entry is found (this is the default case), the exit status of the set_global_record function is 1 and a number of global variables are set:

# echo $record_exists
1
# echo $key_id
3
# echo $key_type
certificate
# echo $key_alias
aixpublic_73
# echo $key_location
/etc/security/pkgverify/certfile/aixpublic_73.pem
# echo $key_modulus
b19c33e5eb0b4e2fbdcff3b2eeec31d5
# echo $key_hash
sha256
#

Now the corresponding keystore is determined using the found certificate entry in dsc_key and with the help of the keystore the public key belonging to the certificate is determined:

  +176          # locate verification key
  +177          s_id=$($odmget -q "type=certificate AND id=$key_id" ${odm_key} 2>/dev/null| $awk '/keystore/{print $3 ; exit}')
  +178          k_id=$($odmget -q "type=key AND keystore=$s_id" ${odm_key} 2>/dev/null| $awk '/id/{print $3 ; exit}')

The keystore ID is stored in the variable s_id, the ID of the public key in the variable k_id:

# echo $s_id
"03"
# echo $k_id
1
#

With the determined ID of the public key (k_id), the corresponding ODM entry is now loaded from the dsc_key ODM:

  +191          set_global_record $k_id KEY

The following variables are set:

# echo $key_id
1
# echo $key_type
key
# echo $key_alias
aixpublic_73
# echo $key_location
/etc/security/pkgverify/key/aixpublic_73.key
# echo $key_modulus
8fc5d19e0ad949c89ab9469531101b37
# echo $key_hash
sha256
#

The public key to be used to verify the signature is located in the file /etc/security/pkgverify/key/aixpublic_73.key (variable key_location) and the algorithm to be used is sha256 (variable key_hash).

The signature from the DSC determined at the very top is now written to a temporary file and then converted from base64 format back to a binary format:

  +210  # verify signature
  +211  echo $signature > $tmpsign
  +212  $openssl base64 -d -in $tmpsign -out $tmpfile || exit 7

The digital signature can now be verified using the openssl command:

  +213  $openssl dgst -${key_hash} -verify $key_location -signature $tmpfile $infile || exit 8

Verification is successful for this fileset:

# /usr/bin/openssl dgst -sha256 -verify /etc/security/pkgverify/key/aixpublic_73.key -signature /tmp/_pkgverify_12779880.dir/_pkgverify_12779880.dgst /tmp/lpps/bos.rte.libc.7.3.2.1.U
Verified OK
#

All temporarily created files and directories will then be deleted.

The case shown is the simplest verification process, and the one that is usually used. However, the script provides a few additional scenarios. If a public key and/or certificate doesn’t exist, they are automatically generated using the keystore. This is only necessary once.