crypto,crypto/x509: implement MessageSigner

And use it in crypto/x509. This allows people to implement single-shot
signers which do the hashing themselves.

Fixes #63405

Change-Id: I038c2e10f77b050b6136c4c0a5b031cb416f59aa
Reviewed-on: https://go-review.googlesource.com/c/go/+/654375
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Auto-Submit: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Roland Shoemaker 2025-03-03 12:31:55 -08:00 committed by Gopher Robot
parent d000963d04
commit 509c11f3a3
7 changed files with 180 additions and 14 deletions

5
api/next/63405.txt Normal file
View File

@ -0,0 +1,5 @@
pkg crypto, func SignMessage(Signer, io.Reader, []uint8, SignerOpts) ([]uint8, error) #63405
pkg crypto, type MessageSigner interface { Public, Sign, SignMessage } #63405
pkg crypto, type MessageSigner interface, Public() PublicKey #63405
pkg crypto, type MessageSigner interface, Sign(io.Reader, []uint8, SignerOpts) ([]uint8, error) #63405
pkg crypto, type MessageSigner interface, SignMessage(io.Reader, []uint8, SignerOpts) ([]uint8, error) #63405

View File

@ -0,0 +1 @@
[MessageSigner] is a new signing interface that can be implemented by signers that wish to hash the message to be signed themselves. A new function is also introduced, [SignMessage] which attempts to update a [Signer] interface to [MessageSigner], using the [MessageSigner.SignMessage] method if successful, and [Signer.Sign] if not. This can be used when code wishes to support both [Signer] and [MessageSigner].

View File

@ -0,0 +1 @@
[CreateCertificate], [CreateCertificateRequest], and [CreateRevocationList] can now accept a [crypto.MessageSigner] signing interface as well as [crypto.Signer]. This allows these functions to use signers which implement "one-shot" signing interfaces, where hashing is done as part of the signing operation, instead of by the caller.

View File

