77e69b9cf3
Signed-off-by: Olivier Gambier <olivier@docker.com>
256 lines
6.5 KiB
Go
256 lines
6.5 KiB
Go
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
|
|
}
|