|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { |
|
ConnectionOptions, |
|
createSecureContext, |
|
PeerCertificate, |
|
SecureContext, |
|
} from 'tls'; |
|
|
|
import { CallCredentials } from './call-credentials'; |
|
import { CIPHER_SUITES, getDefaultRootsData } from './tls-helpers'; |
|
|
|
|
|
function verifyIsBufferOrNull(obj: any, friendlyName: string): void { |
|
if (obj && !(obj instanceof Buffer)) { |
|
throw new TypeError(`${friendlyName}, if provided, must be a Buffer.`); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export type CheckServerIdentityCallback = ( |
|
hostname: string, |
|
cert: PeerCertificate |
|
) => Error | undefined; |
|
|
|
|
|
|
|
|
|
|
|
export interface VerifyOptions { |
|
|
|
|
|
|
|
|
|
checkServerIdentity?: CheckServerIdentityCallback; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
export abstract class ChannelCredentials { |
|
protected callCredentials: CallCredentials; |
|
|
|
protected constructor(callCredentials?: CallCredentials) { |
|
this.callCredentials = callCredentials || CallCredentials.createEmpty(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
abstract compose(callCredentials: CallCredentials): ChannelCredentials; |
|
|
|
|
|
|
|
|
|
_getCallCredentials(): CallCredentials { |
|
return this.callCredentials; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
abstract _getConnectionOptions(): ConnectionOptions | null; |
|
|
|
|
|
|
|
|
|
abstract _isSecure(): boolean; |
|
|
|
|
|
|
|
|
|
|
|
|
|
abstract _equals(other: ChannelCredentials): boolean; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static createSsl( |
|
rootCerts?: Buffer | null, |
|
privateKey?: Buffer | null, |
|
certChain?: Buffer | null, |
|
verifyOptions?: VerifyOptions |
|
): ChannelCredentials { |
|
verifyIsBufferOrNull(rootCerts, 'Root certificate'); |
|
verifyIsBufferOrNull(privateKey, 'Private key'); |
|
verifyIsBufferOrNull(certChain, 'Certificate chain'); |
|
if (privateKey && !certChain) { |
|
throw new Error( |
|
'Private key must be given with accompanying certificate chain' |
|
); |
|
} |
|
if (!privateKey && certChain) { |
|
throw new Error( |
|
'Certificate chain must be given with accompanying private key' |
|
); |
|
} |
|
const secureContext = createSecureContext({ |
|
ca: rootCerts ?? getDefaultRootsData() ?? undefined, |
|
key: privateKey ?? undefined, |
|
cert: certChain ?? undefined, |
|
ciphers: CIPHER_SUITES, |
|
}); |
|
return new SecureChannelCredentialsImpl(secureContext, verifyOptions ?? {}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static createFromSecureContext( |
|
secureContext: SecureContext, |
|
verifyOptions?: VerifyOptions |
|
): ChannelCredentials { |
|
return new SecureChannelCredentialsImpl(secureContext, verifyOptions ?? {}); |
|
} |
|
|
|
|
|
|
|
|
|
static createInsecure(): ChannelCredentials { |
|
return new InsecureChannelCredentialsImpl(); |
|
} |
|
} |
|
|
|
class InsecureChannelCredentialsImpl extends ChannelCredentials { |
|
constructor(callCredentials?: CallCredentials) { |
|
super(callCredentials); |
|
} |
|
|
|
compose(callCredentials: CallCredentials): never { |
|
throw new Error('Cannot compose insecure credentials'); |
|
} |
|
|
|
_getConnectionOptions(): ConnectionOptions | null { |
|
return null; |
|
} |
|
_isSecure(): boolean { |
|
return false; |
|
} |
|
_equals(other: ChannelCredentials): boolean { |
|
return other instanceof InsecureChannelCredentialsImpl; |
|
} |
|
} |
|
|
|
class SecureChannelCredentialsImpl extends ChannelCredentials { |
|
connectionOptions: ConnectionOptions; |
|
|
|
constructor( |
|
private secureContext: SecureContext, |
|
private verifyOptions: VerifyOptions |
|
) { |
|
super(); |
|
this.connectionOptions = { |
|
secureContext, |
|
}; |
|
|
|
if (verifyOptions?.checkServerIdentity) { |
|
this.connectionOptions.checkServerIdentity = |
|
verifyOptions.checkServerIdentity; |
|
} |
|
} |
|
|
|
compose(callCredentials: CallCredentials): ChannelCredentials { |
|
const combinedCallCredentials = |
|
this.callCredentials.compose(callCredentials); |
|
return new ComposedChannelCredentialsImpl(this, combinedCallCredentials); |
|
} |
|
|
|
_getConnectionOptions(): ConnectionOptions | null { |
|
|
|
return { ...this.connectionOptions }; |
|
} |
|
_isSecure(): boolean { |
|
return true; |
|
} |
|
_equals(other: ChannelCredentials): boolean { |
|
if (this === other) { |
|
return true; |
|
} |
|
if (other instanceof SecureChannelCredentialsImpl) { |
|
return ( |
|
this.secureContext === other.secureContext && |
|
this.verifyOptions.checkServerIdentity === |
|
other.verifyOptions.checkServerIdentity |
|
); |
|
} else { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
class ComposedChannelCredentialsImpl extends ChannelCredentials { |
|
constructor( |
|
private channelCredentials: SecureChannelCredentialsImpl, |
|
callCreds: CallCredentials |
|
) { |
|
super(callCreds); |
|
} |
|
compose(callCredentials: CallCredentials) { |
|
const combinedCallCredentials = |
|
this.callCredentials.compose(callCredentials); |
|
return new ComposedChannelCredentialsImpl( |
|
this.channelCredentials, |
|
combinedCallCredentials |
|
); |
|
} |
|
|
|
_getConnectionOptions(): ConnectionOptions | null { |
|
return this.channelCredentials._getConnectionOptions(); |
|
} |
|
_isSecure(): boolean { |
|
return true; |
|
} |
|
_equals(other: ChannelCredentials): boolean { |
|
if (this === other) { |
|
return true; |
|
} |
|
if (other instanceof ComposedChannelCredentialsImpl) { |
|
return ( |
|
this.channelCredentials._equals(other.channelCredentials) && |
|
this.callCredentials._equals(other.callCredentials) |
|
); |
|
} else { |
|
return false; |
|
} |
|
} |
|
} |
|
|