all the refactor
This commit is contained in:
+61
-24
@@ -1,6 +1,7 @@
|
||||
package ucp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
@@ -31,8 +32,8 @@ type SigningConfig struct {
|
||||
// NewSigningConfigFromPEM parses a PEM-encoded ECDSA P-256 private key and
|
||||
// returns a SigningConfig ready for use.
|
||||
//
|
||||
// data — PEM block containing an EC PRIVATE KEY (PKCS#8 or SEC1)
|
||||
// keyID — the kid matched by the UCP profile signing_keys entry
|
||||
// data — PEM block containing an EC PRIVATE KEY (PKCS#8 or SEC1)
|
||||
// keyID — the kid matched by the UCP profile signing_keys entry
|
||||
func NewSigningConfigFromPEM(pemData []byte, keyID string) (*SigningConfig, error) {
|
||||
block, _ := pem.Decode(pemData)
|
||||
if block == nil {
|
||||
@@ -72,8 +73,8 @@ func NewSigningConfigFromPEM(pemData []byte, keyID string) (*SigningConfig, erro
|
||||
|
||||
// WithSigning wraps h with an HTTP middleware that adds RFC 9421 HTTP Message
|
||||
// Signatures to every response. The Signature-Input and Signature headers are
|
||||
// computed over @status, content-type, and x-ucp-timestamp, signed with the
|
||||
// configured ECDSA P-256 key.
|
||||
// computed over @status, content-type, x-ucp-timestamp, and content-digest,
|
||||
// signed with the configured ECDSA P-256 key.
|
||||
//
|
||||
// Mount it on any existing UCP handler:
|
||||
//
|
||||
@@ -83,11 +84,9 @@ func WithSigning(h http.Handler, cfg *SigningConfig) http.Handler {
|
||||
return h // pass through without signing
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
sw := &signedWriter{
|
||||
ResponseWriter: w,
|
||||
cfg: cfg,
|
||||
}
|
||||
sw := newSignedWriter(w, cfg)
|
||||
h.ServeHTTP(sw, r)
|
||||
sw.flush()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -96,10 +95,24 @@ func WithSigning(h http.Handler, cfg *SigningConfig) http.Handler {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type signedWriter struct {
|
||||
http.ResponseWriter
|
||||
cfg *SigningConfig
|
||||
statusCode int
|
||||
wroteHeader bool
|
||||
responseWriter http.ResponseWriter
|
||||
header http.Header
|
||||
body bytes.Buffer
|
||||
cfg *SigningConfig
|
||||
statusCode int
|
||||
wroteHeader bool
|
||||
}
|
||||
|
||||
func newSignedWriter(w http.ResponseWriter, cfg *SigningConfig) *signedWriter {
|
||||
return &signedWriter{
|
||||
responseWriter: w,
|
||||
header: make(http.Header),
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *signedWriter) Header() http.Header {
|
||||
return w.header
|
||||
}
|
||||
|
||||
func (w *signedWriter) WriteHeader(statusCode int) {
|
||||
@@ -108,28 +121,44 @@ func (w *signedWriter) WriteHeader(statusCode int) {
|
||||
}
|
||||
w.wroteHeader = true
|
||||
w.statusCode = statusCode
|
||||
|
||||
created := time.Now().Unix()
|
||||
w.ResponseWriter.Header().Set("x-ucp-timestamp", strconv.FormatInt(created, 10))
|
||||
|
||||
// Add signature headers before flushing.
|
||||
w.addSignatureHeaders(created)
|
||||
|
||||
w.ResponseWriter.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
func (w *signedWriter) Write(p []byte) (int, error) {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
return w.ResponseWriter.Write(p)
|
||||
return w.body.Write(p)
|
||||
}
|
||||
|
||||
func (w *signedWriter) flush() {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
if w.header.Get("Content-Type") == "" && w.body.Len() > 0 {
|
||||
w.header.Set("Content-Type", http.DetectContentType(w.body.Bytes()))
|
||||
}
|
||||
|
||||
created := time.Now().Unix()
|
||||
w.header.Set("x-ucp-timestamp", strconv.FormatInt(created, 10))
|
||||
w.header.Set("Content-Digest", buildContentDigest(w.body.Bytes()))
|
||||
|
||||
// Add signature headers before flushing.
|
||||
w.addSignatureHeaders(created)
|
||||
|
||||
dst := w.responseWriter.Header()
|
||||
for k, vals := range w.header {
|
||||
dst[k] = append([]string(nil), vals...)
|
||||
}
|
||||
w.responseWriter.WriteHeader(w.statusCode)
|
||||
_, _ = w.responseWriter.Write(w.body.Bytes())
|
||||
}
|
||||
|
||||
// addSignatureHeaders computes and writes the Signature-Input and Signature
|
||||
// headers per RFC 9421 §3.2 / §3.3.
|
||||
func (w *signedWriter) addSignatureHeaders(created int64) {
|
||||
contentType := w.ResponseWriter.Header().Get("Content-Type")
|
||||
contentType := w.header.Get("Content-Type")
|
||||
sigTimestamp := strconv.FormatInt(created, 10)
|
||||
contentDigest := w.header.Get("Content-Digest")
|
||||
|
||||
// Covered components (in order). RFC 9421 §3.2: derived component names
|
||||
// (@status) are bare identifiers; HTTP header field names are sf-strings
|
||||
@@ -138,6 +167,7 @@ func (w *signedWriter) addSignatureHeaders(created int64) {
|
||||
"@status",
|
||||
`"content-type"`,
|
||||
`"x-ucp-timestamp"`,
|
||||
`"content-digest"`,
|
||||
}
|
||||
|
||||
// Build the signature base string (RFC 9421 §2.2).
|
||||
@@ -148,6 +178,8 @@ func (w *signedWriter) addSignatureHeaders(created int64) {
|
||||
base.WriteByte('\n')
|
||||
base.WriteString(fmt.Sprintf(`"x-ucp-timestamp": %s`, sigTimestamp))
|
||||
base.WriteByte('\n')
|
||||
base.WriteString(fmt.Sprintf(`"content-digest": %s`, contentDigest))
|
||||
base.WriteByte('\n')
|
||||
|
||||
// Append the signature-params pseudo-line (RFC 9421 §2.2).
|
||||
compList := strings.Join(components, " ")
|
||||
@@ -178,6 +210,11 @@ func (w *signedWriter) addSignatureHeaders(created int64) {
|
||||
// Signature: sig1=:base64url:
|
||||
sigValue := fmt.Sprintf("sig1=:%s:", sigB64)
|
||||
|
||||
w.ResponseWriter.Header().Set("Signature-Input", sigInput)
|
||||
w.ResponseWriter.Header().Set("Signature", sigValue)
|
||||
w.header.Set("Signature-Input", sigInput)
|
||||
w.header.Set("Signature", sigValue)
|
||||
}
|
||||
|
||||
func buildContentDigest(body []byte) string {
|
||||
sum := sha256.Sum256(body)
|
||||
return "sha-256=:" + base64.StdEncoding.EncodeToString(sum[:]) + ":"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user