Guest Post by Dan Anderson, Intel
This is a continuation of my three-part series on Hyperledger Sawtooth Security. I began with Sawtooth consensus algorithms in part one. Here I will continue this series discussing Sawtooth node and transaction processor security.
Sawtooth Node and Transaction Processor Security
Sawtooth has several mechanisms to restrict and secure access to validator peer nodes. These include the following topics, which I’ll discuss below:
- Sawtooth Permissioning, Policies and Roles
- Network Roles
- Challenge-Response Authorization
- Sawtooth Encryption
- Transaction Input/Output lists
- Observability
- Internal Security Mechanisms
Sawtooth Permissioning
Prelude: Configuration
Permissioning restricts who may access a Sawtooth validator node. Permissioning is set with Sawtooth configuration, so before we can discuss permissioning, we need to review configuration. After that, we will discuss various types of Sawtooth Permissioning.
Sawtooth configuration is set with on-chain configuration or off-chain configuration. On-chain configuration is configuration settings recorded in the blockchain, with changes or additions made as new blocks added to the blockchain. On-chain configuration applies to the entire Sawtooth network for that blockchain. Off-chain configuration is configuration settings recorded in the local validator.toml file, located by default at /etc/sawtooth/validator.toml, and applies to only to the local validator node. This allows further local restrictions for a site, if desired.
The initial permission values are configured in the genesis node (node 0), or, if not set, assume default values. On-chain settings can be modified any time by adding a transaction to the blockchain using the Settings Transaction Processor (which is the only mandatory TP). The change does not take effect until the next block (never the current block that contains the new setting).
On-chain settings are changed through a voting mechanism. Voters (individual peer nodes) are listed in sawtooth.settings.vote.authorized_keys. The votes are signed by each peer node as a transaction and recorded on-chain. If only one voter is authorized, the change is immediate. If multiple voters are authorized, the change takes effect when the minimum percentage of votes is reached. The Settings TP manages the election results.
Transaction Family Permissioning
Transaction family permissioning controls what TFs are supported by the current Sawtooth network. All nodes in a Sawtooth network must support the same set of TFs and versions. The applicable setting is sawtooth.validator.transaction_families For example,
[{“family”:”sawtooth_settings”, “version”:”1.0″}, {“family”:”xo”, “version”:”1.0″}]
By default, any Transaction Family is supported by a Sawtooth network.
One can also restrict transaction processors to their own namespaces (the 6 hex character TF namespace). When set, the validator prohibits reads and writes outside a TF namespace. For example,
[{“family”:”sawtooth_settings”, “version”:”1.0″}, {“family”:”intkey”, “version”:”1.0″}]
[{“family”:”sawtooth_settings”, “version”:”1.0″}, {“family”:”intkey”, “version”:”1.0″, “namespaces”:[“1cf126”]}]’
Here is an example of setting the transaction family permissions on the command line on-chain:
$ sawset proposal create –url http://localhost:8008 –key /etc/sawtooth/keys/validator.priv sawtooth.validator.transaction_families='[{“family”:”sawtooth_settings”, “version”:”1.0″}, {“family”:”intkey”, “version”:”1.0″}]’
The above settings can also be set off-chain in a configuration file, in which case it applies only to the local node. For example, in validator.toml :
[permissions]
“sawtooth.validator.transaction_families” = “[{\”family\”:\”sawtooth_settings\”, \”version\”:\”1.0\”}, {\”family\”:\”intkey\”, \”version\”:\”1.0\”}]”
Policies and Roles
Transaction key permissioning use policies and roles, which are implemented using the Identity Transaction Family. A policy is just a set of PERMIT_KEY and DENY_KEY rules that are evaluated in the order listed. A role is an authorization that grants permission to perform operations and access data. Roles and policies may be stored on-chain, as blockchain transactions, or off-chain, in configuration files, in which case they apply to the local node only. An example of a role is transactor.transaction_signer.intkey, which authorizes who can sign intkey transaction family transactions. An example of a policy is
“PERMIT_KEY 03eb5418588737e1b3982f4d863e01e13fd0da03ee2ac51b090860db3bdbbf39b2” “DENY_KEY *”
which denies access to all but the signer identified by their public key beginning with 03eb.
Before roles and policies can be set, sawtooth.identity.allowed_keys must be set to the key(s) of the authorized signers of Identity Transaction Family transactions. For example, the following allows Alice to make Identity TF transactions:
$ sudo sawset proposal create –key /etc/sawtooth/keys/validator.priv sawtooth.identity.allowed_keys=$(cat ~/.sawtooth/keys/alice.pub)
Before roles can be set to policies, policies must be created. A policy is a sequence of PERMIT_KEY and DENY_KEY keywords followed by an identity, which is the public key of a signer. The public key is 64 hex digits, such as 03305c4911bfdbe36c3be526ba665b0638e4376a920844a351708ec94c89ae70fa . A policy can be set on-chain or off-chain. Here’s an example of an on-chain setting:
$ sawtooth identity policy create dans_policy1 \
“PERMIT_KEY 02a1035d8a6277adf5b92e8f831f647235224fe4dc8660f8bcddf85707156307b5” \
“PERMIT_KEY 039e4b768b2c8280501fb7b5c56992088b704fb3ef8fd0efced6204ec975d1382f” \
“DENY_KEY *”
$ sawtooth identity policy list
In the above example, two public keys are permitted and everyone else is denied; For the public key, use a 64 hex character public key from a .pub file.
Off-chain settings, which apply only to a single Sawtooth node, are kept by default in directory /etc/sawtooth/policy/ . For example, file /etc/sawtooth/policy/dans_policy1 may contain
PERMIT_KEY 02a1035d8a6277adf5b92e8f831f647235224fe4dc8660f8bcddf85707156307b5
PERMIT_KEY 039e4b768b2c8280501fb7b5c56992088b704fb3ef8fd0efced6204ec975d1382f
DENY_KEY *
Once we establish policies, we can now set roles to specific policies. For example, if we want to use dans_policy1 above to guide who can submit intkey transactions, set the following on-chain role:
$ sawtooth identity role create transactor.transaction_signer.intkey dans_policy1
Or, if we prefer an off-chain role setting, which applies only to the local node, we can add something like the following to file validator.toml :
[permissions]
“transactor.transaction_signer.intkey” = “dans_policy1”
Note that the key is in quotes, as required by TOML format for dotted keys.
On-chain permissioning is checked with batch submissions from a client and when publishing or validating a block. Off-chain permissioning applies only to batch submissions from a client—not transactions from peer nodes. The latter prevents unnecessary blockchain forks from different permissioning among nodes.
Transaction Key Permissions
Transaction key permissioning controls what clients can submit transactions, based on the signing public key. The relevant permissioning roles are:
- transactor.transaction_signer.<name of TF> controls what clients can sign transactions for a particular Transaction Family (TF). For example, transactor.transaction_signer.intkey controls what clients can sign intkey TF transactions
- transactor.transaction_signer controls what clients can sign transactions for any Transaction Family (TF)
- transactor.batch_signer controls what clients can sign batches (groups of transactions that must be processed atomically—all or none)
- transactor controls what clients can sign transactions or batches
The most specific role takes precedence over a more general role (for example, for batches, transactor.batch_signer is checked first and transactor is checked only if no rule was found in transactor.batch_signer . By default, anyone can sign a transaction or batch.
Challenge-Response Authorization
When a Sawtooth validator node receives a connection request, it has two authorization modes for the other node—Trust Authorization and Challenge Authorization.
For Trust Authorization, a node trusts connections from other nodes. It checks the public key for role authorizations. This is intended mainly for development and is the default value.
For Challenge Authorization, a connecting node must prove who they are. On a connection request, a node sends a challenge response containing a random nonce. The other node signs the nonce and sends it back to prove they are who they say they are. The node verifies the signed nonce is the same one as it sent, to guard against replay attacks.
To set authorization type, use
$ sawtooth-validator –network-auth {trust|challenge}
on the command line or set network = “trust” or network = “challenge” in configuration file validator.toml .
Sawtooth Encryption
Encryption in Sawtooth is used for
- Digests for transactions, batches, and blocks
- Signing transaction and batch headers by the client and blocks by the validator
- Encrypting data in transit—either between peer nodes or between a components within a node
Sawtooth Transaction and Batch Signing
A Sawtooth node receives transactions from a client in the form of a batch list. A batch list contains one or more batches. A batch contains one or more transactions that must be processed, in order, as one atomic unit. For example, here’s a batch list with two batches containing two transactions and one transaction, respectively:
The client creating a transaction calculates the SHA-512 digest and sets it in the transaction header. The digest ensures the payload data in transactions cannot be altered without detection. Each transaction header contains a client-generated nonce value. The nonce makes every transaction unique and prevents anyone from replaying the transaction. The client signs the transaction header and includes the signing public key in the transaction header. The client then signs each batch and includes the batch signer public key in the batch header. The batch signer and transaction signer are usually, but do not have to be, the same. The public key of the batch signer is also in the transaction header to prevent repackaging of the transaction in another batch. The transaction and batch signer public keys in the transaction and batch header, respectively, allows anyone to identify the signers and to verify the signatures.
All Sawtooth signatures, including client-signed transactions and batches, use ECDSA curve secp256k1. This is the same algorithm and curve used by Bitcoin and Ethereum and allows for signature compatibility with these platforms. The 64-byte signature is the concatenation of the “raw” (unencoded) R and S values of a standard ECDSA signature.
Sawtooth Block Signing and Validation
The Sawtooth validator node creates proposed blocks from transactions it receives. These proposed blocks are signed by the validator and transmitted to the peer nodes on the Sawtooth network. The validator node signs blocks with ECDSA curve secp256k1, the same algorithm used for transaction and batch signatures. The peer nodes’ Validator validates candidate blocks proposed by a node, including verifying the block, batch, and transaction signatures. The digests and signatures not only prevent altering the payload data, but also prevents deleting, reordering, or duplicating transactions within a block or blocks within a blockchain.
Sawtooth Communication Encryption
Sawtooth encrypts data-in-motion —that is, communications between Sawtooth nodes and between components within a sawtooth node (such as between Validator, REST API, and Transaction Processor node processes). Sawtooth uses ZeroMQ (ZMQ or 0MQ) for communications. ZMQ encryption and authentication is implemented with CurveZMQ, which uses a 256-bit ECC key with elliptical curve Curve25519.
Transaction Input/Output lists
All Sawtooth transactions (ledger entries) have a list of input addresses and output addresses in the transaction header. These are optional but highly recommended for two reasons:
- It allows transactions that do not conflict to be processed in parallel
- It provides a measure of security by restricting the transaction processor from modifying addresses in state that are not listed in the transaction header.
Observability
Observability is the ability to see what the software is doing. This is important not only for debugging code, but for security analysis. One can see during a breach, or with post-mortem forensics, exactly what went wrong. Sawtooth is observable in that its components log time-stamped entries at various verbosity levels. The -v flag means log warning messages, –vv means log information and warning messages, and –vvv means log debug, info, and warning messages.
Additionally, Sawtooth has event subscriptions. The Sawtooth Events API allows an application to subscribe to “block-commit” events (triggered when a block is committed) and “state-delta” events (triggered when data in the blockchain state changes). Events are extensible in that application-defined events may be created and subscribed to by an application. An event handler could look for anomalies (such as too-frequent or over-limit transactions) and take further action to block or warn on these events.
Internal Security Mechanisms
Some security mechanisms are “under the hood” and are not always visible, but they are still important to mention:
- Major Sawtooth components have been rewritten in Rust, which strongly enforces type, memory, and concurrency safety
- An automatic process manages code reviews and enforces static code analysis before integration, using Github and Jenkins
- Hyperledger has sponsored a security audit for Sawtooth 1.0. The Hyperledger Sawtooth 1.0 pre-release security audit is available at https://www.hyperledger.org/blog/2018/05/22/hyperledger-sawtooth-security-audit
- A reporting mechanism and process for security bugs are publicly documented at https://wiki.hyperledger.org/security This can be done via e-mail to security -at- hyperledger -dot- org or using the JIRA bug tracking mechanism at https://jira.hyperledger.org/projects/STL (the latter requires a free Linux Foundation ID)
Conclusion
This concludes part two of my blog on Hyperledger Sawtooth Security, where I discussed Sawtooth node and transaction processor security. This provides a toolbox to tighten down Sawtooth nodes as your needs require—tightening allowed transaction signers, transaction families, and peer nodes. I also discussed other security mechanisms including node authorization, encryption, observability, and internal security processes. Part three will conclude this series with a discussion on Sawtooth client application security and network security.