wangzhibo
2026-05-09 53418655c061ac26f66058253f39200aced1ab1d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */
 
import { ICrypto, SignedHttpRequestParameters } from "./ICrypto.js";
import * as TimeUtils from "../utils/TimeUtils.js";
import { UrlString } from "../url/UrlString.js";
import { IPerformanceClient } from "../telemetry/performance/IPerformanceClient.js";
import * as PerformanceEvents from "../telemetry/performance/PerformanceEvents.js";
import { invokeAsync } from "../utils/FunctionWrappers.js";
import { Logger } from "../logger/Logger.js";
 
/**
 * See eSTS docs for more info.
 * - A kid element, with the value containing an RFC 7638-compliant JWK thumbprint that is base64 encoded.
 * -  xms_ksl element, representing the storage location of the key's secret component on the client device. One of two values:
 *      - sw: software storage
 *      - uhw: hardware storage
 */
type ReqCnf = {
    kid: string;
    xms_ksl: KeyLocation;
};
 
export type ReqCnfData = {
    kid: string;
    reqCnfString: string;
};
 
const KeyLocation = {
    SW: "sw",
    UHW: "uhw",
} as const;
export type KeyLocation = (typeof KeyLocation)[keyof typeof KeyLocation];
 
/** @internal */
export class PopTokenGenerator {
    private cryptoUtils: ICrypto;
    private performanceClient: IPerformanceClient;
 
    constructor(cryptoUtils: ICrypto, performanceClient: IPerformanceClient) {
        this.cryptoUtils = cryptoUtils;
        this.performanceClient = performanceClient;
    }
 
    /**
     * Generates the req_cnf validated at the RP in the POP protocol for SHR parameters
     * and returns an object containing the keyid, the full req_cnf string and the req_cnf string hash
     * @param request
     * @returns
     */
    async generateCnf(
        request: SignedHttpRequestParameters,
        logger: Logger
    ): Promise<ReqCnfData> {
        const reqCnf = await invokeAsync(
            this.generateKid.bind(this),
            PerformanceEvents.PopTokenGenerateCnf,
            logger,
            this.performanceClient,
            request.correlationId
        )(request);
        const reqCnfString: string = this.cryptoUtils.base64UrlEncode(
            JSON.stringify(reqCnf)
        );
 
        return {
            kid: reqCnf.kid,
            reqCnfString,
        };
    }
 
    /**
     * Generates key_id for a SHR token request
     * @param request
     * @returns
     */
    async generateKid(request: SignedHttpRequestParameters): Promise<ReqCnf> {
        const kidThumbprint = await this.cryptoUtils.getPublicKeyThumbprint(
            request
        );
 
        return {
            kid: kidThumbprint,
            xms_ksl: KeyLocation.SW,
        };
    }
 
    /**
     * Signs the POP access_token with the local generated key-pair
     * @param accessToken
     * @param request
     * @returns
     */
    async signPopToken(
        accessToken: string,
        keyId: string,
        request: SignedHttpRequestParameters
    ): Promise<string> {
        return this.signPayload(accessToken, keyId, request);
    }
 
    /**
     * Utility function to generate the signed JWT for an access_token
     * @param payload
     * @param kid
     * @param request
     * @param claims
     * @returns
     */
    async signPayload(
        payload: string,
        keyId: string,
        request: SignedHttpRequestParameters,
        claims?: object
    ): Promise<string> {
        // Deconstruct request to extract SHR parameters
        const {
            resourceRequestMethod,
            resourceRequestUri,
            shrClaims,
            shrNonce,
            shrOptions,
        } = request;
 
        const resourceUrlString = resourceRequestUri
            ? new UrlString(resourceRequestUri)
            : undefined;
        const resourceUrlComponents = resourceUrlString?.getUrlComponents();
        return this.cryptoUtils.signJwt(
            {
                at: payload,
                ts: TimeUtils.nowSeconds(),
                m: resourceRequestMethod?.toUpperCase(),
                u: resourceUrlComponents?.HostNameAndPort,
                nonce: shrNonce || this.cryptoUtils.createNewGuid(),
                p: resourceUrlComponents?.AbsolutePath,
                q: resourceUrlComponents?.QueryString
                    ? [[], resourceUrlComponents.QueryString]
                    : undefined,
                client_claims: shrClaims || undefined,
                ...claims,
            },
            keyId,
            shrOptions,
            request.correlationId
        );
    }
}