golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
124.23k stars 17.7k forks source link

proposal: crypto: single-shot signing interface #63405

Open ueno opened 1 year ago

ueno commented 1 year ago

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:

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.

  1. https://github.com/golang/go/compare/master...ueno:go:wip/single-shot-signing
rittneje commented 1 year ago

If this proposal is accepted, I think SignMessage should also take context.Context, for the same reasons mentioned in #56508.

seankhliao commented 1 year ago

cc @golang/security