-
Notifications
You must be signed in to change notification settings - Fork 18.3k
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
Sign
function operates on a digest or a message from the calling sites, as the interpretation ofopts
is 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
Sign
function, 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.Signer
interface, 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