-
Notifications
You must be signed in to change notification settings - Fork 207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds expiry parser with ability to specify hours, days, months and years #47
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package cmd | ||
|
||
import ( | ||
"errors" | ||
"regexp" | ||
"strconv" | ||
"time" | ||
) | ||
|
||
var nowFunc = time.Now | ||
|
||
func parseExpiry(fromNow string) (time.Time, error) { | ||
re := regexp.MustCompile("\\s*(\\d+)\\s*(day|month|year|hour)s?") | ||
matches := re.FindAllStringSubmatch(fromNow, -1) | ||
addDate := map[string]int{ | ||
"day": 0, | ||
"month": 0, | ||
"year": 0, | ||
"hour": 0, | ||
} | ||
for _, r := range matches { | ||
addDate[r[2]], _ = strconv.Atoi(r[1]) | ||
} | ||
|
||
now := nowFunc().UTC() | ||
result := now. | ||
AddDate(addDate["year"], addDate["month"], addDate["day"]). | ||
Add(time.Hour * time.Duration(addDate["hour"])) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit, but I would probably reverse this to make it more readable (i.e. put time.Hour at the end of the multiplication). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure thing |
||
|
||
if now == result { | ||
return now, errors.New("Invalid expiry format") | ||
} | ||
|
||
return result, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
"time" | ||
) | ||
|
||
const dateFormat = "2006-01-02" | ||
|
||
func init() { | ||
nowFunc = func() time.Time { | ||
t, _ := time.Parse(dateFormat, "2017-01-01") | ||
return t | ||
} | ||
} | ||
|
||
func TestParseExpiryWithDays(t *testing.T) { | ||
t1, _ := parseExpiry("1 day") | ||
t2, _ := parseExpiry("1 days") | ||
expected, _ := time.Parse(dateFormat, "2017-01-02") | ||
|
||
if t1 != expected { | ||
t.Fatalf("Parsing expiry 1 day from now (singular) did not return expected value (wanted: %s, got: %s)", expected, t1) | ||
} | ||
|
||
if t2 != expected { | ||
t.Fatalf("Parsing expiry 1 day from now (plural) did not return expected value (wanted: %s, got: %s)", expected, t2) | ||
} | ||
} | ||
|
||
func TestParseExpiryWithMonths(t *testing.T) { | ||
t1, _ := parseExpiry("1 month") | ||
t2, _ := parseExpiry("1 months") | ||
expected, _ := time.Parse(dateFormat, "2017-02-01") | ||
|
||
if t1 != expected { | ||
t.Fatalf("Parsing expiry 1 month from now (singular) did not return expected value (wanted: %s, got: %s)", expected, t1) | ||
} | ||
|
||
if t2 != expected { | ||
t.Fatalf("Parsing expiry 1 month from now (plural) did not return expected value (wanted: %s, got: %s)", expected, t2) | ||
} | ||
} | ||
|
||
func TestParseExpiryWithYears(t *testing.T) { | ||
t1, _ := parseExpiry("1 year") | ||
t2, _ := parseExpiry("1 years") | ||
expected, _ := time.Parse(dateFormat, "2018-01-01") | ||
|
||
if t1 != expected { | ||
t.Fatalf("Parsing expiry 1 year from now (singular) did not return expected value (wanted: %s, got: %s)", expected, t1) | ||
} | ||
|
||
if t2 != expected { | ||
t.Fatalf("Parsing expiry 1 year from now (plural) did not return expected value (wanted: %s, got: %s)", expected, t2) | ||
} | ||
} | ||
|
||
func TestParseExpiryWithMixed(t *testing.T) { | ||
t1, _ := parseExpiry("2 days 3 months 1 year") | ||
t2, _ := parseExpiry("5 years 5 days 6 months") | ||
expectedt1, _ := time.Parse(dateFormat, "2018-04-03") | ||
expectedt2, _ := time.Parse(dateFormat, "2022-07-06") | ||
|
||
if t1 != expectedt1 { | ||
t.Fatalf("Parsing expiry for mixed format t1 did not return expected value (wanted: %s, got: %s)", expectedt1, t1) | ||
} | ||
|
||
if t2 != expectedt2 { | ||
t.Fatalf("Parsing expiry for mixed format t2 did not return expected value (wanted: %s, got: %s)", expectedt2, t2) | ||
} | ||
} | ||
|
||
func TestParseInvalidExpiry(t *testing.T) { | ||
t1, err1 := parseExpiry("53257284647843897") | ||
t2, err2 := parseExpiry("5 y") | ||
expectedt1, _ := time.Parse(dateFormat, "2017-01-01") | ||
expectedt2, _ := time.Parse(dateFormat, "2017-01-01") | ||
|
||
if t1 != expectedt1 && err1 != nil && fmt.Sprintf("%s", err1) == "Invalid expiry format" { | ||
t.Fatalf("Parsing invalid expiry t1 did not produce an error as expected") | ||
} | ||
|
||
if t2 != expectedt2 && err2 != nil && fmt.Sprintf("%s", err2) == "Invalid expiry format" { | ||
t.Fatalf("Parsing invalid expiry t2 did not produce an error as expected") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,7 +37,8 @@ func NewInitCommand() cli.Command { | |
Flags: []cli.Flag{ | ||
cli.StringFlag{"passphrase", "", "Passphrase to encrypt private-key PEM block", ""}, | ||
cli.IntFlag{"key-bits", 4096, "Bit size of RSA keypair to generate", ""}, | ||
cli.IntFlag{"years", 10, "How long until the CA certificate expires", ""}, | ||
cli.IntFlag{"years", 0, "DEPRECATED; Use --expires instead", ""}, | ||
cli.StringFlag{"expires", "10 years", "How long until the certificate expires. Example: 1 year 2 days 3 months 4 hours", ""}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could change this to 18 months as a default while we're at it. @mcpherrinm what do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe default to 1 year? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Happy to change to whatever you guys want; I was maintaining existing behaviour as far as possible. |
||
cli.StringFlag{"organization, o", "", "CA Certificate organization", ""}, | ||
cli.StringFlag{"organizational-unit, ou", "", "CA Certificate organizational unit", ""}, | ||
cli.StringFlag{"country, c", "", "CA Certificate country", ""}, | ||
|
@@ -99,7 +100,20 @@ func initAction(c *cli.Context) { | |
} | ||
} | ||
|
||
crt, err := pkix.CreateCertificateAuthority(key, c.String("organizational-unit"), c.Int("years"), c.String("organization"), c.String("country"), c.String("province"), c.String("locality"), c.String("common-name")) | ||
expires := c.String("expires") | ||
if years := c.Int("years"); years != 0 { | ||
expires = fmt.Sprintf("%s %s years", expires, years) | ||
} | ||
|
||
// Expiry parsing is a naive regex implementation | ||
// Token based parsing would provide better feedback but | ||
expiresTime, err := parseExpiry(expires) | ||
if err != nil { | ||
fmt.Fprintln(os.Stderr, "Invalid expiry format") | ||
os.Exit(1) | ||
} | ||
|
||
crt, err := pkix.CreateCertificateAuthority(key, c.String("organizational-unit"), expiresTime, c.String("organization"), c.String("country"), c.String("province"), c.String("locality"), c.String("common-name")) | ||
if err != nil { | ||
fmt.Fprintln(os.Stderr, "Create certificate error:", err) | ||
os.Exit(1) | ||
|
@@ -130,7 +144,7 @@ func initAction(c *cli.Context) { | |
} | ||
|
||
// Create an empty CRL, this is useful for Java apps which mandate a CRL. | ||
crl, err := pkix.CreateCertificateRevocationList(key, crt, c.Int("years")) | ||
crl, err := pkix.CreateCertificateRevocationList(key, crt, expiresTime) | ||
if err != nil { | ||
fmt.Fprintln(os.Stderr, "Create CRL error:", err) | ||
os.Exit(1) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not check the error here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
r[1]
should have been matched from the first group in the regex which is\d+
so we can say with fair certainty that it's going to be a contiguous string of integers.Thinking about it though Atoi could still fail with an integer overflow (on e.g. "999999999999 days"). Should also probably check for signed int overflow because Atoi would not error in that case but the result is not valid in this context.
I will add those checks.