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.