Remove the Wire CLI invocatation

pull/1671/head
Herman Slatman 5 months ago
parent 7a464cdb17
commit 29fa6621b1
No known key found for this signature in database
GPG Key ID: F4D8A44EA0A75A4F

@ -1,7 +1,6 @@
package acme
import (
"bytes"
"context"
"crypto"
"crypto/ecdsa"
@ -21,8 +20,6 @@ import (
"io"
"net"
"net/url"
"os"
"os/exec"
"reflect"
"strconv"
"strings"
@ -354,13 +351,11 @@ func dns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebK
return nil
}
type WireChallengePayload struct {
// IDToken
type wireOidcPayload struct {
// IDToken contains the OIDC identity token
IDToken string `json:"id_token,omitempty"`
// KeyAuth ({challenge-token}.{jwk-thumbprint})
KeyAuth string `json:"keyauth,omitempty"`
// AccessToken is the token generated by wire-server
AccessToken string `json:"access_token,omitempty"`
}
func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, payload []byte) error {
@ -369,15 +364,15 @@ func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO
return NewErrorISE("no provisioner provided")
}
var wireChallengePayload WireChallengePayload
err := json.Unmarshal(payload, &wireChallengePayload)
var oidcPayload wireOidcPayload
err := json.Unmarshal(payload, &oidcPayload)
if err != nil {
return storeError(ctx, db, ch, false, WrapError(ErrorRejectedIdentifierType, err,
"error unmarshalling Wire challenge payload"))
}
oidcOptions := prov.GetOptions().GetOIDCOptions()
idToken, err := oidcOptions.GetProvider(ctx).Verifier(oidcOptions.GetConfig()).Verify(ctx, wireChallengePayload.IDToken)
idToken, err := oidcOptions.GetProvider(ctx).Verifier(oidcOptions.GetConfig()).Verify(ctx, oidcPayload.IDToken)
if err != nil {
return storeError(ctx, db, ch, false, WrapError(ErrorRejectedIdentifierType, err,
"error verifying ID token signature"))
@ -404,9 +399,9 @@ func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO
if err != nil {
return err
}
if expectedKeyAuth != wireChallengePayload.KeyAuth {
if expectedKeyAuth != oidcPayload.KeyAuth {
return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType,
"keyAuthorization does not match; expected %s, but got %s", expectedKeyAuth, wireChallengePayload.KeyAuth))
"keyAuthorization does not match; expected %s, but got %s", expectedKeyAuth, oidcPayload.KeyAuth))
}
if challengeValues.Name != claims.Name || challengeValues.Handle != claims.Handle {
@ -422,33 +417,36 @@ func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO
return WrapErrorISE(err, "error updating challenge")
}
parsedIDToken, err := jwt.ParseSigned(wireChallengePayload.IDToken)
parsedIDToken, err := jwt.ParseSigned(oidcPayload.IDToken)
if err != nil {
return WrapErrorISE(err, "Invalid OIDC id token")
return WrapErrorISE(err, "invalid OIDC id token")
}
oidcToken := make(map[string]interface{})
if err := parsedIDToken.UnsafeClaimsWithoutVerification(&oidcToken); err != nil {
return WrapErrorISE(err, "Failed parsing OIDC id token")
return WrapErrorISE(err, "failed parsing OIDC id token")
}
orders, err := db.GetAllOrdersByAccountID(ctx, ch.AccountID)
if err != nil {
return WrapErrorISE(err, "Could not find current order by account id")
return WrapErrorISE(err, "could not find current order by account id")
}
if len(orders) == 0 {
return WrapErrorISE(err, "There are not enough orders for this account for this custom OIDC challenge")
return WrapErrorISE(err, "there are not enough orders for this account for this custom OIDC challenge")
}
order := orders[len(orders)-1]
if err := db.CreateOidcToken(ctx, order, oidcToken); err != nil {
return WrapErrorISE(err, "Failed storing OIDC id token")
return WrapErrorISE(err, "failed storing OIDC id token")
}
return nil
}
type wireDpopPayload struct {
// AccessToken is the token generated by wire-server
AccessToken string `json:"access_token,omitempty"`
}
func wireDPOP01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, payload []byte) error {
prov, ok := ProvisionerFromContext(ctx)
if !ok {
@ -459,150 +457,152 @@ func wireDPOP01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO
if thumbprintErr != nil {
return storeError(ctx, db, ch, false, WrapError(ErrorServerInternalType, thumbprintErr, "failed to compute JWK thumbprint"))
}
kid := base64.RawURLEncoding.EncodeToString(rawKid)
dpopOptions := prov.GetOptions().GetDPOPOptions()
key := dpopOptions.GetSigningKey()
var wireChallengePayload WireChallengePayload
err := json.Unmarshal(payload, &wireChallengePayload)
var dpopPayload wireDpopPayload
err := json.Unmarshal(payload, &dpopPayload)
if err != nil {
return storeError(ctx, db, ch, false, WrapError(ErrorRejectedIdentifierType, err,
"error unmarshalling Wire challenge payload"))
}
file, err := os.CreateTemp(os.TempDir(), "acme-validate-challenge-pubkey-")
if err != nil {
return WrapErrorISE(err, "temporary file could not be created")
}
defer file.Close()
defer os.Remove(file.Name())
buf := bytes.NewBuffer(nil)
buf.WriteString(key)
n, err := file.Write(buf.Bytes())
if err != nil {
return WrapErrorISE(err, "Failed writing signature key to temp file")
}
if n != buf.Len() {
return WrapErrorISE(err, "expected to write %d characters to the key file, got %d", buf.Len(), n)
}
challengeValues, err := wire.ParseID([]byte(ch.Value))
wireID, err := wire.ParseID([]byte(ch.Value))
if err != nil {
return WrapErrorISE(err, "error unmarshalling challenge data")
}
clientID, err := wire.ParseClientID(challengeValues.ClientID)
clientID, err := wire.ParseClientID(wireID.ClientID)
if err != nil {
return WrapErrorISE(err, "error parsing device id")
}
dpopOptions := prov.GetOptions().GetDPOPOptions()
issuer, err := dpopOptions.GetTarget(clientID.DeviceID)
if err != nil {
return WrapErrorISE(err, "Invalid Go template registered for 'target'")
return WrapErrorISE(err, "invalid Go template registered for 'target'")
}
key := dpopOptions.GetSigningKey()
expiry := strconv.FormatInt(time.Now().Add(time.Hour*24*365).Unix(), 10)
cmd := exec.CommandContext( //nolint:gosec // TODO(hs): replace this with Go implementation
ctx,
dpopOptions.GetValidationExecPath(),
"verify-access",
"--client-id",
challengeValues.ClientID,
"--handle",
challengeValues.Handle,
"--challenge",
ch.Token,
"--leeway",
"360",
"--max-expiry",
expiry,
"--issuer",
issuer,
"--hash-algorithm",
`SHA-256`,
"--kid",
kid,
"--key",
file.Name(),
)
stdin, err := cmd.StdinPipe()
_, dpop, err := parseAndVerifyAccess(dpopPayload.AccessToken, key, issuer, kid, wireID, expiry, ch) // TODO: right format for key in config?
if err != nil {
return WrapErrorISE(err, "error getting process stdin")
return WrapErrorISE(err, "failed validating token")
}
err = cmd.Start()
if err != nil {
return WrapErrorISE(err, "error starting validation process")
// Update and store the challenge.
ch.Status = StatusValid
ch.Error = nil
ch.ValidatedAt = clock.Now().Format(time.RFC3339)
if err = db.UpdateChallenge(ctx, ch); err != nil {
return WrapErrorISE(err, "error updating challenge")
}
_, err = stdin.Write([]byte(wireChallengePayload.AccessToken))
orders, err := db.GetAllOrdersByAccountID(ctx, ch.AccountID)
if err != nil {
return WrapErrorISE(err, "error writing to stdin")
return WrapErrorISE(err, "could not find current order by account id")
}
if len(orders) == 0 {
return NewErrorISE("there are not enough orders for this account for this custom OIDC challenge")
}
err = stdin.Close()
if err != nil {
return WrapErrorISE(err, "error closing stdin")
order := orders[len(orders)-1]
if err := db.CreateDpopToken(ctx, order, map[string]any(*dpop)); err != nil {
return WrapErrorISE(err, "failed storing DPoP token")
}
err = cmd.Wait()
return nil
}
type cnf struct {
Kid string `json:"kid,omitempty"`
}
type wireAccessToken struct {
jose.Claims
Challenge string `json:"chal,omitempty"`
Cnf *cnf `json:"cnf,omitempty"`
Proof string `json:"proof,omitempty"`
ClientID string `json:"client_id,omitempty"`
APIVersion int `json:"api_version,omitempty"`
Scope string `json:"scope,omitempty"`
}
type wireDpopToken map[string]any
func parseAndVerifyAccess(token string, key string, issuer string, kid string, wireID wire.ID, maxExpiry string, ch *Challenge) (*wireAccessToken, *wireDpopToken, error) {
k, err := pemutil.Parse([]byte(key)) // TODO(hs): move this to earlier in the configuration process? Do it once?
if err != nil {
return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, "error finishing validation: %s", err))
return nil, nil, fmt.Errorf("failed parsing public key: %w", err)
}
// Update and store the challenge.
ch.Status = StatusValid
ch.Error = nil
ch.ValidatedAt = clock.Now().Format(time.RFC3339)
if err = db.UpdateChallenge(ctx, ch); err != nil {
return WrapErrorISE(err, "error updating challenge")
pk, ok := k.(ed25519.PublicKey) // TODO(hs): allow more key types
if !ok {
return nil, nil, fmt.Errorf("unexpected type: %T", k)
}
parsedAccessToken, err := jwt.ParseSigned(wireChallengePayload.AccessToken)
jwt, err := jose.ParseSigned(token)
if err != nil {
return WrapErrorISE(err, "Invalid access token")
return nil, nil, fmt.Errorf("failed parsing token: %w", err)
}
access := make(map[string]interface{})
if err := parsedAccessToken.UnsafeClaimsWithoutVerification(&access); err != nil {
return WrapErrorISE(err, "Failed parsing access token")
var accessToken wireAccessToken
if err = jwt.Claims(pk, &accessToken); err != nil {
return nil, nil, fmt.Errorf("failed getting token claims: %w", err)
}
rawDpop, ok := access["proof"].(string)
if !ok {
return WrapErrorISE(err, "Invalid dpop proof format in access token")
fmt.Println(fmt.Sprintf("%#+v", jwt))
fmt.Println(fmt.Sprintf("%#+v", accessToken))
if err := accessToken.ValidateWithLeeway(jose.Expected{
Time: time.Now().UTC(),
Issuer: issuer,
}, 360*time.Second); err != nil {
return nil, nil, fmt.Errorf("failed validation: %w", err)
}
parsedDpopToken, err := jwt.ParseSigned(rawDpop)
if err != nil {
return WrapErrorISE(err, "Invalid DPoP token")
if accessToken.Cnf == nil {
return nil, nil, errors.New("'cnf' is nil")
}
dpop := make(map[string]interface{})
if err := parsedDpopToken.UnsafeClaimsWithoutVerification(&dpop); err != nil {
return WrapErrorISE(err, "Failed parsing dpop token")
if accessToken.Cnf.Kid != kid {
return nil, nil, fmt.Errorf("expected kid %q; got %q", kid, accessToken.Cnf.Kid)
}
orders, err := db.GetAllOrdersByAccountID(ctx, ch.AccountID)
if err != nil {
return WrapErrorISE(err, "Could not find current order by account id")
if accessToken.ClientID != wireID.ClientID {
return nil, nil, fmt.Errorf("invalid client ID %q", accessToken.ClientID)
}
if len(orders) == 0 {
return WrapErrorISE(err, "There are not enough orders for this account for this custom OIDC challenge")
parsedDpopToken, err := jose.ParseSigned(accessToken.Proof)
if err != nil {
return nil, nil, fmt.Errorf("invalid Wire DPoP token: %w", err)
}
var dpopToken wireDpopToken
if err := parsedDpopToken.UnsafeClaimsWithoutVerification(&dpopToken); err != nil {
return nil, nil, fmt.Errorf("failed parsing dpop token: %w", err)
}
order := orders[len(orders)-1]
// TODO: validate DPoP too? Which key?
if err := db.CreateDpopToken(ctx, order, dpop); err != nil {
return WrapErrorISE(err, "Failed storing DPoP token")
handle, ok := dpopToken["handle"].(string)
if !ok {
return nil, nil, fmt.Errorf("invalid handle in dpop token")
}
return nil
_ = handle
// TODO: compare handle?
// TODO: compare challenge token / value?
// TODO: max expiry?
// "--handle",
// challengeValues.Handle,
// "--challenge",
// ch.Token,
// "--max-expiry",
// expiry,
return &accessToken, &dpopToken, nil
}
type payloadType struct {

@ -33,6 +33,7 @@ import (
"github.com/fxamacker/cbor/v2"
"github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/wire"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/jose"
@ -4301,3 +4302,38 @@ func createSubjectAltNameExtension(dnsNames, emailAddresses x509util.MultiString
Value: rawBytes,
}, nil
}
func Test_wireVerifyAccess(t *testing.T) {
key := `
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
-----END PUBLIC KEY-----`
issuer := "https://wire.example.com/clients/314845990100130665/access-token"
kid := ""
wireID := wire.ID{}
token := `eyJhbGciOiJFZERTQSIsInR5cCI6ImF0K2p3dCIsImp3ayI6eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6IjVjLTROS1pTTlFjUjFUOHFONlNqd2dkUFpRMEdlMTJZbHhfWWVHQUozNWsifX0.eyJpYXQiOjE3MDQ4NDA4OTYsImV4cCI6MTcwNDg0NDg1NiwibmJmIjoxNzA0ODQwODk2LCJpc3MiOiJodHRwczovL3dpcmUuZXhhbXBsZS5jb20vY2xpZW50cy8zMTQ4NDU5OTAxMDAxMzA2NjUvYWNjZXNzLXRva2VuIiwic3ViIjoid2lyZWFwcDovL0pyajNhZjZZUTdlZlhxSVBhM0tUZnchNDVlOGVjMjg2ZGZkYjY5QHdpcmUuY29tIiwiYXVkIjoiaHR0cHM6Ly93aXJlLmV4YW1wbGUuY29tL2NsaWVudHMvMzE0ODQ1OTkwMTAwMTMwNjY1L2FjY2Vzcy10b2tlbiIsImp0aSI6IjI2MjUxNzgzLWUxNDItNDNhNC04ZWE5LWU0MTk3MTJmYjE0MSIsIm5vbmNlIjoiY1hKVU9EUnROemxzWmtocGQwOHhSVEpTVHpSVFowWnBiRGhLWkZKUFdYayIsImNoYWwiOiJiRzlZYVRObk4yUTJiVU5IVEhWb016aHRaREo1WTNSQ05VaFZaR3hXUjBvIiwiY25mIjp7ImtpZCI6IlFBdjZDOXE0N0N5ZmQxdTl6NnVYM1Zfby10MTFTOHA4MXdMSC1vVFJsaDAifSwicHJvb2YiOiJleUpoYkdjaU9pSkZaRVJUUVNJc0luUjVjQ0k2SW1Sd2IzQXJhbmQwSWl3aWFuZHJJanA3SW10MGVTSTZJazlMVUNJc0ltTnlkaUk2SWtWa01qVTFNVGtpTENKNElqb2laMk5VUWpCQmRHUjNkR0pqYmxoUFNtaHlTM2RKWmpob1F6ZFNVR2xtZEV0bFEzSjFUMmRpUVRCRVl5SjlmUS5leUpwWVhRaU9qRTNNRFE0TkRBNE9UWXNJbVY0Y0NJNk1UY3dORGt6TURnNU5pd2libUptSWpveE56QTBPRFF3T0RrMkxDSnpkV0lpT2lKM2FYSmxZWEJ3T2k4dlNuSnFNMkZtTmxsUk4yVm1XSEZKVUdFelMxUm1keUUwTldVNFpXTXlPRFprWm1SaU5qbEFkMmx5WlM1amIyMGlMQ0pxZEdraU9pSTBPVEl4WW1FMk5DMWhOVE0yTFRRd05qSXRZamhoTkMwNVpHVXlaR1l3WlRBMlpEWWlMQ0p1YjI1alpTSTZJbU5ZU2xWUFJGSjBUbnBzYzFwcmFIQmtNRGg0VWxSS1UxUjZVbFJhTUZwd1lrUm9TMXBHU2xCWFdHc2lMQ0pvZEcwaU9pSlFUMU5VSWl3aWFIUjFJam9pYUhSMGNITTZMeTkzYVhKbExtVjRZVzF3YkdVdVkyOXRMMk5zYVdWdWRITXZNekUwT0RRMU9Ua3dNVEF3TVRNd05qWTFMMkZqWTJWemN5MTBiMnRsYmlJc0ltTm9ZV3dpT2lKaVJ6bFpZVlJPYms0eVVUSmlWVTVJVkVoV2IwMTZhSFJhUkVvMVdUTlNRMDVWYUZaYVIzaFhVakJ2SWl3aWFHRnVaR3hsSWpvaWQybHlaV0Z3Y0Rvdkx5VTBNR0psYkhSeVlXMWZkMmx5WlVCM2FYSmxMbU52YlNJc0luUmxZVzBpT2lKM2FYSmxJbjAuUGVMaXEtZWlVWXhDREszT3dHMGtsN25lR0RQYUhtYW5KY1BlOEJOZ0pJemRHUm1nVEE1UVZQNTJsdzcwendJcy0yZ0JZTWxyOVVPb1VXX1l1bnN4RHciLCJjbGllbnRfaWQiOiJ3aXJlYXBwOi8vSnJqM2FmNllRN2VmWHFJUGEzS1RmdyE0NWU4ZWMyODZkZmRiNjlAd2lyZS5jb20iLCJhcGlfdmVyc2lvbiI6NSwic2NvcGUiOiJ3aXJlX2NsaWVudF9pZCJ9.VwMJGkXRaP0lC9UDe5iGU8fxOSeBKCXfHXhqcbu_n5JiP5b7WTJAymiCFmVyAaKWZIK6S9qxncqSj5AUPAfQAg`
maxExpiry := ""
ch := &Challenge{}
at, dpop, err := parseAndVerifyAccess(token, key, issuer, kid, wireID, maxExpiry, ch)
require.NoError(t, err)
fmt.Println(fmt.Sprintf("%#+v", at))
fmt.Println(fmt.Sprintf("%#+v", dpop))
t.Fail()
// tests := []struct {
// name string
// wantErr bool
// }{
// // TODO: Add test cases.
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// if err := wireVerifyAccess(); (err != nil) != tt.wantErr {
// t.Errorf("wireVerifyAccess() error = %v, wantErr %v", err, tt.wantErr)
// }
// })
// }
}

Loading…
Cancel
Save