--- /dev/null
+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
--- /dev/null
+[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].
\ No newline at end of file
--- /dev/null
+[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.
\ No newline at end of file
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].
type SignerOpts interface {
// HashFunc returns an identifier for the hash function used to produce
}
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)
+}
--- /dev/null
+// 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)
+ }
+}
}
func signTBS(tbs []byte, key crypto.Signer, sigAlg SignatureAlgorithm, rand io.Reader) ([]byte, error) {
- signed := tbs
hashFunc := sigAlg.hashFunc()
- if hashFunc != 0 {
- h := hashFunc.New()
- h.Write(signed)
- signed = h.Sum(nil)
- }
var signerOpts crypto.SignerOpts = hashFunc
if sigAlg.isRSAPSS() {
}
}
- signature, err := key.Sign(rand, signed, signerOpts)
+ signature, err := crypto.SignMessage(key, rand, tbs, signerOpts)
if err != nil {
return nil, err
}
//
// 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
-// 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,
// unless the resulting certificate is self-signed. Otherwise the value from
// - Attributes (deprecated)
//
// 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
-// Public() method must return a *rsa.PublicKey or a *ecdsa.PublicKey or a
-// ed25519.PublicKey. (A *rsa.PrivateKey, *ecdsa.PrivateKey or
-// ed25519.PrivateKey satisfies this.)
+// key will be included in the CSR. It must implement crypto.Signer or
+// crypto.MessageSigner and its Public() method must return a *rsa.PublicKey or
+// a *ecdsa.PublicKey or a ed25519.PublicKey. (A *rsa.PrivateKey,
+// *ecdsa.PrivateKey or ed25519.PrivateKey satisfies this.)
//
// The returned slice is the certificate request in DER encoding.
func CreateCertificateRequest(rand io.Reader, template *CertificateRequest, priv any) (csr []byte, err error) {
// CreateRevocationList creates a new X.509 v2 [Certificate] Revocation List,
// according to RFC 5280, based on template.
//
-// The CRL is signed by priv which should be the private key associated with
-// the public key in the issuer certificate.
+// The CRL is signed by priv which should be a crypto.Signer or
+// 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
// order to use it as a CRL issuer.
"encoding/gob"
"encoding/hex"
"encoding/pem"
+ "errors"
"fmt"
"internal/testenv"
"io"
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)
+ }
+}