linux/sks.go (136 lines of code) (raw):
// +build linux
// Copyright (c) Facebook, Inc. and its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package linux
import (
"crypto/elliptic"
"encoding/asn1"
"fmt"
"github.com/facebookincubator/sks/diskio"
"github.com/facebookincubator/sks/utils"
"github.com/facebookincubator/flog"
"github.com/google/go-tpm/tpm2"
"github.com/google/go-tpm/tpmutil"
)
// This file defines the public interface of SKS for Linux. The implementation
// of the functions exposed in sks_linux.go are defined here. This is the only
// exception to having all TPM device functions in tpm.go and exists solely for
// keeping the public SKS API in one easily-discovered location.
func (tpm *tpmDevice) GenKeyPair(keyID string) (b []byte, err error) {
newKeyHandle, flush, err := tpm.keyHandler.Get(keyID)
if err != nil {
return nil, fmt.Errorf("cannot reseve handle for key ID %q: %w", keyID, err)
}
defer func() {
flush(err == nil)
}()
if flog.V(5) {
flog.Debugf("Got key handle for %q: %#x", keyID, newKeyHandle)
}
// First, validate we have an organization root key
orgRootKey, err := tpm.GetOrgRootKey()
if err != nil {
return nil, fmt.Errorf("error while getting root key: %w", err)
}
defer tpm.FlushKey(orgRootKey, true)
if flog.V(5) {
flog.Debug("Got org root key")
}
// Possible shortcut: we may be asked for the org root key here.
var newKey CryptoKey
if newKeyHandle != orgRootKey.GetHandle() {
newKey, err = tpm.LoadKey(keyID, orgRootKey.GetHandle(), newKeyHandle, nil)
if err != nil {
return nil, err
}
defer tpm.FlushKey(newKey, true)
if flog.V(5) {
flog.Debugf("Got %q key", keyID)
}
} else {
newKey = orgRootKey
}
pubkey, err := newKey.GetECPublicKey()
if err != nil {
return nil, fmt.Errorf("error generating ECC key: %w", err)
}
return elliptic.Marshal(elliptic.P256(), pubkey.X, pubkey.Y), nil
}
func (tpm *tpmDevice) SignWithKey(keyID string, digest []byte) ([]byte, error) {
// First get the org root key so the requested child key can be loaded and
// used.
orgRootKey, err := tpm.GetOrgRootKey()
if err != nil {
return nil, fmt.Errorf("error while getting root key: %w", err)
}
defer tpm.FlushKey(orgRootKey, true)
// Next get the requested key, or bail if it can't be loaded.
key, err := tpm.LoadKey(keyID, orgRootKey.GetHandle(), 0, nil)
if err != nil {
return nil, fmt.Errorf("could not load requested key %q: %w", keyID, err)
}
defer tpm.FlushKey(key, true)
signature, err := tpm2.Sign(
tpm.rwc,
key.GetLoadedHandle(),
"",
digest,
nil,
&tpm2.SigScheme{
Alg: tpm2.AlgECDSA,
Hash: tpm2.AlgSHA256,
},
)
if err != nil {
return nil, fmt.Errorf("could not sign with key %q: %w", keyID, err)
}
// The standard elliptic Marshal includes more data than needed, which
// causes signature validation problems. Create a simple struct with just
// the EC points and marshal that in ASN.1 format.
basicSig := utils.ECCSignature{
R: signature.ECC.R,
S: signature.ECC.S,
}
derBytes, err := asn1.Marshal(basicSig)
if err != nil {
return nil, fmt.Errorf("failed to marshal ECC signature: %w", err)
}
return derBytes, nil
}
func (tpm *tpmDevice) FindPubKey(keyID string) ([]byte, error) {
// Load the stored key from disk
key, err := tpm.LoadDiskKey(keyID)
if err != nil {
return nil, fmt.Errorf("could not find key %q: %w", keyID, err)
}
if key == nil {
return nil, nil
}
if key.IsEmpty() {
return nil, fmt.Errorf("key is empty: %q", keyID)
}
defer key.Close()
pubKey, err := key.GetECPublicKey()
if err != nil {
return nil, fmt.Errorf("error getting public key for key %q: %w", keyID, err)
}
return elliptic.Marshal(elliptic.P256(), pubKey.X, pubKey.Y), nil
}
func (tpm *tpmDevice) DeleteKey(keyID string) error {
key, err := tpm.LoadDiskKey(keyID)
if err != nil {
return fmt.Errorf("error while loading key %q: %w", keyID, err)
}
if key == nil {
return fmt.Errorf("key not found: %q", keyID)
}
flush := tpm.keyHandler.Remove(keyID)
err = tpm.doKeyDeletion(keyID, key.GetHandle(), true)
flush(err == nil)
return err
}
// This allows deleting a key from the TPM. The mustExist parameter determines
// the action taken if the TPM has no key present at the given handle: if true
// an error is returned if there is no key present, if false a missing key is
// silently ignored.
func (tpm *tpmDevice) doKeyDeletion(keyID string, keyHandle tpmutil.Handle, mustExist bool) (err error) {
// Attempt to evict the key from the TPM
err = tpm2.EvictControl(
tpm.rwc,
"",
tpm2.HandleOwner,
keyHandle,
keyHandle,
)
if err != nil && mustExist {
return err
}
// Finally, delete from disk
db, err := diskio.OpenDB()
if err != nil {
return err
}
err = db.Delete(keyID)
if err != nil && mustExist {
return err
}
return nil
}
// vim: filetype=go:noexpandtab:ts=2:sts=2:sw=2:autoindent