]> Cypherpunks.ru repositories - gocheese.git/commitdiff
PyPI certificate's hash authentication
authorSergey Matveev <stargrave@stargrave.org>
Fri, 22 Jan 2021 11:35:27 +0000 (14:35 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Fri, 22 Jan 2021 11:35:27 +0000 (14:35 +0300)
gocheese.go
gocheese.texi
refresh.go

index 416bf7a2e4213a4062bc33fc159ae5890c46f101..63854169f549e0e301fd72a2af488b74e77196a7 100644 (file)
@@ -22,7 +22,10 @@ package main
 import (
        "bytes"
        "context"
+       "crypto/sha256"
+       "crypto/tls"
        "encoding/hex"
+       "errors"
        "flag"
        "fmt"
        "io/ioutil"
@@ -43,7 +46,7 @@ import (
 )
 
 const (
-       Version   = "2.5.0"
+       Version   = "2.6.0"
        HTMLBegin = `<!DOCTYPE html>
 <html>
   <head>
@@ -95,6 +98,7 @@ var (
        refreshURLPath   = flag.String("refresh", "/simple/", "Auto-refreshing URL path")
        gpgUpdateURLPath = flag.String("gpgupdate", "/gpgupdate/", "GPG forceful refreshing URL path")
        pypiURL          = flag.String("pypi", "https://pypi.org/simple/", "Upstream PyPI URL")
+       pypiCertHash     = flag.String("pypi-cert-hash", "", "Authenticate PyPI by its X.509 certificate's SHA256 hash")
        passwdPath       = flag.String("passwd", "passwd", "Path to file with authenticators")
        logTimestamped   = flag.Bool("log-timestamped", false, "Prepend timestmap to log messages")
        passwdCheck      = flag.Bool("passwd-check", false, "Test the -passwd file for syntax errors and exit")
@@ -169,7 +173,7 @@ func listDir(
        var result bytes.Buffer
        result.WriteString(fmt.Sprintf(HTMLBegin, pkgName))
        for _, algo := range knownHashAlgos {
-               for fn, _ := range files {
+               for fn := range files {
                        if killed {
                                // Skip expensive I/O when shutting down
                                http.Error(w, "shutting down", http.StatusInternalServerError)
@@ -298,6 +302,25 @@ func main() {
                log.Fatalln(err)
        }
        refreshPasswd()
+       if *pypiCertHash == "" {
+               pypiHTTPTransport = http.Transport{}
+       } else {
+               ourDgst, err := hex.DecodeString(*pypiCertHash)
+               if err != nil {
+                       log.Fatalln(err)
+               }
+               pypiHTTPTransport = http.Transport{
+                       TLSClientConfig: &tls.Config{
+                               VerifyConnection: func(s tls.ConnectionState) error {
+                                       spki := s.VerifiedChains[0][0].RawSubjectPublicKeyInfo
+                                       theirDgst := sha256.Sum256(spki)
+                                       if bytes.Compare(ourDgst, theirDgst[:]) != 0 {
+                                               return errors.New("certificate's digest mismatch")
+                                       }
+                                       return nil
+                               }},
+               }
+       }
 
        ln, err := net.Listen("tcp", *bind)
        if err != nil {
index e52441dd52162ad256e575b95be99b5f5826a248..587e9431cc02846c4720ca1e8db9b9cc15dfc157 100644 (file)
@@ -45,6 +45,7 @@ but nearly all the code was rewritten. It has huge differences:
 @item Integrity check of proxied packages: MD5, SHA256, SHA512, BLAKE2b-256
 @item SHA256 checksums for stored packages
 @item Verifying of SHA256 checksum for uploaded packages
+@item Ability to authenticate upstream PyPI with its X.509 certificate's hash
 @item Storing of uploaded GPG signatures
 @item Secure Argon2i (or SHA256) stored passwords hashing
 @item No YAML configuration, just command-line arguments
index 11c3ab7a52a73db2c3b13aa08a0f4723c7cfbc8a..11325e275c3d6c9f2002f97cd2dce6079e25c211 100644 (file)
@@ -37,6 +37,8 @@ import (
        "golang.org/x/crypto/blake2b"
 )
 
+var pypiHTTPTransport http.Transport
+
 func blake2b256New() hash.Hash {
        h, err := blake2b.New256(nil)
        if err != nil {
@@ -54,7 +56,8 @@ func refreshDir(
        if _, err := os.Stat(filepath.Join(*root, pkgName, InternalFlag)); err == nil {
                return true
        }
-       resp, err := http.Get(*pypiURL + pkgName + "/")
+       c := http.Client{Transport: &pypiHTTPTransport}
+       resp, err := c.Get(*pypiURL + pkgName + "/")
        if err != nil {
                log.Println("error", r.RemoteAddr, "refresh", pkgName, err)
                http.Error(w, err.Error(), http.StatusBadGateway)