// ExportKeys exports this sync's keys to the given path, protecting them with // the given password. func (s *SyncInfo) ExportKeys(outPath, pw string) error { salt, err := gocrypt.SecureBytes(aes.KeyLength) if err != nil { return &ErrSync{"Unable to get salt for export", err} } exportKeys := generatePbkdf2KeyCombo(pw, salt) origBuf := memstream.New() origBuf.Write(s.Keys().CryptoKey) origBuf.Write(s.Keys().AuthKey) origBuf.Rewind() encryptedBuf := memstream.New() encryptedBuf.Write(salt) _, _, err = aes.Encrypt(origBuf, encryptedBuf, exportKeys) if err != nil { return &ErrSync{"Unable to encrypt keys", err} } encoded := gocrypt.BytesToB64(encryptedBuf.Bytes()) err = ioutil.WriteFile(outPath, []byte(encoded), 0770) if err != nil { return &ErrSync{"Unable to open key file", err} } return nil }
// ImportKeys imports the keys from the given file, replacing the current keys for this sync func (s *SyncInfo) ImportKeys(inPath, pw string) error { encoded, err := ioutil.ReadFile(inPath) if err != nil { return &ErrSync{"Unable to open key file", err} } encrypted, err := gocrypt.BytesFromB64(string(encoded)) if err != nil { return &ErrSync{"Unable to decode key file", err} } salt := encrypted[:aes.KeyLength] exportKeys := generatePbkdf2KeyCombo(pw, salt) decrypted := memstream.New() _, cnt, err := aes.Decrypt(bytes.NewBuffer(encrypted[aes.KeyLength:]), decrypted, exportKeys) if err != nil { return &ErrSync{"Unable to decrypt keys", err} } if cnt != int64(aes.KeyLength*2) { return &ErrSync{"Keys read, but do not appear to be the correct format", nil} } decrypted.Rewind() cryptoKey := gocrypt.Key(make([]byte, aes.KeyLength)) var read int if read, err = decrypted.Read(cryptoKey); err != nil && err != io.EOF { return &ErrSync{"Failed to read crypto key", err} } cryptoKey = cryptoKey[:read] authKey := gocrypt.Key(make([]byte, aes.KeyLength)) if read, err = decrypted.Read(authKey); err != nil && err != io.EOF { return &ErrSync{"Failed to read auth key", err} } authKey = authKey[:read] if len(cryptoKey) != aes.KeyLength || len(authKey) != aes.KeyLength { return &ErrSync{"Keys read, but they do not appear to be long enough", nil} } s.Set(cryptoKeyKey, cryptoKey.String()) s.Set(authKeyKey, authKey.String()) return nil }
// OpenProtectedFile reads the header of the given protected file and returns the data // needed to work further with that file. func OpenProtectedFile(sync *SyncInfo, protFilePath string) (*Header, error) { f, err := os.Open(filepath.Join(sync.RemoteBase(), protFilePath)) if err != nil { return nil, err } defer f.Close() // Decrypt header headBytes := memstream.New() keys := sync.Keys() encHeadLen, _, err := aes.Decrypt(f, headBytes, &keys) if err != nil { return nil, err } header := new(Header) header.sync = sync header.headerLen = int(encHeadLen) header.remotePath = protFilePath header.contentKeys = new(gocrypt.KeyCombo) // Interpret headBytes.Rewind() buf := bufio.NewReader(headBytes) version, err := buf.ReadByte() if err != nil { return nil, &ErrProtectedFile{"Unable to read header version", err} } switch version { case 1: // Format: // 1 - version (currently at version 1) // 16 - crypto key for contents // 16 - auth key // 32 - hash of DECRYPTED contents // 8 - length of Path // <Variable> - local relative path header.contentKeys.CryptoKey, err = readKey(buf) if err != nil { return nil, &ErrProtectedFile{"Unable to read crypto key", err} } header.contentKeys.AuthKey, err = readKey(buf) if err != nil { return nil, &ErrProtectedFile{"Unable to read auth key", err} } header.contentHash, err = readFixedSize(buf, hash.Sha256Size) if err != nil { return nil, &ErrProtectedFile{"Unable to read content hash", err} } lenBytes, err := readFixedSize(buf, 8) pathLen := gocrypt.BytesToUint64(lenBytes) if err != nil { return nil, &ErrProtectedFile{"Unable to read path length", err} } pathBytes, err := readFixedSize(buf, int(pathLen)) header.localPath = string(pathBytes) if err != nil { return nil, &ErrProtectedFile{"Unable to read path", err} } default: return nil, &ErrProtectedFile{fmt.Sprintf("Version %v of header is not recognized", version), nil} } return header, nil }