diff --git a/asymetric/rsa.go b/asymetric/rsa.go new file mode 100644 index 0000000..c445fb5 --- /dev/null +++ b/asymetric/rsa.go @@ -0,0 +1,59 @@ +package asymetric + +import ( + _ "crypto/sha256" + _ "crypto/sha512" + + "crypto" + "crypto/rsa" + "errors" + "fmt" + "io" +) + +type RsaOaepPss struct { + PrivateKey *rsa.PrivateKey + PublicKey *rsa.PublicKey + Hash crypto.Hash + + Random io.Reader +} + +func (this *RsaOaepPss) getHash() (crypto.Hash, error) { + if !this.Hash.Available() { + return 0, errors.New(fmt.Sprintf("hash function %v is unabailable", this.Hash)) + } else { + return this.Hash, nil + } +} + +func (this *RsaOaepPss) Encrypt(data []byte) ([]byte, error) { + label := make([]byte, 0) + if hash, err := this.getHash(); err != nil { + return nil, err + } else { + return rsa.EncryptOAEP(hash.New(), this.Random, this.PublicKey, data, label) + } + +} + +func (this *RsaOaepPss) Decrypt(ciphertext []byte) ([]byte, error) { + label := make([]byte, 0) + if hash, err := this.getHash(); err != nil { + return nil, err + } else { + return rsa.DecryptOAEP(hash.New(), this.Random, this.PrivateKey, ciphertext, label) + } +} + +func (this *RsaOaepPss) Sign(hashedData []byte) ([]byte, error) { + ret, err := rsa.SignPSS(this.Random, this.PrivateKey, this.Hash, hashedData, nil) + if err != nil { + fmt.Println(">>> ", len(hashedData)) + } + return ret, err +} + +func (this *RsaOaepPss) VerifySignature(hashedData []byte, signature []byte) error { + return rsa.VerifyPSS(this.PublicKey, this.Hash, hashedData, signature, nil) +} diff --git a/asymetric/rsa_test.go b/asymetric/rsa_test.go new file mode 100644 index 0000000..9f300f2 --- /dev/null +++ b/asymetric/rsa_test.go @@ -0,0 +1,104 @@ +package asymetric + +import ( + _ "crypto/sha512" + + "crypto" + "crypto/rsa" + "testing" + + "github.com/didiercrunch/filou/helper" +) + +var random = helper.NewMockRandomReader() + +func generateKeyPair() (*rsa.PrivateKey, *rsa.PublicKey) { + if key, err := rsa.GenerateKey(random, 1024); err != nil { + panic(err) + } else { + return key, &key.PublicKey + } +} + +func TestEncrypt(t *testing.T) { + bobPrivKey, bobPubKey := generateKeyPair() + + r := &RsaOaepPss{nil, bobPubKey, crypto.SHA384, random} + + ciphertext, err := r.Encrypt([]byte("this is a secret message")) + if err != nil { + t.Error(err) + return + } + orgTest, err := rsa.DecryptOAEP(crypto.SHA384.New(), random, bobPrivKey, ciphertext, []byte{}) + if err != nil { + t.Error(err) + return + } + if string(orgTest) != "this is a secret message" { + t.Error("the decryption of the message does not fit the original message") + } +} + +func TestEncryptDecrypt(t *testing.T) { + bobPrivKey, bobPubKey := generateKeyPair() + + message := "this is a secret message" + + rAlice := &RsaOaepPss{nil, bobPubKey, crypto.SHA384, random} + rBob := &RsaOaepPss{bobPrivKey, nil, crypto.SHA384, random} + + if ciphertext, err := rAlice.Encrypt([]byte(message)); err != nil { + t.Error(err) + return + } else if decryptedText, err := rBob.Decrypt(ciphertext); err != nil { + t.Error(err) + } else if string(decryptedText) != message { + t.Error("the decryption of the message does not fit the original message") + } +} + +func TestSign(t *testing.T) { + alicePrivKey, alicePubKey := generateKeyPair() + h := crypto.SHA384.New() + h.Write([]byte("this is a secret message")) + hashedMessage := h.Sum(nil) + + rAlice := &RsaOaepPss{alicePrivKey, nil, crypto.SHA384, random} + signature, err := rAlice.Sign(hashedMessage) + if err != nil { + t.Error(err) + return + } + if err := rsa.VerifyPSS(alicePubKey, crypto.SHA384, hashedMessage, signature, nil); err != nil { + t.Error(err) + } +} + +func TestSignAndVerify(t *testing.T) { + alicePrivKey, alicePubKey := generateKeyPair() + h := crypto.SHA384.New() + h.Write([]byte("this is a secret message")) + hashedMessage := h.Sum(nil) + + rAlice := &RsaOaepPss{alicePrivKey, nil, crypto.SHA384, random} + rBob := &RsaOaepPss{nil, alicePubKey, crypto.SHA384, random} + if signature, err := rAlice.Sign(hashedMessage); err != nil { + t.Error(err) + } else if err := rBob.VerifySignature(hashedMessage, signature); err != nil { + t.Error(err) + } +} + +func TestAvailableHashes(t *testing.T) { + hashes := map[crypto.Hash]bool{crypto.SHA224: true, crypto.SHA256: true, crypto.SHA384: true, crypto.SHA512: true} + var i crypto.Hash + for i = 0; i < 22; i++ { + r := &RsaOaepPss{Hash: i} + if _, err := r.getHash(); err != nil && hashes[i] { + t.Error("should support", i, "hash type but does not") + } else if err == nil && !hashes[i] { + t.Error("should not support", i, "hash type but does") + } + } +} diff --git a/contract/private_contract.go b/contract/private_contract.go new file mode 100644 index 0000000..6cfcb25 --- /dev/null +++ b/contract/private_contract.go @@ -0,0 +1,10 @@ +package contract + +import ( + "github.com/didiercrunch/filou/keys" +) + +type PrivateContract struct { + PublicContract + RSAPrivateKey []*keys.RSAPrivateKey `yaml:"rsa_private_key,omitempty" json:"rsa_private_key,omitempty"` +} diff --git a/contract/public_contract.go b/contract/public_contract.go new file mode 100644 index 0000000..1ada899 --- /dev/null +++ b/contract/public_contract.go @@ -0,0 +1,11 @@ +package contract + +import ( + "github.com/didiercrunch/filou/keys" +) + +type PublicContract struct { + AcceptedHashes []string `yaml:"accepted_hashes" json:"accepted_hashes"` + AcceptedBlockCypher []string `yaml:"accepted_block_cypher" json:"accepted_block_cypher"` + RSAPublicKey []*keys.RSAPublicKey `yaml:"rsa_public_key, omitempty" json:"rsa_public_key,omitempty"` +} diff --git a/contract/public_contract_test.go b/contract/public_contract_test.go new file mode 100644 index 0000000..495ea08 --- /dev/null +++ b/contract/public_contract_test.go @@ -0,0 +1,63 @@ +package contract + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "testing" + + "github.com/didiercrunch/filou/helper" + "github.com/didiercrunch/filou/keys" +) + +func assertJSONMarshalling(c *PublicContract) error { + ret := new(PublicContract) + if data, err := json.Marshal(c); err != nil { + return err + } else if err = json.Unmarshal(data, ret); err != nil { + return err + } else if !reflect.DeepEqual(ret, c) { + fmt.Println(string(data)) + return errors.New("bad serisalization") + } + return nil +} + +func expectJson(c *PublicContract, jsonData string) error { + ret := new(PublicContract) + if err := json.Unmarshal([]byte(jsonData), ret); err != nil { + return err + } else if !reflect.DeepEqual(ret.RSAPublicKey, c.RSAPublicKey) { + return errors.New("bad serisalization") + } + return nil +} + +func TestSimpleJsonification(t *testing.T) { + c := &PublicContract{ + AcceptedHashes: []string{"sha246"}, + AcceptedBlockCypher: []string{"aes"}, + RSAPublicKey: []*keys.RSAPublicKey{&keys.RSAPublicKey{helper.B(12), 3}}, + } + if err := assertJSONMarshalling(c); err != nil { + t.Error(err) + } +} + +func TestEmptyRSAPublicKey(t *testing.T) { + c := &PublicContract{ + AcceptedHashes: []string{"sha246"}, + AcceptedBlockCypher: []string{"aes"}, + RSAPublicKey: []*keys.RSAPublicKey{&keys.RSAPublicKey{helper.B(12), 3}}, + } + jsonData := `{ + "accepted_hashes": ["sha246"], + "accepted_block_cypher": ["aes"], + "rsa_public_key": [{"n": "c", "e": 3}] + }` + if err := expectJson(c, jsonData); err != nil { + t.Error(err) + } + +} diff --git a/doc/fileformat.md b/doc/fileformat.md index 8cf5f6b..e4ea000 100644 --- a/doc/fileformat.md +++ b/doc/fileformat.md @@ -1,4 +1,46 @@ +### public contract + +A public contract is the information a user publish to allow anybody to send +him encrypted files. Of course the public contract contains the user +public key but it contains much more. + +The contract itself does not need to be sign. The signature is level of +complexity above the public contract + + +~~~ +--- +# example of a contract + +name: john dear + +organizarion: JohnDeer corporation + +email: johndear@johndeer.co + +url: "the url where to fiofbnd an exact copy of the contract" + +root_url: "the url where to find the most up to date version of the contract" + +accepted_hashs: + - sha512 + +accepted_block_cypher: + - aes + +accepted_modes: + - ofb + +signatures_required: yes + +rsa_public_keys: + - + n: "hexadecimal n" + e: 3424 +~~~ + + ### public key Note that any private key are a public key. diff --git a/envelop/envelop.go b/envelop/envelop.go index f054001..55b0619 100644 --- a/envelop/envelop.go +++ b/envelop/envelop.go @@ -105,7 +105,12 @@ func (this *Enveloper) WriteEncryptedKey(z *zip.Writer) error { func (this *Enveloper) WriteToWriter(w io.Writer) error { z := zip.NewWriter(w) defer z.Close() - if err := this.WritePayload(z); err != nil { + errc := make(chan error) + go func() { + errc <- this.WritePayload(z) + }() + + if err := <-errc; err != nil { return err } if err := this.CreateEnvelopCompletely(); err != nil { diff --git a/integration/contract_test.go b/integration/contract_test.go new file mode 100644 index 0000000..bd6a8af --- /dev/null +++ b/integration/contract_test.go @@ -0,0 +1,9 @@ +package integration + +import ( + "testing" +) + +func TestEnvelop(t *testing.T) { + +} diff --git a/integration/enveloppayload_test.go b/integration/enveloppayload_test.go index 2bc206e..11dcb86 100644 --- a/integration/enveloppayload_test.go +++ b/integration/enveloppayload_test.go @@ -3,8 +3,11 @@ package integration import ( "archive/zip" "bytes" + "crypto" + "crypto/rsa" "testing" + "github.com/didiercrunch/filou/asymetric" "github.com/didiercrunch/filou/envelop" "github.com/didiercrunch/filou/helper" "github.com/didiercrunch/filou/payload" @@ -23,6 +26,22 @@ func (this *mockEncryptor) Encrypt(data []byte) ([]byte, error) { type mockSigner struct{} +var random = helper.NewMockRandomReader() + +func generateRSAKeyPair() (*rsa.PrivateKey, *rsa.PublicKey) { + if key, err := rsa.GenerateKey(random, 2048); err != nil { + panic(err) + } else { + return key, &key.PublicKey + } +} + +func getEncryterAndSigner() *asymetric.RsaOaepPss { + alicePrivateKey, _ := generateRSAKeyPair() + _, bobPubKey := generateRSAKeyPair() + return &asymetric.RsaOaepPss{alicePrivateKey, bobPubKey, crypto.SHA512, random} +} + func (this *mockSigner) Sign(data []byte) ([]byte, error) { // a slice with the sum of the data plus a "secret" value var sum byte = 36 @@ -33,11 +52,13 @@ func (this *mockSigner) Sign(data []byte) ([]byte, error) { } func TestPayloadCanBeUseInEnvelop(t *testing.T) { + encryterAndSigner := getEncryterAndSigner() payload_ := payload.GetDefaultPayload(bytes.NewBufferString("some data to encrypt")) - envelop := envelop.NewEnveloper(&mockEncryptor{}, payload_, &mockSigner{}) + envelop := envelop.NewEnveloper(encryterAndSigner, payload_, encryterAndSigner) w := helper.NewMockIoWriter() if err := envelop.WriteToWriter(w); err != nil { t.Error(err) + return } r, err := zip.NewReader(w.ReaderAt(), int64(w.Length())) if err != nil { diff --git a/keys/encoding.go b/keys/encoding.go index 427aea5..55115e2 100644 --- a/keys/encoding.go +++ b/keys/encoding.go @@ -10,11 +10,11 @@ import ( // All the method to encode/decode keys to json, yaml... type encodingRSAKey struct { - N string - P string - Q string - D string - E int + N string `json:"n,omitempty"` + P string `json:"p,omitempty"` + Q string `json:"q,omitempty"` + D string `json:"d,omitempty"` + E int `json:"e,omitempty"` } func (this *encodingRSAKey) CreateFromRSAPrivateKey(key *RSAPrivateKey) *encodingRSAKey { diff --git a/keys/encoding_test.go b/keys/encoding_test.go index 4e587a9..c039113 100644 --- a/keys/encoding_test.go +++ b/keys/encoding_test.go @@ -41,3 +41,15 @@ func TestRSAPublicKeyJsonification(t *testing.T) { t.Fail() } } + +func TestRSAPublicKeyJsonificationFromString(t *testing.T) { + data := []byte(`{"N":"3ff","E":89}`) + key := new(RSAPublicKey) + if err := json.Unmarshal(data, key); err != nil { + t.Error(err) + return + } + if !reflect.DeepEqual(key, &RSAPublicKey{B(1023), 89}) { + t.Fail() + } +} diff --git a/payload/payload.go b/payload/payload.go index f54e4ce..7b64931 100644 --- a/payload/payload.go +++ b/payload/payload.go @@ -137,7 +137,6 @@ func (this *Payload) computeHash(reader io.Reader) (io.Reader, error) { }(readerHash) return read, nil - } func (this *Payload) encrypt(reader io.Reader, stream cipher.Stream) (io.Reader, error) { @@ -148,5 +147,4 @@ func (this *Payload) encrypt(reader io.Reader, stream cipher.Stream) (io.Reader, cipherWriter.Close() }(stream) return this.computeHash(ret) - }