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:
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 of opts is up to the implementation of crypto.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:
// 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.
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: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 parameterdigest
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 inopts
. This design has the following drawbacks:Sign
function operates on a digest or a message from the calling sites, as the interpretation ofopts
is up to the implementation ofcrypto.Signer
Sign
function, which can consume unreasonable amount of memorycrypto.Signer
interface, it is not straightforward to enforce the requirementProposal
This proposal describes a new interface with a single
SignMessage
function which takesio.Reader
as the input: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:
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.