To answer my own question...
CERTCertificate contains two member variables derCert and derPublicKey (both of type SECItem) that I was interested in.
Save/Load public key
To get the public key you can either save CERTCertificate derPublicKey value or get the same value from SECKEYPublicKey:
// cert is of type CERTCertificate
SECKEYPublicKey* publicKey = CERT_ExtractPublicKey( cert );
SECItem* derPublicKey = SECKEY_EncodeDERSubjectPublicKeyInfo( publicKey );
// put the key into string
std::string keyString( (char*)derPublicKey->data, derPublicKey->len );
To decode the public key from string you use:
SECItem derKeyItem = {
.type = siBuffer,
.data = (unsigned char*)keyString.c_str(),
.len = (unsigned int)keyString.size()
};
CERTSubjectPublicKeyInfo* pubInf = SECKEY_DecodeDERSubjectPublicKeyInfo( &derKeyItem );
SECKEYPublicKey* publicKey = SECKEY_ExtractPublicKey( pubInf );
Save/Load certificate and get public key
To save certificate you save derCert.
To load certificate and get public key:
SECItem derCertItem = {
.type = siBuffer,
.data = (unsigned char*)certStr.c_str(),
.len = (unsigned int)certStr.size()
};
CERTCertificate cert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &derCertItem, nullptr, false, false);
SECKEYPublicKey* publicKey = CERT_ExtractPublicKey(cert);
Note
The above code is sample code. For production code smart pointers (unique/shared) should be used and their destructors should call the appropriate nss destroy functions.