# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
#
# Copyright (C) 2017-2019 Regents of the University of California.
# Author: Jeff Thompson <[email protected]>
# Author: From ndn-cxx security https://github.com/named-data/ndn-cxx/blob/master/ndn-cxx/security/tpm/tpm.cpp
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# A copy of the GNU Lesser General Public License is in the file COPYING.
"""
The TPM (Trusted Platform Module) stores the private portion of a user's
cryptography keys. The format and location of stored information is indicated by
the TPM locator. The TPM is designed to work with a PIB (Public Information
Base) which stores public keys and related information such as certificates.
The TPM also provides functionalities of cryptographic transformation, such as
signing and decryption.
A TPM consists of a unified front-end interface and a backend implementation.
The front-end caches the handles of private keys which are provided by the
backend implementation.
Note: A Tpm instance is created and managed only by the KeyChain. It is returned
by the KeyChain getTpm() method, through which it is possible to check for the
existence of private keys, get public keys for the private keys, sign, and
decrypt the supplied buffers using managed private keys.
"""
from pyndn.util.blob import Blob
from pyndn.name import Name
from pyndn.security.security_types import KeyType
[docs]class Tpm(object):
def __init__(self, scheme, location, backEnd):
"""
Create a new TPM instance with the specified location. This constructor
should only be called by KeyChain.
:param str scheme: The scheme for the TPM.
:param str location: The location for the TPM.
:param TpmBackEnd backEnd: The TPM back-end implementation.
"""
# Name => TpmKeyHandle
self._keys = {}
self._scheme = scheme
self._location = location
self._backEnd = backEnd
[docs] class Error(Exception):
"""
Create a Tpm.Error which represents a semantic error in TPM processing.
:param str message: The error message.
"""
def __init__(self, message):
super(Tpm.Error, self).__init__(message)
[docs] def getTpmLocator(self):
return self._scheme + ":" + self._location
[docs] def hasKey(self, keyName):
"""
Check if the key with name keyName exists in the TPM.
:param Name keyName: The name of the key.
:return: True if the key exists.
:rtype: bool
"""
return self._backEnd.hasKey(keyName)
[docs] def getPublicKey(self, keyName):
"""
Get the public portion of an asymmetric key pair with name keyName.
:param Name keyName: The name of the key.
:return: The encoded public key, or an isNull Blob if the key does not
exist.
:rtype: Blob
"""
key = self._findKey(keyName)
if key == None:
return Blob()
else:
return key.derivePublicKey()
[docs] def sign(self, data, keyName, digestAlgorithm):
"""
Compute a digital signature from the byte buffer using the key with name
keyName.
:param data: The input byte buffer.
:type data: an array which implements the buffer protocol
:param Name keyName: The name of the key.
:param digestAlgorithm: The digest algorithm for the signature.
:type digestAlgorithm: int from DigestAlgorithm
:return: The signature Blob, or an isNull Blob if the key does not
exist, or for an unrecognized digestAlgorithm.
:rtype: Blob
"""
key = self._findKey(keyName)
if key == None:
return Blob()
else:
return key.sign(digestAlgorithm, data)
[docs] def decrypt(self, cipherText, keyName):
"""
Return the plain text which is decrypted from cipherText using the key
with name keyName.
:param cipherText: The cipher text byte buffer.
:type cipherText: an array which implements the buffer protocol
:param Name keyName: The name of the key.
:return: The decrypted data, or an isNull Blob if the key does not exist.
:rtype: Blob
"""
key = self._findKey(keyName)
if key == None:
return Blob()
else:
return key.decrypt(cipherText)
# TPM Management
[docs] def isTerminalMode(self):
"""
Check if the TPM is in terminal mode.
:return: True if in terminal mode.
:rtype: bool
"""
return self._backEnd.isTerminalMode()
[docs] def setTerminalMode(self, isTerminal):
"""
Set the terminal mode of the TPM. In terminal mode, the TPM will not ask
for a password from the GUI.
:param bool isTerminal: True to enable terminal mode.
"""
self._backEnd.setTerminalMode(isTerminal)
[docs] def isTpmLocked(self):
"""
Check if the TPM is locked.
:return: True if the TPM is locked, otherwise False.
:rtype: bool
"""
return self._backEnd.isTpmLocked()
[docs] def unlockTpm(self, password):
"""
Unlock the TPM. If !isTerminalMode(), prompt for a password from the GUI.
:param password: The password to unlock TPM.
:type password: an array which implements the buffer protocol
:return: True if the TPM was unlocked.
:rtype: bool
"""
return self._backEnd.unlockTpm(password)
def _getBackEnd(self):
"""
Get the TpmBackEnd. This should only be called by KeyChain.
:rtype: TpmBackEnd
"""
return self._backEnd
def _createKey(self, identityName, params):
"""
Create a key for the identityName according to params. The created key
is named /<identityName>/[keyId]/KEY . This should only be called by
KeyChain.
:param Name identityName: The name if the identity.
:param KeyParams params: The KeyParams for creating the key.
:return: The name of the created key.
:rtype: Name
:raises Tpm.Error: If params is invalid or the key type is unsupported.
:raises TpmBackEnd.Error: If the key already exists or cannot be created.
"""
if (params.getKeyType() == KeyType.RSA or
params.getKeyType() == KeyType.EC):
keyHandle = self._backEnd.createKey(identityName, params)
keyName = keyHandle.getKeyName()
self._keys[keyName] = keyHandle
return keyName
else:
raise Tpm.Error("createKey: Unsupported key type")
def _deleteKey(self, keyName):
"""
Delete the key with name keyName. If the key doesn't exist, do nothing.
Note: Continuing to use existing Key handles on a deleted key results in
undefined behavior. This should only be called by KeyChain.
:param Name keyName: The name of the key.
:raises TpmBackEnd.Error: If the deletion fails.
"""
try:
del self._keys[keyName]
except KeyError:
# Do nothing if it doesn't exist.
pass
self._backEnd.deleteKey(keyName)
def _exportPrivateKey(self, keyName, password):
"""
Get the encoded private key with name keyName in PKCS #8 format, possibly
encrypted. This should only be called by KeyChain.
:param Name keyName: The name of the key in the TPM.
:param password: The password for encrypting the private key, which
should have characters in the range of 1 to 127. If the password is
supplied, use it to return a PKCS #8 EncryptedPrivateKeyInfo. If the
password is None, return an unencrypted PKCS #8 PrivateKeyInfo.
:type password: an array which implements the buffer protocol
:return: The private key encoded in PKCS #8 format.
:rtype: Blob
:raises TpmBackEnd.Error: If the key does not exist or if the key cannot
be exported, e.g., insufficient privileges.
"""
return self._backEnd.exportKey(keyName, password)
def _importPrivateKey(self, keyName, pkcs8, password):
"""
Import an encoded private key with name keyName in PKCS #8 format,
possibly password-encrypted. This should only be called by KeyChain.
:param Name keyName: The name of the key to use in the TPM.
:param pkcs8: The input byte buffer. If the password is supplied, this
is a PKCS #8 EncryptedPrivateKeyInfo. If the password is None, this is
an unencrypted PKCS #8 PrivateKeyInfo.
:type pkcs8: an array which implements the buffer protocol
:param password: The password for decrypting the private key, which
should have characters in the range of 1 to 127. If the password is
supplied, use it to decrypt the PKCS #8 EncryptedPrivateKeyInfo. If
the password is None, import an unencrypted PKCS #8 PrivateKeyInfo.
:type password: an array which implements the buffer protocol
:raises TpmBackEnd.Error: For an error importing the key.
"""
self._backEnd.importKey(keyName, pkcs8, password)
def _findKey(self, keyName):
"""
Get the TpmKeyHandle with name keyName, using _backEnd.getKeyHandle if
it is not already cached in _keys.
:param Name keyName: The name of the key, which is copied.
:return: The key handle in the _keys cache, or None if no key exists
with name keyName.
:rtype: TpmKeyHandle
"""
try:
handle = self._keys[keyName]
except KeyError:
handle = None
if handle != None:
return handle
handle = self._backEnd.getKeyHandle(keyName)
if handle != None:
# Copy the Name.
self._keys[Name(keyName)] = handle
return handle
return None