-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
303 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package main | ||
|
||
import ( | ||
"crypto" | ||
"io" | ||
|
||
"golang.org/x/crypto/ssh" | ||
) | ||
|
||
// Implementation of the OpenSSH Certificate Authority. | ||
// | ||
// This encapsulates the signing code, as well as the RNG source to be used | ||
// for signing operations. This contains no logic regarding certificate policy | ||
// or principal policy, rather, it's just the underlying code to do the | ||
// signing. | ||
type CA struct { | ||
// RNG source. This should almost always be crypto/rand.Reader, unless | ||
// your underlying crypto.Signer has an on-chip RNG, in which case this | ||
// may be set to something like `nil`. | ||
Rand io.Reader | ||
|
||
// Wrapped `crypto.Signer` to preform OpenSSH CA operations. | ||
Signer ssh.Signer | ||
} | ||
|
||
// Sign an SSH Certificate template (with `Key` set), and return the base64 | ||
// encoded ssh key entry (something like `ssh-*-cert`) that the user can | ||
// import. | ||
func (s CA) Sign(template ssh.Certificate) ([]byte, error) { | ||
return CreateCertificate( | ||
s.Rand, | ||
template, | ||
s.Signer.PublicKey(), | ||
template.Key, | ||
s.Signer, | ||
) | ||
} | ||
|
||
// | ||
func (s CA) SignAndParse(template ssh.Certificate) (ssh.PublicKey, []byte, error) { | ||
bytes, err := s.Sign(template) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
pubKey, err := ssh.ParsePublicKey(bytes) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
return pubKey, bytes, nil | ||
} | ||
|
||
// Create a new SSH Certificate Authority to sign ssh public keys. | ||
func New(rand io.Reader, priv crypto.Signer) (*CA, error) { | ||
signer, err := ssh.NewSignerFromSigner(priv) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &CA{ | ||
Rand: rand, | ||
Signer: signer, | ||
}, nil | ||
} | ||
|
||
// Generate 32 bytes of entropy from the given rand source. | ||
func generateNonce32(rand io.Reader) ([]byte, error) { | ||
var b [32]byte | ||
if _, err := rand.Read(b[:]); err != nil { | ||
return nil, err | ||
} | ||
return b[:], nil | ||
} | ||
|
||
// Create a Certificate. This signature looks similar to the | ||
// x509.CreateCertificate signature for ease of use. | ||
func CreateCertificate( | ||
rand io.Reader, | ||
template ssh.Certificate, | ||
parent ssh.PublicKey, | ||
pub ssh.PublicKey, | ||
priv ssh.Signer, | ||
) ([]byte, error) { | ||
nonce, err := generateNonce32(rand) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
cert := ssh.Certificate{ | ||
Nonce: nonce, | ||
Key: pub, | ||
Serial: template.Serial, | ||
CertType: template.CertType, | ||
KeyId: template.KeyId, | ||
ValidPrincipals: template.ValidPrincipals, | ||
ValidAfter: template.ValidAfter, | ||
ValidBefore: template.ValidBefore, | ||
SignatureKey: parent, | ||
} | ||
|
||
cert.Permissions.CriticalOptions = template.CriticalOptions | ||
cert.Permissions.Extensions = template.Extensions | ||
|
||
err = cert.SignCert(rand, priv) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return cert.Marshal(), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
module github.com/alex/hallow | ||
|
||
go 1.14 | ||
|
||
require ( | ||
github.com/aws/aws-lambda-go v1.13.3 | ||
github.com/aws/aws-sdk-go v1.28.9 | ||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | ||
github.com/aws/aws-lambda-go v1.13.3 h1:SuCy7H3NLyp+1Mrfp+m80jcbi9KYWAs9/BXwppwRDzY= | ||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= | ||
github.com/aws/aws-sdk-go v1.28.9 h1:grIuBQc+p3dTRXerh5+2OxSuWFi0iXuxbFdTSg0jaW0= | ||
github.com/aws/aws-sdk-go v1.28.9/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= | ||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= | ||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= | ||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U= | ||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package kmssigner | ||
|
||
import ( | ||
"context" | ||
"crypto" | ||
"crypto/x509" | ||
"io" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/service/kms" | ||
) | ||
|
||
type kmsSigner struct { | ||
kmsapi *kms.KMS | ||
keyArn string | ||
pubKey crypto.PublicKey | ||
} | ||
|
||
func New(kmsapi *kms.KMS, keyArn string) (crypto.Signer, error) { | ||
pubKeyResponse, err := kmsapi.GetPublicKeyWithContext(context.TODO(), &kms.GetPublicKeyInput{ | ||
KeyId: aws.String(keyArn), | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
pubKey, err := x509.ParsePKIXPublicKey(pubKeyResponse.PublicKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return kmsSigner{ | ||
kmsapi: kmsapi, | ||
keyArn: keyArn, | ||
pubKey: pubKey, | ||
}, nil | ||
} | ||
|
||
func (k kmsSigner) Public() crypto.PublicKey { | ||
return k.pubKey | ||
} | ||
|
||
func (k kmsSigner) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { | ||
signatureResponse, err := k.kmsapi.SignWithContext(context.TODO(), &kms.SignInput{ | ||
KeyId: aws.String(k.keyArn), | ||
Message: digest, | ||
MessageType: aws.String("DIGEST"), | ||
SigningAlgorithm: aws.String(cryptoHashToKmsHash[opts.HashFunc()]), | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return signatureResponse.Signature, nil | ||
} | ||
|
||
// TODO: Fill in more hashes and take into account key type. | ||
var cryptoHashToKmsHash = map[crypto.Hash]string{ | ||
crypto.SHA256: kms.SigningAlgorithmSpecEcdsaSha256, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"crypto/rand" | ||
"encoding/base64" | ||
"encoding/binary" | ||
"fmt" | ||
"log" | ||
"os" | ||
"time" | ||
|
||
"golang.org/x/crypto/ssh" | ||
|
||
"github.com/aws/aws-lambda-go/events" | ||
"github.com/aws/aws-lambda-go/lambda" | ||
"github.com/aws/aws-sdk-go/aws/session" | ||
"github.com/aws/aws-sdk-go/service/kms" | ||
|
||
"github.com/alex/hallow/kmssigner" | ||
) | ||
|
||
func stringSliceContains(s string, v []string) bool { | ||
for _, x := range v { | ||
if s == x { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
type config struct { | ||
ca CA | ||
allowedKeyTypes []string | ||
certAge time.Duration | ||
} | ||
|
||
func (c *config) handleRequest(ctx context.Context, event events.APIGatewayProxyRequest) (string, error) { | ||
principal := event.RequestContext.Identity.UserArn | ||
publicKey, comment, _, _, err := ssh.ParseAuthorizedKey([]byte(event.Body)) | ||
if err != nil { | ||
log.Println(err) | ||
return "", err | ||
} | ||
if !stringSliceContains(publicKey.Type(), c.allowedKeyTypes) { | ||
return "", fmt.Errorf("Disallowed public key type: %s", publicKey.Type()) | ||
} | ||
|
||
var b [8]byte | ||
if _, err := c.ca.Rand.Read(b[:]); err != nil { | ||
log.Println(err) | ||
return "", err | ||
} | ||
|
||
serial := int64(binary.LittleEndian.Uint64(b[:])) | ||
template := ssh.Certificate{ | ||
Key: publicKey, | ||
Serial: uint64(serial), | ||
CertType: ssh.UserCert, | ||
KeyId: comment, | ||
ValidPrincipals: []string{principal}, | ||
ValidAfter: uint64(time.Now().Add(-time.Second * 5).Unix()), | ||
ValidBefore: uint64(time.Now().Add(c.certAge).Unix()), | ||
} | ||
|
||
sshCert, _, err := c.ca.SignAndParse(template) | ||
if err != nil { | ||
log.Println(err) | ||
return "", err | ||
} | ||
return fmt.Sprintf("%s %s\n", sshCert.Type(), base64.StdEncoding.EncodeToString(sshCert.Marshal())), nil | ||
} | ||
|
||
func main() { | ||
sess := session.New() | ||
signer, err := kmssigner.New(kms.New(sess), os.Getenv("HALLOW_KMS_KEY_ARN")) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
sshSigner, err := ssh.NewSignerFromSigner(signer) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
c := &config{ | ||
ca: CA{ | ||
Rand: rand.Reader, | ||
Signer: sshSigner, | ||
}, | ||
certAge: 30 * time.Minute, | ||
allowedKeyTypes: []string{ | ||
ssh.KeyAlgoED25519, | ||
ssh.KeyAlgoECDSA521, | ||
ssh.KeyAlgoECDSA384, | ||
ssh.KeyAlgoECDSA256, | ||
ssh.KeyAlgoSKED25519, | ||
ssh.KeyAlgoSKECDSA256, | ||
}, | ||
} | ||
lambda.Start(c.handleRequest) | ||
} |