Background
Sorry for the long read ;). But I think this needs lots of thinking and the details are just needed.
I have been studying currently approved PKCS#11 specifications 2.40+errata 1 [1, 2, 3, 6, 7, 8] and trying to figure out how we could control who can access and operate on which PKCS#11 objects.
Within context of embedded systems there seems to be a need to differentiate between USER's who operate and by Groups/Applications that operate on TOKENS and in OBJECTS within those TOKENS.
Within context of embedded systems we get additional problems of manufacturing time storage and then also runtime usage when device is in the field. In an example if device is programmed with 802.1AR-IDevID [10] during manufacturing process then it would be really nice if those objects cannot be "accidentially" deleted as that would render device inoperable. Then in example if 802.1AR-LDevID would be issued from the cloud (or from the enterprise network) then that access might need to be protected for system software. And if there is 3rd party/customer software running on the device then it might be that not all applications get equal access to OBJECTS stored in TOKENS.
PKCS#11 mechanism itself are some what "different use case thinking" and only access control mechanism that they provide out-of-the-box is either Security Officer (SO) or User login and then what those privileges provide.
In specification SO is more or less used manage User Login PINs should they be lost and thats where that extra power kinda ends. So difference between SO and User is not really usable for access control for objects as there is also a legit case for User to be able to create own OBJECTS.
When token is not yet initialized one can completely take over the token and program it to callees wishes.
If token is configured with CKF_PROTECTED_AUTHENTICATION_PATH then there can be auxilary method to provide PIN code eg. by the hardware PIN entry device.
With in example with nCipher's HSM there is preload facility that can be loaded before PKCS#11 application. In this model preload asks for PIN code and then application can just provide empty/any PIN and it just succeeds because preload facility already handled the login. Their Cryptoki Library just detects this situation from environment and changes the internal behavior.
With HSM's and SmartCards the thinking model is that we protect access to hardware (HSM + smart card), then to smart cards with HSM for authentication/security world and protecting additionally the associated PIN's to protect different cards. This protection method is not as such so usable withing context of embedded systems.
Now for embedded systems the SO/User PIN entry becomes somewhat cumbersome to manage and I believe that should be replaced with user/group/application access control. Problem for this is that only standard method within this area is CKF_PROTECTED_AUTHENTICATION_PATH which is slightly a miss but we could use this method to indicate that now PKCS#11 TA has been compiled not to actually use PIN codes but require external authentication.
Now that we have hardening in the kernel for Client UUID we can utilize Client UUIDs for access control.
Case 1 - Login as SO or as USER
We could configure somehow the Client UUID for Security Officer and Client UUID for User. This could be in example group id based so we can say that in order to login as security officer calling process need to be member of group "pkcs11-so" and in order to login as user calling process need to be member of "pkcs11-user".
Now in PKCS11 TA when SO Login is requested it would check that now Client UUID is configured for "pkcs11-so" and it can then allow login to happen.
This brings couple of new problems. How is this configuration being done:
- Nicest would be that during token initialization from OS side we could configure also the Client UUID for SO and Client UUID for User.
- Static approach would be to configure Client UUID's for those logins during PKCS11 TA compilation.
In order to do 1) we could configure token(s) to be CKF_PROTECTED_AUTHENTICATION_PATH and then use PIN value for SO/User for Client UUID entry for the token:
- PKCS11 TA connection with Client UUID="gid:"
- C_InitToken(slot, pin="", label="mylabel")
- PKCS11 TA need to syntax check that SO Client UUID is valid
- PKCS11 TA need to compare given SO Client UUID against Client UUID from connection.
- PKCS11 TA need to associated SO Client UUID for the
- C_OpenSession(slot, session=&session, ...)
- C_Login(session, userType=CKU_SO, pin="")
- PKCS11 TA need to compare given SO Client UUID against Client UUID from connection.
- PKCS11 TA need to check that Client UUID is allowed for SO login for
- C_InitPIN(session, pin="")
- PKCS11 TA need to syntax check that User Client UUID is valid
- PKCS11 TA need to associated User Client UUID for the
- C_Logout(session)
- C_CloseSession(session)
- NOTE: Now we have a problem here -- we need to close the PKCS11 TA connection and open a new for CKU_USER so that we can configure Client UUID before actually issuing C_Login. -- in theory if this is a different process one shouldn't notice a thing.
- Open new PKCS11 TA connection with Client UUID="gid:"
- C_OpenSession(slot, session=&session, ...)
- C_Login(session, userType=CKU_USER, pin="")
- PKCS11 TA need to compare given User Client UUID against Client UUID from connection.
- PKCS11 TA need to check that Client UUID is allowed for User login for the
OR with pin=NULL_PTR's:
- Open new PKCS11 TA connection with Client UUID="gid:"
- C_InitToken(slot, pin=NULL_PTR, label="mylabel")
- PKCS11 TA would record current Client UUID as SO Client UUID
- PKCS11 TA need to associated SO Client UUID for the
- C_OpenSession(slot, session=&session, ...)
- C_Login(session, userType=CKU_SO, pin=NULL_PTR)
- PKCS11 TA would record current Client UUID as SO Client UUID
- PKCS11 TA need to check that Client UUID is allowed for SO login for the
- C_InitPIN(session, pin="")
- NOTE: Now we have a problem here. As we need to login with CKU_SO and calling client's Client UUID need to be SO Client UUID so we cannot just autodetect User Client UUID from connection by using pin=NULL_PTR. We need to provide User Client UUID as argument and parse that and just use it.
- PKCS11 TA need to syntax check that User Client UUID is valid
- PKCS11 TA need to associated User Client UUID for the
- C_Logout(session)
- C_CloseSession(session)
- NOTE: Now we have a problem here -- we need to close the PKCS11 TA connection and open a new for CKU_USER so that we can configure Client UUID before actually issuing C_Login. -- in theory if this is a different process one shouldn't notice a thing.
- Open new PKCS11 TA connection with Client UUID="gid:"
- C_OpenSession(slot, session=&session, ...)
- C_Login(session, userType=CKU_USER, pin=NULL_PTR)
- PKCS11 TA would record current Client UUID as User Client UUID
- PKCS11 TA need to check that Client UUID is allowed for User login for the
Probably the C_InitToken(pin=NULL_PTR) approach is actually better because then we could auto detect whether we want to use Client UUID's or PINs for authentication. If pin=NULL_PTR would be given here the token would need to be assoicated for Client UUID authentication. Then in C_Login() we could just ignore pin value altogether and just use Client UUID.
In order to do 2) that would change above in a way that SO/User Client UUID's would be fixed and pin values would be just ignored.
Now this "associate token with" brings a new problem. How to store this information. PKCS11 has lots of different ATTRIBUTES. So if we would have some ATTRIBUTES assigned for the purpose we could utilize those and store them for token object (CKA_TOKEN). In PKCS11 there exists space for vendor extensions CKA_VENDOR_DEFINED (0x80000000UL) and also OASIS PKCS11 TC [9] has a mechanism to register identifiers (PKCS11 Technical Committee Standing Rule on Identifier Allocation). In order to do this properly I think it is best to allocate official ID's either in vendor space or in common space for the purpose so there would not be collisions.
Following ATTRIBUTES would be needed for TOKEN objects:
- CKA_<TEE|OPTEE|GP_TEE>_SO_CLIENT_UUID with value of UUID
- CKA_<TEE|OPTEE|GP_TEE>_USER_CLIENT_UUID with value of UUID
Case 2 - Separating different users
In theory we could have multiple TOKENs compiled in PKCS11 TA and then just initialize them for different Client UUID's.
Some use cases could be handled by this -- expect for the rights to protect manufacturing time issued objects from DELETION while still allowing applications to use them.
In theory we could abuse a bit Security Officer role and configure different objects so that only Security Officer can delete them. But those need to be most likely OBJECT based controls.
When creating a object (or attach new ATTRIBUTE later) one would add a new feature for OBJECT to remove rights for User to DELETE (eg. call C_DestroyObject).
If we want to protect operations right from creation time then when new secret is generated by C_GenerateKey or by C_GenerateKeyPair we need to have ATTRIBUTE that defines wanted behavior on template for operation. Later on we should be able to use C_SetAttributeValue to change ATTRIBUTES while still protecting so that USER cannot alter attributes for protected OBJECTS.
If we want to protect OBJECTS by different users in same TOKEN we need to have some ACL ATTRIBUTES.
We could have something like:
- CKA_<TEE|OPTEE|GP_TEE|COMMON>_ACL_USER_DELETE with value of Boolean (default: true)
- CKA_<TEE|OPTEE|GP_TEE|COMMON>_ACL_USER_OPERATE with value of Boolean (default: true)
- CKA_<TEE|OPTEE|GP_TEE|COMMON>_ACL_USER_READ with value of Boolean (default: true)
OR with more fine grained model:
- CKA_<TEE|OPTEE|GP_TEE>_ACL_DELETE with value of List (default: true if not defined for OBJECT, otherwise needs to be in list)
- CKA_<TEE|OPTEE|GP_TEE>_ACL_OPERATE with value of List (default: true if not defined for OBJECT, otherwise needs to be in list)
- CKA_<TEE|OPTEE|GP_TEE>_ACL_READ with value of List (default: true if not defined for OBJECT, otherwise needs to be in list)
Now this brings a bit of a new issue that we might want to have also List stored for TOKEN access. So that different user/groups/applications can still login as USER but has access separation within the TOKEN.
So perhaps we need to change ATTRIBUTES defined for TOKEN above with:
- CKA_<TEE|OPTEE|GP_TEE>_ACL_SO_CLIENT_UUID with value of List
- CKA_<TEE|OPTEE|GP_TEE>_ACL_USER_CLIENT_UUID with value of List
Case 3 - Protecting against exporting
PKCS#11 provides C_WrapKey that can be used to export secrets.
We need to have possibility to make sure that private keys and secrets that are not meant exported from secure storage can be marked so (eg. set CKA_EXTRACTABLE=CK_FALSE).
I believe for this everything is there.
Case 4 - Backing up secure storage
Now this is a bit of case do we really want to do it or not.
But someone could have a use case that we should be able to backup specific TOKEN or specific OBJECT.
I suppose for OBJECT one could set CKA_EXTRACTABLE=CK_TRUE then OBJECT secret could be wrapped out. But this kinda feels a bit bad but would be standard method that could be utilized if someone wants to use that. This is also only method if one wants to make a backup so that it can be restored later to a new device if original devices gets broken. But I would recommend not to use this method and just issue a new credentials for new device.
As a slight note on my thinking on protecting secrets then we have a question of backing up data in a way that it can be restored back to same device. Eg. take backup before firmware update and if later the storage gets corrupted by some how one could restore backup and then device would just functional like before.
Now this has some additional problems -- we could use CKA_WRAP_WITH_TRUSTED and then just make sure that one cannot easily set CKA_TRUSTED for keys -- as that has a problem with CKA_TRUSTED it might be a good idea to have separate ATTRIBUTE for the special backup use case. Also if factory reset is perfomred and secure storage would be reset then we would need to have exactly same secret in order to unwrap the key back in.
We could have something like CKA_TEE_TRUSTED that can only be set when generating key from HUK. Which would require some attribute in template list to indicate that now we create HUK based secret. This of course needs to be always same when creating it in same device.
Case 5 - Protecting manufacturing time created secrets
Now if we issues IDevID for the device it should be for life. In any event in the device it should NOT BE possible to remove those.
Now we have a bit of a problem of secure storage and how it behaves in OP-TEE.
In REE side we cannot know what is on secure storage files when they are stored in file system. But what could be done is backing up whole secure storage into secondary location.
Eg. when manufacturing flow has been done and data has been synchronized (eg. written to the real storage device) a backup of whole secure storage could be made to secondary area that will be read-only after device manufacturing phase.
If one performs factory reset then normal secure storage would be "zapped" and one restored from manufacruing backup of secure storage. This should work as long as secure storage is in file system.
If secure storage is in RPMB area then we have some extra problems...
In theory we could have multiple locations for secure storage but that requires new defines for where to store what secure storage items. If such thing would be implemented it would probably need to be PKCS#11 TOKEN based. And probably would need to be configured during compilation time. Also read-only status during normal life is something that REE would need to provide back to OP-TEE.
References
[1] http://docs.oasis-open.org/pkcs11/pkcs11-ug/v2.40/pkcs11-ug-v2.40.html
[2] http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/pkcs11-base-v2.40.html
[3] http://docs.oasis-open.org/pkcs11/pkcs11-profiles/v2.40/pkcs11-profiles-v2.40.html
[4] http://docs.oasis-open.org/pkcs11/pkcs11-curr/v2.40/pkcs11-curr-v2.40.html
[5] http://docs.oasis-open.org/pkcs11/pkcs11-hist/v2.40/pkcs11-hist-v2.40.html
[6] http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/errata01/os/include/pkcs11-v2.40/pkcs11.h
[7] http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/errata01/os/include/pkcs11-v2.40/pkcs11t.h
[8] http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/errata01/os/include/pkcs11-v2.40/pkcs11f.h
[9] https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=pkcs11
[10] https://1.ieee802.org/security/802-1ar/
Background
Sorry for the long read ;). But I think this needs lots of thinking and the details are just needed.
I have been studying currently approved PKCS#11 specifications 2.40+errata 1 [1, 2, 3, 6, 7, 8] and trying to figure out how we could control who can access and operate on which PKCS#11 objects.
Within context of embedded systems there seems to be a need to differentiate between USER's who operate and by Groups/Applications that operate on TOKENS and in OBJECTS within those TOKENS.
Within context of embedded systems we get additional problems of manufacturing time storage and then also runtime usage when device is in the field. In an example if device is programmed with 802.1AR-IDevID [10] during manufacturing process then it would be really nice if those objects cannot be "accidentially" deleted as that would render device inoperable. Then in example if 802.1AR-LDevID would be issued from the cloud (or from the enterprise network) then that access might need to be protected for system software. And if there is 3rd party/customer software running on the device then it might be that not all applications get equal access to OBJECTS stored in TOKENS.
PKCS#11 mechanism itself are some what "different use case thinking" and only access control mechanism that they provide out-of-the-box is either Security Officer (SO) or User login and then what those privileges provide.
In specification SO is more or less used manage User Login PINs should they be lost and thats where that extra power kinda ends. So difference between SO and User is not really usable for access control for objects as there is also a legit case for User to be able to create own OBJECTS.
When token is not yet initialized one can completely take over the token and program it to callees wishes.
If token is configured with CKF_PROTECTED_AUTHENTICATION_PATH then there can be auxilary method to provide PIN code eg. by the hardware PIN entry device.
With in example with nCipher's HSM there is preload facility that can be loaded before PKCS#11 application. In this model preload asks for PIN code and then application can just provide empty/any PIN and it just succeeds because preload facility already handled the login. Their Cryptoki Library just detects this situation from environment and changes the internal behavior.
With HSM's and SmartCards the thinking model is that we protect access to hardware (HSM + smart card), then to smart cards with HSM for authentication/security world and protecting additionally the associated PIN's to protect different cards. This protection method is not as such so usable withing context of embedded systems.
Now for embedded systems the SO/User PIN entry becomes somewhat cumbersome to manage and I believe that should be replaced with user/group/application access control. Problem for this is that only standard method within this area is CKF_PROTECTED_AUTHENTICATION_PATH which is slightly a miss but we could use this method to indicate that now PKCS#11 TA has been compiled not to actually use PIN codes but require external authentication.
Now that we have hardening in the kernel for Client UUID we can utilize Client UUIDs for access control.
Case 1 - Login as SO or as USER
We could configure somehow the Client UUID for Security Officer and Client UUID for User. This could be in example group id based so we can say that in order to login as security officer calling process need to be member of group "pkcs11-so" and in order to login as user calling process need to be member of "pkcs11-user".
Now in PKCS11 TA when SO Login is requested it would check that now Client UUID is configured for "pkcs11-so" and it can then allow login to happen.
This brings couple of new problems. How is this configuration being done:
In order to do 1) we could configure token(s) to be CKF_PROTECTED_AUTHENTICATION_PATH and then use PIN value for SO/User for Client UUID entry for the token:
OR with pin=NULL_PTR's:
Probably the C_InitToken(pin=NULL_PTR) approach is actually better because then we could auto detect whether we want to use Client UUID's or PINs for authentication. If pin=NULL_PTR would be given here the token would need to be assoicated for Client UUID authentication. Then in C_Login() we could just ignore pin value altogether and just use Client UUID.
In order to do 2) that would change above in a way that SO/User Client UUID's would be fixed and pin values would be just ignored.
Now this "associate token with" brings a new problem. How to store this information. PKCS11 has lots of different ATTRIBUTES. So if we would have some ATTRIBUTES assigned for the purpose we could utilize those and store them for token object (CKA_TOKEN). In PKCS11 there exists space for vendor extensions CKA_VENDOR_DEFINED (0x80000000UL) and also OASIS PKCS11 TC [9] has a mechanism to register identifiers (PKCS11 Technical Committee Standing Rule on Identifier Allocation). In order to do this properly I think it is best to allocate official ID's either in vendor space or in common space for the purpose so there would not be collisions.
Following ATTRIBUTES would be needed for TOKEN objects:
Case 2 - Separating different users
In theory we could have multiple TOKENs compiled in PKCS11 TA and then just initialize them for different Client UUID's.
Some use cases could be handled by this -- expect for the rights to protect manufacturing time issued objects from DELETION while still allowing applications to use them.
In theory we could abuse a bit Security Officer role and configure different objects so that only Security Officer can delete them. But those need to be most likely OBJECT based controls.
When creating a object (or attach new ATTRIBUTE later) one would add a new feature for OBJECT to remove rights for User to DELETE (eg. call C_DestroyObject).
If we want to protect operations right from creation time then when new secret is generated by C_GenerateKey or by C_GenerateKeyPair we need to have ATTRIBUTE that defines wanted behavior on template for operation. Later on we should be able to use C_SetAttributeValue to change ATTRIBUTES while still protecting so that USER cannot alter attributes for protected OBJECTS.
If we want to protect OBJECTS by different users in same TOKEN we need to have some ACL ATTRIBUTES.
We could have something like:
OR with more fine grained model:
Now this brings a bit of a new issue that we might want to have also List stored for TOKEN access. So that different user/groups/applications can still login as USER but has access separation within the TOKEN.
So perhaps we need to change ATTRIBUTES defined for TOKEN above with:
Case 3 - Protecting against exporting
PKCS#11 provides C_WrapKey that can be used to export secrets.
We need to have possibility to make sure that private keys and secrets that are not meant exported from secure storage can be marked so (eg. set CKA_EXTRACTABLE=CK_FALSE).
I believe for this everything is there.
Case 4 - Backing up secure storage
Now this is a bit of case do we really want to do it or not.
But someone could have a use case that we should be able to backup specific TOKEN or specific OBJECT.
I suppose for OBJECT one could set CKA_EXTRACTABLE=CK_TRUE then OBJECT secret could be wrapped out. But this kinda feels a bit bad but would be standard method that could be utilized if someone wants to use that. This is also only method if one wants to make a backup so that it can be restored later to a new device if original devices gets broken. But I would recommend not to use this method and just issue a new credentials for new device.
As a slight note on my thinking on protecting secrets then we have a question of backing up data in a way that it can be restored back to same device. Eg. take backup before firmware update and if later the storage gets corrupted by some how one could restore backup and then device would just functional like before.
Now this has some additional problems -- we could use CKA_WRAP_WITH_TRUSTED and then just make sure that one cannot easily set CKA_TRUSTED for keys -- as that has a problem with CKA_TRUSTED it might be a good idea to have separate ATTRIBUTE for the special backup use case. Also if factory reset is perfomred and secure storage would be reset then we would need to have exactly same secret in order to unwrap the key back in.
We could have something like CKA_TEE_TRUSTED that can only be set when generating key from HUK. Which would require some attribute in template list to indicate that now we create HUK based secret. This of course needs to be always same when creating it in same device.
Case 5 - Protecting manufacturing time created secrets
Now if we issues IDevID for the device it should be for life. In any event in the device it should NOT BE possible to remove those.
Now we have a bit of a problem of secure storage and how it behaves in OP-TEE.
In REE side we cannot know what is on secure storage files when they are stored in file system. But what could be done is backing up whole secure storage into secondary location.
Eg. when manufacturing flow has been done and data has been synchronized (eg. written to the real storage device) a backup of whole secure storage could be made to secondary area that will be read-only after device manufacturing phase.
If one performs factory reset then normal secure storage would be "zapped" and one restored from manufacruing backup of secure storage. This should work as long as secure storage is in file system.
If secure storage is in RPMB area then we have some extra problems...
In theory we could have multiple locations for secure storage but that requires new defines for where to store what secure storage items. If such thing would be implemented it would probably need to be PKCS#11 TOKEN based. And probably would need to be configured during compilation time. Also read-only status during normal life is something that REE would need to provide back to OP-TEE.
References
[1] http://docs.oasis-open.org/pkcs11/pkcs11-ug/v2.40/pkcs11-ug-v2.40.html
[2] http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/pkcs11-base-v2.40.html
[3] http://docs.oasis-open.org/pkcs11/pkcs11-profiles/v2.40/pkcs11-profiles-v2.40.html
[4] http://docs.oasis-open.org/pkcs11/pkcs11-curr/v2.40/pkcs11-curr-v2.40.html
[5] http://docs.oasis-open.org/pkcs11/pkcs11-hist/v2.40/pkcs11-hist-v2.40.html
[6] http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/errata01/os/include/pkcs11-v2.40/pkcs11.h
[7] http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/errata01/os/include/pkcs11-v2.40/pkcs11t.h
[8] http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/errata01/os/include/pkcs11-v2.40/pkcs11f.h
[9] https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=pkcs11
[10] https://1.ieee802.org/security/802-1ar/