Commit 1a77f7dd authored by ElyKar's avatar ElyKar

Add import and export command - US 183

parent 85ccbf85
Pipeline #342 failed with stage
......@@ -8,9 +8,11 @@ import (
"crypto/x509/pkix"
"encoding/pem"
"errors"
"github.com/pborman/uuid"
"fmt"
"math/big"
"time"
"github.com/pborman/uuid"
)
// GetCertificateRequest creates a request to be sent to any authoritative signer, as a PEM-encoded array of bytes.
......@@ -116,6 +118,9 @@ func GetSelfSignedCertificate(days int, serial uint64, country, organization, un
// PEMToCertificate tries to decode a PEM-encoded array of bytes to a certificate
func PEMToCertificate(data []byte) (*x509.Certificate, error) {
block, _ := pem.Decode(data)
if block == nil {
return nil, fmt.Errorf("Data is not a valid pem-encoding")
}
return x509.ParseCertificate(block.Bytes)
}
......
......@@ -6,6 +6,7 @@ import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
)
// Cipher used to encrypt private key content in PEM format
......@@ -50,6 +51,9 @@ func EncryptedPEMToPrivateKey(data []byte, pwd string) (*rsa.PrivateKey, error)
var err error
block, _ := pem.Decode(data)
if block == nil {
return nil, fmt.Errorf("Data is not a valid pem-encoding")
}
decodedData := block.Bytes
if pwd != "" {
......
package main
import (
"dfss/dfssc/user"
"fmt"
"os"
)
// export the certificate and private key of the user
func exportConf(confFile string) {
fmt.Println("Export user configuration")
var keyPassphrase, confPassphrase string
config, err := user.NewConfig(fkey, fcert)
if err != nil {
_, _ = os.Stderr.WriteString(fmt.Sprintf("Couldn't open the files: %s", err))
os.Exit(1)
return
}
err = readPassphrases(&keyPassphrase, &confPassphrase, true)
if err != nil {
_, _ = os.Stderr.WriteString(fmt.Sprintf("An error occurred: %s", err))
os.Exit(1)
return
}
err = config.SaveConfigToFile(confFile, confPassphrase, keyPassphrase)
if err != nil {
_, _ = os.Stderr.WriteString(fmt.Sprintf("Couldn't save the configuration on the disk: %s", err))
os.Exit(1)
return
}
}
// Read two passphrases for the configuration
func readPassphrases(keyPassphrase, confPassphrase *string, second bool) error {
fmt.Println("Enter the passphrase of the configuration")
err := readPassword(confPassphrase, second)
if err != nil {
return err
}
fmt.Println("Enter the passphrase of your current key (if any)")
err = readPassword(keyPassphrase, false)
if err != nil {
return err
}
return nil
}
// import the configuration
func importConf(confFile string) {
var keyPassphrase, confPassphrase string
err := readPassphrases(&keyPassphrase, &confPassphrase, false)
if err != nil {
_, _ = os.Stderr.WriteString(fmt.Sprintf("An error occurred: %s", err))
os.Exit(1)
return
}
config, err := user.DecodeConfiguration(confFile, keyPassphrase, confPassphrase)
if err != nil {
_, _ = os.Stderr.WriteString(fmt.Sprintf("Couldnd't decrypt the configuration: %s", err))
os.Exit(1)
return
}
err = config.SaveUserInformations()
if err != nil {
_, _ = os.Stderr.WriteString(fmt.Sprintf("Couldn't save the certificate and private key: %s", err))
os.Exit(1)
return
}
}
......@@ -31,12 +31,14 @@ func init() {
fmt.Println(" dfssc [flags] command")
fmt.Println("\nThe commands are:")
fmt.Println(" help print this help")
fmt.Println(" version print dfss client version")
fmt.Println(" register register a new client")
fmt.Println(" auth authenticate a new client")
fmt.Println(" new create a new contract")
fmt.Println(" show <c> print contract information from file c")
fmt.Println(" help print this help")
fmt.Println(" version print dfss client version")
fmt.Println(" register register a new client")
fmt.Println(" auth authenticate a new client")
fmt.Println(" new create a new contract")
fmt.Println(" show <c> print contract information from file c")
fmt.Println(" export <c> export certificate and private key of the user to file c")
fmt.Println(" import <c> import private key and certificate from file c")
fmt.Println("\nFlags:")
flag.PrintDefaults()
......@@ -61,6 +63,10 @@ func main() {
newContract()
case "show":
showContract(flag.Arg(1))
case "export":
exportConf(flag.Arg(1))
case "import":
importConf(flag.Arg(1))
default:
flag.Usage()
}
......
package security
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"dfss/auth"
"dfss/dfssc/common"
"encoding/base64"
"fmt"
"io"
"strings"
)
// GetCertificate return the Certificate stored on the disk
......@@ -37,3 +45,49 @@ func GetPrivateKey(filename, passphrase string) (*rsa.PrivateKey, error) {
return key, nil
}
// AES-256 requires a 32 bytes key, this function extend the key to this length
func extendKey(key string) string {
key = strings.Repeat(key, 32/len(key)+1)
return key[:32]
}
// EncryptStringAES enciphers the data using AES-256 algorithm
func EncryptStringAES(key string, data []byte) ([]byte, error) {
key = extendKey(key)
block, err := aes.NewCipher(bytes.NewBufferString(key).Bytes())
if err != nil {
return nil, err
}
b := base64.StdEncoding.EncodeToString(data)
ciphertext := make([]byte, aes.BlockSize+len(b))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
cfb := cipher.NewCFBEncrypter(block, iv)
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b))
return ciphertext, nil
}
// DecryptAES deciphers the data using AES-256 algorithm
func DecryptAES(key string, data []byte) ([]byte, error) {
key = extendKey(key)
block, err := aes.NewCipher(bytes.NewBufferString(key).Bytes())
if err != nil {
return nil, err
}
if len(data) < aes.BlockSize {
return nil, fmt.Errorf("Ciphertext is not correctly encrypted")
}
iv := data[:aes.BlockSize]
data = data[aes.BlockSize:]
cfb := cipher.NewCFBDecrypter(block, iv)
cfb.XORKeyStream(data, data)
decoded, err := base64.StdEncoding.DecodeString(string(data))
if err != nil {
return nil, err
}
return decoded, nil
}
package user
import (
"dfss/auth"
"dfss/dfssc/common"
"dfss/dfssc/security"
"encoding/json"
"fmt"
)
// Config represents the config file to be marshalled in json
type Config struct {
KeyFile string `json:"key"`
KeyData []byte `json:"keyData"`
CertFile string `json:"cert"`
CertData []byte `json:"certData"`
}
// NewConfig creates a new config object from the key and certificate provided
// The validity of those is checked later
func NewConfig(keyFile, certFile string) (*Config, error) {
if !common.FileExists(keyFile) {
return nil, fmt.Errorf("No such file: %s", keyFile)
}
if !common.FileExists(certFile) {
return nil, fmt.Errorf("No such file: %s", certFile)
}
key, err := common.ReadFile(keyFile)
if err != nil {
return nil, err
}
cert, err := common.ReadFile(certFile)
if err != nil {
return nil, err
}
return &Config{
KeyData: key,
KeyFile: keyFile,
CertData: cert,
CertFile: certFile,
}, nil
}
// SaveConfigToFile marshals checks the validity of the certificate and private key,
// marshals the struct in JSON, encrypt the string using AES-256 with the provided passphrase,
// and finally save it to a file
func (c *Config) SaveConfigToFile(fileName, passphrase, keyPassphrase string) error {
if common.FileExists(fileName) {
return fmt.Errorf("Cannot overwrite file: %s", fileName)
}
if len(passphrase) < 4 {
return fmt.Errorf("Passphrase should be at least 4 characters long")
}
err := c.checkData(keyPassphrase)
if err != nil {
return err
}
data, err := json.Marshal(c)
if err != nil {
return err
}
encodedData, err := security.EncryptStringAES(passphrase, data)
if err != nil {
return err
}
err = common.SaveToDisk(encodedData, fileName)
return err
}
// DecodeConfiguration : decrypt and unmarshal the given configuration file
// to create a Config object. It also checks the validity of the certificate and private key
func DecodeConfiguration(fileName, keyPassphrase, confPassphrase string) (*Config, error) {
if !common.FileExists(fileName) {
return nil, fmt.Errorf("No such file: %s", fileName)
}
if len(confPassphrase) < 4 {
return nil, fmt.Errorf("Passphrase should be at least 4 characters long")
}
encodedData, err := common.ReadFile(fileName)
if err != nil {
return nil, err
}
decodedData, err := security.DecryptAES(confPassphrase, encodedData)
if err != nil {
return nil, err
}
var config Config
err = json.Unmarshal(decodedData, &config)
if err != nil {
return nil, err
}
err = config.checkData(keyPassphrase)
if err != nil {
return nil, err
}
return &config, nil
}
// Check that the certificate is valid, and that the private key is valid too
// using the passphrase
func (c *Config) checkData(keyPassphrase string) error {
_, err := auth.PEMToCertificate(c.CertData)
if err != nil {
return err
}
_, err = auth.EncryptedPEMToPrivateKey(c.KeyData, keyPassphrase)
if err != nil {
return err
}
return nil
}
// SaveUserInformations save the certificate and private key to the files specified in the Config struct
func (c *Config) SaveUserInformations() error {
if common.FileExists(c.KeyFile) {
return fmt.Errorf("Cannot overwrite file: %s", c.KeyFile)
}
if common.FileExists(c.CertFile) {
return fmt.Errorf("Cannot overwrite file: %s", c.CertFile)
}
err := common.SaveToDisk(c.KeyData, c.KeyFile)
if err != nil {
common.DeleteQuietly(c.KeyFile)
return err
}
err = common.SaveToDisk(c.CertData, c.CertFile)
if err != nil {
common.DeleteQuietly(c.KeyFile)
common.DeleteQuietly(c.CertFile)
return err
}
return nil
}
package user
import (
"dfss/dfssc/common"
"dfss/dfssc/security"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/bmizerany/assert"
)
const certFixture = `-----BEGIN CERTIFICATE-----
MIIB5TCCAY+gAwIBAgIJAKId2y6Lo9T8MA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV
BAYTAkZSMQ0wCwYDVQQKDARERlNTMRswGQYDVQQLDBJERlNTIFBsYXRmb3JtIHYw
LjExEjAQBgNVBAMMCWxvY2FsaG9zdDAgFw0xNjAxMjYxNTM2NTNaGA80NDgwMDMw
ODE1MzY1M1owTTELMAkGA1UEBhMCRlIxDTALBgNVBAoMBERGU1MxGzAZBgNVBAsM
EkRGU1MgUGxhdGZvcm0gdjAuMTESMBAGA1UEAwwJbG9jYWxob3N0MFwwDQYJKoZI
hvcNAQEBBQADSwAwSAJBAMGAgCtkRLePYFRTUN0V/0v/6phm0guHGS6f0TkSEas4
CGZTKFJVTBksMGIBtfyYw3XQx2bO8myeypDN5nV05DcCAwEAAaNQME4wHQYDVR0O
BBYEFO09nxx5/qeLK5Wig1+3kg66gn/mMB8GA1UdIwQYMBaAFO09nxx5/qeLK5Wi
g1+3kg66gn/mMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADQQCqNSH+rt/Z
ru2rkabLiHOGjI+AenSOvqWZ2dWAlLksYcyuQHKwjGWgpmqkiQCnkIDwIxZvu69Y
OBz0ASFn7eym
-----END CERTIFICATE-----
`
const keyFixture = `-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBAMGAgCtkRLePYFRTUN0V/0v/6phm0guHGS6f0TkSEas4CGZTKFJV
TBksMGIBtfyYw3XQx2bO8myeypDN5nV05DcCAwEAAQJAHSdRKDh5KfbOGqZa3pR7
3GV4YPHM37PBFYc6rJCOXO9W8L4Q1kvEhjKXp7ke18Cge7bVmlKspvxvC62gxSQm
QQIhAPMYwpp29ZREdk8yU65Sp6w+EbZS9TjZkC+pk3syYjaxAiEAy8XWnnDMsUxb
6vp1SaaIfxI441AYzh3+8c56CAvt02cCIQDQ2jfvHz7zyDHg7rsILMkTaSwseW9n
DTwcRtOHZ40LsQIgDWEVAVwopG9+DYSaVNahWa6Jm6szpbzkc136NzMJT3sCIQDv
T2KSQQIYEvPYZmE+1b9f3rs/w7setrGtqVFkm/fTWQ==
-----END RSA PRIVATE KEY-----
`
var cPath = os.TempDir()
// TestInvalidFiles assert an error is raised when provided with wrong files
func TestInvalidFiles(t *testing.T) {
keyFile := filepath.Join(cPath, "invalidKey.pem")
certFile := filepath.Join(cPath, "invalidCert.pem")
defer deleteFiles(keyFile, certFile, "invalidConf.pem")
_, err := NewConfig("inexistantKey", "inexistantCert")
assert.Tf(t, err != nil, "No key file nor cert file, expected error")
_ = common.SaveStringToDisk(fmt.Sprintf("%s", []byte(keyFixture)), keyFile)
_, err = NewConfig(keyFile, "inexistantCert")
assert.Tf(t, err != nil, "No cert file, expected error")
_ = security.SaveCertificate(fmt.Sprintf("%s", []byte(certFixture)), certFile)
_, err = NewConfig(keyFile, certFile)
assert.Tf(t, err == nil, "Expected no error, files are present and valid")
}
// TestErrorDumpingConfig checks the error that may be raised while dumping the configuration to the disk
func TestErrorDumpingConfig(t *testing.T) {
keyFile := filepath.Join(cPath, "privKey.pem")
certFile := filepath.Join(cPath, "cert.pem")
defer deleteFiles(keyFile, certFile, "invalidConf.pem")
err := common.SaveStringToDisk(fmt.Sprintf("%s", []byte(keyFixture)), keyFile)
assert.Tf(t, err == nil, "Expected no error, file is present")
err = security.SaveCertificate(fmt.Sprintf("%s", []byte(certFixture)), certFile)
assert.Tf(t, err == nil, "Expected no error, cert is valid")
config, err := NewConfig(keyFile, certFile)
assert.Tf(t, err == nil, "Expected no error, files are present and valid")
err = config.SaveConfigToFile("file", "abc", "")
assert.Tf(t, err != nil, "Expected an error, passphrase is too short (< 4 char)")
err = config.SaveConfigToFile(keyFile, "passphrase", "")
assert.Tf(t, err != nil, "Expected an error, file is already there")
common.DeleteQuietly(keyFile)
_ = common.SaveStringToDisk("Invalid key", keyFile)
config, _ = NewConfig(keyFile, certFile)
err = config.SaveConfigToFile("file", "passphrase", "passphrase")
assert.Tf(t, err != nil, "Expected an error, private key is invalid")
common.DeleteQuietly(certFile)
common.DeleteQuietly(keyFile)
_ = common.SaveStringToDisk(fmt.Sprintf("%s", []byte(keyFixture)), keyFile)
_ = common.SaveStringToDisk("Invalid certificate", certFile)
config, _ = NewConfig(keyFile, certFile)
err = config.SaveConfigToFile("file", "passphrase", "passphrase")
assert.Tf(t, err != nil, "Expected an error, certificate is invalid")
}
// TestDumpingFile tries to save the configuration and checks there are no problems
func TestDumpingFile(t *testing.T) {
keyFile := filepath.Join(cPath, "privKey2.pem")
certFile := filepath.Join(cPath, "cert2.pem")
configPath := filepath.Join(os.TempDir(), "dfss.conf")
common.DeleteQuietly(configPath)
defer deleteFiles(keyFile, certFile, configPath)
err := common.SaveStringToDisk(fmt.Sprintf("%s", []byte(keyFixture)), keyFile)
assert.Tf(t, err == nil, "Expected no error, file is present")
err = security.SaveCertificate(fmt.Sprintf("%s", []byte(certFixture)), certFile)
assert.Tf(t, err == nil, "Expected no error, cert is valid")
config, err := NewConfig(keyFile, certFile)
assert.Tf(t, err == nil, "Expected no error, files are present and valid")
err = config.SaveConfigToFile(configPath, "passphrase", "")
assert.Tf(t, err == nil, "Expected no error, config is valid")
assert.Tf(t, common.FileExists(configPath), "Expected a config file present")
_, err = common.ReadFile(configPath)
assert.Tf(t, err == nil, "Expected no error, config is present")
}
// TestErrorDecodeFile tries to decode a configuration file and checks the errors raised
func TestErrorDecodeFile(t *testing.T) {
keyFile := filepath.Join(cPath, "privKey2.pem")
certFile := filepath.Join(cPath, "cert2.pem")
configPath := filepath.Join(os.TempDir(), "dfss2.conf")
common.DeleteQuietly(configPath)
defer deleteFiles(keyFile, certFile, configPath)
_ = common.SaveStringToDisk(fmt.Sprintf("%s", []byte(keyFixture)), keyFile)
_ = security.SaveCertificate(fmt.Sprintf("%s", []byte(certFixture)), certFile)
_, err := DecodeConfiguration("inexistantFile", "passphrase", "")
assert.Tf(t, err != nil, "File is invalid, impossible to decode configuration")
_, err = DecodeConfiguration(keyFile, "pas", "")
assert.Tf(t, err != nil, "Passphrase is invalid, should be at least 4 char long")
config, err := NewConfig(keyFile, certFile)
assert.Tf(t, err == nil, "Expected no error, files are present and valid")
err = config.SaveConfigToFile(configPath, "passphrase", "")
assert.Tf(t, err == nil, "Expected no error, config is valid")
config, err = DecodeConfiguration(configPath, "pass", "")
assert.Tf(t, err != nil, "Expected error, wrong passphrase")
}
// TestDecodeConfig tries to decode the configuration and checks it is right
func TestDecodeConfig(t *testing.T) {
keyFile := filepath.Join(cPath, "privKey4.pem")
certFile := filepath.Join(cPath, "cert4.pem")
configPath := filepath.Join(os.TempDir(), "dfss4.conf")
common.DeleteQuietly(configPath)
defer deleteFiles(keyFile, certFile, configPath)
_ = common.SaveStringToDisk(fmt.Sprintf("%s", []byte(keyFixture)), keyFile)
_ = security.SaveCertificate(fmt.Sprintf("%s", []byte(certFixture)), certFile)
config, err := NewConfig(keyFile, certFile)
assert.Tf(t, err == nil, "Expected no error, files are present and valid")
err = config.SaveConfigToFile(configPath, "passphrase", "")
assert.Tf(t, err == nil, "Expected no error, config is valid")
decoded, err := DecodeConfiguration(configPath, "", "passphrase")
assert.Tf(t, err == nil, "Expected no error, config should have been decodedi")
assert.Equalf(t, config.KeyFile, decoded.KeyFile, "Wrong keyFile parameter")
assert.Equalf(t, config.KeyData, decoded.KeyData, "Wrong keyData parameter")
assert.Equalf(t, config.CertFile, decoded.CertFile, "Wrong certFile parameter")
assert.Equalf(t, config.CertData, decoded.CertData, "Wrong certData parameter")
}
// TestSaveFilesToDisk tries to create the certificate and private key on the disk from
// the config file
func TestSaveFilesToDisk(t *testing.T) {
keyFile := filepath.Join(cPath, "privKey5.pem")
certFile := filepath.Join(cPath, "cert5.pem")
configPath := filepath.Join(os.TempDir(), "dfss5.conf")
deleteFiles(keyFile, certFile, configPath)
defer deleteFiles(keyFile, certFile, configPath)
_ = common.SaveStringToDisk(fmt.Sprintf("%s", []byte(keyFixture)), keyFile)
_ = security.SaveCertificate(fmt.Sprintf("%s", []byte(certFixture)), certFile)
config, err := NewConfig(keyFile, certFile)
assert.Tf(t, err == nil, "Expected no error, files are present and valid")
err = config.SaveUserInformations()
assert.Tf(t, err != nil, "Expected an error, files are already present")
common.DeleteQuietly(keyFile)
err = config.SaveUserInformations()
assert.Tf(t, err != nil, "Expected an error, certificate file is already present")
common.DeleteQuietly(certFile)
err = config.SaveUserInformations()
assert.Tf(t, err == nil, "No error expected, files are not here")
assert.Tf(t, common.FileExists(keyFile), "Expected private key file")
assert.Tf(t, common.FileExists(certFile), "Expected certificate file")
}
// Helper function to delete all the files
func deleteFiles(keyFile, certFile, confFile string) {
common.DeleteQuietly(keyFile)
common.DeleteQuietly(certFile)
common.DeleteQuietly(confFile)
}
package tests
import (
"dfss/dfssc/common"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/bmizerany/assert"
)
// TestExport tries to export the certificate and pricate key of the client
//
// CLASSIC SCENARIO
// 1. Export is node successfully
//
// BAD CASES
// 1. Wrong passphrase for unlocking private key