blob: c526de5455bcf040d4f3ebddb40318240147f854 [file] [log] [blame]
package libtrust
import (
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
)
var (
// ErrKeyFileDoesNotExist indicates that the private key file does not exist.
ErrKeyFileDoesNotExist = errors.New("key file does not exist")
)
func readKeyFileBytes(filename string) ([]byte, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
if os.IsNotExist(err) {
err = ErrKeyFileDoesNotExist
} else {
err = fmt.Errorf("unable to read key file %s: %s", filename, err)
}
return nil, err
}
return data, nil
}
/*
Loading and Saving of Public and Private Keys in either PEM or JWK format.
*/
// LoadKeyFile opens the given filename and attempts to read a Private Key
// encoded in either PEM or JWK format (if .json or .jwk file extension).
func LoadKeyFile(filename string) (PrivateKey, error) {
contents, err := readKeyFileBytes(filename)
if err != nil {
return nil, err
}
var key PrivateKey
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
key, err = UnmarshalPrivateKeyJWK(contents)
if err != nil {
return nil, fmt.Errorf("unable to decode private key JWK: %s", err)
}
} else {
key, err = UnmarshalPrivateKeyPEM(contents)
if err != nil {
return nil, fmt.Errorf("unable to decode private key PEM: %s", err)
}
}
return key, nil
}
// LoadPublicKeyFile opens the given filename and attempts to read a Public Key
// encoded in either PEM or JWK format (if .json or .jwk file extension).
func LoadPublicKeyFile(filename string) (PublicKey, error) {
contents, err := readKeyFileBytes(filename)
if err != nil {
return nil, err
}
var key PublicKey
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
key, err = UnmarshalPublicKeyJWK(contents)
if err != nil {
return nil, fmt.Errorf("unable to decode public key JWK: %s", err)
}
} else {
key, err = UnmarshalPublicKeyPEM(contents)
if err != nil {
return nil, fmt.Errorf("unable to decode public key PEM: %s", err)
}
}
return key, nil
}
// SaveKey saves the given key to a file using the provided filename.
// This process will overwrite any existing file at the provided location.
func SaveKey(filename string, key PrivateKey) error {
var encodedKey []byte
var err error
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
// Encode in JSON Web Key format.
encodedKey, err = json.MarshalIndent(key, "", " ")
if err != nil {
return fmt.Errorf("unable to encode private key JWK: %s", err)
}
} else {
// Encode in PEM format.
pemBlock, err := key.PEMBlock()
if err != nil {
return fmt.Errorf("unable to encode private key PEM: %s", err)
}
encodedKey = pem.EncodeToMemory(pemBlock)
}
err = ioutil.WriteFile(filename, encodedKey, os.FileMode(0600))
if err != nil {
return fmt.Errorf("unable to write private key file %s: %s", filename, err)
}
return nil
}
// SavePublicKey saves the given public key to the file.
func SavePublicKey(filename string, key PublicKey) error {
var encodedKey []byte
var err error
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
// Encode in JSON Web Key format.
encodedKey, err = json.MarshalIndent(key, "", " ")
if err != nil {
return fmt.Errorf("unable to encode public key JWK: %s", err)
}
} else {
// Encode in PEM format.
pemBlock, err := key.PEMBlock()
if err != nil {
return fmt.Errorf("unable to encode public key PEM: %s", err)
}
encodedKey = pem.EncodeToMemory(pemBlock)
}
err = ioutil.WriteFile(filename, encodedKey, os.FileMode(0644))
if err != nil {
return fmt.Errorf("unable to write public key file %s: %s", filename, err)
}
return nil
}
// Public Key Set files
type jwkSet struct {
Keys []json.RawMessage `json:"keys"`
}
// LoadKeySetFile loads a key set
func LoadKeySetFile(filename string) ([]PublicKey, error) {
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
return loadJSONKeySetFile(filename)
}
// Must be a PEM format file
return loadPEMKeySetFile(filename)
}
func loadJSONKeySetRaw(data []byte) ([]json.RawMessage, error) {
if len(data) == 0 {
// This is okay, just return an empty slice.
return []json.RawMessage{}, nil
}
keySet := jwkSet{}
err := json.Unmarshal(data, &keySet)
if err != nil {
return nil, fmt.Errorf("unable to decode JSON Web Key Set: %s", err)
}
return keySet.Keys, nil
}
func loadJSONKeySetFile(filename string) ([]PublicKey, error) {
contents, err := readKeyFileBytes(filename)
if err != nil && err != ErrKeyFileDoesNotExist {
return nil, err
}
return UnmarshalPublicKeyJWKSet(contents)
}
func loadPEMKeySetFile(filename string) ([]PublicKey, error) {
data, err := readKeyFileBytes(filename)
if err != nil && err != ErrKeyFileDoesNotExist {
return nil, err
}
return UnmarshalPublicKeyPEMBundle(data)
}
// AddKeySetFile adds a key to a key set
func AddKeySetFile(filename string, key PublicKey) error {
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
return addKeySetJSONFile(filename, key)
}
// Must be a PEM format file
return addKeySetPEMFile(filename, key)
}
func addKeySetJSONFile(filename string, key PublicKey) error {
encodedKey, err := json.Marshal(key)
if err != nil {
return fmt.Errorf("unable to encode trusted client key: %s", err)
}
contents, err := readKeyFileBytes(filename)
if err != nil && err != ErrKeyFileDoesNotExist {
return err
}
rawEntries, err := loadJSONKeySetRaw(contents)
if err != nil {
return err
}
rawEntries = append(rawEntries, json.RawMessage(encodedKey))
entriesWrapper := jwkSet{Keys: rawEntries}
encodedEntries, err := json.MarshalIndent(entriesWrapper, "", " ")
if err != nil {
return fmt.Errorf("unable to encode trusted client keys: %s", err)
}
err = ioutil.WriteFile(filename, encodedEntries, os.FileMode(0644))
if err != nil {
return fmt.Errorf("unable to write trusted client keys file %s: %s", filename, err)
}
return nil
}
func addKeySetPEMFile(filename string, key PublicKey) error {
// Encode to PEM, open file for appending, write PEM.
file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, os.FileMode(0644))
if err != nil {
return fmt.Errorf("unable to open trusted client keys file %s: %s", filename, err)
}
defer file.Close()
pemBlock, err := key.PEMBlock()
if err != nil {
return fmt.Errorf("unable to encoded trusted key: %s", err)
}
_, err = file.Write(pem.EncodeToMemory(pemBlock))
if err != nil {
return fmt.Errorf("unable to write trusted keys file: %s", err)
}
return nil
}