Skip to content

Commit

Permalink
Create initial version for signing certs with KMS key
Browse files Browse the repository at this point in the history
  • Loading branch information
tlbdk committed Jan 30, 2020
1 parent 9b49a56 commit 49241b4
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 12 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
.vscode
/auth-wrapper
vendor
/dist
67 changes: 67 additions & 0 deletions cmd/cert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package main

import (
"crypto/rand"
"fmt"
"io/ioutil"
"log"

"github.com/connectedcars/auth-wrapper/sshagent"
"golang.org/x/crypto/ssh"
)

// https://medium.com/tarkalabs/ssh-recipes-in-go-an-interlude-6fa88a03d458
// https://gitlab.openebs.ci/openebs/maya/blob/b5f23e9b2e0c3e9d9503a5c1ae9c15cf8e439db5/vendor/golang.org/x/crypto/ssh/agent/client_test.go
// https://github.com/cloudtools/ssh-cert-authority
// https://github.com/signmykeyio/signmykey

func signCert(key string) (int, error) {
// Parse public key string
userPubkey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key))
if err != nil {
log.Fatal(err)
}

cert := &ssh.Certificate{
Key: userPubkey,
KeyId: "test",
CertType: ssh.UserCert,
ValidPrincipals: []string{"tlb"},
ValidAfter: 0,
ValidBefore: ssh.CertTimeInfinity, // uint64(time.Now().Add(time.Minute * 60).Unix()),
Permissions: ssh.Permissions{
CriticalOptions: map[string]string{},
Extensions: map[string]string{},
},
}

/*sshKeyPath := "/Users/f736trbe/.ssh/id_rsa"
privateKeyBytes, err := ioutil.ReadFile(sshKeyPath)
if err != nil {
return 1, fmt.Errorf("Failed to read SSHPrivateKey from %s: %v", sshKeyPath, err)
}
caPrivateKey, err := sshagent.ParsePrivateSSHKey(privateKeyBytes, "")
if err != nil {
return 1, fmt.Errorf("Failed to read SSHPrivateKey from %s: %v", sshKeyPath, err)
}
sshSigner, err := ssh.NewSignerFromKey(caPrivateKey)
if err != nil {
return 1, fmt.Errorf("Failed to read SSHPrivateKey from %s: %v", sshKeyPath, err)
}*/

cryptoSigner, err := sshagent.NewKMSSigner("projects/connectedcars-staging/locations/global/keyRings/cloudbuilder/cryptoKeys/ssh-key/cryptoKeyVersions/3", true)
if err != nil {
return 1, fmt.Errorf("Failed to read NewKMSSigner from: %v", err)
}
sshSigner, err := sshagent.NewSSHSignerFromKMSSigner(cryptoSigner)
if err != nil {
return 1, fmt.Errorf("Failed NewSignerFromSigner from: %v", err)
}

err = cert.SignCert(rand.Reader, sshSigner)
if err != nil {
return 1, fmt.Errorf("Failed SignCert from %v", err)
}
ioutil.WriteFile("/Users/f736trbe/git/connectedcars/auth-wrapper/cert.pub", ssh.MarshalAuthorizedKey(cert), 0644)
return 1, nil
}
6 changes: 3 additions & 3 deletions sshagent/kms-keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
)

