diff --git a/dnssec_keyscan.go b/dnssec_keyscan.go index 9c9972db6e..f38cf610ae 100644 --- a/dnssec_keyscan.go +++ b/dnssec_keyscan.go @@ -5,6 +5,7 @@ import ( "crypto" "crypto/ecdsa" "crypto/ed25519" + "crypto/elliptic" "crypto/rsa" "io" "math/big" @@ -14,6 +15,7 @@ import ( // NewPrivateKey returns a PrivateKey by parsing the string s. // s should be in the same form of the BIND private key files. +// Will set the PublicKey in the DNSKEY to the PublicKey contained within the PrivateKey func (k *DNSKEY) NewPrivateKey(s string) (crypto.PrivateKey, error) { if s == "" || s[len(s)-1] != '\n' { // We need a closing newline return k.ReadPrivateKey(strings.NewReader(s+"\n"), "") @@ -23,8 +25,6 @@ func (k *DNSKEY) NewPrivateKey(s string) (crypto.PrivateKey, error) { // ReadPrivateKey reads a private key from the io.Reader q. The string file is // only used in error reporting. -// The public key must be known, because some cryptographic algorithms embed -// the public inside the privatekey. func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, error) { m, err := parseKey(q, file) if m == nil { @@ -36,37 +36,53 @@ func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, er if m["private-key-format"] != "v1.2" && m["private-key-format"] != "v1.3" { return nil, ErrPrivKey } - // TODO(mg): check if the pubkey matches the private key algoStr, _, _ := strings.Cut(m["algorithm"], " ") - algo, err := strconv.ParseUint(algoStr, 10, 8) + _algo, err := strconv.ParseUint(algoStr, 10, 8) + algo := uint8(_algo) if err != nil { return nil, ErrPrivKey } - switch uint8(algo) { + if algo != k.Algorithm { + return nil, ErrKeyAlgMismatch + } + prevPublicKey := k.PublicKey + switch algo { case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512: priv, err := readPrivateKeyRSA(m) if err != nil { return nil, err } - pub := k.publicKeyRSA() - if pub == nil { - return nil, ErrKey + k.setPublicKeyRSA(priv.PublicKey.E, priv.PublicKey.N) + if prevPublicKey != "" && prevPublicKey != k.PublicKey { + return nil, ErrPubKeyMismatch } - priv.PublicKey = *pub return priv, nil case ECDSAP256SHA256, ECDSAP384SHA384: priv, err := readPrivateKeyECDSA(m) if err != nil { return nil, err } - pub := k.publicKeyECDSA() - if pub == nil { - return nil, ErrKey + switch algo { + case ECDSAP256SHA256: + priv.PublicKey.Curve = elliptic.P256() + case ECDSAP384SHA384: + priv.PublicKey.Curve = elliptic.P384() + } + priv.PublicKey.X, priv.PublicKey.Y = priv.PublicKey.Curve.ScalarBaseMult(priv.D.Bytes()) + k.setPublicKeyECDSA(priv.PublicKey.X, priv.PublicKey.Y) + if prevPublicKey != "" && prevPublicKey != k.PublicKey { + return nil, ErrPubKeyMismatch } - priv.PublicKey = *pub return priv, nil case ED25519: - return readPrivateKeyED25519(m) + priv, err := readPrivateKeyED25519(m) + if err != nil { + return nil, err + } + if prevPublicKey != "" && prevPublicKey != k.PublicKey { + return nil, ErrPubKeyMismatch + } + return priv, nil default: return nil, ErrAlg } diff --git a/msg.go b/msg.go index 5fa7f9e833..6f4d6756b5 100644 --- a/msg.go +++ b/msg.go @@ -48,27 +48,29 @@ const ( // Errors defined in this package. var ( - ErrAlg error = &Error{err: "bad algorithm"} // ErrAlg indicates an error with the (DNSSEC) algorithm. - ErrAuth error = &Error{err: "bad authentication"} // ErrAuth indicates an error in the TSIG authentication. - ErrBuf error = &Error{err: "buffer size too small"} // ErrBuf indicates that the buffer used is too small for the message. - ErrConnEmpty error = &Error{err: "conn has no connection"} // ErrConnEmpty indicates a connection is being used before it is initialized. - ErrExtendedRcode error = &Error{err: "bad extended rcode"} // ErrExtendedRcode ... - ErrFqdn error = &Error{err: "domain must be fully qualified"} // ErrFqdn indicates that a domain name does not have a closing dot. - ErrId error = &Error{err: "id mismatch"} // ErrId indicates there is a mismatch with the message's ID. - ErrKeyAlg error = &Error{err: "bad key algorithm"} // ErrKeyAlg indicates that the algorithm in the key is not valid. - ErrKey error = &Error{err: "bad key"} - ErrKeySize error = &Error{err: "bad key size"} - ErrLongDomain error = &Error{err: fmt.Sprintf("domain name exceeded %d wire-format octets", maxDomainNameWireOctets)} - ErrNoSig error = &Error{err: "no signature found"} - ErrPrivKey error = &Error{err: "bad private key"} - ErrRcode error = &Error{err: "bad rcode"} - ErrRdata error = &Error{err: "bad rdata"} - ErrRRset error = &Error{err: "bad rrset"} - ErrSecret error = &Error{err: "no secrets defined"} - ErrShortRead error = &Error{err: "short read"} - ErrSig error = &Error{err: "bad signature"} // ErrSig indicates that a signature can not be cryptographically validated. - ErrSoa error = &Error{err: "no SOA"} // ErrSOA indicates that no SOA RR was seen when doing zone transfers. - ErrTime error = &Error{err: "bad time"} // ErrTime indicates a timing error in TSIG authentication. + ErrAlg error = &Error{err: "bad algorithm"} // ErrAlg indicates an error with the (DNSSEC) algorithm. + ErrAuth error = &Error{err: "bad authentication"} // ErrAuth indicates an error in the TSIG authentication. + ErrBuf error = &Error{err: "buffer size too small"} // ErrBuf indicates that the buffer used is too small for the message. + ErrConnEmpty error = &Error{err: "conn has no connection"} // ErrConnEmpty indicates a connection is being used before it is initialized. + ErrExtendedRcode error = &Error{err: "bad extended rcode"} // ErrExtendedRcode ... + ErrFqdn error = &Error{err: "domain must be fully qualified"} // ErrFqdn indicates that a domain name does not have a closing dot. + ErrId error = &Error{err: "id mismatch"} // ErrId indicates there is a mismatch with the message's ID. + ErrKeyAlg error = &Error{err: "bad key algorithm"} // ErrKeyAlg indicates that the algorithm in the key is not valid. + ErrKeyAlgMismatch error = &Error{err: "algorithm in file does not match key"} // ErrKeyAlgMismatch indicates that the algorithm in the key doesn't match the loaded algorithm from file + ErrPubKeyMismatch error = &Error{err: "public key in file does not match public key"} // ErrPubKeyMismatch indicates that the public key in the key doesn't match the public key from the private key loaded from file + ErrKey error = &Error{err: "bad key"} + ErrKeySize error = &Error{err: "bad key size"} + ErrLongDomain error = &Error{err: fmt.Sprintf("domain name exceeded %d wire-format octets", maxDomainNameWireOctets)} + ErrNoSig error = &Error{err: "no signature found"} + ErrPrivKey error = &Error{err: "bad private key"} + ErrRcode error = &Error{err: "bad rcode"} + ErrRdata error = &Error{err: "bad rdata"} + ErrRRset error = &Error{err: "bad rrset"} + ErrSecret error = &Error{err: "no secrets defined"} + ErrShortRead error = &Error{err: "short read"} + ErrSig error = &Error{err: "bad signature"} // ErrSig indicates that a signature can not be cryptographically validated. + ErrSoa error = &Error{err: "no SOA"} // ErrSOA indicates that no SOA RR was seen when doing zone transfers. + ErrTime error = &Error{err: "bad time"} // ErrTime indicates a timing error in TSIG authentication. ) // Id by default returns a 16-bit random number to be used as a message id. The diff --git a/parse_test.go b/parse_test.go index da94cc38e4..13bf4643f1 100644 --- a/parse_test.go +++ b/parse_test.go @@ -1285,6 +1285,44 @@ func TestNewPrivateKey(t *testing.T) { t.Errorf("[%v] Private keys differ:\n%#v\n%#v", AlgorithmToString[algo.name], privkey, newPrivKey) } } + + rsaKey := new(DNSKEY) + rsaKey.Hdr.Rrtype = TypeDNSKEY + rsaKey.Hdr.Name = "miek.nl." + rsaKey.Hdr.Class = ClassINET + rsaKey.Hdr.Ttl = 14400 + rsaKey.Flags = 256 + rsaKey.Protocol = 3 + rsaKey.Algorithm = RSASHA256 + _, err := rsaKey.Generate(512) + if err != nil { + t.Fatal(err) + } + ecdsapKey := new(DNSKEY) + ecdsapKey.Hdr.Rrtype = TypeDNSKEY + ecdsapKey.Hdr.Name = "miek.nl." + ecdsapKey.Hdr.Class = ClassINET + ecdsapKey.Hdr.Ttl = 14400 + ecdsapKey.Flags = 256 + ecdsapKey.Protocol = 3 + ecdsapKey.Algorithm = ECDSAP256SHA256 + ecdsapPrivkey, err := ecdsapKey.Generate(256) + if err != nil { + t.Fatal(err) + } + + // test that algo in input matches algo in DNSKEY + _, err = rsaKey.NewPrivateKey(ecdsapKey.PrivateKeyString(ecdsapPrivkey)) + if err != ErrKeyAlgMismatch { + t.Fatalf("unexpected err: %v", err) + } + + // test that public key in input matches public key in DNSKEY + ecdsapKey.PublicKey = rsaKey.PublicKey + _, err = ecdsapKey.NewPrivateKey(ecdsapKey.PrivateKeyString(ecdsapPrivkey)) + if err != ErrPubKeyMismatch { + t.Fatalf("unexpected err: %v", err) + } } // special input test