From 96be7e47497b0daca053386ed0a2a8149f9c81f5 Mon Sep 17 00:00:00 2001 From: Josh Hawn Date: Wed, 20 Aug 2014 18:50:43 -0700 Subject: [PATCH] Refactor with more test coverage Refactor of some of the key file functions to be more generic and ensure that all types of keys and output formats are covered. More unit tests to cover added functionalities. Updated tlsdemo to match current interface. --- ec_key.go | 2 + ec_key_test.go | 18 +++++++ filter.go | 39 ++++++-------- filter_test.go | 79 +++++++++++++++++++++++++++ key.go | 63 +++++++++++++++++++--- key_files.go | 132 ++++++++++++++++++++++++++++++--------------- key_files_test.go | 42 +++++++++++++-- rsa_key.go | 2 + tlsdemo/client.go | 19 ++++--- tlsdemo/gencert.go | 20 +++++-- tlsdemo/genkeys.go | 24 ++++++--- tlsdemo/server.go | 6 +-- util.go | 29 ++++++++++ 13 files changed, 374 insertions(+), 101 deletions(-) create mode 100644 filter_test.go diff --git a/ec_key.go b/ec_key.go index 0923e38..f517db9 100644 --- a/ec_key.go +++ b/ec_key.go @@ -151,6 +151,7 @@ func (k *ecPublicKey) PEMBlock() (*pem.Block, error) { if err != nil { return nil, fmt.Errorf("unable to serialize EC PublicKey to DER-encoded PKIX format: %s", err) } + k.extended["keyID"] = k.KeyID() return createPemBlock("PUBLIC KEY", derBytes, k.extended) } @@ -345,6 +346,7 @@ func (k *ecPrivateKey) PEMBlock() (*pem.Block, error) { if err != nil { return nil, fmt.Errorf("unable to serialize EC PrivateKey to DER-encoded PKIX format: %s", err) } + k.extended["keyID"] = k.KeyID() return createPemBlock("EC PRIVATE KEY", derBytes, k.extended) } diff --git a/ec_key_test.go b/ec_key_test.go index 72fe65d..26ac381 100644 --- a/ec_key_test.go +++ b/ec_key_test.go @@ -137,3 +137,21 @@ func TestFromCryptoECKeys(t *testing.T) { } } } + +func TestExtendedFields(t *testing.T) { + key, err := GenerateECP256PrivateKey() + if err != nil { + t.Fatal(err) + } + + key.AddExtendedField("test", "foobar") + val := key.GetExtendedField("test") + + gotVal, ok := val.(string) + if !ok { + t.Fatalf("value is not a string") + } else if gotVal != val { + t.Fatalf("value %q is not equal to %q", gotVal, val) + } + +} diff --git a/filter.go b/filter.go index 345b9e8..f4920cb 100644 --- a/filter.go +++ b/filter.go @@ -4,41 +4,34 @@ import ( "path/filepath" ) +// FilterByHosts does something. func FilterByHosts(keys []PublicKey, host string, includeEmpty bool) ([]PublicKey, error) { filtered := make([]PublicKey, 0, len(keys)) - for i := range keys { - var hosts interface{} - switch k := keys[i].(type) { - case *ecPublicKey: - hosts = k.GetExtendedField("hosts") - case *rsaPublicKey: - hosts = k.GetExtendedField("hosts") - default: - continue - } - hostList, ok := hosts.([]interface{}) - if !ok || (ok && len(hostList) == 0) { + + for _, pubKey := range keys { + hosts, ok := pubKey.GetExtendedField("hosts").([]string) + + if !ok || (ok && len(hosts) == 0) { if includeEmpty { - filtered = append(filtered, keys[i]) + filtered = append(filtered, pubKey) } continue } - // Check if any hostList match pattern - for _, h := range hostList { - hString, ok := h.(string) - if !ok { - continue - } - match, matchErr := filepath.Match(hString, host) - if matchErr != nil { - return nil, matchErr + + // Check if any hosts match pattern + for _, hostPattern := range hosts { + match, err := filepath.Match(hostPattern, host) + if err != nil { + return nil, err } + if match { - filtered = append(filtered, keys[i]) + filtered = append(filtered, pubKey) continue } } } + return filtered, nil } diff --git a/filter_test.go b/filter_test.go new file mode 100644 index 0000000..ed9c575 --- /dev/null +++ b/filter_test.go @@ -0,0 +1,79 @@ +package libtrust + +import ( + "testing" +) + +func compareKeySlices(t *testing.T, sliceA, sliceB []PublicKey) { + if len(sliceA) != len(sliceB) { + t.Fatalf("slice size %d, expected %d", len(sliceA), len(sliceB)) + } + + for i, itemA := range sliceA { + itemB := sliceB[i] + if itemA != itemB { + t.Fatalf("slice index %d not equal: %#v != %#v", i, itemA, itemB) + } + } +} + +func TestFilter(t *testing.T) { + keys := make([]PublicKey, 0, 8) + + // Create 8 keys and add host entries. + for i := 0; i < cap(keys); i++ { + key, err := GenerateECP256PrivateKey() + if err != nil { + t.Fatal(err) + } + + switch { + case i == 0: + // Don't add entries for this key, key 0. + break + case i%2 == 0: + // Should catch keys 2, 4, and 6. + key.AddExtendedField("hosts", []string{"*.even.example.com"}) + case i == 7: + // Should catch only the last key, and make it match any hostname. + key.AddExtendedField("hosts", []string{"*"}) + default: + // should catch keys 1, 3, 5. + key.AddExtendedField("hosts", []string{"*.example.com"}) + } + + keys = append(keys, key) + } + + // Should match 2 keys, the empty one, and the one that matches all hosts. + matchedKeys, err := FilterByHosts(keys, "foo.bar.com", true) + if err != nil { + t.Fatal(err) + } + expectedMatch := []PublicKey{keys[0], keys[7]} + compareKeySlices(t, expectedMatch, matchedKeys) + + // Should match 1 key, the one that matches any host. + matchedKeys, err = FilterByHosts(keys, "foo.bar.com", false) + if err != nil { + t.Fatal(err) + } + expectedMatch = []PublicKey{keys[7]} + compareKeySlices(t, expectedMatch, matchedKeys) + + // Should match keys that end in "example.com", and the key that matches anything. + matchedKeys, err = FilterByHosts(keys, "foo.example.com", false) + if err != nil { + t.Fatal(err) + } + expectedMatch = []PublicKey{keys[1], keys[3], keys[5], keys[7]} + compareKeySlices(t, expectedMatch, matchedKeys) + + // Should match all of the keys except the empty key. + matchedKeys, err = FilterByHosts(keys, "foo.even.example.com", false) + if err != nil { + t.Fatal(err) + } + expectedMatch = keys[1:] + compareKeySlices(t, expectedMatch, matchedKeys) +} diff --git a/key.go b/key.go index 27bfc2e..73642db 100644 --- a/key.go +++ b/key.go @@ -103,12 +103,33 @@ func UnmarshalPublicKeyPEM(data []byte) (PublicKey, error) { return nil, fmt.Errorf("unable to get PublicKey from PEM type: %s", pemBlock.Type) } - cryptoPublicKey, err := x509.ParsePKIXPublicKey(pemBlock.Bytes) - if err != nil { - return nil, fmt.Errorf("unable to decode Public Key PEM data: %s", err) + return pubKeyFromPEMBlock(pemBlock) +} + +// UnmarshalPublicKeyPEMBundle parses the PEM encoded data as a bundle of +// PEM blocks appended one after the other and returns a slice of PublicKey +// objects that it finds. +func UnmarshalPublicKeyPEMBundle(data []byte) ([]PublicKey, error) { + pubKeys := []PublicKey{} + + for { + var pemBlock *pem.Block + pemBlock, data = pem.Decode(data) + if pemBlock == nil { + break + } else if pemBlock.Type != "PUBLIC KEY" { + return nil, fmt.Errorf("unable to get PublicKey from PEM type: %s", pemBlock.Type) + } + + pubKey, err := pubKeyFromPEMBlock(pemBlock) + if err != nil { + return nil, err + } + + pubKeys = append(pubKeys, pubKey) } - return FromCryptoPublicKey(cryptoPublicKey) + return pubKeys, nil } // UnmarshalPrivateKeyPEM parses the PEM encoded data and returns a libtrust @@ -119,22 +140,31 @@ func UnmarshalPrivateKeyPEM(data []byte) (PrivateKey, error) { return nil, errors.New("unable to find PEM encoded data") } + var key PrivateKey + switch { case pemBlock.Type == "RSA PRIVATE KEY": rsaPrivateKey, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes) if err != nil { return nil, fmt.Errorf("unable to decode RSA Private Key PEM data: %s", err) } - return fromRSAPrivateKey(rsaPrivateKey), nil + key = fromRSAPrivateKey(rsaPrivateKey) case pemBlock.Type == "EC PRIVATE KEY": ecPrivateKey, err := x509.ParseECPrivateKey(pemBlock.Bytes) if err != nil { return nil, fmt.Errorf("unable to decode EC Private Key PEM data: %s", err) } - return fromECPrivateKey(ecPrivateKey) + key, err = fromECPrivateKey(ecPrivateKey) + if err != nil { + return nil, err + } default: return nil, fmt.Errorf("unable to get PrivateKey from PEM type: %s", pemBlock.Type) } + + addPEMHeadersToKey(pemBlock, key.PublicKey()) + + return key, nil } // UnmarshalPublicKeyJWK unmarshals the given JSON Web Key into a generic @@ -169,6 +199,27 @@ func UnmarshalPublicKeyJWK(data []byte) (PublicKey, error) { } } +// UnmarshalPublicKeyJWKSet parses the JSON encoded data as a JSON Web Key Set +// and returns a slice of Public Key objects. +func UnmarshalPublicKeyJWKSet(data []byte) ([]PublicKey, error) { + rawKeys, err := loadJSONKeySetRaw(data) + if err != nil { + return nil, err + } + + pubKeys := make([]PublicKey, 0, len(rawKeys)) + + for _, rawKey := range rawKeys { + pubKey, err := UnmarshalPublicKeyJWK(rawKey) + if err != nil { + return nil, err + } + pubKeys = append(pubKeys, pubKey) + } + + return pubKeys, nil +} + // UnmarshalPrivateKeyJWK unmarshals the given JSON Web Key into a generic // Private Key to be used with libtrust. func UnmarshalPrivateKeyJWK(data []byte) (PrivateKey, error) { diff --git a/key_files.go b/key_files.go index 5a7966e..c526de5 100644 --- a/key_files.go +++ b/key_files.go @@ -5,7 +5,6 @@ import ( "encoding/pem" "errors" "fmt" - "io" "io/ioutil" "os" "strings" @@ -16,6 +15,21 @@ var ( 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. */ @@ -23,11 +37,9 @@ var ( // 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 := ioutil.ReadFile(filename) - if os.IsNotExist(err) { - return nil, ErrKeyFileDoesNotExist - } else if err != nil { - return nil, fmt.Errorf("unable to read private key file %s: %s", filename, err) + contents, err := readKeyFileBytes(filename) + if err != nil { + return nil, err } var key PrivateKey @@ -50,11 +62,9 @@ func LoadKeyFile(filename string) (PrivateKey, error) { // 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 := ioutil.ReadFile(filename) - if os.IsNotExist(err) { - return nil, ErrKeyFileDoesNotExist - } else if err != nil { - return nil, fmt.Errorf("unable to read public key file %s: %s", filename, err) + contents, err := readKeyFileBytes(filename) + if err != nil { + return nil, err } var key PublicKey @@ -131,69 +141,82 @@ func SavePublicKey(filename string, key PublicKey) error { return nil } +// Public Key Set files + type jwkSet struct { Keys []json.RawMessage `json:"keys"` } -func loadJsonKeySet(filename string) ([]json.RawMessage, error) { - var set jwkSet - f, err := os.Open(filename) - if err != nil { - if os.IsNotExist(err) { - return nil, ErrKeyFileDoesNotExist - } - return nil, err +// LoadKeySetFile loads a key set +func LoadKeySetFile(filename string) ([]PublicKey, error) { + if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") { + return loadJSONKeySetFile(filename) } - defer f.Close() - decoder := json.NewDecoder(f) + // Must be a PEM format file + return loadPEMKeySetFile(filename) +} - err = decoder.Decode(&set) - if err != nil { - if err == io.EOF { - return nil, nil - } - return nil, err +func loadJSONKeySetRaw(data []byte) ([]json.RawMessage, error) { + if len(data) == 0 { + // This is okay, just return an empty slice. + return []json.RawMessage{}, nil } - return set.Keys, nil -} + keySet := jwkSet{} -func loadJsonKeySetFile(filename string) ([]PublicKey, error) { - messages, err := loadJsonKeySet(filename) + err := json.Unmarshal(data, &keySet) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to decode JSON Web Key Set: %s", err) } - keys := make([]PublicKey, len(messages)) - for i, raw := range messages { - key, err := UnmarshalPublicKeyJWK(raw) - if err != nil { - return nil, err - } - keys[i] = key + return keySet.Keys, nil +} + +func loadJSONKeySetFile(filename string) ([]PublicKey, error) { + contents, err := readKeyFileBytes(filename) + if err != nil && err != ErrKeyFileDoesNotExist { + return nil, err } - return keys, nil + return UnmarshalPublicKeyJWKSet(contents) } -// LoadKeySetFile loads a key set -func LoadKeySetFile(filename string) ([]PublicKey, error) { - return loadJsonKeySetFile(filename) +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) } - rawEntries, err := loadJsonKeySet(filename) + 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} @@ -209,3 +232,24 @@ func AddKeySetFile(filename string, key PublicKey) error { 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 +} diff --git a/key_files_test.go b/key_files_test.go index dced452..66c71dd 100644 --- a/key_files_test.go +++ b/key_files_test.go @@ -25,6 +25,19 @@ func TestKeyFiles(t *testing.T) { t.Fatal(err) } + testKeyFiles(t, key) + + key, err = GenerateRSA2048PrivateKey() + if err != nil { + t.Fatal(err) + } + + testKeyFiles(t, key) +} + +func testKeyFiles(t *testing.T, key PrivateKey) { + var err error + privateKeyFilename := makeTempFile(t, "private_key") privateKeyFilenamePEM := privateKeyFilename + ".pem" privateKeyFilenameJWK := privateKeyFilename + ".jwk" @@ -95,7 +108,18 @@ func TestKeyFiles(t *testing.T) { func TestTrustedHostKeysFile(t *testing.T) { trustedHostKeysFilename := makeTempFile(t, "trusted_host_keys") + trustedHostKeysFilenamePEM := trustedHostKeysFilename + ".pem" + trustedHostKeysFilenameJWK := trustedHostKeysFilename + ".json" + testTrustedHostKeysFile(t, trustedHostKeysFilenamePEM) + testTrustedHostKeysFile(t, trustedHostKeysFilenameJWK) + + os.Remove(trustedHostKeysFilename) + os.Remove(trustedHostKeysFilenamePEM) + os.Remove(trustedHostKeysFilenameJWK) +} + +func testTrustedHostKeysFile(t *testing.T, trustedHostKeysFilename string) { hostAddress1 := "docker.example.com:2376" hostKey1, err := GenerateECP256PrivateKey() if err != nil { @@ -119,7 +143,7 @@ func TestTrustedHostKeysFile(t *testing.T) { } hostAddress2 := "192.168.59.103:2376" - hostKey2, err := GenerateECP384PrivateKey() + hostKey2, err := GenerateRSA2048PrivateKey() if err != nil { t.Fatal(err) } @@ -140,12 +164,22 @@ func TestTrustedHostKeysFile(t *testing.T) { t.Logf("Host Key: %s\n\n", hostKey) } - os.Remove(trustedHostKeysFilename) } func TestTrustedClientKeysFile(t *testing.T) { trustedClientKeysFilename := makeTempFile(t, "trusted_client_keys") + trustedClientKeysFilenamePEM := trustedClientKeysFilename + ".pem" + trustedClientKeysFilenameJWK := trustedClientKeysFilename + ".json" + testTrustedClientKeysFile(t, trustedClientKeysFilenamePEM) + testTrustedClientKeysFile(t, trustedClientKeysFilenameJWK) + + os.Remove(trustedClientKeysFilename) + os.Remove(trustedClientKeysFilenamePEM) + os.Remove(trustedClientKeysFilenameJWK) +} + +func testTrustedClientKeysFile(t *testing.T, trustedClientKeysFilename string) { clientKey1, err := GenerateECP256PrivateKey() if err != nil { t.Fatal(err) @@ -165,7 +199,7 @@ func TestTrustedClientKeysFile(t *testing.T) { t.Logf("Client Key: %s\n", clientKey) } - clientKey2, err := GenerateECP384PrivateKey() + clientKey2, err := GenerateRSA2048PrivateKey() if err != nil { t.Fatal(err) } @@ -183,6 +217,4 @@ func TestTrustedClientKeysFile(t *testing.T) { for _, clientKey := range trustedClientKeys { t.Logf("Client Key: %s\n", clientKey) } - - os.Remove(trustedClientKeysFilename) } diff --git a/rsa_key.go b/rsa_key.go index 5af10a8..b4c62b9 100644 --- a/rsa_key.go +++ b/rsa_key.go @@ -108,6 +108,7 @@ func (k *rsaPublicKey) PEMBlock() (*pem.Block, error) { if err != nil { return nil, fmt.Errorf("unable to serialize RSA PublicKey to DER-encoded PKIX format: %s", err) } + k.extended["keyID"] = k.KeyID() return createPemBlock("PUBLIC KEY", derBytes, k.extended) } @@ -273,6 +274,7 @@ func (k *rsaPrivateKey) MarshalJSON() (data []byte, err error) { // PEMBlock serializes this Private Key to DER-encoded PKIX format. func (k *rsaPrivateKey) PEMBlock() (*pem.Block, error) { derBytes := x509.MarshalPKCS1PrivateKey(k.PrivateKey) + k.extended["keyID"] = k.KeyID() return createPemBlock("RSA PRIVATE KEY", derBytes, k.extended) } diff --git a/tlsdemo/client.go b/tlsdemo/client.go index 66dbf40..0a699a0 100644 --- a/tlsdemo/client.go +++ b/tlsdemo/client.go @@ -5,6 +5,7 @@ import ( "fmt" "io/ioutil" "log" + "net" "net/http" "github.com/docker/libtrust" @@ -12,8 +13,8 @@ import ( var ( serverAddress = "localhost:8888" - privateKeyFilename = "client_data/private_key.json" - trustedHostsFilename = "client_data/trusted_hosts.json" + privateKeyFilename = "client_data/private_key.pem" + trustedHostsFilename = "client_data/trusted_hosts.pem" ) func main() { @@ -30,19 +31,23 @@ func main() { } // Load trusted host keys. - hostKeys, err := libtrust.LoadTrustedHostKeysFile(trustedHostsFilename) + hostKeys, err := libtrust.LoadKeySetFile(trustedHostsFilename) if err != nil { log.Fatal(err) } // Ensure the host we want to connect to is trusted! - serverKey, ok := hostKeys[serverAddress] - if !ok { - log.Fatalf("%q is not a known and trusted host", serverAddress) + host, _, err := net.SplitHostPort(serverAddress) + if err != nil { + log.Fatal(err) + } + serverKeys, err := libtrust.FilterByHosts(hostKeys, host, false) + if err != nil { + log.Fatalf("%q is not a known and trusted host", host) } // Generate a CA pool with the trusted host's key. - caPool, err := libtrust.GenerateCACertPool(clientKey, []libtrust.PublicKey{serverKey}) + caPool, err := libtrust.GenerateCACertPool(clientKey, serverKeys) if err != nil { log.Fatal(err) } diff --git a/tlsdemo/gencert.go b/tlsdemo/gencert.go index 1f21944..c65f3b6 100644 --- a/tlsdemo/gencert.go +++ b/tlsdemo/gencert.go @@ -4,14 +4,15 @@ import ( "encoding/pem" "fmt" "log" + "net" "github.com/docker/libtrust" ) var ( serverAddress = "localhost:8888" - clientPrivateKeyFilename = "client_data/private_key.json" - trustedHostsFilename = "client_data/trusted_hosts.json" + clientPrivateKeyFilename = "client_data/private_key.pem" + trustedHostsFilename = "client_data/trusted_hosts.pem" ) func main() { @@ -36,13 +37,22 @@ func main() { encodedCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) fmt.Printf("Client Cert:\n\n%s\n", string(encodedCert)) - trustedServerKeys, err := libtrust.LoadTrustedHostKeysFile(trustedHostsFilename) + trustedServerKeys, err := libtrust.LoadKeySetFile(trustedHostsFilename) if err != nil { log.Fatal(err) } - serverKey := trustedServerKeys[serverAddress] - caCert, err := libtrust.GenerateCACert(key, serverKey) + hostname, _, err := net.SplitHostPort(serverAddress) + if err != nil { + log.Fatal(err) + } + + trustedServerKeys, err = libtrust.FilterByHosts(trustedServerKeys, hostname, false) + if err != nil { + log.Fatal(err) + } + + caCert, err := libtrust.GenerateCACert(key, trustedServerKeys[0]) if err != nil { log.Fatal(err) } diff --git a/tlsdemo/genkeys.go b/tlsdemo/genkeys.go index 1463ecc..9dc8842 100644 --- a/tlsdemo/genkeys.go +++ b/tlsdemo/genkeys.go @@ -7,46 +7,54 @@ import ( ) func main() { - // Generate and save client key. + // Generate client key. clientKey, err := libtrust.GenerateECP256PrivateKey() if err != nil { log.Fatal(err) } - err = libtrust.SaveKey("client_data/private_key.json", clientKey) + // Add a comment for the client key. + clientKey.AddExtendedField("comment", "TLS Demo Client") + + // Save the client key, public and private versions. + err = libtrust.SaveKey("client_data/private_key.pem", clientKey) if err != nil { log.Fatal(err) } - err = libtrust.SavePublicKey("client_data/public_key.json", clientKey.PublicKey()) + err = libtrust.SavePublicKey("client_data/public_key.pem", clientKey.PublicKey()) if err != nil { log.Fatal(err) } - // Generate and save server key. + // Generate server key. serverKey, err := libtrust.GenerateECP256PrivateKey() if err != nil { log.Fatal(err) } - err = libtrust.SaveKey("server_data/private_key.json", serverKey) + // Set the list of addresses to use for the server. + serverKey.AddExtendedField("hosts", []string{"localhost", "docker.example.com"}) + + // Save the server key, public and private versions. + err = libtrust.SaveKey("server_data/private_key.pem", serverKey) if err != nil { log.Fatal(err) } - err = libtrust.SavePublicKey("server_data/public_key.json", serverKey.PublicKey()) + err = libtrust.SavePublicKey("server_data/public_key.pem", serverKey.PublicKey()) if err != nil { log.Fatal(err) } // Generate Authorized Keys file for server. - err = libtrust.SaveTrustedClientKey("server_data/trusted_clients.json", "TLS Demo Client", clientKey.PublicKey()) + err = libtrust.AddKeySetFile("server_data/trusted_clients.pem", clientKey.PublicKey()) if err != nil { log.Fatal(err) } // Generate Known Host Keys file for client. - err = libtrust.SaveTrustedHostKey("client_data/trusted_hosts.json", "localhost:8888", serverKey.PublicKey()) + err = libtrust.AddKeySetFile("client_data/trusted_hosts.pem", serverKey.PublicKey()) if err != nil { log.Fatal(err) } diff --git a/tlsdemo/server.go b/tlsdemo/server.go index 9777425..d3cb2ea 100644 --- a/tlsdemo/server.go +++ b/tlsdemo/server.go @@ -13,8 +13,8 @@ import ( var ( serverAddress = "localhost:8888" - privateKeyFilename = "server_data/private_key.json" - authorizedClientsFilename = "server_data/trusted_clients.json" + privateKeyFilename = "server_data/private_key.pem" + authorizedClientsFilename = "server_data/trusted_clients.pem" ) func requestHandler(w http.ResponseWriter, r *http.Request) { @@ -40,7 +40,7 @@ func main() { } // Load authorized client keys. - authorizedClients, err := libtrust.LoadTrustedClientKeysFile(authorizedClientsFilename) + authorizedClients, err := libtrust.LoadKeySetFile(authorizedClientsFilename) if err != nil { log.Fatal(err) } diff --git a/util.go b/util.go index d822276..3b2fac9 100644 --- a/util.go +++ b/util.go @@ -3,6 +3,7 @@ package libtrust import ( "bytes" "crypto/elliptic" + "crypto/x509" "encoding/base32" "encoding/base64" "encoding/binary" @@ -178,3 +179,31 @@ func createPemBlock(name string, derBytes []byte, headers map[string]interface{} return pemBlock, nil } + +func pubKeyFromPEMBlock(pemBlock *pem.Block) (PublicKey, error) { + cryptoPublicKey, err := x509.ParsePKIXPublicKey(pemBlock.Bytes) + if err != nil { + return nil, fmt.Errorf("unable to decode Public Key PEM data: %s", err) + } + + pubKey, err := FromCryptoPublicKey(cryptoPublicKey) + if err != nil { + return nil, err + } + + addPEMHeadersToKey(pemBlock, pubKey) + + return pubKey, nil +} + +func addPEMHeadersToKey(pemBlock *pem.Block, pubKey PublicKey) { + for key, value := range pemBlock.Headers { + var safeVal interface{} + if key == "hosts" { + safeVal = strings.Split(value, ",") + } else { + safeVal = value + } + pubKey.AddExtendedField(key, safeVal) + } +}