@@ -17,8 +17,16 @@ import { Subscription } from "rxjs/Subscription";
1717import { promisify } from "util" ;
1818import { Readable } from "stream" ;
1919import { URL } from "url" ;
20-
21- import { ProtocolClient , Content , ContentSerdes , Form , SecurityScheme , createLoggers } from "@node-wot/core" ;
20+ import {
21+ ProtocolClient ,
22+ Content ,
23+ ContentSerdes ,
24+ Form ,
25+ SecurityScheme ,
26+ createLoggers ,
27+ AllOfSecurityScheme ,
28+ OneOfSecurityScheme ,
29+ } from "@node-wot/core" ;
2230
2331import {
2432 ClientSession ,
@@ -36,19 +44,29 @@ import {
3644 VariantArrayType ,
3745 Variant ,
3846 VariantOptions ,
47+ SecurityPolicy ,
3948} from "node-opcua-client" ;
40- import { ArgumentDefinition , getBuiltInDataType , readNamespaceArray } from "node-opcua-pseudo-session" ;
41-
49+ import {
50+ AnonymousIdentity ,
51+ ArgumentDefinition ,
52+ getBuiltInDataType ,
53+ readNamespaceArray ,
54+ UserIdentityInfo ,
55+ } from "node-opcua-pseudo-session" ;
4256import { makeNodeId , NodeId , NodeIdLike , NodeIdType , resolveNodeId } from "node-opcua-nodeid" ;
4357import { AttributeIds , BrowseDirection , makeResultMask } from "node-opcua-data-model" ;
4458import { makeBrowsePath } from "node-opcua-service-translate-browse-path" ;
4559import { StatusCodes } from "node-opcua-status-code" ;
46-
47- import { schemaDataValue } from "./codec" ;
60+ import { coercePrivateKeyPem , readPrivateKey } from "node-opcua-crypto" ;
4861import { opcuaJsonEncodeVariant } from "node-opcua-json" ;
49- import { Argument , BrowseDescription , BrowseResult } from "node-opcua-types" ;
62+ import { Argument , BrowseDescription , BrowseResult , MessageSecurityMode , UserTokenType } from "node-opcua-types" ;
5063import { isGoodish2 , ReferenceTypeIds } from "node-opcua" ;
5164
65+ import { schemaDataValue } from "./codec" ;
66+ import { OPCUACAuthenticationScheme , OPCUAChannelSecurityScheme } from "./security_scheme" ;
67+ import { CertificateManagerSingleton } from "./certificate-manager-singleton" ;
68+ import { resolveChannelSecurity , resolvedUserIdentity } from "./opcua-security-resolver" ;
69+
5270const { debug } = createLoggers ( "binding-opcua" , "opcua-protocol-client" ) ;
5371
5472export type Command = "Read" | "Write" | "Subscribe" ;
@@ -141,6 +159,10 @@ function _variantToJSON(variant: Variant, contentType: string) {
141159export class OPCUAProtocolClient implements ProtocolClient {
142160 private _connections : Map < string , OPCUAConnectionEx > = new Map < string , OPCUAConnectionEx > ( ) ;
143161
162+ private _securityMode : MessageSecurityMode = MessageSecurityMode . None ;
163+ private _securityPolicy : SecurityPolicy = SecurityPolicy . None ;
164+ private _userIdentity : UserIdentityInfo = < AnonymousIdentity > { type : UserTokenType . Anonymous } ;
165+
144166 private async _withConnection < T > ( form : OPCUAForm , next : ( connection : OPCUAConnection ) => Promise < T > ) : Promise < T > {
145167 const endpoint = form . href ;
146168 const matchesScheme : boolean = endpoint ?. match ( / ^ o p c .t c p : \/ \/ / ) != null ;
@@ -150,11 +172,15 @@ export class OPCUAProtocolClient implements ProtocolClient {
150172 }
151173 let c : OPCUAConnectionEx | undefined = this . _connections . get ( endpoint ) ;
152174 if ( ! c ) {
175+ const clientCertificateManager = await CertificateManagerSingleton . getCertificateManager ( ) ;
153176 const client = OPCUAClient . create ( {
154177 endpointMustExist : false ,
155178 connectionStrategy : {
156179 maxRetry : 1 ,
157180 } ,
181+ securityMode : this . _securityMode ,
182+ securityPolicy : this . _securityPolicy ,
183+ clientCertificateManager,
158184 } ) ;
159185 client . on ( "backoff" , ( ) => {
160186 debug ( `connection:backoff: cannot connection to ${ endpoint } ` ) ;
@@ -168,7 +194,19 @@ export class OPCUAProtocolClient implements ProtocolClient {
168194 this . _connections . set ( endpoint , c ) ;
169195 try {
170196 await client . connect ( endpoint ) ;
171- const session = await client . createSession ( ) ;
197+ } catch ( err ) {
198+ const errMessage = "Cannot connected to endpoint " + endpoint + "\nmsg = " + ( < Error > err ) . message ;
199+ debug ( errMessage ) ;
200+ throw new Error ( errMessage ) ;
201+ }
202+ try {
203+ // adjust with private key
204+ if ( this . _userIdentity . type === UserTokenType . Certificate && ! this . _userIdentity . privateKey ) {
205+ const internalKey = readPrivateKey ( client . clientCertificateManager . privateKey ) ;
206+ const privateKeyPem = coercePrivateKeyPem ( internalKey ) ;
207+ this . _userIdentity . privateKey = privateKeyPem ;
208+ }
209+ const session = await client . createSession ( this . _userIdentity ) ;
172210 c . session = session ;
173211
174212 const subscription = await session . createSubscription2 ( {
@@ -187,7 +225,10 @@ export class OPCUAProtocolClient implements ProtocolClient {
187225
188226 this . _connections . set ( endpoint , c ) ;
189227 } catch ( err ) {
190- throw new Error ( "Cannot connected to endpoint " + endpoint + "\nmsg = " + ( < Error > err ) . message ) ;
228+ await client . disconnect ( ) ;
229+ const errMessage = "Cannot handle session on " + endpoint + "\nmsg = " + ( < Error > err ) . message ;
230+ debug ( errMessage ) ;
231+ throw new Error ( errMessage ) ;
191232 }
192233 }
193234 if ( c . pending ) {
@@ -464,16 +505,56 @@ export class OPCUAProtocolClient implements ProtocolClient {
464505
465506 async stop ( ) : Promise < void > {
466507 debug ( "stop" ) ;
467- for ( const c of this . _connections . values ( ) ) {
468- await c . subscription . terminate ( ) ;
469- await c . session . close ( ) ;
470- await c . client . disconnect ( ) ;
508+ for ( const connection of this . _connections . values ( ) ) {
509+ await connection . subscription . terminate ( ) ;
510+ await connection . session . close ( ) ;
511+ await connection . client . disconnect ( ) ;
471512 }
513+ CertificateManagerSingleton . releaseCertificateManager ( ) ;
514+ }
515+
516+ #setChannelSecurity( security : OPCUAChannelSecurityScheme ) : boolean {
517+ const { messageSecurityMode, securityPolicy } = resolveChannelSecurity ( security ) ;
518+ this . _securityMode = messageSecurityMode ;
519+ this . _securityPolicy = securityPolicy ;
520+ return true ;
472521 }
473522
474- setSecurity ( metadata : SecurityScheme [ ] , credentials ?: unknown ) : boolean {
523+ #setAuthentication( security : OPCUACAuthenticationScheme ) : boolean {
524+ this . _userIdentity = resolvedUserIdentity ( security ) ;
525+ return true ;
526+ }
527+
528+ setSecurity ( securitySchemes : SecurityScheme [ ] , credentials ?: unknown ) : boolean {
529+ for ( const securityScheme of securitySchemes ) {
530+ let success = true ;
531+ switch ( securityScheme . scheme ) {
532+ case "uav:channel-security" :
533+ success = this . #setChannelSecurity( securityScheme as OPCUAChannelSecurityScheme ) ;
534+ break ;
535+ case "uav:authentication" :
536+ success = this . #setAuthentication( securityScheme as OPCUACAuthenticationScheme ) ;
537+ break ;
538+ case "combo" : {
539+ const combo = securityScheme as AllOfSecurityScheme | OneOfSecurityScheme ;
540+ if ( combo . allOf !== undefined ) {
541+ success = this . setSecurity ( combo . allOf , credentials ) ;
542+ } else if ( combo . oneOf !== undefined ) {
543+ // pick the first one for now
544+ // later we might use credentials to select the most appropriate one
545+ success = this . setSecurity ( [ combo . oneOf [ 0 ] ] , credentials ) ;
546+ } else {
547+ success = false ;
548+ }
549+ break ;
550+ }
551+ default :
552+ // not for us , ignored
553+ break ;
554+ }
555+ if ( ! success ) return false ;
556+ }
475557 return true ;
476- // throw new Error("Method not implemented.");
477558 }
478559
479560 private _monitoredItems : Map <
0 commit comments