Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions cmd/notation/internal/sign/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,16 @@ package sign

import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"

"github.com/notaryproject/notation-core-go/signature"
"github.com/notaryproject/notation-go"
"github.com/notaryproject/notation-go/config"
"github.com/notaryproject/notation-go/dir"
Expand All @@ -26,6 +34,54 @@ import (
"github.com/notaryproject/notation/v2/cmd/notation/internal/flag"
)

// localPrimitiveSigner implements signature.Signer for local key signing
// using PKCS#1 v1.5 (RSA) or ECDSA, required for PKCS#7 dm-verity signatures.
type localPrimitiveSigner struct {
keySpec signature.KeySpec
key crypto.PrivateKey
certs []*x509.Certificate
}

func (s *localPrimitiveSigner) Sign(payload []byte) ([]byte, []*x509.Certificate, error) {
var hash crypto.Hash
switch s.keySpec.Size {
case 256:
hash = crypto.SHA256
case 384:
hash = crypto.SHA384
case 512, 521:
hash = crypto.SHA512
default:
hash = crypto.SHA256
}

h := hash.New()
h.Write(payload)
digest := h.Sum(nil)

var sig []byte
var err error

switch k := s.key.(type) {
case *rsa.PrivateKey:
sig, err = rsa.SignPKCS1v15(rand.Reader, k, hash, digest)
case *ecdsa.PrivateKey:
sig, err = ecdsa.SignASN1(rand.Reader, k, digest)
default:
return nil, nil, fmt.Errorf("unsupported key type: %T", s.key)
}

if err != nil {
return nil, nil, err
}

return sig, s.certs, nil
}

func (s *localPrimitiveSigner) KeySpec() (signature.KeySpec, error) {
return s.keySpec, nil
}

// Signer is embedded with notation.BlobSigner and notation.Signer.
type Signer interface {
notation.BlobSigner
Expand Down Expand Up @@ -82,3 +138,104 @@ func resolveKey(name string) (config.KeySuite, error) {
}
return signingKeys.Get(name)
}

// SigningSchemeConfigKey is the plugin config key for requesting PKCS#1 v1.5 signing.
const SigningSchemeConfigKey = "signing_scheme"

// SigningSchemePKCS1v15 requests RSASSA-PKCS1-v1_5 from plugins (required for kernel dm-verity).
const SigningSchemePKCS1v15 = "rsassa-pkcs1-v1_5"

// GetPrimitiveSigner returns a signature.Signer for PKCS#7 dm-verity signing.
// Unlike GetSigner (which returns notation.Signer for JWS/COSE), this returns
// a raw signer used with the PKCS#7 envelope. For plugins, it automatically
// sets signing_scheme=rsassa-pkcs1-v1_5 since the Linux kernel only supports
// PKCS#1 v1.5 signature verification.
func GetPrimitiveSigner(ctx context.Context, opts *flag.SignerFlagOpts) (signature.Signer, error) {
if opts.KeyID != "" && opts.PluginName != "" && opts.Key == "" {
mgr := plugin.NewCLIManager(dir.PluginFS())
signPlugin, err := mgr.Get(ctx, opts.PluginName)
if err != nil {
return nil, err
}

pluginConfig := map[string]string{
SigningSchemeConfigKey: SigningSchemePKCS1v15,
}

keySpec, err := signer.GetKeySpecFromPlugin(ctx, signPlugin, opts.KeyID, pluginConfig)
if err != nil {
return nil, err
}

return signer.NewPluginPrimitiveSigner(ctx, signPlugin, opts.KeyID, keySpec, pluginConfig), nil
}

key, err := resolveKey(opts.Key)
if err != nil {
return nil, err
}

if key.X509KeyPair != nil {
return NewLocalSignerFromFiles(key.X509KeyPair.KeyPath, key.X509KeyPair.CertificatePath)
}

if key.ExternalKey != nil {
mgr := plugin.NewCLIManager(dir.PluginFS())
signPlugin, err := mgr.Get(ctx, key.PluginName)
if err != nil {
return nil, err
}

pluginConfig := make(map[string]string)
for k, v := range key.PluginConfig {
pluginConfig[k] = v
}
pluginConfig[SigningSchemeConfigKey] = SigningSchemePKCS1v15

keySpec, err := signer.GetKeySpecFromPlugin(ctx, signPlugin, key.ExternalKey.ID, pluginConfig)
if err != nil {
return nil, err
}

return signer.NewPluginPrimitiveSigner(ctx, signPlugin, key.ExternalKey.ID, keySpec, pluginConfig), nil
}

return nil, errors.New("unsupported key for primitive signing, either provide a local key and certificate file paths, or a key name in config.json")
}

// NewLocalSignerFromFiles creates a signature.Signer from local key and certificate files.
func NewLocalSignerFromFiles(keyPath, certPath string) (signature.Signer, error) {
if keyPath == "" {
return nil, errors.New("key path not specified")
}
if certPath == "" {
return nil, errors.New("certificate path not specified")
}

cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, err
}
if len(cert.Certificate) == 0 {
return nil, fmt.Errorf("%q does not contain certificate", certPath)
}

certs := make([]*x509.Certificate, len(cert.Certificate))
for i, c := range cert.Certificate {
certs[i], err = x509.ParseCertificate(c)
if err != nil {
return nil, err
}
}

keySpec, err := signature.ExtractKeySpec(certs[0])
if err != nil {
return nil, fmt.Errorf("failed to extract key spec: %w", err)
}

return &localPrimitiveSigner{
keySpec: keySpec,
key: cert.PrivateKey,
certs: certs,
}, nil
}
Loading
Loading