mirror of https://github.com/golang/go.git
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:
parent
d000963d04
commit
509c11f3a3
|
|
@ -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
|
||||||
|
|
@ -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].
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue