Skip to content

Commit

Permalink
feat: add loaders for pkcs8 keys in cloudfront/sign (#2313)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucix-aws authored Oct 13, 2023
1 parent e3b97d2 commit 6e4fae3
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 4 deletions.
8 changes: 8 additions & 0 deletions .changelog/71a17e4b0d334a06a7b2398a0dc1ec87.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "71a17e4b-0d33-4a06-a7b2-398a0dc1ec87",
"type": "feature",
"description": "Add support for loading PKCS8-formatted private keys.",
"modules": [
"feature/cloudfront/sign"
]
}
67 changes: 67 additions & 0 deletions feature/cloudfront/sign/privkey.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package sign

import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
Expand Down Expand Up @@ -44,6 +47,10 @@ func LoadPEMPrivKey(reader io.Reader) (*rsa.PrivateKey, error) {
// LoadEncryptedPEMPrivKey decrypts the PEM encoded private key using the
// password provided returning a RSA private key. If the PEM data is invalid,
// or unable to decrypt an error will be returned.
//
// Deprecated: RFC 1423 PEM encryption is insecure. Callers using encrypted
// keys should instead decrypt that payload externally and pass it to
// [LoadPEMPrivKey].
func LoadEncryptedPEMPrivKey(reader io.Reader, password []byte) (*rsa.PrivateKey, error) {
block, err := loadPem(reader)
if err != nil {
Expand All @@ -58,6 +65,66 @@ func LoadEncryptedPEMPrivKey(reader io.Reader, password []byte) (*rsa.PrivateKey
return x509.ParsePKCS1PrivateKey(decryptedBlock)
}

// LoadPEMPrivKeyPKCS8 reads a PEM-encoded RSA private key in PKCS8 format from
// the given reader.
//
// x509.ParsePKCS8PrivateKey can return multiple key types and this API does
// not discern between them. Callers in need of the underlying value must
// obtain it via type assertion:
//
// key, err := LoadPEMPrivKeyPKCS8(r)
// if err != nil { /* ... */ }
//
// switch key.(type) {
// case *rsa.PrivateKey:
// // ...
// case *ecdsa.PrivateKey:
// // ...
// case ed25519.PrivateKey:
// // ...
// default:
// panic("unrecognized private key type")
// }
//
// See aforementioned API docs for a full list of possible key types.
//
// If calling code can opaquely handle the returned key as a crypto.Signer, use
// [LoadPEMPrivKeyPKCS8AsSigner] instead.
func LoadPEMPrivKeyPKCS8(reader io.Reader) (interface{}, error) {
block, err := loadPem(reader)
if err != nil {
return nil, fmt.Errorf("load pem: %v", err)
}

key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("parse pkcs8 key: %v", err)
}

return key, nil
}

var (
_ crypto.Signer = (*rsa.PrivateKey)(nil)
_ crypto.Signer = (*ecdsa.PrivateKey)(nil)
_ crypto.Signer = (ed25519.PrivateKey)(nil)
)

// LoadPEMPrivKeyPKCS8AsSigner wraps [LoadPEMPrivKeyPKCS8] to expect a crypto.Signer.
func LoadPEMPrivKeyPKCS8AsSigner(reader io.Reader) (crypto.Signer, error) {
key, err := LoadPEMPrivKeyPKCS8(reader)
if err != nil {
return nil, fmt.Errorf("load key: %v", err)
}

signer, ok := key.(crypto.Signer)
if !ok {
return nil, fmt.Errorf("key of type %T is not a crypto.Signer", key)
}

return signer, nil
}

func loadPem(reader io.Reader) (*pem.Block, error) {
b, err := ioutil.ReadAll(reader)
if err != nil {
Expand Down
125 changes: 125 additions & 0 deletions feature/cloudfront/sign/privkey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ package sign

import (
"bytes"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
cryptorand "crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"math/rand"
"strings"
Expand Down Expand Up @@ -88,3 +93,123 @@ func TestLoadEncryptedPEMPrivKeyWrongPassword(t *testing.T) {
t.Errorf("Expected nil privKey but got %#v", privKey)
}
}

func TestLoadPEMPrivKeyPKCS8_RSA(t *testing.T) {
r, err := generatePKCS8(keyTypeRSA)
if err != nil {
t.Errorf("generate pkcs8 key: %v", err)
}

var rr bytes.Buffer
tee := io.TeeReader(r, &rr)

key, err := LoadPEMPrivKeyPKCS8(tee)
if err != nil {
t.Errorf("load pkcs8 key: %v", err)
}

if _, ok := key.(*rsa.PrivateKey); !ok {
t.Errorf("key should be rsa but was %T", key)
}

_, err = LoadPEMPrivKeyPKCS8AsSigner(&rr)
if err != nil {
t.Errorf("load pkcs8 key as signer: %v", err)
}
}

func TestLoadPEMPrivKeyPKCS8_ECDSA(t *testing.T) {
r, err := generatePKCS8(keyTypeECDSA)
if err != nil {
t.Errorf("generate pkcs8 key: %v", err)
}

var rr bytes.Buffer
tee := io.TeeReader(r, &rr)

key, err := LoadPEMPrivKeyPKCS8(tee)
if err != nil {
t.Errorf("load pkcs8 key: %v", err)
}

if _, ok := key.(*ecdsa.PrivateKey); !ok {
t.Errorf("key should be ecdsa but was %T", key)
}

_, err = LoadPEMPrivKeyPKCS8AsSigner(&rr)
if err != nil {
t.Errorf("load pkcs8 key as signer: %v", err)
}
}

func TestLoadPEMPrivKeyPKCS8_ED25519(t *testing.T) {
r, err := generatePKCS8(keyTypeED25519)
if err != nil {
t.Errorf("generate pkcs8 key: %v", err)
}

var rr bytes.Buffer
tee := io.TeeReader(r, &rr)

key, err := LoadPEMPrivKeyPKCS8(tee)
if err != nil {
t.Errorf("load pkcs8 key: %v", err)
}

if _, ok := key.(ed25519.PrivateKey); !ok {
t.Errorf("key should be ed25519 but was %T", key)
}

_, err = LoadPEMPrivKeyPKCS8AsSigner(&rr)
if err != nil {
t.Errorf("load pkcs8 key as signer: %v", err)
}
}

type keyType int

const (
keyTypeRSA keyType = iota
keyTypeECDSA
keyTypeED25519
)

func generatePKCS8(typ keyType) (io.Reader, error) {
key, pemType, err := generatePrivateKey(typ)
if err != nil {
return nil, err
}

b, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return nil, err
}

block := &pem.Block{
Type: pemType,
Bytes: b,
}

var buf bytes.Buffer
if err := pem.Encode(&buf, block); err != nil {
return nil, err
}

return &buf, err
}

func generatePrivateKey(typ keyType) (interface{}, string, error) {
switch typ {
case keyTypeRSA:
key, err := rsa.GenerateKey(cryptorand.Reader, 1024)
return key, "RSA PRIVATE KEY", err
case keyTypeECDSA:
key, err := ecdsa.GenerateKey(elliptic.P224(), cryptorand.Reader)
return key, "ECDSA PRIVATE KEY", err
case keyTypeED25519:
_, key, err := ed25519.GenerateKey(cryptorand.Reader)
return key, "ED25519 PRIVATE KEY", err
default:
return nil, "", fmt.Errorf("unsupported key type %v", typ)
}
}
17 changes: 13 additions & 4 deletions feature/cloudfront/sign/sign_url.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,23 @@
// More information about signed URLs and their structure can be found at:
// http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-canned-policy.html
//
// To sign a URL create a URLSigner with your private key and credential pair key ID.
// Once you have a URLSigner instance you can call Sign or SignWithPolicy to
// sign the URLs.
// To sign a URL create a [URLSigner] with your private key and credential pair
// key ID. Once you have a URLSigner instance you can call [URLSigner.Sign] or
// [URLSigner.SignWithPolicy] to sign the URLs.
//
// Example:
//
// // Sign URL to be valid for 1 hour from now.
// // Load our key from a PEM block.
// privKey, err := sign.LoadPEMPrivKey(block)
// if err != nil {
// log.Fatalf("Failed to load private key, err: %s\n", err.Error())
// }
//
// // Create our signer. Keys loaded via the LoadPEMPrivKey* family of APIs
// // implement crypto.Signer and can be passed to this directly.
// signer := sign.NewURLSigner(keyID, privKey)
//
// // Sign URL to be valid for 1 hour from now.
// signedURL, err := signer.Sign(rawURL, time.Now().Add(1*time.Hour))
// if err != nil {
// log.Fatalf("Failed to sign url, err: %s\n", err.Error())
Expand Down

0 comments on commit 6e4fae3

Please sign in to comment.