mirror of
https://github.com/cloudflare/redoctober.git
synced 2026-05-28 10:40:49 +00:00
@@ -7,9 +7,9 @@ package core
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"github.com/cloudflare/redoctober/keycache"
|
||||
"github.com/cloudflare/redoctober/passvault"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -102,7 +102,7 @@ func TestSummary(t *testing.T) {
|
||||
if data.Admin != true {
|
||||
t.Fatalf("Error in summary of account, record incorrect")
|
||||
}
|
||||
if data.Type != passvault.RSARecord {
|
||||
if data.Type != passvault.DefaultRecordType {
|
||||
t.Fatalf("Error in summary of account, record incorrect")
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ func TestSummary(t *testing.T) {
|
||||
if data.Admin != true {
|
||||
t.Fatalf("Error in summary of account, record missing")
|
||||
}
|
||||
if data.Type != passvault.RSARecord {
|
||||
if data.Type != passvault.DefaultRecordType {
|
||||
t.Fatalf("Error in summary of account, record missing")
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ func TestSummary(t *testing.T) {
|
||||
if data.Admin != false {
|
||||
t.Fatalf("Error in summary of account, record missing")
|
||||
}
|
||||
if data.Type != passvault.RSARecord {
|
||||
if data.Type != passvault.DefaultRecordType {
|
||||
t.Fatalf("Error in summary of account, record missing")
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ func TestSummary(t *testing.T) {
|
||||
if dataLive.Admin != false {
|
||||
t.Fatalf("Error in summary of account, record missing")
|
||||
}
|
||||
if dataLive.Type != passvault.RSARecord {
|
||||
if dataLive.Type != passvault.DefaultRecordType {
|
||||
t.Fatalf("Error in summary of account, record missing")
|
||||
}
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/cloudflare/redoctober/keycache"
|
||||
"github.com/cloudflare/redoctober/padding"
|
||||
"github.com/cloudflare/redoctober/passvault"
|
||||
"github.com/cloudflare/redoctober/symcrypt"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
@@ -33,7 +33,7 @@ type MultiWrappedKey struct {
|
||||
}
|
||||
|
||||
// SingleWrappedKey is a structure containing a 16-byte key encrypted
|
||||
// by an RSA key.
|
||||
// by an RSA or EC key.
|
||||
type SingleWrappedKey struct {
|
||||
Key []byte
|
||||
aesKey []byte
|
||||
@@ -51,16 +51,9 @@ type EncryptedData struct {
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
// makeRandom is a helper to make new buffer full of random data
|
||||
func makeRandom(length int) (bytes []byte, err error) {
|
||||
bytes = make([]byte, length)
|
||||
_, err = rand.Read(bytes)
|
||||
return
|
||||
}
|
||||
|
||||
// encryptKey encrypts data with the key associated with name inner,
|
||||
// then name outer
|
||||
func encryptKey(nameInner, nameOuter string, clearKey []byte, rsaKeys map[string]SingleWrappedKey) (out MultiWrappedKey, err error) {
|
||||
func encryptKey(nameInner, nameOuter string, clearKey []byte, pubKeys map[string]SingleWrappedKey) (out MultiWrappedKey, err error) {
|
||||
out.Name = []string{nameOuter, nameInner}
|
||||
|
||||
recInner, ok := passvault.GetRecord(nameInner)
|
||||
@@ -85,19 +78,18 @@ func encryptKey(nameInner, nameOuter string, clearKey []byte, rsaKeys map[string
|
||||
var overrideOuter SingleWrappedKey
|
||||
|
||||
// For AES records, use the live user key
|
||||
// For RSA records, use the public key from the passvault
|
||||
// For RSA and ECC records, use the public key from the passvault
|
||||
switch recInner.Type {
|
||||
case passvault.RSARecord:
|
||||
if overrideInner, ok = rsaKeys[nameInner]; !ok {
|
||||
case passvault.RSARecord, passvault.ECCRecord:
|
||||
if overrideInner, ok = pubKeys[nameInner]; !ok {
|
||||
err = errors.New("Missing user in file")
|
||||
return
|
||||
}
|
||||
|
||||
if overrideOuter, ok = rsaKeys[nameOuter]; !ok {
|
||||
if overrideOuter, ok = pubKeys[nameOuter]; !ok {
|
||||
err = errors.New("Missing user in file")
|
||||
return
|
||||
}
|
||||
|
||||
case passvault.AESRecord:
|
||||
break
|
||||
|
||||
@@ -119,7 +111,7 @@ func encryptKey(nameInner, nameOuter string, clearKey []byte, rsaKeys map[string
|
||||
}
|
||||
|
||||
// unwrapKey decrypts first key in keys whose encryption keys are in keycache
|
||||
func unwrapKey(keys []MultiWrappedKey, rsaKeys map[string]SingleWrappedKey) (unwrappedKey []byte, err error) {
|
||||
func unwrapKey(keys []MultiWrappedKey, pubKeys map[string]SingleWrappedKey) (unwrappedKey []byte, err error) {
|
||||
var (
|
||||
keyFound error
|
||||
fullMatch bool = false
|
||||
@@ -133,9 +125,9 @@ func unwrapKey(keys []MultiWrappedKey, rsaKeys map[string]SingleWrappedKey) (unw
|
||||
tmpKeyValue := mwKey.Key
|
||||
|
||||
for _, mwName := range mwKey.Name {
|
||||
rsaEncrypted := rsaKeys[mwName]
|
||||
pubEncrypted := pubKeys[mwName]
|
||||
// if this is null, it's an AES encrypted key
|
||||
if tmpKeyValue, keyFound = keycache.DecryptKey(tmpKeyValue, mwName, rsaEncrypted.Key); keyFound != nil {
|
||||
if tmpKeyValue, keyFound = keycache.DecryptKey(tmpKeyValue, mwName, pubEncrypted.Key); keyFound != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -266,7 +258,7 @@ func Encrypt(in []byte, names []string, min int) (resp []byte, err error) {
|
||||
}
|
||||
|
||||
// Generate random IV and encryption key
|
||||
ivBytes, err := makeRandom(16)
|
||||
ivBytes, err := symcrypt.MakeRandom(16)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -275,7 +267,7 @@ func Encrypt(in []byte, names []string, min int) (resp []byte, err error) {
|
||||
// encrypted.IV
|
||||
|
||||
encrypted.IV = append([]byte{}, ivBytes...)
|
||||
clearKey, err := makeRandom(16)
|
||||
clearKey, err := symcrypt.MakeRandom(16)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -294,9 +286,9 @@ func Encrypt(in []byte, names []string, min int) (resp []byte, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if rec.GetType() == passvault.RSARecord {
|
||||
if rec.GetType() == passvault.RSARecord || rec.GetType() == passvault.ECCRecord {
|
||||
// only wrap key with RSA key if found
|
||||
if singleWrappedKey.aesKey, err = makeRandom(16); err != nil {
|
||||
if singleWrappedKey.aesKey, err = symcrypt.MakeRandom(16); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
102
ecdh/ecdh.go
Normal file
102
ecdh/ecdh.go
Normal file
@@ -0,0 +1,102 @@
|
||||
// Package ecdh encrypts and decrypts data using elliptic curve keys. Data
|
||||
// is encrypted with AES-128-CBC with HMAC-SHA1 message tags using
|
||||
// ECDHE to generate a shared key. The P256 curve is chosen in
|
||||
// keeping with the use of AES-128 for encryption.
|
||||
package ecdh
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"github.com/cloudflare/redoctober/padding"
|
||||
"github.com/cloudflare/redoctober/symcrypt"
|
||||
)
|
||||
|
||||
var Curve = elliptic.P256
|
||||
|
||||
func zero(in []byte) {
|
||||
inLen := len(in)
|
||||
for i := 0; i < inLen; i++ {
|
||||
in[i] ^= in[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt secures and authenticates its input using the public key
|
||||
// using ECDHE with AES-128-CBC-HMAC-SHA1.
|
||||
func Encrypt(pub ecdsa.PublicKey, in []byte) (out []byte, err error) {
|
||||
ephemeral, err := ecdsa.GenerateKey(Curve(), rand.Reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
x, _ := pub.Curve.ScalarMult(pub.X, pub.Y, ephemeral.D.Bytes())
|
||||
if x == nil {
|
||||
return nil, errors.New("Failed to generate encryption key")
|
||||
}
|
||||
shared := sha256.Sum256(x.Bytes())
|
||||
iv, err := symcrypt.MakeRandom(16)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
paddedIn := padding.AddPadding(in)
|
||||
ct, err := symcrypt.EncryptCBC(paddedIn, iv, shared[:16])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ephPub := elliptic.Marshal(pub.Curve, ephemeral.PublicKey.X, ephemeral.PublicKey.Y)
|
||||
out = make([]byte, 1+len(ephPub)+16)
|
||||
out[0] = byte(len(ephPub))
|
||||
copy(out[1:], ephPub)
|
||||
copy(out[1+len(ephPub):], iv)
|
||||
out = append(out, ct...)
|
||||
|
||||
h := hmac.New(sha1.New, shared[16:])
|
||||
h.Write(iv)
|
||||
h.Write(ct)
|
||||
out = h.Sum(out)
|
||||
return
|
||||
}
|
||||
|
||||
// Decrypt authentications and recovers the original message from
|
||||
// its input using the private key and the ephemeral key included in
|
||||
// the message.
|
||||
func Decrypt(priv *ecdsa.PrivateKey, in []byte) (out []byte, err error) {
|
||||
ephLen := int(in[0])
|
||||
ephPub := in[1 : 1+ephLen]
|
||||
ct := in[1+ephLen:]
|
||||
if len(ct) < (sha1.Size + aes.BlockSize) {
|
||||
return nil, errors.New("Invalid ciphertext")
|
||||
}
|
||||
|
||||
x, y := elliptic.Unmarshal(Curve(), ephPub)
|
||||
if x == nil {
|
||||
return nil, errors.New("Invalid public key")
|
||||
}
|
||||
|
||||
x, _ = priv.Curve.ScalarMult(x, y, priv.D.Bytes())
|
||||
if x == nil {
|
||||
return nil, errors.New("Failed to generate encryption key")
|
||||
}
|
||||
shared := sha256.Sum256(x.Bytes())
|
||||
|
||||
tagStart := len(ct) - sha1.Size
|
||||
h := hmac.New(sha1.New, shared[16:])
|
||||
h.Write(ct[:tagStart])
|
||||
mac := h.Sum(nil)
|
||||
if !hmac.Equal(mac, ct[tagStart:]) {
|
||||
return nil, errors.New("Invalid MAC")
|
||||
}
|
||||
|
||||
paddedOut, err := symcrypt.DecryptCBC(ct[aes.BlockSize:tagStart], ct[:aes.BlockSize], shared[:16])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out, err = padding.RemovePadding(paddedOut)
|
||||
return
|
||||
}
|
||||
36
ecdh/ecdh_test.go
Normal file
36
ecdh/ecdh_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package ecdh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testKey *ecdsa.PrivateKey
|
||||
|
||||
func TestGenerateKey(t *testing.T) {
|
||||
var err error
|
||||
testKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrypt(t *testing.T) {
|
||||
message := []byte("One ping only, please.")
|
||||
out, err := Encrypt(testKey.PublicKey, message)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
out, err = Decrypt(testKey, out)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(out, message) {
|
||||
t.Fatal("Decryption return different plaintext than original message.")
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,12 @@ package keycache
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"github.com/cloudflare/redoctober/ecdh"
|
||||
"github.com/cloudflare/redoctober/passvault"
|
||||
"log"
|
||||
"time"
|
||||
@@ -28,6 +30,7 @@ type ActiveUser struct {
|
||||
|
||||
aesKey []byte
|
||||
rsaKey rsa.PrivateKey
|
||||
eccKey *ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
// matchUser returns the matching active user if present
|
||||
@@ -93,6 +96,8 @@ func AddKeyFromRecord(record passvault.PasswordRecord, name string, password str
|
||||
current.aesKey, err = record.GetKeyAES(password)
|
||||
case passvault.RSARecord:
|
||||
current.rsaKey, err = record.GetKeyRSA(password)
|
||||
case passvault.ECCRecord:
|
||||
current.eccKey, err = record.GetKeyECC(password)
|
||||
default:
|
||||
err = errors.New("Unknown record type")
|
||||
}
|
||||
@@ -113,7 +118,8 @@ func AddKeyFromRecord(record passvault.PasswordRecord, name string, password str
|
||||
|
||||
// EncryptKey encrypts a 16 byte key using the cached key corresponding to name.
|
||||
// For AES keys, use the cached key.
|
||||
// For RSA keys, the cache is not necessary use the override key instead.
|
||||
// For RSA and EC keys, the cache is not necessary; use the override
|
||||
// key instead.
|
||||
func EncryptKey(in []byte, name string, override []byte) (out []byte, err error) {
|
||||
Refresh()
|
||||
|
||||
@@ -150,9 +156,10 @@ func EncryptKey(in []byte, name string, override []byte) (out []byte, err error)
|
||||
|
||||
// DecryptKey decrypts a 16 byte key using the key corresponding to the name parameter
|
||||
// for AES keys, the cached AES key is used directly to decrypt in
|
||||
// for RSA keys, the cached RSA key is used to decrypt the rsaEncryptedKey
|
||||
// which is then used to decrypt the input buffer.
|
||||
func DecryptKey(in []byte, name string, rsaEncryptedKey []byte) (out []byte, err error) {
|
||||
// for RSA and EC keys, the cached RSA/EC key is used to decrypt
|
||||
// the pubEncryptedKey which is then used to decrypt the input
|
||||
// buffer.
|
||||
func DecryptKey(in []byte, name string, pubEncryptedKey []byte) (out []byte, err error) {
|
||||
Refresh()
|
||||
|
||||
decryptKey, ok := matchUser(name)
|
||||
@@ -168,12 +175,18 @@ func DecryptKey(in []byte, name string, rsaEncryptedKey []byte) (out []byte, err
|
||||
aesKey = decryptKey.aesKey
|
||||
|
||||
case passvault.RSARecord:
|
||||
// extract the aes key from the rsaEncryptedKey
|
||||
aesKey, err = rsa.DecryptOAEP(sha1.New(), rand.Reader, &decryptKey.rsaKey, rsaEncryptedKey, nil)
|
||||
// extract the aes key from the pubEncryptedKey
|
||||
aesKey, err = rsa.DecryptOAEP(sha1.New(), rand.Reader, &decryptKey.rsaKey, pubEncryptedKey, nil)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
case passvault.ECCRecord:
|
||||
// extract the aes key from the pubEncryptedKey
|
||||
aesKey, err = ecdh.Decrypt(decryptKey.eccKey, pubEncryptedKey)
|
||||
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("unknown type")
|
||||
}
|
||||
|
||||
@@ -10,14 +10,18 @@ import (
|
||||
"bytes"
|
||||
"code.google.com/p/go.crypto/scrypt"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/cloudflare/redoctober/ecdh"
|
||||
"github.com/cloudflare/redoctober/padding"
|
||||
"github.com/cloudflare/redoctober/symcrypt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
mrand "math/rand"
|
||||
@@ -31,6 +35,8 @@ const (
|
||||
ECCRecord = "ECC"
|
||||
)
|
||||
|
||||
var DefaultRecordType = ECCRecord
|
||||
|
||||
// Constants for scrypt
|
||||
const (
|
||||
KEYLENGTH = 16 // 16-byte output from scrypt
|
||||
@@ -62,6 +68,11 @@ type PasswordRecord struct {
|
||||
RSAPrimeQIV []byte
|
||||
RSAPublic rsa.PublicKey
|
||||
}
|
||||
ECKey struct {
|
||||
ECPriv []byte
|
||||
ECPrivIV []byte
|
||||
ECPublic ecdsa.PublicKey
|
||||
}
|
||||
Admin bool
|
||||
}
|
||||
|
||||
@@ -86,7 +97,7 @@ type Summary struct {
|
||||
|
||||
func init() {
|
||||
// seed math.random from crypto.random
|
||||
seedBytes, _ := makeRandom(8)
|
||||
seedBytes, _ := symcrypt.MakeRandom(8)
|
||||
seedBuf := bytes.NewBuffer(seedBytes)
|
||||
n64, _ := binary.ReadVarint(seedBuf)
|
||||
mrand.Seed(n64)
|
||||
@@ -98,48 +109,58 @@ func hashPassword(password string, salt []byte) ([]byte, error) {
|
||||
return scrypt.Key([]byte(password), salt, N, R, P, KEYLENGTH)
|
||||
}
|
||||
|
||||
// makeRandom is a helper that makes a new buffer full of random data
|
||||
func makeRandom(length int) ([]byte, error) {
|
||||
bytes := make([]byte, length)
|
||||
_, err := rand.Read(bytes)
|
||||
return bytes, err
|
||||
}
|
||||
|
||||
// encryptRSARecord takes an RSA private key and encrypts it with
|
||||
// a password key
|
||||
func encryptRSARecord(newRec *PasswordRecord, rsaPriv *rsa.PrivateKey, passKey []byte) (err error) {
|
||||
if newRec.RSAKey.RSAExpIV, err = makeRandom(16); err != nil {
|
||||
if newRec.RSAKey.RSAExpIV, err = symcrypt.MakeRandom(16); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
paddedExponent := padding.AddPadding(rsaPriv.D.Bytes())
|
||||
if newRec.RSAKey.RSAExp, err = encryptCBC(paddedExponent, newRec.RSAKey.RSAExpIV, passKey); err != nil {
|
||||
if newRec.RSAKey.RSAExp, err = symcrypt.EncryptCBC(paddedExponent, newRec.RSAKey.RSAExpIV, passKey); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if newRec.RSAKey.RSAPrimePIV, err = makeRandom(16); err != nil {
|
||||
if newRec.RSAKey.RSAPrimePIV, err = symcrypt.MakeRandom(16); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
paddedPrimeP := padding.AddPadding(rsaPriv.Primes[0].Bytes())
|
||||
if newRec.RSAKey.RSAPrimeP, err = encryptCBC(paddedPrimeP, newRec.RSAKey.RSAPrimePIV, passKey); err != nil {
|
||||
if newRec.RSAKey.RSAPrimeP, err = symcrypt.EncryptCBC(paddedPrimeP, newRec.RSAKey.RSAPrimePIV, passKey); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if newRec.RSAKey.RSAPrimeQIV, err = makeRandom(16); err != nil {
|
||||
if newRec.RSAKey.RSAPrimeQIV, err = symcrypt.MakeRandom(16); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
paddedPrimeQ := padding.AddPadding(rsaPriv.Primes[1].Bytes())
|
||||
newRec.RSAKey.RSAPrimeQ, err = encryptCBC(paddedPrimeQ, newRec.RSAKey.RSAPrimeQIV, passKey)
|
||||
newRec.RSAKey.RSAPrimeQ, err = symcrypt.EncryptCBC(paddedPrimeQ, newRec.RSAKey.RSAPrimeQIV, passKey)
|
||||
return
|
||||
}
|
||||
|
||||
// encryptECCRecord takes an ECDSA private key and encrypts it with
|
||||
// a password key.
|
||||
func encryptECCRecord(newRec *PasswordRecord, ecPriv *ecdsa.PrivateKey, passKey []byte) (err error) {
|
||||
ecX509, err := x509.MarshalECPrivateKey(ecPriv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if newRec.ECKey.ECPrivIV, err = symcrypt.MakeRandom(16); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
paddedX509 := padding.AddPadding(ecX509)
|
||||
newRec.ECKey.ECPriv, err = symcrypt.EncryptCBC(paddedX509, newRec.ECKey.ECPrivIV, passKey)
|
||||
return
|
||||
}
|
||||
|
||||
// createPasswordRec creates a new record from a username and password
|
||||
func createPasswordRec(password string, admin bool) (newRec PasswordRecord, err error) {
|
||||
newRec.Type = RSARecord
|
||||
newRec.Type = DefaultRecordType
|
||||
|
||||
if newRec.PasswordSalt, err = makeRandom(16); err != nil {
|
||||
if newRec.PasswordSalt, err = symcrypt.MakeRandom(16); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -147,7 +168,7 @@ func createPasswordRec(password string, admin bool) (newRec PasswordRecord, err
|
||||
return
|
||||
}
|
||||
|
||||
if newRec.KeySalt, err = makeRandom(16); err != nil {
|
||||
if newRec.KeySalt, err = symcrypt.MakeRandom(16); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -157,20 +178,33 @@ func createPasswordRec(password string, admin bool) (newRec PasswordRecord, err
|
||||
}
|
||||
|
||||
// generate a key pair
|
||||
rsaPriv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return
|
||||
switch DefaultRecordType {
|
||||
case RSARecord:
|
||||
var rsaPriv *rsa.PrivateKey
|
||||
rsaPriv, err = rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// encrypt RSA key with password key
|
||||
if err = encryptRSARecord(&newRec, rsaPriv, passKey); err != nil {
|
||||
return
|
||||
}
|
||||
newRec.RSAKey.RSAPublic = rsaPriv.PublicKey
|
||||
case ECCRecord:
|
||||
var ecPriv *ecdsa.PrivateKey
|
||||
ecPriv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// encrypt ECDSA key with password key
|
||||
if err = encryptECCRecord(&newRec, ecPriv, passKey); err != nil {
|
||||
return
|
||||
}
|
||||
newRec.ECKey.ECPublic = ecPriv.PublicKey
|
||||
}
|
||||
|
||||
// encrypt RSA key with password key
|
||||
if err = encryptRSARecord(&newRec, rsaPriv, passKey); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
newRec.RSAKey.RSAPublic = rsaPriv.PublicKey
|
||||
|
||||
// encrypt AES key with password key
|
||||
aesKey, err := makeRandom(16)
|
||||
aesKey, err := symcrypt.MakeRandom(16)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -216,36 +250,6 @@ func encryptECB(data, key []byte) (encryptedData []byte, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// decryptCBC decrypt bytes using a key and IV with AES in CBC mode.
|
||||
func decryptCBC(data, iv, key []byte) (decryptedData []byte, err error) {
|
||||
aesCrypt, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ivBytes := append([]byte{}, iv...)
|
||||
|
||||
decryptedData = make([]byte, len(data))
|
||||
aesCBC := cipher.NewCBCDecrypter(aesCrypt, ivBytes)
|
||||
aesCBC.CryptBlocks(decryptedData, data)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// encryptCBC encrypt data using a key and IV with AES in CBC mode.
|
||||
func encryptCBC(data, iv, key []byte) (encryptedData []byte, err error) {
|
||||
aesCrypt, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ivBytes := append([]byte{}, iv...)
|
||||
|
||||
encryptedData = make([]byte, len(data))
|
||||
aesCBC := cipher.NewCBCEncrypter(aesCrypt, ivBytes)
|
||||
aesCBC.CryptBlocks(encryptedData, data)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// InitFromDisk reads the record from disk and initialize global context.
|
||||
func InitFromDisk(path string) error {
|
||||
jsonDiskRecord, err := ioutil.ReadFile(path)
|
||||
@@ -304,6 +308,11 @@ func InitFromDisk(path string) error {
|
||||
return formatErr
|
||||
}
|
||||
}
|
||||
if rec.Type == ECCRecord {
|
||||
if len(rec.ECKey.ECPriv) == 0 {
|
||||
return formatErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the Version field is 0 then it indicates that nothing was
|
||||
@@ -312,7 +321,7 @@ func InitFromDisk(path string) error {
|
||||
if records.Version == 0 {
|
||||
records.Version = DEFAULT_VERSION
|
||||
records.VaultId = int(mrand.Int31())
|
||||
records.HmacKey, err = makeRandom(16)
|
||||
records.HmacKey, err = symcrypt.MakeRandom(16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -361,6 +370,7 @@ func ChangePassword(name, password, newPassword string) (err error) {
|
||||
// decrypt key
|
||||
var key []byte
|
||||
var rsaKey rsa.PrivateKey
|
||||
var ecKey *ecdsa.PrivateKey
|
||||
if pr.Type == AESRecord {
|
||||
key, err = pr.GetKeyAES(password)
|
||||
if err != nil {
|
||||
@@ -371,20 +381,25 @@ func ChangePassword(name, password, newPassword string) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if pr.Type == ECCRecord {
|
||||
ecKey, err = pr.GetKeyECC(password)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = errors.New("Unkown record type")
|
||||
return
|
||||
}
|
||||
|
||||
// add the password salt and hash
|
||||
if pr.PasswordSalt, err = makeRandom(16); err != nil {
|
||||
if pr.PasswordSalt, err = symcrypt.MakeRandom(16); err != nil {
|
||||
return
|
||||
}
|
||||
if pr.HashedPassword, err = hashPassword(newPassword, pr.PasswordSalt); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if pr.KeySalt, err = makeRandom(16); err != nil {
|
||||
if pr.KeySalt, err = symcrypt.MakeRandom(16); err != nil {
|
||||
return
|
||||
}
|
||||
newPassKey, err := derivePasswordKey(newPassword, pr.KeySalt)
|
||||
@@ -404,6 +419,12 @@ func ChangePassword(name, password, newPassword string) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if pr.Type == ECCRecord {
|
||||
// encrypt ECDSA key with password key
|
||||
err = encryptECCRecord(&pr, ecKey, newPassKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = errors.New("Unkown record type")
|
||||
return
|
||||
@@ -504,9 +525,15 @@ func (pr PasswordRecord) GetType() string {
|
||||
return pr.Type
|
||||
}
|
||||
|
||||
// EncryptKey encrypts a 16-byte key with the RSA key of the record.
|
||||
// EncryptKey encrypts a 16-byte key with the RSA or EC key of the record.
|
||||
func (pr PasswordRecord) EncryptKey(in []byte) (out []byte, err error) {
|
||||
return rsa.EncryptOAEP(sha1.New(), rand.Reader, &pr.RSAKey.RSAPublic, in, nil)
|
||||
if pr.Type == RSARecord {
|
||||
return rsa.EncryptOAEP(sha1.New(), rand.Reader, &pr.RSAKey.RSAPublic, in, nil)
|
||||
} else if pr.Type == ECCRecord {
|
||||
return ecdh.Encrypt(pr.ECKey.ECPublic, in)
|
||||
} else {
|
||||
return nil, errors.New("Invalid function for record type")
|
||||
}
|
||||
}
|
||||
|
||||
// GetKeyAES returns the 16-byte key of the record.
|
||||
@@ -528,7 +555,7 @@ func (pr PasswordRecord) GetKeyAES(password string) (key []byte, err error) {
|
||||
return decryptECB(pr.AESKey, passKey)
|
||||
}
|
||||
|
||||
// GetKeyAES returns the RSA public key of the record.
|
||||
// GetKeyRSAPub returns the RSA public key of the record.
|
||||
func (pr PasswordRecord) GetKeyRSAPub() (out *rsa.PublicKey, err error) {
|
||||
if pr.Type != RSARecord {
|
||||
return out, errors.New("Invalid function for record type")
|
||||
@@ -536,7 +563,42 @@ func (pr PasswordRecord) GetKeyRSAPub() (out *rsa.PublicKey, err error) {
|
||||
return &pr.RSAKey.RSAPublic, err
|
||||
}
|
||||
|
||||
// GetKeyAES returns the RSA private key of the record given the correct password.
|
||||
// GetKeyECCPub returns the ECDSA public key out of the record.
|
||||
func (pr PasswordRecord) GetKeyECCPub() (out *ecdsa.PublicKey, err error) {
|
||||
if pr.Type != ECCRecord {
|
||||
return out, errors.New("Invalid function for record type")
|
||||
}
|
||||
return &pr.ECKey.ECPublic, err
|
||||
}
|
||||
|
||||
// GetKeyECC returns the ECDSA private key of the record given the correct password.
|
||||
func (pr PasswordRecord) GetKeyECC(password string) (key *ecdsa.PrivateKey, err error) {
|
||||
if pr.Type != ECCRecord {
|
||||
return key, errors.New("Invalid function for record type")
|
||||
}
|
||||
|
||||
if err = pr.ValidatePassword(password); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
passKey, err := derivePasswordKey(password, pr.KeySalt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
x509Padded, err := symcrypt.DecryptCBC(pr.ECKey.ECPriv, pr.ECKey.ECPrivIV, passKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ecX509, err := padding.RemovePadding(x509Padded)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return x509.ParseECPrivateKey(ecX509)
|
||||
}
|
||||
|
||||
// GetKeyRSA returns the RSA private key of the record given the correct password.
|
||||
func (pr PasswordRecord) GetKeyRSA(password string) (key rsa.PrivateKey, err error) {
|
||||
if pr.Type != RSARecord {
|
||||
return key, errors.New("Invalid function for record type")
|
||||
@@ -552,7 +614,7 @@ func (pr PasswordRecord) GetKeyRSA(password string) (key rsa.PrivateKey, err err
|
||||
return
|
||||
}
|
||||
|
||||
rsaExponentPadded, err := decryptCBC(pr.RSAKey.RSAExp, pr.RSAKey.RSAExpIV, passKey)
|
||||
rsaExponentPadded, err := symcrypt.DecryptCBC(pr.RSAKey.RSAExp, pr.RSAKey.RSAExpIV, passKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -561,7 +623,7 @@ func (pr PasswordRecord) GetKeyRSA(password string) (key rsa.PrivateKey, err err
|
||||
return
|
||||
}
|
||||
|
||||
rsaPrimePPadded, err := decryptCBC(pr.RSAKey.RSAPrimeP, pr.RSAKey.RSAPrimePIV, passKey)
|
||||
rsaPrimePPadded, err := symcrypt.DecryptCBC(pr.RSAKey.RSAPrimeP, pr.RSAKey.RSAPrimePIV, passKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -570,7 +632,7 @@ func (pr PasswordRecord) GetKeyRSA(password string) (key rsa.PrivateKey, err err
|
||||
return
|
||||
}
|
||||
|
||||
rsaPrimeQPadded, err := decryptCBC(pr.RSAKey.RSAPrimeQ, pr.RSAKey.RSAPrimeQIV, passKey)
|
||||
rsaPrimeQPadded, err := symcrypt.DecryptCBC(pr.RSAKey.RSAPrimeQ, pr.RSAKey.RSAPrimeQIV, passKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
)
|
||||
|
||||
func TestRSAEncryptDecrypt(t *testing.T) {
|
||||
oldDefaultRecordType := DefaultRecordType
|
||||
DefaultRecordType = RSARecord
|
||||
myRec, err := createPasswordRec("mypasswordisweak", true)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating record")
|
||||
@@ -33,4 +35,30 @@ func TestRSAEncryptDecrypt(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Error validating RSA key")
|
||||
}
|
||||
DefaultRecordType = oldDefaultRecordType
|
||||
}
|
||||
|
||||
func TestECCEncryptDecrypt(t *testing.T) {
|
||||
oldDefaultRecordType := DefaultRecordType
|
||||
DefaultRecordType = ECCRecord
|
||||
myRec, err := createPasswordRec("mypasswordisweak", true)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating record")
|
||||
}
|
||||
|
||||
_, err = myRec.GetKeyECCPub()
|
||||
if err != nil {
|
||||
t.Fatalf("Error extracting EC pub")
|
||||
}
|
||||
|
||||
_, err = myRec.GetKeyECC("mypasswordiswrong")
|
||||
if err == nil {
|
||||
t.Fatalf("Incorrect password did not fail")
|
||||
}
|
||||
|
||||
_, err = myRec.GetKeyECC("mypasswordisweak")
|
||||
if err != nil {
|
||||
t.Fatalf("Error decrypting EC key")
|
||||
}
|
||||
DefaultRecordType = oldDefaultRecordType
|
||||
}
|
||||
|
||||
46
symcrypt/symcrypt.go
Normal file
46
symcrypt/symcrypt.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Package symcrypt contains common symmetric encryption functions.
|
||||
package symcrypt
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
)
|
||||
|
||||
// DecryptCBC decrypt bytes using a key and IV with AES in CBC mode.
|
||||
func DecryptCBC(data, iv, key []byte) (decryptedData []byte, err error) {
|
||||
aesCrypt, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ivBytes := append([]byte{}, iv...)
|
||||
|
||||
decryptedData = make([]byte, len(data))
|
||||
aesCBC := cipher.NewCBCDecrypter(aesCrypt, ivBytes)
|
||||
aesCBC.CryptBlocks(decryptedData, data)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// EncryptCBC encrypt data using a key and IV with AES in CBC mode.
|
||||
func EncryptCBC(data, iv, key []byte) (encryptedData []byte, err error) {
|
||||
aesCrypt, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ivBytes := append([]byte{}, iv...)
|
||||
|
||||
encryptedData = make([]byte, len(data))
|
||||
aesCBC := cipher.NewCBCEncrypter(aesCrypt, ivBytes)
|
||||
aesCBC.CryptBlocks(encryptedData, data)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MakeRandom is a helper that makes a new buffer full of random data.
|
||||
func MakeRandom(length int) ([]byte, error) {
|
||||
bytes := make([]byte, length)
|
||||
_, err := io.ReadFull(rand.Reader, bytes)
|
||||
return bytes, err
|
||||
}
|
||||
39
symcrypt/symcrypt_test.go
Normal file
39
symcrypt/symcrypt_test.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package symcrypt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/cloudflare/redoctober/padding"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCrypt(t *testing.T) {
|
||||
msg := []byte("One ping only, please.")
|
||||
padMsg := padding.AddPadding(msg)
|
||||
|
||||
key, err := MakeRandom(16)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
iv, err := MakeRandom(16)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
out, err := EncryptCBC(padMsg, iv, key)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
out, err = DecryptCBC(out, iv, key)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
unpadOut, err := padding.RemovePadding(out)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
} else if !bytes.Equal(unpadOut, msg) {
|
||||
t.Fatal("Decrypted message doesn't match original plaintext.")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user