diff --git a/checker/checker.go b/checker/checker.go index 5d510aa..a094070 100644 --- a/checker/checker.go +++ b/checker/checker.go @@ -19,6 +19,7 @@ import ( const ( DEFAULT_RESOLVER = "2a01:e0a:2b:2250::1" + year68 = 1 << 31 // For RFC1982 (Serial Arithmetic) calculations in 32 bits. Taken from miekg/dns ) var verbose = false @@ -88,9 +89,11 @@ func check_dns(domain, ip string) (aaaa net.IP, err error) { if r == nil { err = errors.New("response is nil") + return } if r.Rcode != dns.RcodeSuccess { err = errors.New("failed to get a valid answer") + return } for _, answer := range r.Answer { @@ -102,6 +105,168 @@ func check_dns(domain, ip string) (aaaa net.IP, err error) { return } +func check_dnssec(domain, ip string) (err error) { + client := dns.Client{Net: "tcp", Timeout: time.Second * 10} + + // Get DNSKEY + m := new(dns.Msg) + m.SetEdns0(4096, true) + m.SetQuestion(domain, dns.TypeDNSKEY) + + var r *dns.Msg + r, _, err = client.Exchange(m, fmt.Sprintf("[%s]:53", ip)) + if err != nil { + return + } + + if r == nil { + return errors.New("response is nil") + } + if r.Rcode != dns.RcodeSuccess { + return errors.New("failed to get a valid answer when getting DNSKEY") + } + + var rrs []dns.RR + var dnskeys []*dns.DNSKEY + var dnskeysig *dns.RRSIG + for _, answer := range r.Answer { + if t, ok := answer.(*dns.DNSKEY); ok { + dnskeys = append(dnskeys, t) + rrs = append(rrs, dns.RR(t)) + } else if t, ok := answer.(*dns.RRSIG); ok { + dnskeysig = t + } + } + + if dnskeysig == nil { + return fmt.Errorf("Unable to verify DNSKEY record signature: No RRSIG found for DNSKEY record.") + } + + found := false + for _, dnskey := range dnskeys { + if err = dnskeysig.Verify(dnskey, rrs); err == nil { + found = true + break + } + } + + if !found { + return fmt.Errorf("Unable to verify DNSKEY record signature: %w", err) + } + + // Check AAAA validity + m = new(dns.Msg) + m.SetEdns0(4096, true) + m.SetQuestion(domain, dns.TypeAAAA) + + r, _, err = client.Exchange(m, fmt.Sprintf("[%s]:53", ip)) + if err != nil { + return + } + + if r == nil { + return errors.New("response is nil") + } + if r.Rcode != dns.RcodeSuccess { + return errors.New("failed to get a valid answer when getting AAAA records") + } + + rrs = []dns.RR{} + var aaaas []*dns.AAAA + var aaaasig *dns.RRSIG + for _, answer := range r.Answer { + if t, ok := answer.(*dns.AAAA); ok { + aaaas = append(aaaas, t) + rrs = append(rrs, t) + } else if t, ok := answer.(*dns.RRSIG); ok { + aaaasig = t + } + } + + if len(aaaas) == 0 { + return errors.New("Something odd happen: no AAAA record found.") + } + + if aaaasig == nil { + return fmt.Errorf("Unable to verify AAAA record signature: No RRSIG found for AAAA record.") + } + + found = false + for _, dnskey := range dnskeys { + if err = aaaasig.Verify(dnskey, rrs); err == nil { + found = true + + if !aaaasig.ValidityPeriod(time.Now()) { + utc := time.Now().UTC().Unix() + + modi := (int64(aaaasig.Inception) - utc) / year68 + ti := int64(aaaasig.Inception) + modi*year68 + + mode := (int64(aaaasig.Expiration) - utc) / year68 + te := int64(aaaasig.Expiration) + mode*year68 + + if ti > utc { + return fmt.Errorf("Unable to verify AAAA record signature: signature not yet valid") + } else if utc > te { + return fmt.Errorf("Unable to verify AAAA record signature: signature expired") + } else { + return fmt.Errorf("Unable to verify AAAA record signature: signature expired or not yet valid") + } + } + + break + } + } + + if !found { + return fmt.Errorf("Unable to verify AAAA record signature: %w", err) + } + + // Check DS + m = new(dns.Msg) + m.SetQuestion(domain, dns.TypeDS) + m.RecursionDesired = false + m.SetEdns0(4096, true) + + r, _, err = client.Exchange(m, "[2a01:e0a:2b:2250::b]:53") + if err != nil { + return + } + + if r == nil { + return errors.New("response is nil") + } + if r.Rcode != dns.RcodeSuccess { + return errors.New("failed to get a valid answer when getting DS records in parent server") + } + + found = false + for _, answer := range r.Answer { + if t, ok := answer.(*dns.DS); ok { + for _, dnskey := range dnskeys { + expectedDS := dnskey.ToDS(dns.SHA256) + if expectedDS.KeyTag == t.KeyTag && expectedDS.Algorithm == t.Algorithm && expectedDS.DigestType == t.DigestType && expectedDS.Digest == t.Digest { + found = true + err = nil + break + } else { + err = fmt.Errorf("DS record found in parent zone differs from DNSKEY %v vs. %v.", expectedDS, t) + } + } + } + } + + if !found { + if err == nil { + return fmt.Errorf("Unable to find a valid DS record in parent zone.") + } else { + return err + } + } + + return +} + // PORT 80 func check_http(ip, dn string) (err error) { @@ -170,6 +335,7 @@ func check_https(domain, ip string) (err error) { type matrix_result struct { WellKnownResult struct { + Server string `json:"m.server"` Result string `json:"result"` } DNSResult struct { @@ -187,7 +353,7 @@ type matrix_result struct { Name string `json:"name"` Version string `json:"version"` } - FederationOK bool + FederationOK bool `json:"FederationOK"` } func check_matrix(domain string) (version string, err error) { @@ -199,16 +365,16 @@ func check_matrix(domain string) (version string, err error) { defer resp.Body.Close() if resp.StatusCode >= 300 { - return "", fmt.Errorf("Sorry, the federation tester is broken. Check on https://federationtester.matrix.org/#%s", domain) + return "", fmt.Errorf("Sorry, the federation tester is broken. Check on https://federationtester.matrix.org/#%s", strings.TrimSuffix(domain, ".")) } var federationTest matrix_result - if federationTest.FederationOK { + if err = json.NewDecoder(resp.Body).Decode(&federationTest); err != nil { + log.Printf("Error in check_matrix, when decoding json: %w", err.Error()) + return "", fmt.Errorf("Sorry, the federation tester is broken. Check on https://federationtester.matrix.org/#%s", strings.TrimSuffix(domain, ".")) + } else if federationTest.FederationOK { version = federationTest.Version.Name + " " + federationTest.Version.Version return version, nil - } else if err = json.NewDecoder(resp.Body).Decode(&federationTest); err != nil { - log.Printf("Error in chech_matrix, when decoding json:", err.Error()) - return "", fmt.Errorf("Sorry, the federation tester is broken. Check on https://federationtester.matrix.org/#%s", domain) } else if federationTest.DNSResult.SRVError != nil && federationTest.WellKnownResult.Result != "" { return "", fmt.Errorf("%s OR %s", federationTest.DNSResult.SRVError.Message, federationTest.WellKnownResult.Result) } else if len(federationTest.ConnectionErrors) > 0 { @@ -222,8 +388,10 @@ func check_matrix(domain string) (version string, err error) { msg.WriteString(cerr.Message) } return "", fmt.Errorf("Connection errors: %s", msg.String()) + } else if federationTest.WellKnownResult.Server != strings.TrimSuffix(domain, ".") { + return "", fmt.Errorf("Bad homeserver_name: got %s, expected %s.", federationTest.WellKnownResult.Server, strings.TrimSuffix(domain, ".")) } else { - return "", fmt.Errorf("An unimplemented error occurs. Please report to nemunaire. But know that federation seems to be broken. Check https://federationtester.matrix.org/#%s", domain) + return "", fmt.Errorf("An unimplemented error occurs. Please report to nemunaire. But know that federation seems to be broken. Check https://federationtester.matrix.org/#%s", strings.TrimSuffix(domain, ".")) } } @@ -348,7 +516,7 @@ func studentsChecker() { } } - // Check Matrix (only if GLUE Ok and) + // Check Matrix (only if GLUE Ok and defer contraint) if glueErr == nil && istd%10 == check_matrix_for { if v, err := check_matrix(std.MyDelegatedDomain()); err == nil { if verbose { @@ -364,6 +532,23 @@ func studentsChecker() { } } } + + // Check DNSSEC (only if GLUE Ok) + if glueErr == nil { + if err := check_dnssec(std.MyDelegatedDomain(), dnsIP); err == nil { + if verbose { + log.Printf("%s just unlocked DNSSEC challenge\n", std.Login) + } + if _, err := std.UnlockChallenge(100*(tunnel_version-1)+7, ""); err != nil { + log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error()) + } + } else { + std.RegisterChallengeError(100*(tunnel_version-1)+7, err) + if verbose { + log.Printf("%s and DNSSEC: %s\n", std.Login, err) + } + } + } } } else { if errreg := std.RegisterChallengeError(100*(tunnel_version-1)+3, err); errreg != nil { diff --git a/token-validator/check.go b/token-validator/check.go index 722e54f..c9e0ddc 100644 --- a/token-validator/check.go +++ b/token-validator/check.go @@ -29,7 +29,7 @@ func init() { } func check_GLUE_respond(student *adlin.Student, domain string, ip string) (err error) { if !strings.HasPrefix(ip, adlin.StudentIP(student.Id).String()) { - return fmt.Errorf("%q is not your IP range") + return fmt.Errorf("%q is not your IP range", ip) } client := dns.Client{Net: "tcp", Timeout: time.Second * 5} diff --git a/token-validator/domain.go b/token-validator/domain.go index 596f3bd..37ecb26 100644 --- a/token-validator/domain.go +++ b/token-validator/domain.go @@ -205,17 +205,17 @@ func parseZoneRead(globalDomain string, domain string) (rr []Entry, err error) { for _, r := range response.RR { if strings.HasSuffix(r.Header().Name, domain) { - var z string + z := []string{} if v, ok := r.(*dns.A); ok { - z = fmt.Sprintf("%s", v.A) + z = append(z, fmt.Sprintf("%s", v.A)) } else if v, ok := r.(*dns.AAAA); ok { - z = fmt.Sprintf("%s", v.AAAA) + z = append(z, fmt.Sprintf("%s", v.AAAA)) } else if v, ok := r.(*dns.NS); ok { - z = fmt.Sprintf("%s", v.Ns) + z = append(z, fmt.Sprintf("%s", v.Ns)) } else if v, ok := r.(*dns.DS); ok { - z = fmt.Sprintf("%s", v.Digest) + z = append(z, fmt.Sprintf("%d", v.KeyTag), fmt.Sprintf("%d", v.Algorithm), fmt.Sprintf("%d", v.DigestType), fmt.Sprintf("%s", v.Digest)) } - rr = append(rr, Entry{r.Header().Name, r.Header().Ttl, dns.TypeToString[r.Header().Rrtype], "", []string{z}}) + rr = append(rr, Entry{r.Header().Name, r.Header().Ttl, dns.TypeToString[r.Header().Rrtype], "", z}) } } } @@ -260,7 +260,7 @@ func delAssociatedDomains(student *adlin.Student, dn string) (err error) { m1.Id = dns.Id() m1.Opcode = dns.OpcodeUpdate m1.Question = make([]dns.Question, 1) - m1.Question[0] = dns.Question{adlin.AssociatedDomainSuffix, dns.TypeSOA, dns.ClassINET} + m1.Question[0] = dns.Question{Name: adlin.AssociatedDomainSuffix, Qtype: dns.TypeSOA, Qclass: dns.ClassINET} var rrs []dns.RR for _, domain := range adomains { @@ -302,7 +302,7 @@ func AddAssociatedDomains(student *adlin.Student, aaaa net.IP) (err error) { m2.Id = dns.Id() m2.Opcode = dns.OpcodeUpdate m2.Question = make([]dns.Question, 1) - m2.Question[0] = dns.Question{adlin.AssociatedDomainSuffix, dns.TypeSOA, dns.ClassINET} + m2.Question[0] = dns.Question{Name: adlin.AssociatedDomainSuffix, Qtype: dns.TypeSOA, Qclass: dns.ClassINET} rrA := new(dns.A) rrA.Hdr = dns.RR_Header{Name: student.DefaultAssociatedDomain(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 3600} @@ -354,7 +354,7 @@ func AddNSDelegatedDomain(student *adlin.Student, dn string, ttl uint32, ns stri m1.Id = dns.Id() m1.Opcode = dns.OpcodeUpdate m1.Question = make([]dns.Question, 1) - m1.Question[0] = dns.Question{adlin.DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET} + m1.Question[0] = dns.Question{Name: adlin.DelegatedDomainSuffix, Qtype: dns.TypeSOA, Qclass: dns.ClassINET} rrNS := new(dns.NS) rrNS.Hdr = dns.RR_Header{Name: d, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: ttl} @@ -377,7 +377,7 @@ func UpdateNSDelegatedDomain(student *adlin.Student, dn string, ttl uint32, oldn m1.Id = dns.Id() m1.Opcode = dns.OpcodeUpdate m1.Question = make([]dns.Question, 1) - m1.Question[0] = dns.Question{adlin.DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET} + m1.Question[0] = dns.Question{Name: adlin.DelegatedDomainSuffix, Qtype: dns.TypeSOA, Qclass: dns.ClassINET} rrOldNS := new(dns.NS) rrOldNS.Hdr = dns.RR_Header{Name: d, Rrtype: dns.TypeNS, Class: dns.ClassINET} @@ -417,7 +417,7 @@ func AddGLUEDelegatedDomain(student *adlin.Student, dn string, ttl uint32, aaaa m1.Id = dns.Id() m1.Opcode = dns.OpcodeUpdate m1.Question = make([]dns.Question, 1) - m1.Question[0] = dns.Question{adlin.DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET} + m1.Question[0] = dns.Question{Name: adlin.DelegatedDomainSuffix, Qtype: dns.TypeSOA, Qclass: dns.ClassINET} var rr dns.RR rr, err = dns.NewRR(fmt.Sprintf("%s %d IN AAAA %s", dn, ttl, aaaa)) @@ -453,7 +453,7 @@ func UpdateGLUEDelegatedDomain(student *adlin.Student, dn string, ttl uint32, ol m1.Id = dns.Id() m1.Opcode = dns.OpcodeUpdate m1.Question = make([]dns.Question, 1) - m1.Question[0] = dns.Question{adlin.DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET} + m1.Question[0] = dns.Question{Name: adlin.DelegatedDomainSuffix, Qtype: dns.TypeSOA, Qclass: dns.ClassINET} var rr dns.RR @@ -509,7 +509,7 @@ func AddDSDelegatedDomain(student *adlin.Student, dn string, ttl uint32, rdata s m1.Id = dns.Id() m1.Opcode = dns.OpcodeUpdate m1.Question = make([]dns.Question, 1) - m1.Question[0] = dns.Question{adlin.DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET} + m1.Question[0] = dns.Question{Name: adlin.DelegatedDomainSuffix, Qtype: dns.TypeSOA, Qclass: dns.ClassINET} var ds *dns.DS ds = dnskey.ToDS(dns.SHA256) @@ -545,7 +545,7 @@ func DeleteRRDelegatedDomain(student *adlin.Student, dn string, rr string, value m1.Id = dns.Id() m1.Opcode = dns.OpcodeUpdate m1.Question = make([]dns.Question, 1) - m1.Question[0] = dns.Question{adlin.DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET} + m1.Question[0] = dns.Question{Name: adlin.DelegatedDomainSuffix, Qtype: dns.TypeSOA, Qclass: dns.ClassINET} rrr, errr := dns.NewRR(fmt.Sprintf("%s %s %s", dn, rr, strings.Join(values, " "))) if errr != nil { diff --git a/token-validator/htdocs/dashboard.html b/token-validator/htdocs/dashboard.html index 8783eb6..5999dd3 100644 --- a/token-validator/htdocs/dashboard.html +++ b/token-validator/htdocs/dashboard.html @@ -104,11 +104,11 @@