@ -198,6 +198,23 @@ type Signer interface {
Sign(rand io.Reader, digest []byte, opts SignerOpts) (signature []byte, err error) Sign(rand io.Reader, digest []byte, opts SignerOpts) (signature []byte, err error)
} }
// MessageSigner is an interface for an opaque private key that can be used for
// signing operations where the message is not pre-hashed by the caller.
// It is a superset of the Signer interface so that it can be passed to APIs
// which accept Signer, which may try to do an interface upgrade.
//
// MessageSigner.SignMessage and MessageSigner.Sign should produce the same
// result given the same opts. In particular, MessageSigner.SignMessage should
// only accept a zero opts.HashFunc if the Signer would also accept messages
// which are not pre-hashed.
//
// Implementations which do not provide the pre-hashed Sign API should implement
// Signer.Sign by always returning an error.
type MessageSigner interface {
Signer
SignMessage(rand io.Reader, msg []byte, opts SignerOpts) (signature []byte, err error)
}
// SignerOpts contains options for signing with a [Signer]. // SignerOpts contains options for signing with a [Signer].
type SignerOpts interface { type SignerOpts interface {
// HashFunc returns an identifier for the hash function used to produce // HashFunc returns an identifier for the hash function used to produce
@ -221,3 +238,18 @@ type Decrypter interface {
} }
type DecrypterOpts any type DecrypterOpts any
// SignMessage signs msg with signer. If signer implements [MessageSigner],
// [MessageSigner.SignMessage] is called directly. Otherwise, msg is hashed
// with opts.HashFunc() and signed with [Signer.Sign].
func SignMessage(signer Signer, rand io.Reader, msg []byte, opts SignerOpts) (signature []byte, err error) {
if ms, ok := signer.(MessageSigner); ok {
return ms.SignMessage(rand, msg, opts)
}
if opts.HashFunc() != 0 {
h := opts.HashFunc().New()
h.Write(msg)
msg = h.Sum(nil)
}
return signer.Sign(rand, msg, opts)
}

90
src/crypto/crypto_test.go Normal file
View File

@ -0,0 +1,90 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package crypto_test
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"io"
"strings"
"testing"
)
type messageSignerOnly struct {
k *rsa.PrivateKey
}
func (s *messageSignerOnly) Public() crypto.PublicKey {
return s.k.Public()
}
func (s *messageSignerOnly) Sign(_ io.Reader, _ []byte, _ crypto.SignerOpts) ([]byte, error) {
return nil, errors.New("unimplemented")
}
func (s *messageSignerOnly) SignMessage(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) {
h := opts.HashFunc().New()
h.Write(msg)
digest := h.Sum(nil)
return s.k.Sign(rand, digest, opts)
}
func TestSignMessage(t *testing.T) {
block, _ := pem.Decode([]byte(strings.ReplaceAll(
`-----BEGIN RSA TESTING KEY-----
MIIEowIBAAKCAQEAsPnoGUOnrpiSqt4XynxA+HRP7S+BSObI6qJ7fQAVSPtRkqso
tWxQYLEYzNEx5ZSHTGypibVsJylvCfuToDTfMul8b/CZjP2Ob0LdpYrNH6l5hvFE
89FU1nZQF15oVLOpUgA7wGiHuEVawrGfey92UE68mOyUVXGweJIVDdxqdMoPvNNU
l86BU02vlBiESxOuox+dWmuVV7vfYZ79Toh/LUK43YvJh+rhv4nKuF7iHjVjBd9s
B6iDjj70HFldzOQ9r8SRI+9NirupPTkF5AKNe6kUhKJ1luB7S27ZkvB3tSTT3P59
3VVJvnzOjaA1z6Cz+4+eRvcysqhrRgFlwI9TEwIDAQABAoIBAEEYiyDP29vCzx/+
dS3LqnI5BjUuJhXUnc6AWX/PCgVAO+8A+gZRgvct7PtZb0sM6P9ZcLrweomlGezI
FrL0/6xQaa8bBr/ve/a8155OgcjFo6fZEw3Dz7ra5fbSiPmu4/b/kvrg+Br1l77J
aun6uUAs1f5B9wW+vbR7tzbT/mxaUeDiBzKpe15GwcvbJtdIVMa2YErtRjc1/5B2
BGVXyvlJv0SIlcIEMsHgnAFOp1ZgQ08aDzvilLq8XVMOahAhP1O2A3X8hKdXPyrx
IVWE9bS9ptTo+eF6eNl+d7htpKGEZHUxinoQpWEBTv+iOoHsVunkEJ3vjLP3lyI/
fY0NQ1ECgYEA3RBXAjgvIys2gfU3keImF8e/TprLge1I2vbWmV2j6rZCg5r/AS0u
pii5CvJ5/T5vfJPNgPBy8B/yRDs+6PJO1GmnlhOkG9JAIPkv0RBZvR0PMBtbp6nT
Y3yo1lwamBVBfY6rc0sLTzosZh2aGoLzrHNMQFMGaauORzBFpY5lU50CgYEAzPHl
u5DI6Xgep1vr8QvCUuEesCOgJg8Yh1UqVoY/SmQh6MYAv1I9bLGwrb3WW/7kqIoD
fj0aQV5buVZI2loMomtU9KY5SFIsPV+JuUpy7/+VE01ZQM5FdY8wiYCQiVZYju9X
Wz5LxMNoz+gT7pwlLCsC4N+R8aoBk404aF1gum8CgYAJ7VTq7Zj4TFV7Soa/T1eE
k9y8a+kdoYk3BASpCHJ29M5R2KEA7YV9wrBklHTz8VzSTFTbKHEQ5W5csAhoL5Fo
qoHzFFi3Qx7MHESQb9qHyolHEMNx6QdsHUn7rlEnaTTyrXh3ifQtD6C0yTmFXUIS
CW9wKApOrnyKJ9nI0HcuZQKBgQCMtoV6e9VGX4AEfpuHvAAnMYQFgeBiYTkBKltQ
XwozhH63uMMomUmtSG87Sz1TmrXadjAhy8gsG6I0pWaN7QgBuFnzQ/HOkwTm+qKw
AsrZt4zeXNwsH7QXHEJCFnCmqw9QzEoZTrNtHJHpNboBuVnYcoueZEJrP8OnUG3r
UjmopwKBgAqB2KYYMUqAOvYcBnEfLDmyZv9BTVNHbR2lKkMYqv5LlvDaBxVfilE0
2riO4p6BaAdvzXjKeRrGNEKoHNBpOSfYCOM16NjL8hIZB1CaV3WbT5oY+jp7Mzd5
7d56RZOE+ERK2uz/7JX9VSsM/LbH9pJibd4e8mikDS9ntciqOH/3
-----END RSA TESTING KEY-----`, "TESTING KEY", "PRIVATE KEY")))
k, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
msg := []byte("hello :)")
h := crypto.SHA256.New()
h.Write(msg)
digest := h.Sum(nil)
sig, err := crypto.SignMessage(k, rand.Reader, msg, &rsa.PSSOptions{Hash: crypto.SHA256})
if err != nil {
t.Fatalf("SignMessage failed with Signer: %s", err)
}
if err := rsa.VerifyPSS(&k.PublicKey, crypto.SHA256, digest, sig, nil); err != nil {
t.Errorf("VerifyPSS failed for Signer signature: %s", err)
}
sig, err = crypto.SignMessage(&messageSignerOnly{k}, rand.Reader, msg, &rsa.PSSOptions{Hash: crypto.SHA256})
if err != nil {
t.Fatalf("SignMessage failed with MessageSigner: %s", err)
}
if err := rsa.VerifyPSS(&k.PublicKey, crypto.SHA256, digest, sig, nil); err != nil {
t.Errorf("VerifyPSS failed for MessageSigner signature: %s", err)
}
}

