key.go (120 lines of code) (raw):
// 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 sks
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/sha1"
"errors"
"fmt"
"io"
"strings"
)
// Key is an interface that implements the crypto.Signer interface along with
// extra functions specific to SKS
type Key interface {
crypto.Signer
// Remove removes that key from SKS
Remove() error
// Hash returns the SHA1 hash of the public portion of the key
Hash() []byte
// Label returns the label of the key
Label() string
// Tag returns the tag of the key
Tag() string
// UpdateLabel changes the key's label to a new one
UpdateLabel(newLabel string) error
// AccessibleWhenUnlockedOnly returns whether or not the key is only
// accessible when the device is unlocked
AccessibleWhenUnlockedOnly() (bool, error)
}
// regularKey is an ECDSA P-256 key whose private portion is stored in SKS
type regularKey struct {
pubKey *ecdsa.PublicKey
label string
tag string
}
// LoadKey returns an existing key backed by SKS given the corresponding label, tag, and hash
func LoadKey(label, tag string, hash []byte) (Key, error) {
if pubKey, err := findPubKey(label, tag, hash); err != nil {
return nil, err
} else if pubKey != nil {
return ®ularKey{
pubKey: rawToEcdsa(pubKey),
label: label,
tag: tag,
}, nil
}
return nil, fmt.Errorf(ErrFindPubKeyNil, label, tag)
}
// NewKey returns a new key backed by SKS given the corresponding label and tag
// useBiometrics and accessibleWhenUnlockedOnly are not taken into account if the key already exist
func NewKey(label, tag string, useBiometrics, accessibleWhenUnlockedOnly bool, hash []byte) (Key, error) {
if pubKey, err := findPubKey(label, tag, hash); err != nil {
return nil, err
} else if pubKey != nil {
return ®ularKey{
pubKey: rawToEcdsa(pubKey),
label: label,
tag: tag,
}, nil
}
pubKey, err := genKeyPair(label, tag, useBiometrics, accessibleWhenUnlockedOnly)
if err != nil {
return nil, err
}
return ®ularKey{
pubKey: rawToEcdsa(pubKey),
label: label,
tag: tag,
}, nil
}
// Remove removes the key from the SKS
func (k *regularKey) Remove() error {
if k.label == "" || k.tag == "" {
return errors.New(ErrLabelOrTagUnspecified)
}
_, err := removeKey(k.label, k.tag, k.Hash())
return err
}
// Public returns the public key of this key
func (k *regularKey) Public() crypto.PublicKey {
if k.pubKey != nil {
return k.pubKey
}
pubKey, err := findPubKey(k.label, k.tag, nil)
if err != nil {
return nil
}
if pubKey == nil {
return nil
}
return rawToEcdsa(pubKey)
}
// Sign signs the arbitrary data in digest with the key
// The first argument `rand` is discarded in favour of the internal implementation
func (k *regularKey) Sign(_ io.Reader, digest []byte, _ crypto.SignerOpts) ([]byte, error) {
return signWithKey(k.label, k.tag, k.Hash(), digest)
}
// HashKey returns the SHA1 hash of the key
func (k *regularKey) Hash() []byte {
if k.pubKey == nil {
return nil
}
rawKey := elliptic.Marshal(k.pubKey.Curve, k.pubKey.X, k.pubKey.Y)
h := sha1.Sum(rawKey)
return h[:]
}
// Label returns the label of the key
func (k *regularKey) Label() string {
return k.label
}
// Tag returns the tag of the key
func (k *regularKey) Tag() string {
return k.tag
}
// AccessibleWhenUnlockedOnly returns whether or not the key is only
// accessible when the device is unlocked
func (k *regularKey) AccessibleWhenUnlockedOnly() (bool, error) {
return accessibleWhenUnlockedOnly(k.label, k.tag, k.Hash())
}
// UpdateLabel changes the key's label to a new one.
func (k *regularKey) UpdateLabel(newLabel string) error {
err := updateKeyLabel(k.label, k.tag, newLabel, k.Hash())
if err == nil {
k.label = newLabel
}
return err
}
// FromLabelTag constructs a Key identified by label and tag
// without looking up the key in SKS so the public key of the
// structure is not populated.
func FromLabelTag(labelTag string) Key {
f := strings.SplitN(labelTag, ":", 2)
k := ®ularKey{
label: f[0],
}
if len(f) > 1 {
k.tag = f[1]
}
return k
}
// rawToEcdsa turns an ASN.1 encoded byte stream to an ecdsa public key
// It is assumed that the curve of the key is P-256
func rawToEcdsa(raw []byte) *ecdsa.PublicKey {
ecKey := new(ecdsa.PublicKey)
ecKey.Curve = elliptic.P256()
ecKey.X, ecKey.Y = elliptic.Unmarshal(ecKey.Curve, raw)
return ecKey
}