117 lines
2.7 KiB
Go
117 lines
2.7 KiB
Go
package oci
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type nopCloser struct {
|
|
io.Reader
|
|
}
|
|
|
|
func (nopCloser) Close() error {
|
|
return nil
|
|
}
|
|
|
|
// Transport adds OCI signature authentication to each outgoing request.
|
|
type Transport struct {
|
|
transport http.RoundTripper
|
|
config *Config
|
|
}
|
|
|
|
// NewTransport creates a new Transport to add OCI signature authentication
|
|
// to each outgoing request.
|
|
func NewTransport(transport http.RoundTripper, config *Config) *Transport {
|
|
return &Transport{
|
|
transport: transport,
|
|
config: config,
|
|
}
|
|
}
|
|
|
|
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
var buf *bytes.Buffer
|
|
|
|
if req.Body != nil {
|
|
buf = new(bytes.Buffer)
|
|
buf.ReadFrom(req.Body)
|
|
req.Body = nopCloser{buf}
|
|
}
|
|
if req.Header.Get("date") == "" {
|
|
req.Header.Set("date", time.Now().UTC().Format(http.TimeFormat))
|
|
}
|
|
if req.Header.Get("content-type") == "" {
|
|
req.Header.Set("content-type", "application/json")
|
|
}
|
|
if req.Header.Get("accept") == "" {
|
|
req.Header.Set("accept", "application/json")
|
|
}
|
|
if req.Header.Get("host") == "" {
|
|
req.Header.Set("host", req.URL.Host)
|
|
}
|
|
|
|
var signheaders []string
|
|
if (req.Method == "PUT" || req.Method == "POST") && buf != nil {
|
|
signheaders = []string{"(request-target)", "host", "date",
|
|
"content-length", "content-type", "x-content-sha256"}
|
|
|
|
if req.Header.Get("content-length") == "" {
|
|
req.Header.Set("content-length", strconv.Itoa(buf.Len()))
|
|
}
|
|
|
|
hasher := sha256.New()
|
|
hasher.Write(buf.Bytes())
|
|
hash := hasher.Sum(nil)
|
|
req.Header.Set("x-content-sha256", base64.StdEncoding.EncodeToString(hash))
|
|
} else {
|
|
signheaders = []string{"date", "host", "(request-target)"}
|
|
}
|
|
|
|
var signbuffer bytes.Buffer
|
|
for idx, header := range signheaders {
|
|
signbuffer.WriteString(header)
|
|
signbuffer.WriteString(": ")
|
|
|
|
if header == "(request-target)" {
|
|
signbuffer.WriteString(strings.ToLower(req.Method))
|
|
signbuffer.WriteString(" ")
|
|
signbuffer.WriteString(req.URL.RequestURI())
|
|
} else {
|
|
signbuffer.WriteString(req.Header.Get(header))
|
|
}
|
|
|
|
if idx < len(signheaders)-1 {
|
|
signbuffer.WriteString("\n")
|
|
}
|
|
}
|
|
|
|
h := sha256.New()
|
|
h.Write(signbuffer.Bytes())
|
|
digest := h.Sum(nil)
|
|
signature, err := rsa.SignPKCS1v15(rand.Reader, t.config.Key, crypto.SHA256, digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
authHeader := fmt.Sprintf("Signature headers=\"%s\","+
|
|
"keyId=\"%s/%s/%s\","+
|
|
"algorithm=\"rsa-sha256\","+
|
|
"signature=\"%s\","+
|
|
"version=\"1\"",
|
|
strings.Join(signheaders, " "),
|
|
t.config.Tenancy, t.config.User, t.config.Fingerprint,
|
|
base64.StdEncoding.EncodeToString(signature))
|
|
req.Header.Add("Authorization", authHeader)
|
|
|
|
return t.transport.RoundTrip(req)
|
|
}
|