Skip to content

Commit

Permalink
Merge pull request #28 from adavis10006/multiple-key-usage
Browse files Browse the repository at this point in the history
address issue #26 multiple key usage error
  • Loading branch information
maditya authored Feb 24, 2020
2 parents f33a9e6 + affc9f0 commit 5d56b97
Show file tree
Hide file tree
Showing 18 changed files with 612 additions and 80 deletions.
39 changes: 29 additions & 10 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,24 +118,43 @@ func (c *Config) validate() error {
}
c.TLSServerName = strings.TrimSpace(c.TLSServerName)
// Do a basic validation on Keys and KeyUsages.
identifierMap := make(map[string]*KeyConfig, len(c.Keys))
slotMap := make(map[uint]string, len(c.Keys))
for idx, key := range c.Keys {
if _, exist := identifierMap[key.Identifier]; key.Identifier == "" || exist {
return fmt.Errorf("key %q: require a unique name for Identifier field", key.Identifier)
}
identifierMap[key.Identifier] = &c.Keys[idx]

if key.UserPinPath == "" {
return fmt.Errorf("key %q: require the pin code file path for slot number #%d", key.Identifier, key.SlotNumber)
}
if cachedPinPath, exist := slotMap[key.SlotNumber]; exist && key.UserPinPath != cachedPinPath {
return fmt.Errorf("key %q: unmatched pin code path for slot number #%d", key.Identifier, key.SlotNumber)
}
slotMap[key.SlotNumber] = key.UserPinPath

if key.CreateCACertIfNotExist && key.X509CACertLocation == "" {
return fmt.Errorf("key %q: CA cert is supposed to be created if it doesn't exist but X509CACertLocation is not specified", key.Identifier)
}

if key.KeyType < crypki.RSA || key.KeyType > crypki.ECDSA {
return fmt.Errorf("key %q: invalid Key type specified", key.Identifier)
}
}

