From 0bda1e43269af186e0b61280410e4630d67c5fcb Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Mon, 8 May 2017 17:36:16 +0300 Subject: Add support for certificate name subdomain wildcard --- bpkg/auth.cxx | 85 +++++++++++++++++++--- tests/auth/cert | 9 +++ tests/auth/self-any-cert.pem | 30 ++++++++ tests/auth/self-any-openssl.cnf | 22 ++++++ tests/auth/self-cert.pem | 30 ++++++++ tests/auth/self-openssl.cnf | 22 ++++++ tests/auth/subdomain-cert.pem | 30 ++++++++ tests/auth/subdomain-openssl.cnf | 22 ++++++ tests/rep-auth.test | 152 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 392 insertions(+), 10 deletions(-) create mode 100644 tests/auth/self-any-cert.pem create mode 100644 tests/auth/self-any-openssl.cnf create mode 100644 tests/auth/self-cert.pem create mode 100644 tests/auth/self-openssl.cnf create mode 100644 tests/auth/subdomain-cert.pem create mode 100644 tests/auth/subdomain-openssl.cnf diff --git a/bpkg/auth.cxx b/bpkg/auth.cxx index b60e998..ddbb284 100644 --- a/bpkg/auth.cxx +++ b/bpkg/auth.cxx @@ -653,18 +653,83 @@ namespace bpkg f = *conf / certs_dir / path (cert.fingerprint + ".pem"); } - const string& c (cert.name); - const string& r (rl.canonical_name ()); - const size_t cn (c.size ()); - const size_t rn (r.size ()); + // Make sure that the repository canonical name hostname matches the + // certificate hostname (that can contain a subdomain wildcard), and its + // optional /prefix/path (will call it just path down the road) part is a + // subpath of the certificate name path. + // + // Split a name into the host and path parts. + // + auto split = [] (const string& name) -> pair + { + size_t p (name.find ('/')); + return make_pair (name.substr (0, p), + p != string::npos ? path (name.substr (p)) : path ()); + }; - // Make sure the names are either equal or the certificate name is a - // prefix (at /-boundary) of the repository name. + pair c (split (cert.name)); + pair r (split (rl.canonical_name ())); + + // Match host names. + // + // The certificate hostname, that contains a subdomain wildcards, can have + // one of the following forms: + // + // *.example.com - matches any single-level subdomain of example.com + // **.example.com - matches any subdomain of example.com + // *example.com - matches example.com and any its single-level subdomain + // **example.com - matches example.com and any its subdomain // - if (!(r.compare (0, cn, c) == 0 && - (rn == cn || (rn > cn && r[cn] == '/')))) - fail << "certificate name mismatch for repository " << r << - info << "certificate name is " << c; + bool match (false); + { + string& ch (c.first); + const string& rh (r.first); + + if (ch[0] == '*') // Subdomain wildcard. + { + size_t p (1); + + bool any (ch[p] == '*'); + if (any) + ++p; + + bool self (ch[p] != '.'); + if (!self) + ++p; + + ch = ch.substr (p); // Strip wildcard prefix. + + const size_t cn (ch.size ()); + const size_t rn (rh.size ()); + + // If hostnames are equal, then the repository hostname matches the + // certificate hostname if self-matching is allowed. Otherwise, it + // matches being a subdomain of the first level, or any level if + // allowed. + // + if (rh == ch) + match = self; + else if (rn > cn && rh.compare (p = rn - cn, cn, ch) == 0 && + rh[p - 1] == '.') + match = any || rh.find ('.') == p - 1; + } + else + // If the certificate hostname doesn't contain a subdomain wildcard, + // then the repository hostname must match it exactly. + // + match = rh == ch; + } + + // Match the repository canonical name path part (must be a subpath of the + // certificate name path). + // + if (match) + match = r.second.sub (c.second); + + if (!match) + fail << "certificate name mismatch for repository " + << rl.canonical_name () << + info << "certificate name is " << cert.name; try { diff --git a/tests/auth/cert b/tests/auth/cert index 41b3b9c..5cb1237 100755 --- a/tests/auth/cert +++ b/tests/auth/cert @@ -16,6 +16,15 @@ openssl req -x509 -new -key key.pem -days 1825 -config mismatch-openssl.cnf > \ openssl req -x509 -new -key key.pem -days 1825 -config noemail-openssl.cnf > \ noemail-cert.pem +openssl req -x509 -new -key key.pem -days 1825 \ + -config subdomain-openssl.cnf > subdomain-cert.pem + +openssl req -x509 -new -key key.pem -days 1825 -config self-openssl.cnf > \ + self-cert.pem + +openssl req -x509 -new -key key.pem -days 1825 -config self-any-openssl.cnf > \ + self-any-cert.pem + # Normally, you have no reason to regenerate expired-cert.pem, as need to keep # it expired for the testing purposes. But if you do, copy expired-cert.pem # content to the certificate value of the following manifest files: diff --git a/tests/auth/self-any-cert.pem b/tests/auth/self-any-cert.pem new file mode 100644 index 0000000..88553c0 --- /dev/null +++ b/tests/auth/self-any-cert.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFMzCCAxugAwIBAgIJAL6WhYgIDFucMA0GCSqGSIb3DQEBCwUAMDUxFzAVBgNV +BAoMDkNvZGUgU3ludGhlc2lzMRowGAYDVQQDDBFuYW1lOioqYnVpbGQyLm9yZzAe +Fw0xNzA1MDgxNDA4NTlaFw0yMjA1MDcxNDA4NTlaMDUxFzAVBgNVBAoMDkNvZGUg +U3ludGhlc2lzMRowGAYDVQQDDBFuYW1lOioqYnVpbGQyLm9yZzCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBANq78SXuzFzCMoFU1RnzEeAfzE0UUYGynS3F +2lG7viH3coxjLt+BrFBudVs4XDTpjXS19hRxIohEgD71W1jhDvmUC9yCMW13PCII +jRKTTz0efEhTcMHdhOgvKZsje0IV7svoKVXcG7DfUVl51wWPQPSbUrfsQbsXg7Pz +5HaDx+Dt2i9hwdE1M0z4R2dtwQkszFyCKiX8RF9oPXirTz5ETLC3f19JUapLrY5l +5ZylzQifLhPMlHLlrT0n7KkohH7waX3KyeLa0M2IIl3zaeAsuN+ErFVecAdlJIvX +00cth2OO/Gxy09sIKlagi2q7ZDik2sMvG8dAv7gNZsXp+FOj/XXCiOI9f6D5ospJ +dK9B5UCABjmGc8W5Odv6ZLey5Ui76luI7ciITOKfAoEkbyMiNHiRxLdM7aAeizdc +wHU4bm6JlmiJk8UyyV85f33mvCSfuo7D+DQYiK650/xwRdTFBIqi38IwME62gT7a +h/AOmiPshj7FjwIU7ZWHskyr9qpExQOEKJXoLZJo1rf6MRc8AsJyz6zdfQhT1BTz +hogNfru4xjVM6fSrjRUF34msuWcz/HKo9W350Aw2y5F59kziP+m7G6uBYrqmElv/ +13Vamg2ZZ1b38KMz5Ss3SkfcDErOzz/D+0hRlOaCIeWts1G2zWcQvBnn+zGA+sTI +u0xAFOCRAgMBAAGjRjBEMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggr +BgEFBQcDAzAaBgNVHREEEzARgQ9pbmZvQGJ1aWxkMi5vcmcwDQYJKoZIhvcNAQEL +BQADggIBAMkex3gIuU2G1kDg57PG2H188pDU0lRQzkC3KTy8o0n5gwH5ZPAN6hYb +BauJj92sRYLwGP57TWPqgVwFQWYQSXQTP5mu8RbIfW6nxK88mwcHj0nne8fdO14Y +FJms52uvuq+delypw0+pnsUUgt3MkVf+9hVhJlxxpEAH9rhJ4roSdNdvuB0JnjgE +eKUX+9Vyptch4krlUrTrFm6aSBEm8NzI1OAsTmOLtrB59xkLTKej14YNUq09kyVA +JsueKlXSHtHO3CxisoFWHfczonSbIJpOUJn3DDZDZ4UPft2dD+oyW3zMrDoXczKm +DI+CTSvSqWVpwiUTHsO2IO+XI50HHZCCoMF0or3Gg0zyq9+Dj9yX7VAUeqxV2jIw +ZvCm//k/zveCmJZrhW4doKNy0AudnSRwzufcFLVI0H6ID/q/Udb5g5J1eYXrLJRo +V3pfY/HhtTZ3wYT2uTd+++NHSmoXud/w3hPHnHQ4zuw+6Qpb0QhyBSODNarMzxBX +aT1KHZcF6OW/90932nesY+4IIzYHzVrWfBnR23GaXRPhfnnYneCVB5SsUhY5kEGa +NjQDXtwFGNxiFd60nFtU7PFUVLSNx6MRy09+8XyUu4mg2smCZyDoSzKFTICaU0Gq +vQ5Nhvg8bdSTkBJyOKPD8SNyxWs3Bdk9XyZnpCKssz1KnUk9y+VA +-----END CERTIFICATE----- diff --git a/tests/auth/self-any-openssl.cnf b/tests/auth/self-any-openssl.cnf new file mode 100644 index 0000000..c0d72eb --- /dev/null +++ b/tests/auth/self-any-openssl.cnf @@ -0,0 +1,22 @@ +repository = **build2.org +company = Code Synthesis +email = info@build2.org + + +[ req ] + +distinguished_name = req_distinguished_name +x509_extensions = v3_req +prompt = no +utf8 = yes + +[ req_distinguished_name ] + +O = $company +CN = name:$repository + +[ v3_req ] + +keyUsage = critical,digitalSignature +extendedKeyUsage = critical,codeSigning +subjectAltName = email:$email diff --git a/tests/auth/self-cert.pem b/tests/auth/self-cert.pem new file mode 100644 index 0000000..1553a2a --- /dev/null +++ b/tests/auth/self-cert.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFMTCCAxmgAwIBAgIJAJlKDlkC6IwmMA0GCSqGSIb3DQEBCwUAMDQxFzAVBgNV +BAoMDkNvZGUgU3ludGhlc2lzMRkwFwYDVQQDDBBuYW1lOipidWlsZDIub3JnMB4X +DTE3MDUwODE0MDg1OVoXDTIyMDUwNzE0MDg1OVowNDEXMBUGA1UECgwOQ29kZSBT +eW50aGVzaXMxGTAXBgNVBAMMEG5hbWU6KmJ1aWxkMi5vcmcwggIiMA0GCSqGSIb3 +DQEBAQUAA4ICDwAwggIKAoICAQDau/El7sxcwjKBVNUZ8xHgH8xNFFGBsp0txdpR +u74h93KMYy7fgaxQbnVbOFw06Y10tfYUcSKIRIA+9VtY4Q75lAvcgjFtdzwiCI0S +k089HnxIU3DB3YToLymbI3tCFe7L6ClV3Buw31FZedcFj0D0m1K37EG7F4Oz8+R2 +g8fg7dovYcHRNTNM+EdnbcEJLMxcgiol/ERfaD14q08+REywt39fSVGqS62OZeWc +pc0Iny4TzJRy5a09J+ypKIR+8Gl9ysni2tDNiCJd82ngLLjfhKxVXnAHZSSL19NH +LYdjjvxsctPbCCpWoItqu2Q4pNrDLxvHQL+4DWbF6fhTo/11wojiPX+g+aLKSXSv +QeVAgAY5hnPFuTnb+mS3suVIu+pbiO3IiEzinwKBJG8jIjR4kcS3TO2gHos3XMB1 +OG5uiZZoiZPFMslfOX995rwkn7qOw/g0GIiuudP8cEXUxQSKot/CMDBOtoE+2ofw +Dpoj7IY+xY8CFO2Vh7JMq/aqRMUDhCiV6C2SaNa3+jEXPALCcs+s3X0IU9QU84aI +DX67uMY1TOn0q40VBd+JrLlnM/xyqPVt+dAMNsuRefZM4j/puxurgWK6phJb/9d1 +WpoNmWdW9/CjM+UrN0pH3AxKzs8/w/tIUZTmgiHlrbNRts1nELwZ5/sxgPrEyLtM +QBTgkQIDAQABo0YwRDAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYB +BQUHAwMwGgYDVR0RBBMwEYEPaW5mb0BidWlsZDIub3JnMA0GCSqGSIb3DQEBCwUA +A4ICAQBhhZHfxie6sB6GO00NGBj+8Jcbg4CltB1hq2dFA3Ytx2VSFFl4bkq1jSff +fciWh+GoVNmGIYnDok3Sdj+G5x6r53hn3zRZuZDK5CzAZ5fmagn/hgJpYhrbqCxz +hXkuKJkxCQTLTiZOWRvdNZRu8cApNgVnlUKPcqiv7QEgAkGPqR+ZinmXzbYPDpmV +PjP6r6jtpGMsFyIoO4N3iFgDneiV8MJHLyjSNoNddu9ylPcR9vwfmtOxMnlnr8lY +za1AbJtkYsNJKuIZd5dvB47E1D8d4a7ZL5vIhmt9C9d+9gE80H4PZfXQJ+jPPGCl +SUqbEFiZerRUgybfLtUppgFXtP855uXTKMR9GOeWCOEKWEklLVOmFmHO09OvpzTf +MQSCnwV4d/rDYbIWA5w5FzlS4hB9q/SkY6JFPGu6lLfKvkMcCjIncIANDG2vtafg +tDBTVF49GZmbCR6fSr+Rs/5TliTaRgj7GmZ8V75uIX/fSUFCklSrE1yT6WrrOsf2 +Jq4JpodZ6l+r+u993YJnp3o16r3nwpg6jVeWxI93x7JsdXxI9IRRelVoeQL44BWF +zywo2GPwQfFTdFSOrKB7TrEgR0T+z0dKAJoI0S1lqxTxBleNSVmtBiicglxjFnqZ +GvD6iu0+Z2aFqvfyquMjUWMfiRxioZ3altX+mj4hDjWvY6trQg== +-----END CERTIFICATE----- diff --git a/tests/auth/self-openssl.cnf b/tests/auth/self-openssl.cnf new file mode 100644 index 0000000..a4a8fa8 --- /dev/null +++ b/tests/auth/self-openssl.cnf @@ -0,0 +1,22 @@ +repository = *build2.org +company = Code Synthesis +email = info@build2.org + + +[ req ] + +distinguished_name = req_distinguished_name +x509_extensions = v3_req +prompt = no +utf8 = yes + +[ req_distinguished_name ] + +O = $company +CN = name:$repository + +[ v3_req ] + +keyUsage = critical,digitalSignature +extendedKeyUsage = critical,codeSigning +subjectAltName = email:$email diff --git a/tests/auth/subdomain-cert.pem b/tests/auth/subdomain-cert.pem new file mode 100644 index 0000000..40f7e90 --- /dev/null +++ b/tests/auth/subdomain-cert.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFMzCCAxugAwIBAgIJAPEWdjQimVTMMA0GCSqGSIb3DQEBCwUAMDUxFzAVBgNV +BAoMDkNvZGUgU3ludGhlc2lzMRowGAYDVQQDDBFuYW1lOiouYnVpbGQyLm9yZzAe +Fw0xNzA1MDgxNDA4NTlaFw0yMjA1MDcxNDA4NTlaMDUxFzAVBgNVBAoMDkNvZGUg +U3ludGhlc2lzMRowGAYDVQQDDBFuYW1lOiouYnVpbGQyLm9yZzCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBANq78SXuzFzCMoFU1RnzEeAfzE0UUYGynS3F +2lG7viH3coxjLt+BrFBudVs4XDTpjXS19hRxIohEgD71W1jhDvmUC9yCMW13PCII +jRKTTz0efEhTcMHdhOgvKZsje0IV7svoKVXcG7DfUVl51wWPQPSbUrfsQbsXg7Pz +5HaDx+Dt2i9hwdE1M0z4R2dtwQkszFyCKiX8RF9oPXirTz5ETLC3f19JUapLrY5l +5ZylzQifLhPMlHLlrT0n7KkohH7waX3KyeLa0M2IIl3zaeAsuN+ErFVecAdlJIvX +00cth2OO/Gxy09sIKlagi2q7ZDik2sMvG8dAv7gNZsXp+FOj/XXCiOI9f6D5ospJ +dK9B5UCABjmGc8W5Odv6ZLey5Ui76luI7ciITOKfAoEkbyMiNHiRxLdM7aAeizdc +wHU4bm6JlmiJk8UyyV85f33mvCSfuo7D+DQYiK650/xwRdTFBIqi38IwME62gT7a +h/AOmiPshj7FjwIU7ZWHskyr9qpExQOEKJXoLZJo1rf6MRc8AsJyz6zdfQhT1BTz +hogNfru4xjVM6fSrjRUF34msuWcz/HKo9W350Aw2y5F59kziP+m7G6uBYrqmElv/ +13Vamg2ZZ1b38KMz5Ss3SkfcDErOzz/D+0hRlOaCIeWts1G2zWcQvBnn+zGA+sTI +u0xAFOCRAgMBAAGjRjBEMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggr +BgEFBQcDAzAaBgNVHREEEzARgQ9pbmZvQGJ1aWxkMi5vcmcwDQYJKoZIhvcNAQEL +BQADggIBANBDUE7sch9rO99MOAjEoPCU9gVZcndaAcNbghfCbNpIyPPUq7R5+Jy/ +70kYkHV0JanFFUlB+O99TsLWCkNBcRSQ9sQHqqddyEdI+LN5GUwlXq/uCwF/AcDA +fZjhnLhes3vDHXO5lLfN6K4pvQ+viF5V4qL1KIo4gYKO1dyuoBsGt+JqUJXS9QS3 +xWLEq4IF7iPZiFYJ+fnXL7J8cuNvflkf3EeOlpMPxo356hOYp0ND6/z8P9lQWAXs +0NQWzW4hlL5Cm+YroX/su8+on2INvP3Nx9GW3nMFRuCYXmq6rYGSw6zGZbgv57JD +vA5F3D1XkTe85rytJjsJaKjJAC+xHQl9yzkjyBMzJBLwio75i/4hlkrpKtet649n +PrkEB3LU43LczZXXUKmAWsV8XOEssHdCSZQD+/oyzW6FcW2dHcNeXBxKn/we2/2E +Ss0vuys0uQGPlfT0TlHSuvIoXNIPAqzAefA0h9R2sisazkTYoeu04wWAA/Crobv5 +/NssbZ04/sMY4eTrwP/IZOvgmrS+dZSaEr9kVTUu/TQLmRDTgUtwxS39C0eri4QY +/1M0qGY4Wxji+MPDFGSgmsLj3vrmX3nlsan4fG466TCnIo4yVhYc2c9rmTqZ9u42 +CLoIN099hOYbfUueMBwtiLd7+544cGo1n2z+AnGePmHfoQYxDOqw +-----END CERTIFICATE----- diff --git a/tests/auth/subdomain-openssl.cnf b/tests/auth/subdomain-openssl.cnf new file mode 100644 index 0000000..1c4f91c --- /dev/null +++ b/tests/auth/subdomain-openssl.cnf @@ -0,0 +1,22 @@ +repository = *.build2.org +company = Code Synthesis +email = info@build2.org + + +[ req ] + +distinguished_name = req_distinguished_name +x509_extensions = v3_req +prompt = no +utf8 = yes + +[ req_distinguished_name ] + +O = $company +CN = name:$repository + +[ v3_req ] + +keyUsage = critical,digitalSignature +extendedKeyUsage = critical,codeSigning +subjectAltName = email:$email diff --git a/tests/rep-auth.test b/tests/rep-auth.test index b2331aa..12815f9 100644 --- a/tests/rep-auth.test +++ b/tests/rep-auth.test @@ -43,6 +43,44 @@ cat <<<$cert_manifest >+$out/signed/repositories $rc --key $key $out/signed &$out/signed/packages &$out/signed/signature + # Create the 'self-match' repository. Note that its certificate name is + # the '*build2.org' wildcard (matches build2.org and any single-level + # subdomain). + # + cp -r $src/unsigned $out/self-match + + echo 'certificate: \' >+$out/self-match/repositories + cat <<<$src_base/auth/self-cert.pem >+$out/self-match/repositories + echo '\' >+$out/self-match/repositories + + $rc --key $key $out/self-match &$out/self-match/packages \ + &$out/self-match/signature + + # Create the 'self-any-match' repository. Note that its certificate name is + # the '**build2.org' wildcard (matches build2.org and any subdomain). + # + cp -r $src/unsigned $out/self-any-match + + echo 'certificate: \' >+$out/self-any-match/repositories + cat <<<$src_base/auth/self-any-cert.pem >+$out/self-any-match/repositories + echo '\' >+$out/self-any-match/repositories + + $rc --key $key $out/self-any-match &$out/self-any-match/packages \ + &$out/self-any-match/signature + + # Create the 'subdomain-match' repository. Note that its certificate name is + # the '*.build2.org' wildcard (matches any single-level subdomain of + # build2.org). + # + cp -r $src/unsigned $out/subdomain-match + + echo 'certificate: \' >+$out/subdomain-match/repositories + cat <<<$src_base/auth/subdomain-cert.pem >+$out/subdomain-match/repositories + echo '\' >+$out/subdomain-match/repositories + + $rc --key $key $out/subdomain-match &$out/subdomain-match/packages \ + &$out/subdomain-match/signature + # Create the 'name-mismatch' repository. Note that its certificate name # mismatches the repository location. # @@ -278,6 +316,120 @@ sc = " " # Space character to append to here-document line when required. $rep_info >'name:build2.org' } } + + : subdomain-wildcard + : + { + rep_info += --auth all --trust-yes --cert-name + + : self + : + { + : exact + : + $rep_info $rep/self-match >'name:*build2.org' + + : subdomain + : + if ($remote != true) + { + : first-level + : + { + r = $canonicalize([dir_path] $~/pkg/1/a.build2.org/); + mkdir -p $r; + cp -r $rep/self-match $r; + + $rep_info $r/self-match >'name:*build2.org' + } + + : second-level + : + { + r = $canonicalize([dir_path] $~/pkg/1/b.a.build2.org/); + mkdir -p $r; + cp -r $rep/self-match $r; + + $rep_info $r/self-match 2>>EOE != 0 + error: certificate name mismatch for repository b.a.build2.org/self-match + info: certificate name is *build2.org + EOE + } + } + } + + : self-any + : + { + : exact + : + $rep_info $rep/self-any-match >'name:**build2.org' + + : subdomain + : + if ($remote != true) + { + : first-level + : + { + r = $canonicalize([dir_path] $~/pkg/1/a.build2.org/); + mkdir -p $r; + cp -r $rep/self-any-match $r; + + $rep_info $r/self-any-match >'name:**build2.org' + } + + : second-level + : + { + r = $canonicalize([dir_path] $~/pkg/1/b.a.build2.org/); + mkdir -p $r; + cp -r $rep/self-any-match $r; + + $rep_info $r/self-any-match >'name:**build2.org' + } + } + } + + : subdomain + : + { + : exact + : + $rep_info $rep/subdomain-match 2>>EOE != 0 + error: certificate name mismatch for repository build2.org/rep-auth/subdomain-match + info: certificate name is *.build2.org + EOE + + : subdomain + : + if ($remote != true) + { + : first-level + : + { + r = $canonicalize([dir_path] $~/pkg/1/a.build2.org/); + mkdir -p $r; + cp -r $rep/subdomain-match $r; + + $rep_info $r/subdomain-match >'name:*.build2.org' + } + + : second-level + : + { + r = $canonicalize([dir_path] $~/pkg/1/b.a.build2.org/); + mkdir -p $r; + cp -r $rep/subdomain-match $r; + + $rep_info $r/subdomain-match 2>>EOE != 0 + error: certificate name mismatch for repository b.a.build2.org/subdomain-match + info: certificate name is *.build2.org + EOE + } + } + } + } } : unsigned -- cgit v1.1