Merge pull request #22 from kisom/ecdsa

Add ECC support
This commit is contained in:
Nick Sullivan
2014-01-16 14:14:06 -08:00
9 changed files with 420 additions and 102 deletions

View File

@@ -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")
}

View File

@@ -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
View 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
View 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.")
}
}

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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
View 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
View 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.")
}
}