...

Source file src/github.com/sigstore/rekor/pkg/util/signed_note.go

Documentation: github.com/sigstore/rekor/pkg/util

     1  //
     2  // Copyright 2021 The Sigstore Authors.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package util
    17  
    18  import (
    19  	"bufio"
    20  	"bytes"
    21  	"crypto/ecdsa"
    22  	"crypto/ed25519"
    23  	"crypto/rsa"
    24  	"crypto/sha256"
    25  	"crypto/x509"
    26  	"encoding/base64"
    27  	"encoding/binary"
    28  	"errors"
    29  	"fmt"
    30  	"strings"
    31  
    32  	"github.com/sigstore/sigstore/pkg/signature"
    33  	"github.com/sigstore/sigstore/pkg/signature/options"
    34  	"golang.org/x/mod/sumdb/note"
    35  )
    36  
    37  type SignedNote struct {
    38  	// Textual representation of a note to sign.
    39  	Note string
    40  	// Signatures are one or more signature lines covering the payload
    41  	Signatures []note.Signature
    42  }
    43  
    44  // Sign adds a signature to a SignedCheckpoint object
    45  // The signature is added to the signature array as well as being directly returned to the caller
    46  func (s *SignedNote) Sign(identity string, signer signature.Signer, opts signature.SignOption) (*note.Signature, error) {
    47  	sig, err := signer.SignMessage(bytes.NewReader([]byte(s.Note)), opts)
    48  	if err != nil {
    49  		return nil, fmt.Errorf("signing note: %w", err)
    50  	}
    51  
    52  	pk, err := signer.PublicKey()
    53  	if err != nil {
    54  		return nil, fmt.Errorf("retrieving public key: %w", err)
    55  	}
    56  	pubKeyBytes, err := x509.MarshalPKIXPublicKey(pk)
    57  	if err != nil {
    58  		return nil, fmt.Errorf("marshalling public key: %w", err)
    59  	}
    60  
    61  	pkSha := sha256.Sum256(pubKeyBytes)
    62  
    63  	signature := note.Signature{
    64  		Name:   identity,
    65  		Hash:   binary.BigEndian.Uint32(pkSha[:]),
    66  		Base64: base64.StdEncoding.EncodeToString(sig),
    67  	}
    68  
    69  	s.Signatures = append(s.Signatures, signature)
    70  	return &signature, nil
    71  }
    72  
    73  // Verify checks that one of the signatures can be successfully verified using
    74  // the supplied public key
    75  func (s SignedNote) Verify(verifier signature.Verifier) bool {
    76  	if len(s.Signatures) == 0 {
    77  		return false
    78  	}
    79  
    80  	msg := []byte(s.Note)
    81  	digest := sha256.Sum256(msg)
    82  
    83  	for _, s := range s.Signatures {
    84  		sigBytes, err := base64.StdEncoding.DecodeString(s.Base64)
    85  		if err != nil {
    86  			return false
    87  		}
    88  		pk, err := verifier.PublicKey()
    89  		if err != nil {
    90  			return false
    91  		}
    92  		opts := []signature.VerifyOption{}
    93  		switch pk.(type) {
    94  		case *rsa.PublicKey, *ecdsa.PublicKey:
    95  			opts = append(opts, options.WithDigest(digest[:]))
    96  		case ed25519.PublicKey:
    97  			break
    98  		default:
    99  			return false
   100  		}
   101  		if err := verifier.VerifySignature(bytes.NewReader(sigBytes), bytes.NewReader(msg), opts...); err != nil {
   102  			return false
   103  		}
   104  	}
   105  	return true
   106  }
   107  
   108  // MarshalText returns the common format representation of this SignedNote.
   109  func (s SignedNote) MarshalText() ([]byte, error) {
   110  	return []byte(s.String()), nil
   111  }
   112  
   113  // String returns the String representation of the SignedNote
   114  func (s SignedNote) String() string {
   115  	var b strings.Builder
   116  	b.WriteString(s.Note)
   117  	b.WriteRune('\n')
   118  	for _, sig := range s.Signatures {
   119  		var hbuf [4]byte
   120  		binary.BigEndian.PutUint32(hbuf[:], sig.Hash)
   121  		sigBytes, _ := base64.StdEncoding.DecodeString(sig.Base64)
   122  		b64 := base64.StdEncoding.EncodeToString(append(hbuf[:], sigBytes...))
   123  		fmt.Fprintf(&b, "%c %s %s\n", '\u2014', sig.Name, b64)
   124  	}
   125  
   126  	return b.String()
   127  }
   128  
   129  // UnmarshalText parses the common formatted signed note data and stores the result
   130  // in the SignedNote. THIS DOES NOT VERIFY SIGNATURES INSIDE THE CONTENT!
   131  //
   132  // The supplied data is expected to contain a single Note, followed by a single
   133  // line with no comment, followed by one or more lines with the following format:
   134  //
   135  // \u2014 name signature
   136  //
   137  //   - name is the string associated with the signer
   138  //   - signature is a base64 encoded string; the first 4 bytes of the decoded value is a
   139  //     hint to the public key; it is a big-endian encoded uint32 representing the first
   140  //     4 bytes of the SHA256 hash of the public key
   141  func (s *SignedNote) UnmarshalText(data []byte) error {
   142  	sigSplit := []byte("\n\n")
   143  	// Must end with signature block preceded by blank line.
   144  	split := bytes.LastIndex(data, sigSplit)
   145  	if split < 0 {
   146  		return errors.New("malformed note")
   147  	}
   148  	text, data := data[:split+1], data[split+2:]
   149  	if len(data) == 0 || data[len(data)-1] != '\n' {
   150  		return errors.New("malformed note")
   151  	}
   152  
   153  	sn := SignedNote{
   154  		Note: string(text),
   155  	}
   156  
   157  	b := bufio.NewScanner(bytes.NewReader(data))
   158  	for b.Scan() {
   159  		var name, signature string
   160  		if _, err := fmt.Fscanf(strings.NewReader(b.Text()), "\u2014 %s %s\n", &name, &signature); err != nil {
   161  			return fmt.Errorf("parsing signature: %w", err)
   162  		}
   163  
   164  		sigBytes, err := base64.StdEncoding.DecodeString(signature)
   165  		if err != nil {
   166  			return fmt.Errorf("decoding signature: %w", err)
   167  		}
   168  		if len(sigBytes) < 5 {
   169  			return errors.New("signature is too small")
   170  		}
   171  
   172  		sig := note.Signature{
   173  			Name:   name,
   174  			Hash:   binary.BigEndian.Uint32(sigBytes[0:4]),
   175  			Base64: base64.StdEncoding.EncodeToString(sigBytes[4:]),
   176  		}
   177  		sn.Signatures = append(sn.Signatures, sig)
   178  
   179  	}
   180  	if len(sn.Signatures) == 0 {
   181  		return errors.New("no signatures found in input")
   182  	}
   183  
   184  	// copy sc to s
   185  	*s = sn
   186  	return nil
   187  }
   188  
   189  func SignedNoteValidator(strToValidate string) bool {
   190  	s := SignedNote{}
   191  	return s.UnmarshalText([]byte(strToValidate)) == nil
   192  }
   193  

View as plain text