type kmsKeyring struct {
signer *KMSSigner
signer KMSSigner

locked bool
passphrase []byte
Expand All @@ -27,11 +27,11 @@ var errLocked = errors.New("agent: locked")
// NewKMSKeyring returns an Agent that holds keys in memory. It is safe
// for concurrent use by multiple goroutines.
func NewKMSKeyring(kmsKeyPath string) (sshAgent agent.ExtendedAgent, err error) {
privateKey, err := NewKMSSigner(kmsKeyPath)
privateKey, err := NewKMSSigner(kmsKeyPath, false)
if err != nil {
return nil, err
}
return &kmsKeyring{signer: privateKey.(*KMSSigner)}, nil
return &kmsKeyring{signer: privateKey}, nil
}

func (r *kmsKeyring) RemoveAll() error {
Expand Down
34 changes: 25 additions & 9 deletions sshagent/kms-signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,23 @@ import (
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
)

// KMSSigner is a key
type KMSSigner struct {
// KMSSigner is an interface for an opaque private key that can be used for
// signing operations. For example, an RSA key kept in a hardware module.
type KMSSigner interface {
crypto.Signer
Digest() crypto.Hash
SSHPublicKey() ssh.PublicKey
}

// kmsSigner is a key
type kmsSigner struct {
ctx context.Context
client *cloudkms.KeyManagementClient
keyName string
publicKey crypto.PublicKey
sshPublicKey ssh.PublicKey
digest crypto.Hash
forceDigest bool
}

// CryptoHashLookup maps crypto.hash to string name
Expand All @@ -49,7 +58,7 @@ var CryptoHashLookup = map[crypto.Hash]string{
}

// NewKMSSigner creates a new instance
func NewKMSSigner(keyName string) (signer crypto.Signer, err error) {
func NewKMSSigner(keyName string, forceDigest bool) (signer KMSSigner, err error) {
// Create the KMS client.
ctx := context.Background()
client, err := cloudkms.NewKeyManagementClient(ctx)
Expand Down Expand Up @@ -107,13 +116,20 @@ func NewKMSSigner(keyName string) (signer crypto.Signer, err error) {
return nil, fmt.Errorf("key %q is not supported format", keyName)
}

return &KMSSigner{keyName: keyName, ctx: ctx, client: client, publicKey: publicKey, digest: digestType, sshPublicKey: sshPublicKey}, nil
return &kmsSigner{keyName: keyName,
ctx: ctx,
client: client,
publicKey: publicKey,
digest: digestType,
sshPublicKey: sshPublicKey,
forceDigest: forceDigest,
}, nil
}

// Sign with key
func (kmss *KMSSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
func (kmss *kmsSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
// Check opts to see if the digest algo matches
if opts.HashFunc() != kmss.digest {
if !kmss.forceDigest && opts.HashFunc() != kmss.digest {
return nil, fmt.Errorf("Requested hash: %v, supported hash %v", CryptoHashLookup[opts.HashFunc()], CryptoHashLookup[kmss.digest])
}

Expand Down Expand Up @@ -154,16 +170,16 @@ func (kmss *KMSSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpt
}

// Public fetches public key
func (kmss *KMSSigner) Public() crypto.PublicKey {
func (kmss *kmsSigner) Public() crypto.PublicKey {
return kmss.publicKey
}

// SSHPublicKey fetches public key in ssh format
func (kmss *KMSSigner) SSHPublicKey() ssh.PublicKey {
func (kmss *kmsSigner) SSHPublicKey() ssh.PublicKey {
return kmss.sshPublicKey
}

// Digest returns hash algo used for this key
func (kmss *KMSSigner) Digest() crypto.Hash {
func (kmss *kmsSigner) Digest() crypto.Hash {
return kmss.digest
}
96 changes: 96 additions & 0 deletions sshagent/kms-ssh-signer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package sshagent

import (
"crypto"
"encoding/asn1"
"io"
"math/big"

"golang.org/x/crypto/ssh"
)

type wrappedSigner struct {
signer KMSSigner
pubKey ssh.PublicKey
}

// NewSSHSignerFromKMSSigner takes a KMSSigner implementation and
// returns a corresponding ssh.Signer interface.
func NewSSHSignerFromKMSSigner(signer KMSSigner) (ssh.Signer, error) {
pubKey, err := ssh.NewPublicKey(signer.Public())
if err != nil {
return nil, err
}
return &wrappedSigner{signer, pubKey}, nil
}

func (s *wrappedSigner) PublicKey() ssh.PublicKey {
return s.pubKey
}

func (s *wrappedSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) {
return s.SignWithAlgorithm(rand, data)
}

func (s *wrappedSigner) SignWithAlgorithm(rand io.Reader, data []byte) (*ssh.Signature, error) {
hashFunc := s.signer.Digest()

var digest []byte
if hashFunc != 0 {
h := hashFunc.New()
h.Write(data)
digest = h.Sum(nil)
} else {
digest = data
}

signature, err := s.signer.Sign(rand, digest, hashFunc)
if err != nil {
return nil, err
}

var algorithm string
if s.PublicKey().Type() == "ssh-rsa" {
switch hashFunc {
case crypto.SHA1:
algorithm = ssh.SigAlgoRSA
case crypto.SHA256:
algorithm = ssh.SigAlgoRSASHA2256
case crypto.SHA512:
algorithm = ssh.SigAlgoRSASHA2512
}
} else {
algorithm = s.pubKey.Type()
}

// crypto.Signer.Sign is expected to return an ASN.1-encoded signature
// for ECDSA and DSA, but that's not the encoding expected by SSH, so
// re-encode.
switch s.pubKey.Type() {
case "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521", "ssh-dss":
type asn1Signature struct {
R, S *big.Int
}
asn1Sig := new(asn1Signature)
_, err := asn1.Unmarshal(signature, asn1Sig)
if err != nil {
return nil, err
}

switch s.pubKey.Type() {
case "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521":
signature = ssh.Marshal(asn1Sig)
case "ssh-dss":
signature = make([]byte, 40)
r := asn1Sig.R.Bytes()
s := asn1Sig.S.Bytes()
copy(signature[20-len(r):20], r)
copy(signature[40-len(s):40], s)
}
}

return &ssh.Signature{
Format: algorithm,
Blob: signature,
}, nil
}

0 comments on commit 49241b4

Please sign in to comment.