From b680beb36c0fed42cfbd254717d76347253c9279 Mon Sep 17 00:00:00 2001 From: Martin Frausing Date: Fri, 26 Apr 2024 21:32:41 +0200 Subject: [PATCH] Set public key from private key in DNSKEY instead of copying it from DNSKEY to private key Previously when loading a PrivateKey into a DNSKEY we would return the PrivateKey with the PublicKey set from the DNSKEY struct. Now that behaviour is flipped and the PublicKey is taken from the PrivateKey and set in the DNSKEY. It will now fail if the algorithm or PublicKey in the DNSKEY doesn't match what is loaded in the input file --- dnssec_keyscan.go | 44 ++++++++++++++++++++++++++++++-------------- msg.go | 44 +++++++++++++++++++++++--------------------- parse_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 35 deletions(-) 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