View File

@ -1568,13 +1568,7 @@ func signingParamsForKey(key crypto.Signer, sigAlgo SignatureAlgorithm) (Signatu
} }
func signTBS(tbs []byte, key crypto.Signer, sigAlg SignatureAlgorithm, rand io.Reader) ([]byte, error) { func signTBS(tbs []byte, key crypto.Signer, sigAlg SignatureAlgorithm, rand io.Reader) ([]byte, error) {
signed := tbs
hashFunc := sigAlg.hashFunc() hashFunc := sigAlg.hashFunc()
if hashFunc != 0 {
h := hashFunc.New()
h.Write(signed)
signed = h.Sum(nil)
}
var signerOpts crypto.SignerOpts = hashFunc var signerOpts crypto.SignerOpts = hashFunc
if sigAlg.isRSAPSS() { if sigAlg.isRSAPSS() {
@ -1584,7 +1578,7 @@ func signTBS(tbs []byte, key crypto.Signer, sigAlg SignatureAlgorithm, rand io.R
} }
} }
signature, err := key.Sign(rand, signed, signerOpts) signature, err := crypto.SignMessage(key, rand, tbs, signerOpts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1646,7 +1640,7 @@ var emptyASN1Subject = []byte{0x30, 0}
// //
// The currently supported key types are *rsa.PublicKey, *ecdsa.PublicKey and // The currently supported key types are *rsa.PublicKey, *ecdsa.PublicKey and
// ed25519.PublicKey. pub must be a supported key type, and priv must be a // ed25519.PublicKey. pub must be a supported key type, and priv must be a
// crypto.Signer with a supported public key. // crypto.Signer or crypto.MessageSigner with a supported public key.
// //
// The AuthorityKeyId will be taken from the SubjectKeyId of parent, if any, // The AuthorityKeyId will be taken from the SubjectKeyId of parent, if any,
// unless the resulting certificate is self-signed. Otherwise the value from // unless the resulting certificate is self-signed. Otherwise the value from
@ -2031,10 +2025,10 @@ func parseCSRExtensions(rawAttributes []asn1.RawValue) ([]pkix.Extension, error)
// - Attributes (deprecated) // - Attributes (deprecated)
// //
// priv is the private key to sign the CSR with, and the corresponding public // priv is the private key to sign the CSR with, and the corresponding public
// key will be included in the CSR. It must implement crypto.Signer and its // key will be included in the CSR. It must implement crypto.Signer or
// Public() method must return a *rsa.PublicKey or a *ecdsa.PublicKey or a // crypto.MessageSigner and its Public() method must return a *rsa.PublicKey or
// ed25519.PublicKey. (A *rsa.PrivateKey, *ecdsa.PrivateKey or // a *ecdsa.PublicKey or a ed25519.PublicKey. (A *rsa.PrivateKey,
// ed25519.PrivateKey satisfies this.) // *ecdsa.PrivateKey or ed25519.PrivateKey satisfies this.)
// //
// The returned slice is the certificate request in DER encoding. // The returned slice is the certificate request in DER encoding.
func CreateCertificateRequest(rand io.Reader, template *CertificateRequest, priv any) (csr []byte, err error) { func CreateCertificateRequest(rand io.Reader, template *CertificateRequest, priv any) (csr []byte, err error) {
@ -2376,8 +2370,9 @@ type tbsCertificateList struct {
// CreateRevocationList creates a new X.509 v2 [Certificate] Revocation List, // CreateRevocationList creates a new X.509 v2 [Certificate] Revocation List,
// according to RFC 5280, based on template. // according to RFC 5280, based on template.
// //
// The CRL is signed by priv which should be the private key associated with // The CRL is signed by priv which should be a crypto.Signer or
// the public key in the issuer certificate. // crypto.MessageSigner associated with the public key in the issuer
// certificate.
// //
// The issuer may not be nil, and the crlSign bit must be set in [KeyUsage] in // The issuer may not be nil, and the crlSign bit must be set in [KeyUsage] in
// order to use it as a CRL issuer. // order to use it as a CRL issuer.

View File

@ -22,6 +22,7 @@ import (
"encoding/gob" "encoding/gob"
"encoding/hex" "encoding/hex"
"encoding/pem" "encoding/pem"
"errors"
"fmt" "fmt"
"internal/testenv" "internal/testenv"
"io" "io"
@ -4196,3 +4197,44 @@ func TestRejectCriticalSKI(t *testing.T) {
t.Fatalf("ParseCertificate() unexpected error: %v, want: %s", err, expectedErr) t.Fatalf("ParseCertificate() unexpected error: %v, want: %s", err, expectedErr)
} }
} }
type messageSigner struct{}
func (ms *messageSigner) Public() crypto.PublicKey { return rsaPrivateKey.Public() }
func (ms *messageSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
return nil, errors.New("unimplemented")
}
func (ms *messageSigner) SignMessage(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) {
if _, ok := opts.(*rsa.PSSOptions); ok {
return nil, errors.New("PSSOptions passed instead of hash")
}
h := opts.HashFunc().New()
h.Write(msg)
tbs := h.Sum(nil)
return rsa.SignPKCS1v15(rand, rsaPrivateKey, opts.HashFunc(), tbs)
}
func TestMessageSigner(t *testing.T) {
template := Certificate{
SignatureAlgorithm: SHA256WithRSA,
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "Cert"},
NotBefore: time.Unix(1000, 0),
NotAfter: time.Unix(100000, 0),
BasicConstraintsValid: true,
IsCA: true,
}
certDER, err := CreateCertificate(rand.Reader, &template, &template, rsaPrivateKey.Public(), &messageSigner{})
if err != nil {
t.Fatalf("CreateCertificate failed: %s", err)
}
cert, err := ParseCertificate(certDER)
if err != nil {
t.Fatalf("ParseCertificate failed: %s", err)
}
if err := cert.CheckSignatureFrom(cert); err != nil {
t.Fatalf("CheckSignatureFrom failed: %s", err)
}
}