...

Source file src/github.com/sigstore/rekor/pkg/pki/ssh/ssh.go

Documentation: github.com/sigstore/rekor/pkg/pki/ssh

     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 ssh
    17  
    18  import (
    19  	"crypto"
    20  	"crypto/ecdsa"
    21  	"crypto/ed25519"
    22  	"crypto/elliptic"
    23  	"encoding/binary"
    24  	"errors"
    25  	"fmt"
    26  	"io"
    27  	"net/http"
    28  
    29  	"github.com/asaskevich/govalidator"
    30  	"github.com/sigstore/rekor/pkg/pki/identity"
    31  	"github.com/sigstore/sigstore/pkg/cryptoutils"
    32  	sigsig "github.com/sigstore/sigstore/pkg/signature"
    33  	"golang.org/x/crypto/ssh"
    34  )
    35  
    36  type Signature struct {
    37  	signature *ssh.Signature
    38  	pk        ssh.PublicKey
    39  	hashAlg   string
    40  }
    41  
    42  // NewSignature creates and Validates an ssh signature object
    43  func NewSignature(r io.Reader) (*Signature, error) {
    44  	b, err := io.ReadAll(r)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	sig, err := Decode(b)
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  	return sig, nil
    53  }
    54  
    55  // CanonicalValue implements the pki.Signature interface
    56  func (s Signature) CanonicalValue() ([]byte, error) {
    57  	return []byte(Armor(s.signature, s.pk)), nil
    58  }
    59  
    60  // Verify implements the pki.Signature interface
    61  func (s Signature) Verify(r io.Reader, k interface{}, _ ...sigsig.VerifyOption) error {
    62  	if s.signature == nil {
    63  		return errors.New("ssh signature has not been initialized")
    64  	}
    65  
    66  	key, ok := k.(*PublicKey)
    67  	if !ok {
    68  		return fmt.Errorf("invalid public key type for: %v", k)
    69  	}
    70  
    71  	ck, err := key.CanonicalValue()
    72  	if err != nil {
    73  		return err
    74  	}
    75  	cs, err := s.CanonicalValue()
    76  	if err != nil {
    77  		return err
    78  	}
    79  	return Verify(r, cs, ck)
    80  }
    81  
    82  // PublicKey contains an ssh PublicKey
    83  type PublicKey struct {
    84  	key     ssh.PublicKey
    85  	comment string
    86  }
    87  
    88  // NewPublicKey implements the pki.PublicKey interface
    89  func NewPublicKey(r io.Reader) (*PublicKey, error) {
    90  	// 64K seems generous as a limit for valid SSH keys
    91  	// we use http.MaxBytesReader and pass nil for ResponseWriter to reuse stdlib
    92  	// and not reimplement this; There is a proposal for this to be fixed in 1.20
    93  	// https://github.com/golang/go/issues/51115
    94  	// TODO: switch this to stdlib once golang 1.20 comes out
    95  	rawPub, err := io.ReadAll(http.MaxBytesReader(nil, io.NopCloser(r), 65536))
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	key, comment, _, _, err := ssh.ParseAuthorizedKey(rawPub)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	return &PublicKey{key: key, comment: comment}, nil
   106  }
   107  
   108  // CanonicalValue implements the pki.PublicKey interface
   109  func (k PublicKey) CanonicalValue() ([]byte, error) {
   110  	if k.key == nil {
   111  		return nil, errors.New("ssh public key has not been initialized")
   112  	}
   113  	return ssh.MarshalAuthorizedKey(k.key), nil
   114  }
   115  
   116  // EmailAddresses implements the pki.PublicKey interface
   117  func (k PublicKey) EmailAddresses() []string {
   118  	if govalidator.IsEmail(k.comment) {
   119  		return []string{k.comment}
   120  	}
   121  	return nil
   122  }
   123  
   124  // Subjects implements the pki.PublicKey interface
   125  func (k PublicKey) Subjects() []string {
   126  	return k.EmailAddresses()
   127  }
   128  
   129  // Identities implements the pki.PublicKey interface
   130  func (k PublicKey) Identities() ([]identity.Identity, error) {
   131  	// extract key from SSH certificate if present
   132  	var sshKey ssh.PublicKey
   133  	switch v := k.key.(type) {
   134  	case *ssh.Certificate:
   135  		sshKey = v.Key
   136  	default:
   137  		sshKey = k.key
   138  	}
   139  
   140  	// Extract crypto.PublicKey from SSH key
   141  	// Handle sk public keys which do not implement ssh.CryptoPublicKey
   142  	// Inspired by x/ssh/keys.go
   143  	// TODO: Simplify after https://github.com/golang/go/issues/62518
   144  	var cryptoPubKey crypto.PublicKey
   145  	if v, ok := sshKey.(ssh.CryptoPublicKey); ok {
   146  		cryptoPubKey = v.CryptoPublicKey()
   147  	} else {
   148  		switch sshKey.Type() {
   149  		case ssh.KeyAlgoSKECDSA256:
   150  			var w struct {
   151  				Curve       string
   152  				KeyBytes    []byte
   153  				Application string
   154  				Rest        []byte `ssh:"rest"`
   155  			}
   156  			_, k, ok := parseString(sshKey.Marshal())
   157  			if !ok {
   158  				return nil, fmt.Errorf("error parsing ssh.KeyAlgoSKED25519 key")
   159  			}
   160  			if err := ssh.Unmarshal(k, &w); err != nil {
   161  				return nil, err
   162  			}
   163  			if w.Curve != "nistp256" {
   164  				return nil, errors.New("ssh: unsupported curve")
   165  			}
   166  			ecdsaPubKey := new(ecdsa.PublicKey)
   167  			ecdsaPubKey.Curve = elliptic.P256()
   168  			//nolint:staticcheck // ignore SA1019 for old code
   169  			ecdsaPubKey.X, ecdsaPubKey.Y = elliptic.Unmarshal(ecdsaPubKey.Curve, w.KeyBytes)
   170  			if ecdsaPubKey.X == nil || ecdsaPubKey.Y == nil {
   171  				return nil, errors.New("ssh: invalid curve point")
   172  			}
   173  			cryptoPubKey = ecdsaPubKey
   174  		case ssh.KeyAlgoSKED25519:
   175  			var w struct {
   176  				KeyBytes    []byte
   177  				Application string
   178  				Rest        []byte `ssh:"rest"`
   179  			}
   180  			_, k, ok := parseString(sshKey.Marshal())
   181  			if !ok {
   182  				return nil, fmt.Errorf("error parsing ssh.KeyAlgoSKED25519 key")
   183  			}
   184  			if err := ssh.Unmarshal(k, &w); err != nil {
   185  				return nil, err
   186  			}
   187  			if l := len(w.KeyBytes); l != ed25519.PublicKeySize {
   188  				return nil, fmt.Errorf("invalid size %d for Ed25519 public key", l)
   189  			}
   190  			cryptoPubKey = ed25519.PublicKey(w.KeyBytes)
   191  		default:
   192  			// Should not occur, as rsa, dsa, ecdsa, and ed25519 all implement ssh.CryptoPublicKey
   193  			return nil, fmt.Errorf("unknown key type: %T", sshKey)
   194  		}
   195  	}
   196  
   197  	pkixKey, err := cryptoutils.MarshalPublicKeyToDER(cryptoPubKey)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  	fp := ssh.FingerprintSHA256(k.key)
   202  	return []identity.Identity{{
   203  		Crypto:      k.key,
   204  		Raw:         pkixKey,
   205  		Fingerprint: fp,
   206  	}}, nil
   207  }
   208  
   209  // Copied by x/ssh/keys.go
   210  // TODO: Remove after https://github.com/golang/go/issues/62518
   211  func parseString(in []byte) (out, rest []byte, ok bool) {
   212  	if len(in) < 4 {
   213  		return
   214  	}
   215  	length := binary.BigEndian.Uint32(in)
   216  	in = in[4:]
   217  	if uint32(len(in)) < length {
   218  		return
   219  	}
   220  	out = in[:length]
   221  	rest = in[length:]
   222  	ok = true
   223  	return
   224  }
   225  

View as plain text