diff --git a/proj2/proj2_screen.pdf b/proj2/proj2_screen.pdf new file mode 100644 index 0000000000000000000000000000000000000000..be6a05aaaf87f4e78040a20ee28ad65da5ecf834 Binary files /dev/null and b/proj2/proj2_screen.pdf differ diff --git a/proj2/sample/proj2.go b/proj2/sample/proj2.go new file mode 100644 index 0000000000000000000000000000000000000000..056275492242a99a020b69df920e8c7764823122 --- /dev/null +++ b/proj2/sample/proj2.go @@ -0,0 +1,188 @@ +package proj2 + +// You MUST NOT change what you import. If you add ANY additional +// imports it will break the autograder, and we will be Very Upset. + +import ( + // You neet to add with + // go get github.com/nweaver/cs161-p2/userlib + "github.com/nweaver/cs161-p2/userlib" + + // Life is much easier with json: You are + // going to want to use this so you can easily + // turn complex structures into strings etc... + "encoding/json" + + // Likewise useful for debugging etc + "encoding/hex" + + // UUIDs are generated right based on the crypto RNG + // so lets make life easier and use those too... + // + // You need to add with "go get github.com/google/uuid" + "github.com/google/uuid" + + // Useful for debug messages, or string manipulation for datastore keys + "strings" + + // Want to import errors + "errors" + + // optional + // strconv + + // if you are looking for fmt, we don't give you fmt, but you can use userlib.DebugMsg + // see someUsefulThings() below +) + +// This serves two purposes: It shows you some useful primitives and +// it suppresses warnings for items not being imported +func someUsefulThings() { + // Creates a random UUID + f := uuid.New() + userlib.DebugMsg("UUID as string:%v", f.String()) + + // Example of writing over a byte of f + f[0] = 10 + userlib.DebugMsg("UUID as string:%v", f.String()) + + // takes a sequence of bytes and renders as hex + h := hex.EncodeToString([]byte("fubar")) + userlib.DebugMsg("The hex: %v", h) + + // Marshals data into a JSON representation + // Will actually work with go structures as well + d, _ := json.Marshal(f) + userlib.DebugMsg("The json data: %v", string(d)) + var g uuid.UUID + json.Unmarshal(d, &g) + userlib.DebugMsg("Unmashaled data %v", g.String()) + + // This creates an error type + userlib.DebugMsg("Creation of error %v", errors.New(strings.ToTitle("This is an error"))) + + // And a random RSA key. In this case, ignoring the error + // return value + var pk userlib.PKEEncKey + var sk userlib.PKEDecKey + pk, sk, _ = userlib.PKEKeyGen() + userlib.DebugMsg("Key is %v, %v", pk, sk) +} + +// Helper function: Takes the first 16 bytes and +// converts it into the UUID type +func bytesToUUID(data []byte) (ret uuid.UUID) { + for x := range ret { + ret[x] = data[x] + } + return +} + +// The structure definition for a file record +type File struct { + Filename string + FileUUID uuid.UUID + FileKey []byte + FileSlice int +} + +// The structure definition for a user record +type User struct { + Username string + + // You can add other fields here if you want... + // Note for JSON to marshal/unmarshal, the fields need to + // be public (start with a capital letter) +} + +// This creates a user. It will only be called once for a user +// (unless the keystore and datastore are cleared during testing purposes) + +// It should store a copy of the userdata, suitably encrypted, in the +// datastore and should store the user's public key in the keystore. + +// The datastore may corrupt or completely erase the stored +// information, but nobody outside should be able to get at the stored +// User data: the name used in the datastore should not be guessable +// without also knowing the password and username. + +// You are not allowed to use any global storage other than the +// keystore and the datastore functions in the userlib library. + +// You can assume the user has a STRONG password +func InitUser(username string, password string) (userdataptr *User, err error) { + var userdata User + userdataptr = &userdata + + return &userdata, nil +} + +// This fetches the user information from the Datastore. It should +// fail with an error if the user/password is invalid, or if the user +// data was corrupted, or if the user can't be found. +func GetUser(username string, password string) (userdataptr *User, err error) { + var userdata User + userdataptr = &userdata + + return userdataptr, nil +} + +// This stores a file in the datastore. +// +// The name of the file should NOT be revealed to the datastore! +func (userdata *User) StoreFile(filename string, data []byte) { + return +} + +// This adds on to an existing file. +// +// Append should be efficient, you shouldn't rewrite or reencrypt the +// existing file, but only whatever additional information and +// metadata you need. + +func (userdata *User) AppendFile(filename string, data []byte) (err error) { + return +} + +// This loads a file from the Datastore. +// +// It should give an error if the file is corrupted in any way. +func (userdata *User) LoadFile(filename string) (data []byte, err error) { + return +} + +// You may want to define what you actually want to pass as a +// sharingRecord to serialized/deserialize in the data store. +type sharingRecord struct { +} + +// This creates a sharing record, which is a key pointing to something +// in the datastore to share with the recipient. + +// This enables the recipient to access the encrypted file as well +// for reading/appending. + +// Note that neither the recipient NOR the datastore should gain any +// information about what the sender calls the file. Only the +// recipient can access the sharing record, and only the recipient +// should be able to know the sender. + +func (userdata *User) ShareFile(filename string, recipient string) ( + magic_string string, err error) { + + return +} + +// Note recipient's filename can be different from the sender's filename. +// The recipient should not be able to discover the sender's view on +// what the filename even is! However, the recipient must ensure that +// it is authentically from the sender. +func (userdata *User) ReceiveFile(filename string, sender string, + magic_string string) error { + return nil +} + +// Removes access for all others. +func (userdata *User) RevokeFile(filename string) (err error) { + return +} diff --git a/proj2/sample/proj2_test.go b/proj2/sample/proj2_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b6dd92ec27fd453896759563c7e845226d8b1b01 --- /dev/null +++ b/proj2/sample/proj2_test.go @@ -0,0 +1,95 @@ +package proj2 + +// You MUST NOT change what you import. If you add ANY additional +// imports it will break the autograder, and we will be Very Upset. + +import ( + "testing" + "reflect" + "github.com/nweaver/cs161-p2/userlib" + "encoding/json" + "encoding/hex" + "github.com/google/uuid" + "strings" + "errors" +) + + +func TestInit(t *testing.T) { + t.Log("Initialization test") + + // You may want to turn it off someday + userlib.SetDebugStatus(true) + someUsefulThings() + userlib.SetDebugStatus(false) + u, err := InitUser("alice", "fubar") + if err != nil { + // t.Error says the test fails + t.Error("Failed to initialize user", err) + } + // t.Log() only produces output if you run with "go test -v" + t.Log("Got user", u) + // If you want to comment the line above, + // write _ = u here to make the compiler happy + // You probably want many more tests here. +} + + +func TestStorage(t *testing.T) { + // And some more tests, because + u, err := GetUser("alice", "fubar") + if err != nil { + t.Error("Failed to reload user", err) + return + } + t.Log("Loaded user", u) + + v := []byte("This is a test") + u.StoreFile("file1", v) + + + v2, err2 := u.LoadFile("file1") + if err2 != nil { + t.Error("Failed to upload and download", err2) + } + if !reflect.DeepEqual(v, v2) { + t.Error("Downloaded file is not the same", v, v2) + } +} + +func TestShare(t *testing.T) { + u, err := GetUser("alice", "fubar") + if err != nil { + t.Error("Failed to reload user", err) + } + u2, err2 := InitUser("bob", "foobar") + if err2 != nil { + t.Error("Failed to initialize bob", err2) + } + + var v, v2 []byte + var magic_string string + + v, err = u.LoadFile("file1") + if err != nil { + t.Error("Failed to download the file from alice", err) + } + + magic_string, err = u.ShareFile("file1", "bob") + if err != nil { + t.Error("Failed to share the a file", err) + } + err = u2.ReceiveFile("file2", "alice", magic_string) + if err != nil { + t.Error("Failed to receive the share message", err) + } + + v2, err = u2.LoadFile("file2") + if err != nil { + t.Error("Failed to download the file after sharing", err) + } + if !reflect.DeepEqual(v, v2) { + t.Error("Shared file is not the same", v, v2) + } + +} diff --git a/proj2/userlib/userlib.go b/proj2/userlib/userlib.go new file mode 100644 index 0000000000000000000000000000000000000000..2187ebdf9175cff10ff15692bd88abc20212fb26 --- /dev/null +++ b/proj2/userlib/userlib.go @@ -0,0 +1,351 @@ +package userlib + +import ( + "fmt" + "strings" + "time" + "errors" + "log" + + "io" + + "crypto" + "crypto/rsa" + "crypto/hmac" + "crypto/rand" + "crypto/sha512" + "crypto/aes" + "crypto/cipher" + + "golang.org/x/crypto/argon2" + "github.com/google/uuid" +) + +type UUID = uuid.UUID + +// RSA key size +var RSAKeySize = 2048 + +// AES block size and key size +var AESBlockSize = aes.BlockSize +var AESKeySize = 16 + +// Hash and MAC size +var HashSize = sha512.Size + + +// Debug print true/false +var DebugPrint = false + +// DebugMsg. Helper function: Does formatted printing to stderr if +// the DebugPrint global is set. All our testing ignores stderr, +// so feel free to use this for any sort of testing you want. + +func SetDebugStatus(status bool){ + DebugPrint = status +} + +func DebugMsg(format string, args ...interface{}) { + if DebugPrint { + msg := fmt.Sprintf("%v ", time.Now().Format("15:04:05.00000")) + log.Printf(msg+strings.Trim(format, "\r\n ")+"\n", args...) + } +} + +// RandomBytes. Helper function: Returns a byte slice of the specificed +// size filled with random data +func RandomBytes(bytes int) (data []byte) { + data = make([]byte, bytes) + if _, err := io.ReadFull(rand.Reader, data); err != nil { + panic(err) + } + return +} + +type PublicKeyType struct { + KeyType string + PubKey rsa.PublicKey +} + +type PrivateKeyType struct { + KeyType string + PrivKey rsa.PrivateKey +} + +// Datastore and Keystore variables +var datastore map[UUID][]byte = make(map[UUID][]byte) +var keystore map[string]PublicKeyType = make(map[string]PublicKeyType) + + +/* +******************************************** +** Datastore Functions ** +** DatastoreSet, DatastoreGet, ** +** DatastoreDelete, DatastoreClear ** +******************************************** +*/ + +// Sets the value in the datastore +func DatastoreSet(key UUID, value []byte) { + foo := make([]byte, len(value)) + copy(foo, value) + + datastore[key] = foo +} + +// Returns the value if it exists +func DatastoreGet(key UUID) (value []byte, ok bool) { + value, ok = datastore[key] + if ok && value != nil { + foo := make([]byte, len(value)) + copy(foo, value) + return foo, ok + } + return +} + +// Deletes a key +func DatastoreDelete(key UUID) { + delete(datastore, key) +} + +// Use this in testing to reset the datastore to empty +func DatastoreClear() { + datastore = make(map[UUID][]byte) +} + +// Use this in testing to reset the keystore to empty +func KeystoreClear() { + keystore = make(map[string]PublicKeyType) +} + +// Sets the value in the keystore +func KeystoreSet(key string, value PublicKeyType) error { + _, present := keystore[key] + if present != false { + return errors.New("That entry in the Keystore has been taken.") + } + + keystore[key] = value + return nil +} + +// Returns the value if it exists +func KeystoreGet(key string) (value PublicKeyType, ok bool) { + value, ok = keystore[key] + return +} + +// Use this in testing to get the underlying map if you want +// to play with the datastore. +func DatastoreGetMap() map[UUID][]byte { + return datastore +} + +// Use this in testing to get the underlying map if you want +// to play with the keystore. +func KeystoreGetMap() map[string]PublicKeyType { + return keystore +} + + +/* +******************************************** +** Public Key Encryption ** +** PKEKeyGen, PKEEnc, PKEDec ** +******************************************** +*/ + +// Four structs to help you manage your different keys +// You should only have 1 of each struct +// keyType should be either: +// "PKE": encryption +// "DS": authentication and integrity + +type PKEEncKey = PublicKeyType +type PKEDecKey = PrivateKeyType + +type DSSignKey = PrivateKeyType +type DSVerifyKey = PublicKeyType + +// Generates a key pair for public-key encryption via RSA +func PKEKeyGen() (PKEEncKey, PKEDecKey, error) { + RSAPrivKey, err := rsa.GenerateKey(rand.Reader, RSAKeySize) + RSAPubKey := RSAPrivKey.PublicKey + + var PKEEncKeyRes PKEEncKey + PKEEncKeyRes.KeyType = "PKE" + PKEEncKeyRes.PubKey = RSAPubKey + + var PKEDecKeyRes PKEDecKey + PKEDecKeyRes.KeyType = "PKE" + PKEDecKeyRes.PrivKey = *RSAPrivKey + + return PKEEncKeyRes, PKEDecKeyRes, err +} + +// Encrypts a byte stream via RSA-OAEP with sha512 as hash +func PKEEnc(ek PKEEncKey, plaintext []byte) ([]byte, error) { + RSAPubKey := &ek.PubKey + + if ek.KeyType != "PKE" { + return nil, errors.New("Using a non-PKE key for PKE.") + } + + ciphertext, err := rsa.EncryptOAEP(sha512.New(), rand.Reader, RSAPubKey, plaintext, nil) + + return ciphertext, err +} + +// Decrypts a byte stream encrypted with RSA-OAEP/sha512 +func PKEDec(dk PKEDecKey, ciphertext []byte) ([]byte, error) { + RSAPrivKey := &dk.PrivKey + + if dk.KeyType != "PKE" { + return nil, errors.New("Using a non-PKE key for PKE.") + } + + decryption, err := rsa.DecryptOAEP(sha512.New(), rand.Reader, RSAPrivKey, ciphertext, nil) + + return decryption, err +} + + +/* +******************************************** +** Digital Signature ** +** DSKeyGen, DSSign, DSVerify ** +******************************************** +*/ + +// Generates a key pair for digital signature via RSA +func DSKeyGen() (DSSignKey, DSVerifyKey, error) { + RSAPrivKey, err := rsa.GenerateKey(rand.Reader, RSAKeySize) + RSAPubKey := RSAPrivKey.PublicKey + + var DSSignKeyRes DSSignKey + DSSignKeyRes.KeyType = "DS" + DSSignKeyRes.PrivKey = *RSAPrivKey + + var DSVerifyKeyRes DSVerifyKey + DSVerifyKeyRes.KeyType = "DS" + DSVerifyKeyRes.PubKey = RSAPubKey + + return DSSignKeyRes, DSVerifyKeyRes, err +} + +// Signs a byte stream via SHA256 and PKCS1v15 +func DSSign(sk DSSignKey, msg []byte) ([]byte, error) { + RSAPrivKey := &sk.PrivKey + + if sk.KeyType != "DS" { + return nil, errors.New("Using a non-DS key for DS.") + } + + hashed := sha512.Sum512(msg) + + sig, err := rsa.SignPKCS1v15(rand.Reader, RSAPrivKey, crypto.SHA512, hashed[:]) + + return sig, err +} + +// Verifies a signature signed with SHA256 and PKCS1v15 +func DSVerify(vk DSVerifyKey, msg []byte, sig []byte) error { + RSAPubKey := &vk.PubKey + + if vk.KeyType != "DS" { + return errors.New("Using a non-DS key for DS.") + } + + hashed := sha512.Sum512(msg) + + err := rsa.VerifyPKCS1v15(RSAPubKey, crypto.SHA512, hashed[:], sig) + + return err +} + + +/* +******************************************** +** HMAC ** +** HMACEval, HMACEqual ** +******************************************** +*/ + +// Evaluate the HMAC using sha512 +func HMACEval(key []byte, msg []byte) ([]byte, error) { + if len(key) != 16 && len(key) != 24 && len(key) != 32 { + panic(errors.New("The input as key for HMAC should be a 16-byte key.")) + } + + mac := hmac.New(sha512.New, key) + mac.Write(msg) + res := mac.Sum(nil) + + return res, nil +} + +// Equals comparison for hashes/MACs +// Does NOT leak timing. +func HMACEqual(a []byte, b []byte) bool { + return hmac.Equal(a, b) +} + + +/* +******************************************** +** KDF ** +** Argon2Key ** +******************************************** +*/ + +// Argon2: Automatically choses a decent combination of iterations and memory +// Use this to generate a key from a password +func Argon2Key(password []byte, salt []byte, keyLen uint32) []byte { + return argon2.IDKey(password, salt, 1, 64*1024, 4, keyLen) +} + + +/* +******************************************** +** Symmetric Encryption ** +** SymEnc, SymDec ** +******************************************** +*/ + +// Encrypts a byte slice with AES-CTR +// Length of iv should be == AESBlockSize +func SymEnc(key []byte, iv []byte, plaintext []byte) []byte { + if len(iv) != AESBlockSize { + panic("IV length not equal to AESBlockSize") + } + + block, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + + stream := cipher.NewCTR(block, iv) + ciphertext := make([]byte, AESBlockSize + len(plaintext)) + copy(ciphertext[:AESBlockSize], iv) + + stream.XORKeyStream(ciphertext[AESBlockSize:], plaintext) + + return ciphertext +} + +// Decrypts a ciphertext encrypted with AES-CTR +func SymDec(key []byte, ciphertext []byte) []byte { + block, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + + iv := ciphertext[:AESBlockSize] + plaintext := make([]byte, len(ciphertext) - AESBlockSize) + stream := cipher.NewCTR(block, iv) + + stream.XORKeyStream(plaintext, ciphertext[aes.BlockSize:]) + + return plaintext +} diff --git a/proj2/userlib/userlib_test.go b/proj2/userlib/userlib_test.go new file mode 100644 index 0000000000000000000000000000000000000000..71b9cc64c3596fda72a8cd99ffe7279b81fba67b --- /dev/null +++ b/proj2/userlib/userlib_test.go @@ -0,0 +1,238 @@ +package userlib + +import "testing" +import "bytes" +import "encoding/hex" +import "github.com/google/uuid" + +// Golang has a very powerful routine for building tests. + +// Run with "go test" to run the tests + +// And "go test -v" to run verbosely so you see all the logging and +// what tests pass/fail individually. + +// And "go test -cover" to check your code coverage in your tests + +// Default test strings +var key1 []byte = []byte("cs161teststring1") +var key2 []byte = []byte("cs161teststring2") +var key3 []byte = []byte("cs161teststring3") +var key4 []byte = []byte("cs161teststring4") +var key5 []byte = []byte("cs161teststring5") + +// Creates a UUID from the supplied bytes +// Use for testing only! +func UUIDFromBytes(t *testing.T, b []byte) (u UUID) { + u, err := uuid.FromBytes(b) + if err != nil { + t.Error("Got FromBytes error:", err) + } + + return +} + +func TestUUIDFromBytesDeterministic(t *testing.T) { + UUID1 := UUIDFromBytes(t, key1) + t.Log(UUID1) + + UUID2 := UUIDFromBytes(t, key1) + t.Log(UUID2) + + if UUID1 != UUID2 { + t.Error("UUID1 != UUID2") + t.Log("UUID1:", UUID1) + t.Log("UUID2:", UUID2) + } +} + +func TestDatastore(t *testing.T) { + UUID1 := UUIDFromBytes(t, key1) + UUID2 := UUIDFromBytes(t, key2) + UUID3 := UUIDFromBytes(t, key3) + + DatastoreSet(UUID1, []byte("foo")) + + _, valid := DatastoreGet(UUID3) + if valid { + t.Error("Datastore fetched UUID3 when it wasn't supposed to") + } + + data, valid := DatastoreGet(UUID1) + if !valid || string(data) != "foo" { + t.Error("Error with fetching 'foo' from UUID1") + } + + _, valid = DatastoreGet(UUID3) + if valid { + t.Error("Returned when nothing, oops") + } + + DatastoreSet(UUID2, []byte("bar")) + + data, valid = DatastoreGet(UUID1) + if !valid || string(data) != "foo" { + t.Error("Error with fetching 'foo' from UUID1") + } + + DatastoreDelete(UUID1) + + _, valid = DatastoreGet(UUID1) + if valid { + t.Error("DatastoreGet succeeded even after deleting UUID1") + } + + data, valid = DatastoreGet(UUID2) + if !valid || string(data) != "bar" { + t.Error("Error with fetching 'bar' from UUID2") + } + + DatastoreClear() + + _, valid = DatastoreGet(UUID2) + if valid { + t.Error("DatastoreGet succeeded even after DatastoreClear") + } + + t.Log("Datastore fetch", data) + t.Log("Datastore map", DatastoreGetMap()) + DatastoreClear() + t.Log("Datastore map", DatastoreGetMap()) +} + +func TestKeystore(t *testing.T) { + RSAPubKey, _, err1 := PKEKeyGen() + _, DSVerifyKey, err2 := DSKeyGen() + + if err1 != nil || err2 != nil { + t.Error("PKEKeyGen() failed") + } + + KeystoreSet("user1", RSAPubKey) + KeystoreSet("user2", DSVerifyKey) + + _, valid := KeystoreGet("user3") + if valid { + t.Error("Keystore fetched UUID3 when it wasn't supposed to") + } + + data, valid := KeystoreGet("user1") + if !valid { + t.Error("Key stored at UUID1 doesn't match") + } + + data, valid = KeystoreGet("user2") + if !valid { + t.Error("Key stored at UUID2 doesn't match") + } + + KeystoreClear() + + _, valid = KeystoreGet("user1") + if valid { + t.Error("KeystoreGet succeeded even after KeystoreClear") + } + + t.Log("Keystore fetch", data) + t.Log("Keystore map", KeystoreGetMap()) + KeystoreClear() + t.Log("Keystore map", KeystoreGetMap()) +} + +func TestRSA(t *testing.T) { + + // Test RSA Encrypt and Decrypt + RSAPubKey, RSAPrivKey, err := PKEKeyGen() + if err != nil { + t.Error("PKEKeyGen() failed", err) + } + + t.Log(RSAPubKey) + ciphertext, err := PKEEnc(RSAPubKey, []byte("Squeamish Ossifrage")) + if err != nil { + t.Error("PKEEnc() error", err) + } + + decryption, err := PKEDec(RSAPrivKey, ciphertext) + if err != nil || (string(decryption) != "Squeamish Ossifrage") { + t.Error("Decryption failed", err) + } + + // Test RSA Sign and Verify + DSSignKey, DSVerifyKey, err := DSKeyGen() + if err != nil { + t.Error("DSKeyGen() failed", err) + } + + sign, err := DSSign(DSSignKey, []byte("Squeamish Ossifrage")) + if err != nil { + t.Error("RSA sign failure") + } + + err = DSVerify(DSVerifyKey, []byte("Squeamish Ossifrage"), sign) + if err != nil { + t.Error("RSA verification failure") + } + + err = DSVerify(DSVerifyKey, []byte("foo"), sign) + if err == nil { + t.Error("RSA verification worked when it shouldn't") + } + + t.Log("Error return", err) +} + +func TestHMAC(t *testing.T) { + msga := []byte("foo") + msgb := []byte("bar") + + hmac1a, _ := HMACEval(key1, msga) + hmac1b, _ := HMACEval(key1, msgb) + if HMACEqual(hmac1a, hmac1b) { + t.Error("HMACs are equal for different data") + } + + hmac2a, _ := HMACEval(key2, msga) + if HMACEqual(hmac1a, hmac2a) { + t.Error("HMACs are equal for different key") + } + + hmac1a2, _ := HMACEval(key1, msga) + if !HMACEqual(hmac1a, hmac1a2) { + t.Error("HMACs are not equal when they should be") + } +} + +func TestArgon2(t *testing.T) { + val1 := Argon2Key([]byte("Password"), []byte("nosalt"), 32) + val2 := Argon2Key([]byte("Password"), []byte("nosalt"), 64) + val3 := Argon2Key([]byte("password"), []byte("nosalt"), 32) + + equal := bytes.Equal + + if equal(val1, val2) || equal(val1, val3) || equal(val2, val3) { + t.Error("Argon2 problem") + } + t.Log(hex.EncodeToString(val1)) + t.Log(hex.EncodeToString(val2)) + t.Log(hex.EncodeToString(val3)) +} + +func TestStreamCipher(t *testing.T) { + iv := RandomBytes(16) + t.Log("Random IV", iv) + + ciphertext := SymEnc(key1, iv, []byte("foo")) + decryption := SymDec(key1, ciphertext) + + t.Log("Decrypted messagege:", string(decryption)) + if string(decryption) != "foo" { + t.Error("Symmetric decryption failure") + } +} + +// Deliberate fail example +// func TestFailure(t *testing.T){ +// t.Log("This test will fail") +// t.Error("Test of failure") +//}