-
Notifications
You must be signed in to change notification settings - Fork 18.4k
Description
Abstract
This proposal introduces a new interface for creating digital signatures in a single-shot manner, by combining message hashing and signing as an inseparable operation. The new interface can coexist alongside the existing crypto.Signer interface, while providing a clear indication that an entire message is expected as the input.
Background
The crypto.Signer interface is defined as follows:
type Signer interface {
Public() PublicKey
Sign(rand io.Reader, digest []byte, opts SignerOpts) (signature []byte, err error)
}This interface is implemented for private keys in the RSA (crypto/rsa) and ECDSA (crypto/ecdsa) modules, as well as Ed25519 (crypto/ed25519), which expects the second parameter digest to be either an entire message (Ed25519) or the hash over it (Ed25519ph), depending on whether a special hash function (crypto.Hash(0)) is specified in opts. This design has the following drawbacks:
- Code readability: it is not obvious whether the
Signfunction operates on a digest or a message from the calling sites, as the interpretation ofoptsis up to the implementation ofcrypto.Signer - Memory consumption: when the second parameter is a message, the entire content needs to be loaded before calling the
Signfunction, which can consume unreasonable amount of memory - FIPS compliance: FIPS 140-3 requires both hashing and signing operations to be performed within the same cryptographic module boundary. With the
crypto.Signerinterface, it is not straightforward to enforce the requirement
Proposal
This proposal describes a new interface with a single SignMessage function which takes io.Reader as the input:
type MessageSigner interface {
SignMessage(rand, message io.Reader, opts SignerOpts) (signature []byte, err error)
}This interface can be used in a backward compatible manner, by checking the implementation at run-time, as suggested in https://go.dev/blog/module-compatibility#working-with-interfaces:
// in crypto/ed25519/ed25519.go:
func (priv PrivateKey) SignMessage(rand, message io.Reader, opts crypto.SignerOpts) (signature []byte, err error) {
...
}
// in crypto/tls/handshake_client_tls13.go
func (hs *clientHandshakeStateTLS13) sendClientCertificate() error {
…
if ms, ok := cert.PrivateKey.(crypto.MessageSigner); ok {
// Use single-shot ms.SignMessage without hashing.
} else if hs, ok := cert.PrivateKey.(crypto.Signer); ok {
// Use hs.Sign after hashing the message.
} else {
// Return an error.
}
}Rationale
This approach is non-invasive as the use of the new interface is on an opt-in basis. One shortcoming is the amount of code to be rewritten using the single-shot interface for FIPS compliance.
Compatibility
This change would maintain backward compatibility.
Implementation
There is a preliminary implementation of this proposal at [1], though it currently simply calls the Sign function in crypto.Signer underneath, which could be either optimized by reading a message in a piecemeal fashion or deferred to the single-shot signing API in BoringSSL.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status