for _, ku := range c.KeyUsages {
if _, ok := endpoints[ku.Endpoint]; !ok {
return fmt.Errorf("unknown endpoint %q", ku.Endpoint)
}
// Check that all key identifiers are defined in Keys,
// and all keys used for "/sig/x509-cert" have x509 CA cert configured.
next:
for _, id := range ku.Identifiers {
for _, key := range c.Keys {
if key.KeyType < crypki.RSA || key.KeyType > crypki.ECDSA {
return fmt.Errorf("key %q: invalid KeyType specified", key.Identifier)
}
if key.Identifier == id {
if ku.Endpoint == X509CertEndpoint && key.X509CACertLocation == "" {
return fmt.Errorf("key %q is used for signing x509 certs, but X509CACertLocation is not specified", id)
}
continue next
if key, exist := identifierMap[id]; exist {
if ku.Endpoint == X509CertEndpoint && key.X509CACertLocation == "" {
return fmt.Errorf("key %q: key is used to sign x509 certs, but X509CACertLocation is not specified", id)
}
continue
}
return fmt.Errorf("key identifier %q not found for endpoint %q", id, ku.Endpoint)
}
Expand Down
33 changes: 29 additions & 4 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,38 @@ func TestParse(t *testing.T) {
config: cfg,
expectError: false,
},
"bad-config-unknown-endpoint": {
filePath: "testdata/testconf-bad-unknown-endpoint.json",
expectError: true,
},
"bad-config-unknown-identifier": {
filePath: "testdata/testconf-bad-unknown-identifier.json",
expectError: true,
},
"bad-duplicate-identifier-json": {
filePath: "testdata/testconf-bad-duplicate-identifier.json",
expectError: true,
},
"bad-config-bad-non-specify-identifier": {
filePath: "testdata/testconf-bad-non-specify-identifier.json",
expectError: true,
},
"bad-config-bad-non-specify-pin-path": {
filePath: "testdata/testconf-bad-non-specify-pin-path.json",
expectError: true,
},
"bad-config-bad-non-specify-x509-ca-cert-path": {
filePath: "testdata/testconf-bad-non-specify-x509-ca-cert-path.json",
expectError: true,
},
"bad-config-bad-non-x509-cert-for-x509-endpoint": {
filePath: "testdata/testconf-bad-non-x509-cert-for-x509-endpoint.json",
expectError: true,
},
"bad-config-bad-same-slot-different-pin-path": {
filePath: "testdata/testconf-bad-same-slot-different-pin-path.json",
expectError: true,
},
"bad-config-bad-unsupported-key-type": {
filePath: "testdata/testconf-bad-unsupported-key-type.json",
expectError: true,
},
"bad-config-bad-json": {
filePath: "testdata/testconf-bad-json.json",
expectError: true,
Expand All @@ -67,6 +91,7 @@ func TestParse(t *testing.T) {
if !tt.expectError {
t.Errorf("unexpected err: %v", err)
}
t.Log(err)
return
}
if tt.expectError {
Expand Down
16 changes: 16 additions & 0 deletions config/testdata/testconf-bad-duplicate-identifier.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"TLSServerName": "foo.example.com",
"TLSClientAuthMode": 4,
"X509CACertLocation":"testdata/cacert.pem",
"Keys": [
{"Identifier": "key1", "KeyLabel": "foo", "SlotNumber": 1, "UserPinPath" : "/path/1", "X509CACertLocation": "/path/foo", "CreateCACertIfNotExist": true, "CommonName": "My CA"},
{"Identifier": "key1", "KeyLabel": "bar", "SlotNumber": 2, "UserPinPath" : "/path/2"},
{"Identifier": "key3", "KeyLabel": "baz", "SlotNumber": 3, "UserPinPath" : "/path/3", "X509CACertLocation": "/path/baz"}
],
"KeyUsages": [
{"Endpoint": "/sig/x509-cert", "Identifiers": ["key1", "key3"], "MaxValidity": 3600},
{"Endpoint": "/sig/ssh-host-cert", "Identifiers": ["key1", "key2"], "MaxValidity": 36000},
{"Endpoint": "/sig/ssh-user-cert", "Identifiers": ["key3"], "MaxValidity": 36000},
{"Endpoint": "/sig/blob", "Identifiers": ["key1"], "MaxValidity": 36000}
]
}
16 changes: 16 additions & 0 deletions config/testdata/testconf-bad-non-specify-identifier.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"TLSServerName": "foo.example.com",
"TLSClientAuthMode": 4,
"X509CACertLocation":"testdata/cacert.pem",
"Keys": [
{"KeyLabel": "foo", "SlotNumber": 1, "UserPinPath" : "/path/1", "X509CACertLocation": "/path/foo", "CreateCACertIfNotExist": true, "CommonName": "My CA"},
{"Identifier": "key2", "KeyLabel": "bar", "SlotNumber": 2, "UserPinPath" : "/path/2"},
{"Identifier": "key3", "KeyLabel": "baz", "SlotNumber": 3, "UserPinPath" : "/path/3", "X509CACertLocation": "/path/baz"}
],
"KeyUsages": [
{"Endpoint": "/sig/x509-cert", "Identifiers": ["key1", "key3"], "MaxValidity": 3600},
{"Endpoint": "/sig/ssh-host-cert", "Identifiers": ["key1", "key2"], "MaxValidity": 36000},
{"Endpoint": "/sig/ssh-user-cert", "Identifiers": ["key3"], "MaxValidity": 36000},
{"Endpoint": "/sig/blob", "Identifiers": ["key1"], "MaxValidity": 36000}
]
}
16 changes: 16 additions & 0 deletions config/testdata/testconf-bad-non-specify-pin-path.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"TLSServerName": "foo.example.com",
"TLSClientAuthMode": 4,
"X509CACertLocation":"testdata/cacert.pem",
"Keys": [
{"Identifier": "key1", "KeyLabel": "foo", "SlotNumber": 1, "X509CACertLocation": "/path/foo", "CreateCACertIfNotExist": true, "CommonName": "My CA"},
{"Identifier": "key2", "KeyLabel": "bar", "SlotNumber": 2, "UserPinPath" : "/path/2"},
{"Identifier": "key3", "KeyLabel": "baz", "SlotNumber": 3, "UserPinPath" : "/path/3", "X509CACertLocation": "/path/baz"}
],
"KeyUsages": [
{"Endpoint": "/sig/x509-cert", "Identifiers": ["key1", "key3"], "MaxValidity": 3600},
{"Endpoint": "/sig/ssh-host-cert", "Identifiers": ["key1", "key2"], "MaxValidity": 36000},
{"Endpoint": "/sig/ssh-user-cert", "Identifiers": ["key3"], "MaxValidity": 36000},
{"Endpoint": "/sig/blob", "Identifiers": ["key1"], "MaxValidity": 36000}
]
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
{
"TLSClientAuthMode": 4,
"TLSServerName": "foo.example.com",
"TLSClientAuthMode": 4,
"X509CACertLocation":"testdata/cacert.pem",
"Keys": [
{"Identifier": "key1", "KeyLabel": "foo", "SlotNumber": 1, "UserPinPath" : "/path/1", "CreateCACertIfNotExist": true, "CommonName": "My CA"},
{"Identifier": "key2", "KeyLabel": "bar", "SlotNumber": 2, "UserPinPath" : "/path/2"},
{"Identifier": "key3", "KeyLabel": "baz", "SlotNumber": 3, "UserPinPath" : "/path/3", "X509CACertLocation": "/path/baz"}
],
"KeyUsages": [
{"Endpoint": "/sig/x509-cert", "Identifiers": ["key1", "key3"]},
{"Endpoint": "/foo/bar", "Identifiers": ["key1", "key2"]}
{"Endpoint": "/sig/x509-cert", "Identifiers": ["key1", "key3"], "MaxValidity": 3600},
{"Endpoint": "/sig/ssh-host-cert", "Identifiers": ["key1", "key2"], "MaxValidity": 36000},
{"Endpoint": "/sig/ssh-user-cert", "Identifiers": ["key3"], "MaxValidity": 36000},
{"Endpoint": "/sig/blob", "Identifiers": ["key1"], "MaxValidity": 36000}
]
}
16 changes: 16 additions & 0 deletions config/testdata/testconf-bad-non-x509-cert-for-x509-endpoint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"TLSServerName": "foo.example.com",
"TLSClientAuthMode": 4,
"X509CACertLocation":"testdata/cacert.pem",
"Keys": [
{"Identifier": "key1", "KeyLabel": "foo", "SlotNumber": 1, "UserPinPath" : "/path/1", "X509CACertLocation": "/path/foo", "CreateCACertIfNotExist": true, "CommonName": "My CA"},
{"Identifier": "key2", "KeyLabel": "bar", "SlotNumber": 2, "UserPinPath" : "/path/2"},
{"Identifier": "key3", "KeyLabel": "baz", "SlotNumber": 3, "UserPinPath" : "/path/3"}
],
"KeyUsages": [
{"Endpoint": "/sig/x509-cert", "Identifiers": ["key1", "key3"], "MaxValidity": 3600},
{"Endpoint": "/sig/ssh-host-cert", "Identifiers": ["key1", "key2"], "MaxValidity": 36000},
{"Endpoint": "/sig/ssh-user-cert", "Identifiers": ["key3"], "MaxValidity": 36000},
{"Endpoint": "/sig/blob", "Identifiers": ["key1"], "MaxValidity": 36000}
]
}
17 changes: 17 additions & 0 deletions config/testdata/testconf-bad-same-slot-different-pin-path.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"TLSServerName": "foo.example.com",
"TLSClientAuthMode": 4,
"X509CACertLocation":"testdata/cacert.pem",
"Keys": [
{"Identifier": "key1", "KeyLabel": "foo", "SlotNumber": 1, "UserPinPath" : "/path/1", "X509CACertLocation": "/path/foo", "CreateCACertIfNotExist": true, "CommonName": "My CA"},
{"Identifier": "key2", "KeyLabel": "bar", "SlotNumber": 2, "UserPinPath" : "/path/2"},
{"Identifier": "key2-1", "KeyLabel": "bar", "SlotNumber": 2, "UserPinPath" : "/path/2-1"},
{"Identifier": "key3", "KeyLabel": "baz", "SlotNumber": 3, "UserPinPath" : "/path/3", "X509CACertLocation": "/path/baz"}
],
"KeyUsages": [
{"Endpoint": "/sig/x509-cert", "Identifiers": ["key1", "key3"], "MaxValidity": 3600},
{"Endpoint": "/sig/ssh-host-cert", "Identifiers": ["key1", "key2"], "MaxValidity": 36000},
{"Endpoint": "/sig/ssh-user-cert", "Identifiers": ["key3"], "MaxValidity": 36000},
{"Endpoint": "/sig/blob", "Identifiers": ["key1"], "MaxValidity": 36000}
]
}
16 changes: 16 additions & 0 deletions config/testdata/testconf-bad-unsupported-key-type.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"TLSServerName": "foo.example.com",
"TLSClientAuthMode": 4,
"X509CACertLocation":"testdata/cacert.pem",
"Keys": [
{"Identifier": "key1", "KeyLabel": "foo", "KeyType":3, "SlotNumber": 1, "UserPinPath" : "/path/1", "X509CACertLocation": "/path/foo", "CreateCACertIfNotExist": true, "CommonName": "My CA"},
{"Identifier": "key2", "KeyLabel": "bar", "SlotNumber": 2, "UserPinPath" : "/path/2"},
{"Identifier": "key3", "KeyLabel": "baz", "SlotNumber": 3, "UserPinPath" : "/path/3", "X509CACertLocation": "/path/baz"}
],
"KeyUsages": [
{"Endpoint": "/sig/x509-cert", "Identifiers": ["key1", "key3"], "MaxValidity": 3600},
{"Endpoint": "/sig/ssh-host-cert", "Identifiers": ["key1", "key2"], "MaxValidity": 36000},
{"Endpoint": "/sig/ssh-user-cert", "Identifiers": ["key3"], "MaxValidity": 36000},
{"Endpoint": "/sig/blob", "Identifiers": ["key1"], "MaxValidity": 36000}
]
}
105 changes: 105 additions & 0 deletions pkcs11/login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package pkcs11

