@@ -2,7 +2,10 @@ package nodeauth import ( "crypto/ed25519"
Show 10 hidden lines
+ "crypto/hkdf"+ "crypto/hmac" "crypto/rand"+ "crypto/sha256" "crypto/subtle" "encoding/base64" "errors"@@ -29,6 +32,16 @@ const ( signatureInputHeader = "Signature-Input" signatureHeader = "Signature"
coveredComponentParameters = "(\"@method\" \"@path\" \"@query\" \"content-digest\" \"x-nginx-ui-credential-id\" \"x-nginx-ui-target-instance\")"++ signatureAlgorithmEd25519 = "ed25519"+ signatureAlgorithmHMAC = "hmac-sha256"++ // sharedSecretKeyID marks a signature keyed by the configured node secret+ // rather than by a paired credential. It fills both identifier slots of the+ // envelope because a shared secret is not bound to a credential record or to+ // one target instance.+ sharedSecretKeyID = "node-secret"+ sharedSecretKeyInfo = "nginx-ui/node-shared-secret/hmac-sha256/v1" )
Show 16 hidden lines
var defaultReplayCache = NewReplayCache(maxReplayItems)@@ -38,13 +51,35 @@ type signatureMetadata struct { expires int64 nonce string credentialID string+ algorithm string parameters string } func SignRequestWithKey(request *http.Request, credentialID, targetInstanceID string, privateKey ed25519.PrivateKey, now time.Time) error { if len(privateKey) != ed25519.PrivateKeySize { return errors.New("invalid Ed25519 private key") }+ return signRequest(request, credentialID, targetInstanceID, signatureAlgorithmEd25519, now,+ func(base []byte) ([]byte, error) {+ return ed25519.Sign(privateKey, base), nil
+ })+}++// SignRequestWithSharedSecret authenticates a request with the node secret both+// instances already hold, without ever putting that secret on the wire.+func SignRequestWithSharedSecret(request *http.Request, secret []byte, now time.Time) error {+ if len(secret) == 0 {+ return errors.New("node secret is unavailable")+ }+ return signRequest(request, sharedSecretKeyID, sharedSecretKeyID, signatureAlgorithmHMAC, now,+ func(base []byte) ([]byte, error) {+ return sharedSecretSignature(secret, base)+ })
Show 34 hidden lines
+}++func signRequest(request *http.Request, credentialID, targetInstanceID, algorithm string, now time.Time,+ sign func(base []byte) ([]byte, error),+) error { if !validIdentifier(credentialID) || !validIdentifier(targetInstanceID) { return errors.New("invalid node signature identifier") }@@ -63,12 +98,13 @@ func SignRequestWithKey(request *http.Request, credentialID, targetInstanceID st expires := now.Add(signatureLifetime).Unix() nonce := base64.RawURLEncoding.EncodeToString(nonceBytes) parameters := fmt.Sprintf(- "%s;created=%d;expires=%d;nonce=\"%s\";keyid=\"%s\";alg=\"ed25519\";tag=\"%s\"",+ "%s;created=%d;expires=%d;nonce=\"%s\";keyid=\"%s\";alg=\"%s\";tag=\"%s\"", coveredComponentParameters, created, expires, nonce, credentialID,+ algorithm, signatureTag, ) @@ -77,12 +113,25 @@ func SignRequestWithKey(request *http.Request, credentialID, targetInstanceID st request.Header.Set(CredentialIDHeader, credentialID) request.Header.Set(TargetInstanceHeader, targetInstanceID) request.Header.Set(signatureInputHeader, signatureLabel+"="+parameters)- signatureBase := buildSignatureBase(request, parameters)- signature := ed25519.Sign(privateKey, []byte(signatureBase))+ signature, err := sign([]byte(buildSignatureBase(request, parameters)))+ if err != nil {+ CloseStagedBody(request)+ return err+ }
Defines the public node-secret sentinel and HMAC-SHA256 signing for shared-secret node requests. This branch makes the vulnerable request path reachable without a paired credential or target instance.