Bundle construction example
The code below will showcase how to obtain the required data for the bundle in Python for a simple key with ident test
.
All formatting and encoding functions used are defined in a further section below for readability.
For setup, the nfkm
and nfwar
libraries are imported and a connection with the hardserver is established:
import nfkm
import nfwar
conn = nfkm.connection(needworldinfo=True)
esn = "ABCD-ABCD-ABCD"
appname = "simple"
ident = "test"
bundle = {}
The root name and warrant data can be obtained from the warrant file:
warrant = nfwar.Warrant()
warrant.load(f"/opt/nfast/kmdata/warrants/{esn}")
bundle["root"] = warrant._root
bundle["warrant"] = format_bytes(warrant.warrant)
Some of the required fields can be retrieved from the key object:
key = nfkm.findkey(conn, appname, ident)
bundle["pubkeydata"] = format_pubkey(key.pubblob)
bundle["kcmsg"] = format_bytes(key.kcmsg)
bundle["kcsig"] = format_signature(key.kcsig)
bundle["modstatemsg"] = format_bytes(key.modstatemsg)
bundle["modstatesig"] = format_signature(key.modstatesig)
You can also retrieve some fields from the world info:
world = nfkm.getinfo(conn)
bundle["ciphersuite"] = str(world.suite)
key_hash_fields = ["hkre", "hkra", "hkfips", "hkmc", "hkm"]
for field_name in key_hash_fields:
key_hash = world[field_name]
# skip missing hash, e.g. hkfips in non-FIPS worlds or hkre for non-recoverable keys
if key_hash != nfkm.Hash():
bundle[field_name] = format_hash(key_hash)
The remaining fields can be extracted from the world file:
worldfile = open("/opt/nfast/kmdata/local/world", "rb").read()
worldfile, _ = nfkm.unmarshal(worldfile, nfkm.KeyMgmtFile)
bundle["knsopub"] = format_pubkey(get_kmf_entry(worldfile, "BlobPubKNSO"))
for cert_name in ["CertKMaKMCbKNSO", "CertKMaKMCaKFIPSbKNSO", "CertKREaKRAbKNSO"]:
cert_value = get_kmf_entry(worldfile, cert_name)
# skip missing cert, e.g. CertKMaKMCaKFIPSbKNSO in non-FIPS worlds
if cert_value is not None:
bundle[cert_name] = format_signature(cert_value)
Warrant for nShield 5 modules
For nShield 5 modules, the warrant doesn’t get installed on the filesystem but instead exists only on the module.
It can be requested using the command line function retrievewarrants
or directly from the module:
for module in world.modules:
if module.esn == esn and module.state == "Usable":
cmd = nfkm.Command(
['GetModuleState', 0, module.module, 'attribs_present', ["WarrantKLF2"]]
)
reply = conn.transact(cmd)
attribs_list = reply.reply.state
if len(attribs_list.attribs) == 1:
warrant_attrib = attribs_list.attribs[0]
if (isinstance(warrant_attrib, nfkm.ModuleAttrib) and
warrant_attrib.tag == "WarrantKLF2"):
bundle["warrant"] = format_bytes(bytes(warrant_attrib.value.warrant))
Module state certificate requirements
The module state certificate always needs the ESN and KML or KMLEx attributes. For persistent keys the certificate also needs the KNSO or KNSOEx attributes and the KMList or ModKeyInfoEx attributes. Prior to Security World v13.5, the module state certificate would be generated too early and wouldn’t contain KNSO.
The certificate can be regenerated as long as the right module is available and hasn’t been reinitialized.
# retrieve correct HKML from incomplete modstatemsg
original_hkml = None
modstatemsg, _ = nfkm.unmarshal(modstatemsg, nfkm.ModCertMsg)
for attrib in modstatemsg.data.state.attribs:
if attrib.tag == "KML":
original_hkml = nfkm.KeyHashEx(["SHA1Hash", attrib.value.hkml])
if "KMLEx" in nfkm.ModuleAttribTag.words and attrib.tag == "KMLEx":
original_hkml = attrib.value.hk
# regenerate modstatemsg and modstatesig for correct module
for module in world.modules:
if (module.esn == esn and module.state == "Usable" and
isinstance(original_hkml, nfkm.ByteBlock) and
module.hkml == original_hkml.data.hash):
cmd = nfkm.Command(["SignModuleState", 0, module.module, 0, "KLF2"])
cert = conn.transact(cmd).reply.cert
bundle["modstatemsg"] = format_bytes(cert.modcertmsg)
bundle["modstatesig"] = format_signature(cert.signature)
Formatting functions
import base64
import choosealg
def format_bytes(value):
return str(base64.urlsafe_b64encode(value), "ASCII")
def format_signature(sig):
if not isinstance(sig, nfkm.CipherText):
if isinstance(sig, bytes):
sig = str(sig, "ASCII")
sig = str(sig, "ASCII").split(" ")
sig = nfkm.CipherText(sig)
sig = nfkm.marshal(sig)
return format_bytes(sig)
def format_hash(hash_):
if isinstance(hash_, nfkm.Hash):
hash_ = nfkm.KeyHashEx(["SHA1Hash", hash_])
return format_bytes(nfkm.marshal(hash_))
def unpack_blob(blob_data):
public_blob_keys = {
nfkm.Hash("c2be99fe1c77f1b75d48e2fd2df8dffc0c969bcb"):
nfkm.KeyData(["Rijndael", "00" * 32]),
nfkm.Hash("1d572201be533ebc89f30fdd8f3fac6ca3395bf0"):
nfkm.KeyData(["DES3", "01" * 24]),
}
blob_data, _ = nfkm.unmarshal(blob_data, nfkm.BlobData)
blobkey = public_blob_keys[blob_data.exdata.hkm]
if blob_data.imech & 0x80000000:
mech = blob_data.imech & 0x7FFFFFFF
cipher_text = nfkm.CipherText([mech, blob_data.ka_data])
else:
cipher_text = nfkm.CipherText(["DES3wSHA1", blob_data.ka_data, blob_data.datalen])
plain_text = choosealg.decrypt(blobkey, cipher_text)
bcb, _ = nfkm.unmarshal(plain_text.data.data, nfkm.BlobCryptBlock)
key, _ = nfkm.unmarshal(bcb.key, nfkm.KeyData)
return key
def format_pubkey(k):
if isinstance(k, nfkm.ByteBlock):
k = unpack_blob(k)
return format_bytes(nfkm.marshal(k))
def get_kmf_entry(kmf, entry_type):
entry_type = nfkm.KeyMgmtEntType(entry_type).getvalue()
for entry in kmf.entries:
if entry.type == entry_type:
return entry.data
return None