import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"io/ioutil"

p11 "github.com/miekg/pkcs11"

"github.com/yahoo/crypki/config"
)

// secureBuffer cached an array of bytes as secret.
// It has a `clear` method to overwrite secret with random data.
type secureBuffer struct {
secret []byte
}

func (b *secureBuffer) get() string {
return string(b.secret)
}

func (b *secureBuffer) clear() {
_, _ = rand.Read(b.secret)
}

func newSecureBuffer(secret []byte) *secureBuffer {
return &secureBuffer{secret: bytes.TrimSpace(secret)}
}

type loginHelper interface {
getUserPinCode(string) (*secureBuffer, error)
}

type defaultHelper struct{}

func (h *defaultHelper) getUserPinCode(path string) (*secureBuffer, error) {
return getUserPinCode(path)
}

// openLoginSession opens a PKCS11 session and tries to log in.
func openLoginSession(context PKCS11Ctx, slot uint, userPin string) (p11.SessionHandle, error) {
session, err := context.OpenSession(slot, p11.CKF_SERIAL_SESSION)
if err != nil {
return 0, errors.New("makeLoginSession: error in OpenSession: " + err.Error())
}

if err = context.Login(session, p11.CKU_USER, userPin); err != nil {
context.CloseSession(session)
return 0, errors.New("makeSigner: error in Login: " + err.Error())
}
return session, nil
}

