diff --git a/client/main.go b/client/main.go index 9d9018d..3790eb2 100644 --- a/client/main.go +++ b/client/main.go @@ -16,36 +16,50 @@ import ( "time" ) -func validKey() (foundPublicKey ed25519.PublicKey, foundPrivateKey ed25519.PrivateKey) { +func validKey() (ed25519.PublicKey, ed25519.PrivateKey) { + // A conforming key's final seven hex characters must be 83e followed by + // four characters that, interpreted as MMYY, express a valid month and + // year in the range 01/00 .. 12/99. Formally, the key must match this + // + // regex: /83e(0[1-9]|1[0-2])(\d\d)$/ + // + // Because we have an odd-length hex string, we don't encode the '8' here + // and instead check it specifically in the hot loop... I'm open to ideas + // about how to do this better. I'd like to keep everything in the hot loop + // using the `bytes.compare` function which is assembly on most platforms, + // but we don't have a full byte for the `8` + keyEnd := fmt.Sprintf("3e%s", time.Now().AddDate(2, 0, 0).Format("0106")) + target, err := hex.DecodeString(keyEnd) + if err != nil { + panic(err) + } - expiryYear := time.Now().Year() + 1 - keyEnd := fmt.Sprintf("ed%d", expiryYear) nRoutines := runtime.NumCPU() - 1 var waitGroup sync.WaitGroup var once sync.Once - fmt.Println(" - looking for a key that ends in", keyEnd) - fmt.Println(" - using", nRoutines, "cores") + fmt.Printf(" - looking for a key that ends in %s using %d routines\n", + keyEnd, nRoutines) + + var publicKey ed25519.PublicKey + var privateKey ed25519.PrivateKey waitGroup.Add(nRoutines) for i := 0; i < nRoutines; i++ { go func(num int) { - for foundPublicKey == nil { + for publicKey == nil { pub, priv, err := ed25519.GenerateKey(nil) if err != nil { panic(err) } - target, err := hex.DecodeString(keyEnd) - if err != nil { - panic(err) - } - - if bytes.Compare(pub[29:32], target) == 0 { + // Here's where we check for the `8`; we do it after the + // bytes.Compare to keep the hot loop fast + if bytes.Compare(pub[29:32], target) == 0 && pub[28]&0x0F == 0x08 { once.Do(func() { - fmt.Printf("%s\n", fmt.Sprintf("%x", pub)) - foundPublicKey = pub - foundPrivateKey = priv + fmt.Printf("found %x\n", pub) + publicKey = pub + privateKey = priv }) } } @@ -55,7 +69,7 @@ func validKey() (foundPublicKey ed25519.PublicKey, foundPrivateKey ed25519.Priva waitGroup.Wait() - return + return publicKey, privateKey } func fileExists(name string) bool { diff --git a/server/main.go b/server/main.go index 5e6451c..3a84f25 100644 --- a/server/main.go +++ b/server/main.go @@ -288,19 +288,44 @@ func (s *Spring83Server) publishBoard(w http.ResponseWriter, r *http.Request) { } } - // If the current four-digit year is YYYY, and the - // previous four-digit year is YYYX, the server must - // only accept PUTs for keys that end with the four - // digits YYYY or YYYX, preceded in turn by the two hex - // digits "ed". This is the years-of-use requirement. + // A conforming key's final seven hex characters must be "83e" followed by + // four characters that, interpreted as MMYY, express a valid month and + // year in the range 01/00 .. 12/99. Formally, the key must match this + // regex: + // /83e(0[1-9]|1[0-2])(\d\d)$/ // - // The server must reject other keys with 400 Bad - // Request. + // If the key does not match that regex, the server must reject the + // request, returning 403 Forbidden. + // + // The key is only valid in the two years preceding its encoded expiration + // date, and expires at the end of the last day of the month specified. For + // example, the key last4 := string(keyStr[60:64]) - if keyStr[58:60] != "ed" || - (last4 != time.Now().Format("2006") && - last4 != time.Now().AddDate(1, 0, 0).Format("2006")) { - http.Error(w, "Signature must end with edYYYY", http.StatusBadRequest) + t, err := time.Parse("0106", last4) + if err != nil { + log.Printf("Failed parsing last4 %s", last4) + http.Error(w, "Key must end with 83eMMYY", http.StatusBadRequest) + return + } + + // This isn't quite the correct key expiry date; techncially the key + // expires on the last day of the month of its issuance; here we're just + // giving it an extra month. TODO be more accurate + twoYearsInHours := (365 * 2 * 24.0) + 31*24.0 + timeDiff := t.Sub(time.Now()).Hours() + if keyStr[57:60] != "83e" { + log.Printf("Expected 83e %s", string(keyStr[57:60])) + http.Error(w, "Key must end with 83eMMYY", http.StatusBadRequest) + return + } + if timeDiff > twoYearsInHours { + log.Printf("Too far in future %f", timeDiff) + http.Error(w, "Key is not yet valid", http.StatusBadRequest) + return + } + if timeDiff < 0 { + log.Printf("Key expired %f", timeDiff) + http.Error(w, "Key is expired", http.StatusBadRequest) return } @@ -312,6 +337,8 @@ func (s *Spring83Server) publishBoard(w http.ResponseWriter, r *http.Request) { return } + // TODO: here we should find the