Skip to content

Commit 5fa3f02

Browse files
4gustchlowell
andauthored
4gust/cca 256sha support (#562)
* Updated the Algo to PR256 * Updated the token header keys * reformat code * Added that ADFS and dSTS uses sha1 algo * Add ADFS and DSTS signing support with RS256 algorithm * refactoring code * added comment on nosec issue * Fetching cert from env directly * Added temp running on pipeline * Added secret to yaml file * secret name changed * Update build_test.yaml * Update build_test.yaml * Update build_test.yaml * Update build_test.yaml * Removed the branch name from pipeline * Update apps/tests/integration/integration_test.go Co-authored-by: Charles Lowell <[email protected]> * Update apps/tests/integration/integration_test.go Co-authored-by: Charles Lowell <[email protected]> Co-authored-by: Charles Lowell <[email protected]>
1 parent 5881237 commit 5fa3f02

File tree

4 files changed

+85
-26
lines changed

4 files changed

+85
-26
lines changed

ado/build_test.yaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pr:
55
autoCancel: false
66
branches:
77
include:
8-
- main
8+
- main
99

1010
pool:
1111
vmImage: "ubuntu-latest"
@@ -29,22 +29,24 @@ steps:
2929
- task: Go@0
3030
inputs:
3131
command: "test"
32-
arguments: "-race -short ./apps/cache/... ./apps/confidential/... ./apps/public/... ./apps/internal/..."
32+
arguments: "-race -short ./apps/cache/... ./apps/confidential/... ./apps/public/... ./apps/internal/... ./apps/managedidentity/..."
3333
workingDirectory: "$(System.DefaultWorkingDirectory)"
3434
displayName: "Run Unit Tests"
3535
- task: AzureKeyVault@2
3636
displayName: "Connect to Key Vault"
3737
inputs:
3838
azureSubscription: "AuthSdkResourceManager"
3939
KeyVaultName: "msidlabs"
40-
SecretsFilter: "LabAuth"
40+
SecretsFilter: "LabAuth,IDLABS-APP-Confidential-Client-Cert-OnPrem"
4141
- task: Bash@3
4242
displayName: Installing certificate
4343
inputs:
4444
targetType: "inline"
4545
script: |
4646
echo $(LabAuth) | base64 -d > $(Build.SourcesDirectory)/cert.pfx
47-
openssl pkcs12 -in $(Build.SourcesDirectory)/cert.pfx -out $(Build.SourcesDirectory)/cert.pem -nodes -passin pass:''
47+
OPENSSL_CONF=/dev/null openssl pkcs12 -in $(Build.SourcesDirectory)/cert.pfx -out $(Build.SourcesDirectory)/cert.pem -nodes -passin pass:'' -legacy
48+
echo "$(IDLABS-APP-Confidential-Client-Cert-OnPrem)" | base64 -d > $(Build.SourcesDirectory)/ccaCert.pfx
49+
OPENSSL_CONF=/dev/null openssl pkcs12 -in $(Build.SourcesDirectory)/ccaCert.pfx -out $(Build.SourcesDirectory)/ccaCert.pem -nodes -passin pass:'' -legacy
4850
4951
- task: Go@0
5052
inputs:

apps/confidential/confidential_test.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -605,12 +605,15 @@ func TestInvalidCredential(t *testing.T) {
605605

606606
func TestNewCredFromCert(t *testing.T) {
607607
for _, file := range []struct {
608-
path string
609-
numCerts int
608+
path string
609+
numCerts int
610+
testAuthority string
610611
}{
611-
{"../testdata/test-cert.pem", 1},
612-
{"../testdata/test-cert-chain.pem", 2},
613-
{"../testdata/test-cert-chain-reverse.pem", 2},
612+
{"../testdata/test-cert.pem", 1, fakeAuthority},
613+
{"../testdata/test-cert.pem", 1, "https://fs.msidlab8.com/adfs"},
614+
{"../testdata/test-cert.pem", 1, "https://some.url.dsts.core.azure-test.net/dstsv2/7a433bfc-2514-4697-b467-e0933190487f"},
615+
{"../testdata/test-cert-chain.pem", 2, fakeAuthority},
616+
{"../testdata/test-cert-chain-reverse.pem", 2, fakeAuthority},
614617
} {
615618
f, err := os.Open(filepath.Clean(file.path))
616619
if err != nil {
@@ -651,7 +654,7 @@ func TestNewCredFromCert(t *testing.T) {
651654
AccessToken: token,
652655
ExpiresOn: time.Now().Add(time.Hour),
653656
GrantedScopes: accesstokens.Scopes{Slice: tokenScope},
654-
}, cred, fakeAuthority, opts...)
657+
}, cred, file.testAuthority, opts...)
655658
if err != nil {
656659
t.Fatal(err)
657660
}
@@ -660,8 +663,12 @@ func TestNewCredFromCert(t *testing.T) {
660663
client.base.Token.AccessTokens.(*fake.AccessTokens).ValidateAssertion = func(s string) {
661664
validated = true
662665
tk, err := jwt.Parse(s, func(tk *jwt.Token) (interface{}, error) {
663-
if signingMethod, ok := tk.Method.(*jwt.SigningMethodRSA); !ok {
664-
t.Fatalf("unexpected signing method %T", signingMethod)
666+
algo := jwt.SigningMethodPS256.Alg()
667+
if file.testAuthority != fakeAuthority {
668+
algo = jwt.SigningMethodRS256.Alg()
669+
}
670+
if tk.Method.Alg() != algo {
671+
t.Fatalf("unexpected signing method %v", tk.Method.Alg())
665672
}
666673
return verifyingKey, nil
667674
})

apps/internal/oauth/ops/accesstokens/accesstokens.go

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717

1818
/* #nosec */
1919
"crypto/sha1"
20+
"crypto/sha256"
2021
"crypto/x509"
2122
"encoding/base64"
2223
"encoding/json"
@@ -112,19 +113,31 @@ func (c *Credential) JWT(ctx context.Context, authParams authority.AuthParams) (
112113
}
113114
return c.AssertionCallback(ctx, options)
114115
}
115-
116-
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
116+
claims := jwt.MapClaims{
117117
"aud": authParams.Endpoints.TokenEndpoint,
118118
"exp": json.Number(strconv.FormatInt(time.Now().Add(10*time.Minute).Unix(), 10)),
119119
"iss": authParams.ClientID,
120120
"jti": uuid.New().String(),
121121
"nbf": json.Number(strconv.FormatInt(time.Now().Unix(), 10)),
122122
"sub": authParams.ClientID,
123-
})
123+
}
124+
125+
isADFSorDSTS := authParams.AuthorityInfo.AuthorityType == authority.ADFS ||
126+
authParams.AuthorityInfo.AuthorityType == authority.DSTS
127+
128+
var signingMethod jwt.SigningMethod = jwt.SigningMethodPS256
129+
thumbprintKey := "x5t#S256"
130+
131+
if isADFSorDSTS {
132+
signingMethod = jwt.SigningMethodRS256
133+
thumbprintKey = "x5t"
134+
}
135+
136+
token := jwt.NewWithClaims(signingMethod, claims)
124137
token.Header = map[string]interface{}{
125-
"alg": "RS256",
126-
"typ": "JWT",
127-
"x5t": base64.StdEncoding.EncodeToString(thumbprint(c.Cert)),
138+
"alg": signingMethod.Alg(),
139+
"typ": "JWT",
140+
thumbprintKey: base64.StdEncoding.EncodeToString(thumbprint(c.Cert, signingMethod.Alg())),
128141
}
129142

130143
if authParams.SendX5C {
@@ -133,17 +146,23 @@ func (c *Credential) JWT(ctx context.Context, authParams authority.AuthParams) (
133146

134147
assertion, err := token.SignedString(c.Key)
135148
if err != nil {
136-
return "", fmt.Errorf("unable to sign a JWT token using private key: %w", err)
149+
return "", fmt.Errorf("unable to sign JWT token: %w", err)
137150
}
151+
138152
return assertion, nil
139153
}
140154

141155
// thumbprint runs the asn1.Der bytes through sha1 for use in the x5t parameter of JWT.
142156
// https://tools.ietf.org/html/rfc7517#section-4.8
143-
func thumbprint(cert *x509.Certificate) []byte {
144-
/* #nosec */
145-
a := sha1.Sum(cert.Raw)
146-
return a[:]
157+
func thumbprint(cert *x509.Certificate, alg string) []byte {
158+
switch alg {
159+
case jwt.SigningMethodRS256.Name: // identity providers like ADFS don't support SHA256 assertions, so need to support this
160+
hash := sha1.Sum(cert.Raw) /* #nosec */
161+
return hash[:]
162+
default:
163+
hash := sha256.Sum256(cert.Raw)
164+
return hash[:]
165+
}
147166
}
148167

149168
// Client represents the REST calls to get tokens from token generator backends.

apps/tests/integration/integration_test.go

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const (
3737
// Default values
3838
defaultClientId = "f62c5ae3-bf3a-4af5-afa8-a68b800396e9"
3939
pemFile = "../../../cert.pem"
40+
ccaPemFile = "../../../ccaCert.pem"
4041
)
4142

4243
var httpClient = http.Client{}
@@ -96,7 +97,7 @@ type secret struct {
9697
}
9798

9899
func newLabClient() (*labClient, error) {
99-
cert, privateKey, err := getCertDataFromFile()
100+
cert, privateKey, err := getCertDataFromFile(pemFile)
100101
if err != nil {
101102
return nil, fmt.Errorf("Could not get cert data: %w", err)
102103
}
@@ -114,8 +115,8 @@ func newLabClient() (*labClient, error) {
114115
return &labClient{app: app}, nil
115116
}
116117

117-
func getCertDataFromFile() ([]*x509.Certificate, crypto.PrivateKey, error) {
118-
data, err := os.ReadFile(pemFile)
118+
func getCertDataFromFile(filePath string) ([]*x509.Certificate, crypto.PrivateKey, error) {
119+
data, err := os.ReadFile(filePath)
119120
if err != nil {
120121
fmt.Printf("Error finding certificate: %v\n", err)
121122
}
@@ -480,3 +481,33 @@ func TestAccountFromCache(t *testing.T) {
480481
}
481482

482483
}
484+
485+
func TestAdfsToken(t *testing.T) {
486+
if testing.Short() {
487+
t.Skip("skipping integration test")
488+
}
489+
490+
cert, privateKey, err := getCertDataFromFile(ccaPemFile)
491+
if err != nil {
492+
t.Fatal("Could not get cert data: %w", err)
493+
}
494+
495+
cred, err := confidential.NewCredFromCert(cert, privateKey)
496+
if err != nil {
497+
t.Fatal(err)
498+
}
499+
500+
app, err := confidential.New("https://fs.msidlab8.com/adfs", "ConfidentialClientId", cred)
501+
if err != nil {
502+
t.Fatal(err)
503+
}
504+
scopes := []string{"openid"}
505+
result, err := app.AcquireTokenByCredential(context.Background(), scopes)
506+
if err != nil {
507+
t.Fatalf("TestConfidentialClientwithSecret: on AcquireTokenByCredential(): got err == %s, want err == nil", errors.Verbose(err))
508+
}
509+
if result.AccessToken == "" {
510+
t.Fatal("TestConfidentialClientwithSecret: on AcquireTokenByCredential(): got AccessToken == '', want AccessToken != ''")
511+
}
512+
513+
}

0 commit comments

Comments
 (0)