186 lines
4.9 KiB
Go
186 lines
4.9 KiB
Go
|
// Copyright 2022-2023 The NATS Authors
|
||
|
// 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 nkeys
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"crypto/rand"
|
||
|
"encoding/binary"
|
||
|
"io"
|
||
|
|
||
|
"golang.org/x/crypto/curve25519"
|
||
|
"golang.org/x/crypto/nacl/box"
|
||
|
)
|
||
|
|
||
|
// This package will support safe use of X25519 keys for asymmetric encryption.
|
||
|
// We will be compatible with nacl.Box, but generate random nonces automatically.
|
||
|
// We may add more advanced options in the future for group recipients and better
|
||
|
// end to end algorithms.
|
||
|
|
||
|
const (
|
||
|
curveKeyLen = 32
|
||
|
curveDecodeLen = 35
|
||
|
curveNonceLen = 24
|
||
|
)
|
||
|
|
||
|
type ckp struct {
|
||
|
seed [curveKeyLen]byte // Private raw key.
|
||
|
}
|
||
|
|
||
|
// CreateCurveKeys will create a Curve typed KeyPair.
|
||
|
func CreateCurveKeys() (KeyPair, error) {
|
||
|
return CreateCurveKeysWithRand(rand.Reader)
|
||
|
}
|
||
|
|
||
|
// CreateCurveKeysWithRand will create a Curve typed KeyPair
|
||
|
// with specified rand source.
|
||
|
func CreateCurveKeysWithRand(rr io.Reader) (KeyPair, error) {
|
||
|
var kp ckp
|
||
|
_, err := io.ReadFull(rr, kp.seed[:])
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return &kp, nil
|
||
|
}
|
||
|
|
||
|
// Will create a curve key pair from seed.
|
||
|
func FromCurveSeed(seed []byte) (KeyPair, error) {
|
||
|
pb, raw, err := DecodeSeed(seed)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if pb != PrefixByteCurve || len(raw) != curveKeyLen {
|
||
|
return nil, ErrInvalidCurveSeed
|
||
|
}
|
||
|
var kp ckp
|
||
|
copy(kp.seed[:], raw)
|
||
|
return &kp, nil
|
||
|
}
|
||
|
|
||
|
// Seed will return the encoded seed.
|
||
|
func (pair *ckp) Seed() ([]byte, error) {
|
||
|
return EncodeSeed(PrefixByteCurve, pair.seed[:])
|
||
|
}
|
||
|
|
||
|
// PublicKey will return the encoded public key.
|
||
|
func (pair *ckp) PublicKey() (string, error) {
|
||
|
var pub [curveKeyLen]byte
|
||
|
curve25519.ScalarBaseMult(&pub, &pair.seed)
|
||
|
key, err := Encode(PrefixByteCurve, pub[:])
|
||
|
return string(key), err
|
||
|
}
|
||
|
|
||
|
// PrivateKey will return the encoded private key.
|
||
|
func (pair *ckp) PrivateKey() ([]byte, error) {
|
||
|
return Encode(PrefixBytePrivate, pair.seed[:])
|
||
|
}
|
||
|
|
||
|
func decodePubCurveKey(src string, dest []byte) error {
|
||
|
var raw [curveDecodeLen]byte // should always be 35
|
||
|
n, err := b32Enc.Decode(raw[:], []byte(src))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if n != curveDecodeLen {
|
||
|
return ErrInvalidCurveKey
|
||
|
}
|
||
|
// Make sure it is what we expected.
|
||
|
if prefix := PrefixByte(raw[0]); prefix != PrefixByteCurve {
|
||
|
return ErrInvalidPublicKey
|
||
|
}
|
||
|
var crc uint16
|
||
|
end := n - 2
|
||
|
sum := raw[end:n]
|
||
|
checksum := bytes.NewReader(sum)
|
||
|
if err := binary.Read(checksum, binary.LittleEndian, &crc); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// ensure checksum is valid
|
||
|
if err := validate(raw[:end], crc); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Copy over, ignore prefix byte.
|
||
|
copy(dest, raw[1:end])
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Only version for now, but could add in X3DH in the future, etc.
|
||
|
const XKeyVersionV1 = "xkv1"
|
||
|
const vlen = len(XKeyVersionV1)
|
||
|
|
||
|
// Seal is compatible with nacl.Box.Seal() and can be used in similar situations for small messages.
|
||
|
// We generate the nonce from crypto rand by default.
|
||
|
func (pair *ckp) Seal(input []byte, recipient string) ([]byte, error) {
|
||
|
return pair.SealWithRand(input, recipient, rand.Reader)
|
||
|
}
|
||
|
|
||
|
func (pair *ckp) SealWithRand(input []byte, recipient string, rr io.Reader) ([]byte, error) {
|
||
|
var (
|
||
|
rpub [curveKeyLen]byte
|
||
|
nonce [curveNonceLen]byte
|
||
|
out [vlen + curveNonceLen]byte
|
||
|
err error
|
||
|
)
|
||
|
|
||
|
if err = decodePubCurveKey(recipient, rpub[:]); err != nil {
|
||
|
return nil, ErrInvalidRecipient
|
||
|
}
|
||
|
if _, err := io.ReadFull(rr, nonce[:]); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
copy(out[:vlen], []byte(XKeyVersionV1))
|
||
|
copy(out[vlen:], nonce[:])
|
||
|
return box.Seal(out[:], input, &nonce, &rpub, &pair.seed), nil
|
||
|
}
|
||
|
|
||
|
func (pair *ckp) Open(input []byte, sender string) ([]byte, error) {
|
||
|
if len(input) <= vlen+curveNonceLen {
|
||
|
return nil, ErrInvalidEncrypted
|
||
|
}
|
||
|
var (
|
||
|
spub [curveKeyLen]byte
|
||
|
nonce [curveNonceLen]byte
|
||
|
err error
|
||
|
)
|
||
|
if !bytes.Equal(input[:vlen], []byte(XKeyVersionV1)) {
|
||
|
return nil, ErrInvalidEncVersion
|
||
|
}
|
||
|
copy(nonce[:], input[vlen:vlen+curveNonceLen])
|
||
|
|
||
|
if err = decodePubCurveKey(sender, spub[:]); err != nil {
|
||
|
return nil, ErrInvalidSender
|
||
|
}
|
||
|
|
||
|
decrypted, ok := box.Open(nil, input[vlen+curveNonceLen:], &nonce, &spub, &pair.seed)
|
||
|
if !ok {
|
||
|
return nil, ErrCouldNotDecrypt
|
||
|
}
|
||
|
return decrypted, nil
|
||
|
}
|
||
|
|
||
|
// Wipe will randomize the contents of the secret key
|
||
|
func (pair *ckp) Wipe() {
|
||
|
io.ReadFull(rand.Reader, pair.seed[:])
|
||
|
}
|
||
|
|
||
|
func (pair *ckp) Sign(_ []byte) ([]byte, error) {
|
||
|
return nil, ErrInvalidCurveKeyOperation
|
||
|
}
|
||
|
|
||
|
func (pair *ckp) Verify(_ []byte, _ []byte) error {
|
||
|
return ErrInvalidCurveKeyOperation
|
||
|
}
|