diff --git a/cmd/tle/commands/commands.go b/cmd/tle/commands/commands.go index eac1683..63d1e1e 100644 --- a/cmd/tle/commands/commands.go +++ b/cmd/tle/commands/commands.go @@ -8,7 +8,6 @@ import ( "github.com/kelseyhightower/envconfig" "log" "os" - "time" ) // Default settings. @@ -24,7 +23,7 @@ const usage = `tlock v1.0.0 -- github.com/drand/tlock Usage: tle [--encrypt] (-r round)... [--armor] [-o OUTPUT] [INPUT] If input is a string (not a file) - tle [--encrypt] (-r round)... [--armor] [-o OUTPUT] [--raw_input INPUT] + tle [--encrypt] (-r round)... [--armor] [-o OUTPUT] [--input INPUT] tle --decrypt [-o OUTPUT] [INPUT] @@ -180,6 +179,9 @@ func validateFlags(f *Flags) error { if f.Duration == "" && f.Round == 0 && f.Time == "" { return fmt.Errorf("one of -D/--duration, -r/--round or -T/--time must be specified") } + if f.Duration != "" && f.Round != 0 { + return fmt.Errorf("-D/--duration can't be used with -r/--round") + } if f.Duration != "" && f.Time != "" { return fmt.Errorf("-D/--duration can't be used with -T/--time") } @@ -187,15 +189,11 @@ func validateFlags(f *Flags) error { return fmt.Errorf("-T/--time can't be used with -r/--round") } if f.Time != "" { - t, err := time.Parse(time.RFC3339, f.Time) + duration, err := timestampToDuration(f.Time) if err != nil { - return fmt.Errorf("time format must be RFC3339 (\"2006-01-02T15:04:05Z07:00\")") - } - duration := time.Until(t) - if duration <= 0 { - return fmt.Errorf("must specify a future time") + return err } - f.Duration = fmt.Sprintf("%ds", int(duration.Seconds())) + f.Duration = duration } } diff --git a/cmd/tle/commands/encrypt.go b/cmd/tle/commands/encrypt.go index d19ceb4..04f06ac 100644 --- a/cmd/tle/commands/encrypt.go +++ b/cmd/tle/commands/encrypt.go @@ -120,3 +120,15 @@ func durationFrom(start time.Time, value int, duration rune) time.Duration { } return 0 } + +func timestampToDuration(timestamp string) (string, error) { + t, err := time.Parse(time.RFC3339, timestamp) + if err != nil { + return "", fmt.Errorf("time format must be RFC3339 (\"2006-01-02T15:04:05Z07:00\")") + } + duration := time.Until(t) + if duration <= 0 { + return "", fmt.Errorf("must specify a future time") + } + return fmt.Sprintf("%ds", int(duration.Seconds())), nil +} diff --git a/cmd/tle/commands/flags_test.go b/cmd/tle/commands/flags_test.go index 01926ae..e31c36e 100644 --- a/cmd/tle/commands/flags_test.go +++ b/cmd/tle/commands/flags_test.go @@ -46,6 +46,78 @@ func Test(t *testing.T) { }, shouldError: true, }, + { + name: "parsing encrypt with duration, round as well as timestamp fails", + flags: []KV{ + { + key: "TLE_ENCRYPT", + value: "true", + }, + { + key: "TLE_DURATION", + value: "1d", + }, + { + key: "TLE_ROUND", + value: "1", + }, + { + key: "TLE_TIME", + value: "2999-04-23T08:15:05Z", + }, + }, + shouldError: true, + }, + { + name: "parsing encrypt with both round and timestamp fails", + flags: []KV{ + { + key: "TLE_ENCRYPT", + value: "true", + }, + { + key: "TLE_ROUND", + value: "1", + }, + { + key: "TLE_TIME", + value: "2999-04-23T08:15:05Z", + }, + }, + shouldError: true, + }, + { + name: "parsing encrypt with both duration and timestamp fails", + flags: []KV{ + { + key: "TLE_ENCRYPT", + value: "true", + }, + { + key: "TLE_DURATION", + value: "1d", + }, + { + key: "TLE_TIME", + value: "2999-04-23T08:15:05Z", + }, + }, + shouldError: true, + }, + { + name: "parsing encrypt with timestamp passes", + flags: []KV{ + { + key: "TLE_ENCRYPT", + value: "true", + }, + { + key: "TLE_TIME", + value: "2999-04-23T08:15:05Z", + }, + }, + shouldError: false, + }, { name: "parsing encrypt with round passes", flags: []KV{ diff --git a/tlock_test.go b/tlock_test.go index 6734bbc..d168114 100644 --- a/tlock_test.go +++ b/tlock_test.go @@ -6,6 +6,7 @@ import ( "github.com/drand/drand/crypto" bls "github.com/drand/kyber-bls12381" "github.com/stretchr/testify/require" + "math/rand" "os" "strings" "testing" @@ -92,6 +93,53 @@ func TestEarlyDecryptionWithRound(t *testing.T) { require.ErrorIs(t, err, tlock.ErrTooEarly) } +func TestEncryptionWithTimestamp(t *testing.T) { + if testing.Short() { + t.Skip("skipping live testing in short mode") + } + + network, err := http.NewNetwork(testnetHost, testnetChainHash) + require.NoError(t, err) + + // ========================================================================= + // Encrypt + + // Read the plaintext data to be encrypted. + in, err := os.Open("test_artifacts/data.txt") + require.NoError(t, err) + defer in.Close() + + // Write the encoded information to this buffer. + var cipherData bytes.Buffer + + // Timestamp to duration + rand.Seed(time.Now().UnixNano()) + timestamp := time.Now().Add(4 * time.Second).Format(time.RFC3339) + tstamp, err := time.Parse(time.RFC3339, timestamp) + require.NoError(t, err) + duration := time.Until(tstamp) + + // Encryption with duration + roundNumber := network.RoundNumber(time.Now().Add(duration)) + err = tlock.New(network).Encrypt(&cipherData, in, roundNumber) + require.NoError(t, err) + + // ========================================================================= + // Decrypt + + time.Sleep(5 * time.Second) + + // Write the decoded information to this buffer. + var plainData bytes.Buffer + + err = tlock.New(network).Decrypt(&plainData, &cipherData) + require.NoError(t, err) + + if !bytes.Equal(plainData.Bytes(), dataFile) { + t.Fatalf("decrypted file is invalid; expected %d; got %d", len(dataFile), len(plainData.Bytes())) + } +} + func TestEncryptionWithDuration(t *testing.T) { if testing.Short() { t.Skip("skipping live testing in short mode")