type loginOption interface {
config(map[uint]*secureBuffer) loginHelper
}

// getLoginSessions opens the PKCS11 login session for all keys in the configuration.
// The pin codes used to login are stored in the secure buffer and are overwritten with random data just before function
// returns.
func getLoginSessions(p11ctx PKCS11Ctx, keys []config.KeyConfig, opts ...loginOption) (map[uint]p11.SessionHandle, error) {
login := make(map[uint]p11.SessionHandle)
keyMap := make(map[uint]*secureBuffer)
defer func() {
for _, buffer := range keyMap {
buffer.clear()
}
}()

// `helper` provides the `getUserPinCode` method to get the pin code from input path.
// This helper is also used to help unit test to check the status of the function-scoped variable.
helper := loginHelper(&defaultHelper{})
for _, opt := range opts {
helper = opt.config(keyMap)
}

for _, key := range keys {
// config validation makes sure the pin code paths are equal for the same slot.
if _, exist := keyMap[key.SlotNumber]; !exist {
pin, err := helper.getUserPinCode(key.UserPinPath)
if err != nil {
return nil, fmt.Errorf("unable to read user pin for key with identifier %q, pin path: %v, err: %v", key.Identifier, key.UserPinPath, err)
}
keyMap[key.SlotNumber] = pin
session, err := openLoginSession(p11ctx, key.SlotNumber, pin.get())
if err != nil {
return nil, fmt.Errorf("failed to create a login session for key with identifier %q, pin path: %v, err: %v", key.Identifier, key.UserPinPath, err)
}
login[key.SlotNumber] = session
}
}
return login, nil
}

// getUserPinCode reads the pin code from the path and stores the pin code into the secure buffer.
func getUserPinCode(path string) (*secureBuffer, error) {
pin, err := ioutil.ReadFile(path)
if err != nil {
return nil, errors.New("Failed to open pin file: " + err.Error())
}
return newSecureBuffer(pin), nil
}
Loading

0 comments on commit 5d56b97

Please sign in to comment.