This is an automated email from the ASF dual-hosted git repository.

ocket8888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new 72e93a1602 TPv1 Add the ability to inspect a cert/DS (#7618)
72e93a1602 is described below

commit 72e93a1602131faa3059841a67a28c3215c56aaf
Author: Steve Hamrick <[email protected]>
AuthorDate: Mon Jul 10 00:25:35 2023 -0600

    TPv1 Add the ability to inspect a cert/DS (#7618)
    
    * Implement cert inspector in tpv1
    
    * Add changelog
    
    ---------
    
    Co-authored-by: Steve Hamrick <[email protected]>
    Co-authored-by: ocket8888 <[email protected]>
---
 CHANGELOG.md                                       |   1 +
 traffic_portal/app/src/app.js                      |   3 +
 .../FormDeliveryServiceSslKeysController.js        |   5 +
 .../form.deliveryServiceSslKeys.tpl.html           | 174 +++++++++++-------
 .../modules/form/ssl/CertInspectController.js      |  35 ++++
 .../src/common/modules/form/ssl/_inspect-ssl.scss  |  25 +++
 .../common/modules/form/ssl/cert-inspect.tpl.html  |  47 +++++
 .../app/src/common/modules/form/ssl/index.js       |  16 ++
 .../common/modules/navigation/navigation.tpl.html  |   1 +
 .../src/common/modules/ssl/CertAuthorController.js |  36 ++++
 .../app/src/common/modules/ssl/CertViewer.d.ts     |  44 +++++
 .../src/common/modules/ssl/CertViewerController.js | 200 +++++++++++++++++++++
 .../app/src/common/modules/ssl/_ssl.scss           |  28 +++
 .../src/common/modules/ssl/cert-author.tpl.html    |  66 +++++++
 .../app/src/common/modules/ssl/cert-view.tpl.html  |  99 ++++++++++
 traffic_portal/app/src/common/modules/ssl/index.js |  17 ++
 .../app/src/modules/private/ssl/index.js           |  28 +++
 .../src/modules/private/ssl/inspect-cert.tpl.html  |  18 ++
 traffic_portal/app/src/scripts/shared-libs.js      |   3 +
 traffic_portal/app/src/styles/main.scss            |   2 +
 traffic_portal/package-lock.json                   |  59 ++++--
 traffic_portal/package.json                        |   4 +-
 22 files changed, 833 insertions(+), 78 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 60d99aad61..6abfae4288 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,7 @@ The format is based on [Keep a 
Changelog](http://keepachangelog.com/en/1.0.0/).
 - [#7543](https://github.com/apache/trafficcontrol/pull/7543) *Traffic Portal* 
New Ansible Role to use Traffic Portal v2
 - [#7516](https://github.com/apache/trafficcontrol/pull/7516) *t3c* added 
command line arg to control go_direct in parent.config
 - [#7602](https://github.com/apache/trafficcontrol/pull/7602) *t3c* added 
installed package data to t3c-apply-metadata.json
+- [#7618](https://github.com/apache/trafficcontrol/pull/7618) *Traffic Portal* 
Add the ability to inspect a user provider cert, or the cert chain on DS SSL 
keys.
 - [#7619](https://github.com/apache/trafficcontrol/pull/7619) Traffic Ops* 
added optional field `oauth_user_attribute` for OAuth login credentials
 
 ### Changed
diff --git a/traffic_portal/app/src/app.js b/traffic_portal/app/src/app.js
index 92bf0ddec0..e4de35b0b8 100644
--- a/traffic_portal/app/src/app.js
+++ b/traffic_portal/app/src/app.js
@@ -72,6 +72,7 @@ var trafficPortal = angular.module('trafficPortal', [
         require('./modules/private/capabilities/users').name,
         require('./modules/private/cdns').name,
         require('./modules/private/cdns/config').name,
+        require("./modules/private/ssl").name,
         require('./modules/private/cdns/deliveryServices').name,
         require('./modules/private/cdns/dnssecKeys').name,
         require('./modules/private/cdns/dnssecKeys/generate').name,
@@ -260,6 +261,7 @@ var trafficPortal = angular.module('trafficPortal', [
         require('./common/modules/locks').name,
         require('./common/modules/message').name,
         require('./common/modules/navigation').name,
+        require("./common/modules/ssl").name,
         require('./common/modules/notifications').name,
         require('./common/modules/release').name,
 
@@ -334,6 +336,7 @@ var trafficPortal = angular.module('trafficPortal', [
         require('./common/modules/form/serviceCategory').name,
         require('./common/modules/form/serviceCategory/edit').name,
         require('./common/modules/form/serviceCategory/new').name,
+        require("./common/modules/form/ssl").name,
         require('./common/modules/form/status').name,
         require('./common/modules/form/status/edit').name,
         require('./common/modules/form/status/new').name,
diff --git 
a/traffic_portal/app/src/common/modules/form/deliveryServiceSslKeys/FormDeliveryServiceSslKeysController.js
 
b/traffic_portal/app/src/common/modules/form/deliveryServiceSslKeys/FormDeliveryServiceSslKeysController.js
index 64a2644665..4037e35dc9 100644
--- 
a/traffic_portal/app/src/common/modules/form/deliveryServiceSslKeys/FormDeliveryServiceSslKeysController.js
+++ 
b/traffic_portal/app/src/common/modules/form/deliveryServiceSslKeys/FormDeliveryServiceSslKeysController.js
@@ -87,6 +87,11 @@ var FormDeliveryServiceSslKeysController = 
function(deliveryService, sslKeys, $s
                locationUtils.navigateToPath('/delivery-services/' + 
deliveryService.id + '/ssl-keys/generate');
        };
 
+       $scope.navState = 0;
+       $scope.updateState = function(newState) {
+               $scope.navState = newState;
+       }
+
        $scope.renewCert = function() {
                var params = {
                        title: 'Renew SSL Keys for Delivery Service: ' + 
deliveryService.xmlId
diff --git 
a/traffic_portal/app/src/common/modules/form/deliveryServiceSslKeys/form.deliveryServiceSslKeys.tpl.html
 
b/traffic_portal/app/src/common/modules/form/deliveryServiceSslKeys/form.deliveryServiceSslKeys.tpl.html
index 053489d19c..fd59128b30 100644
--- 
a/traffic_portal/app/src/common/modules/form/deliveryServiceSslKeys/form.deliveryServiceSslKeys.tpl.html
+++ 
b/traffic_portal/app/src/common/modules/form/deliveryServiceSslKeys/form.deliveryServiceSslKeys.tpl.html
@@ -21,12 +21,15 @@ under the License.
     <div class="x_title">
         <ol class="breadcrumb pull-left">
             <li><a ng-click="navigateToPath('/delivery-services')">Delivery 
Services</a></li>
-            <li><a ng-click="navigateToPath('/delivery-services/' + 
deliveryService.id + '?dsType=' + 
deliveryService.type)">{{deliveryService.xmlId}}</a></li>
+            <li>
+                <a ng-click="navigateToPath('/delivery-services/' + 
deliveryService.id + '?dsType=' + 
deliveryService.type)">{{deliveryService.xmlId}}</a>
+            </li>
             <li class="active">SSL keys</li>
         </ol>
         <div class="pull-right" role="group">
             <div class="btn-group" role="group" uib-dropdown 
is-open="more.isopen">
-                <button type="button" class="btn btn-default dropdown-toggle" 
uib-dropdown-toggle aria-haspopup="true" aria-expanded="false">
+                <button type="button" class="btn btn-default dropdown-toggle" 
uib-dropdown-toggle aria-haspopup="true"
+                        aria-expanded="false">
                     More&nbsp;
                     <span class="caret"></span>
                 </button>
@@ -39,69 +42,112 @@ under the License.
         <div class="clearfix"></div>
     </div>
     <div class="x_content">
-        <br>
-        <form name="dsSslKeyForm" class="form-horizontal form-label-left" 
novalidate>
-            <div class="form-group">
-                <label for="version" class="control-label col-md-2 col-sm-2 
col-xs-12">Version</label>
-                <div class="col-md-10 col-sm-10 col-xs-12">
-                    <input id="version" name="version" type="text" 
class="form-control" ng-model="sslKeys.version" readonly>
-                </div>
+        <ul class="nav nav-tabs" role="tablist">
+            <li role="presentation" ng-class="{active: navState === 0 }">
+                <a data-toggle="tab" ng-click="updateState(0)">Stored</a>
+            </li>
+            <li role="presentation" ng-class="{active: navState === 1 }">
+                <a data-toggle="tab" ng-click="updateState(1)">Processed</a>
+            </li>
+        </ul>
+        <br >
+        <div class="tab-content">
+            <div class="tab-pane" role="tabpanel" ng-class="{show: navState 
=== 0 }">
+                <form name="dsSslKeyForm" class="form-horizontal 
form-label-left" novalidate>
+                    <div class="form-group">
+                        <label for="version" class="control-label col-md-2 
col-sm-2 col-xs-12">Version</label>
+                        <div class="col-md-10 col-sm-10 col-xs-12">
+                            <input id="version" name="version" type="text" 
class="form-control" ng-model="sslKeys.version"
+                                   readonly>
+                        </div>
+                    </div>
+                    <div class="form-group"
+                         ng-class="{'has-error': 
hasError(dsSslKeyForm.hostname), 'has-feedback': 
hasError(dsSslKeyForm.hostname)}">
+                        <label for="hostname" class="control-label col-md-2 
col-sm-2 col-xs-12">Common Name *</label>
+                        <div class="col-md-10 col-sm-10 col-xs-12">
+                            <input id="hostname" name="hostname" type="text" 
class="form-control" ng-model="sslKeys.hostname"
+                                   required autofocus>
+                            <small class="input-error"
+                                   
ng-show="hasPropertyError(dsSslKeyForm.hostname, 'required')">Required</small>
+                            <span ng-show="hasError(dsSslKeyForm.hostname)" 
class="form-control-feedback"><i
+                                    class="fa fa-times"></i></span>
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label for="expiration" class="control-label col-md-2 
col-sm-2 col-xs-12">Expiration</label>
+                        <div class="col-md-10 col-sm-10 col-xs-12">
+                            <output name="expiration" 
class="form-control">{{formattedExpiration}}</output>
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label for="sans" class="control-label col-md-2 
col-sm-2 col-xs-12">SANs</label>
+                        <div class="col-md-10 col-sm-10 col-xs-12">
+                            <output name="sans" 
class="form-control">{{sans}}</output>
+                        </div>
+                    </div>
+                    <div class="form-group"
+                         ng-class="{'has-error': 
hasError(dsSslKeyForm.authType), 'has-feedback': 
hasError(dsSslKeyForm.authType)}">
+                        <label for="authType" class="control-label col-md-2 
col-sm-2 col-xs-12">Certificate Source (Self Signed,
+                            CA, etc) *</label>
+                        <div class="col-md-10 col-sm-10 col-xs-12">
+                            <select id="authType" name="authType" type="text" 
class="form-control" ng-model="acmeProvider"
+                                    ng-options="acmeProviderOption as 
acmeProviderOption for acmeProviderOption in acmeProviders"
+                                    ng-change="updateProvider()" 
required></select>
+                            <small class="input-error"
+                                   
ng-show="hasPropertyError(dsSslKeyForm.authType, 'required')">Required</small>
+                            <span ng-show="hasError(dsSslKeyForm.authType)" 
class="form-control-feedback"><i
+                                    class="fa fa-times"></i></span>
+                        </div>
+                    </div>
+                    <div class="form-group"
+                         ng-class="{'has-error': 
hasError(dsSslKeyForm.privateKey), 'has-feedback': 
hasError(dsSslKeyForm.privateKey)}">
+                        <label for="privateKey" class="control-label col-md-2 
col-sm-2 col-xs-12">Private Key *</label>
+                        <div class="col-md-10 col-sm-10 col-xs-12">
+                    <textarea id="privateKey" name="privateKey" type="text" 
class="form-control private-text"
+                              ng-model="sslKeys.certificate.key" rows="25" 
required></textarea>
+                            <small class="input-error"
+                                   
ng-show="hasPropertyError(dsSslKeyForm.privateKey, 'required')">Required</small>
+                            <span ng-show="hasError(dsSslKeyForm.privateKey)" 
class="form-control-feedback"><i
+                                    class="fa fa-times"></i></span>
+                        </div>
+                    </div>
+                    <div class="form-group"
+                         ng-class="{'has-error': 
hasError(dsSslKeyForm.certificateSigningRequest), 'has-feedback': 
hasError(dsSslKeyForm.certificateSigningRequest)}"
+                         ng-if="sslKeys.authType !== 'Lets Encrypt'">
+                        <label for="certificateSigningRequest" 
class="control-label col-md-2 col-sm-2 col-xs-12">Certificate
+                            Signing Request *</label>
+                        <div class="col-md-10 col-sm-10 col-xs-12">
+                    <textarea id="certificateSigningRequest" 
name="certificateSigningRequest" type="text"
+                              class="form-control" 
ng-model="sslKeys.certificate.csr" rows="25"
+                              ng-required="sslKeys.authType !== 'Lets 
Encrypt'"></textarea>
+                            <small class="input-error"
+                                   
ng-show="hasPropertyError(dsSslKeyForm.certificateSigningRequest, 
'required')">Required</small>
+                            <span 
ng-show="hasError(dsSslKeyForm.certificateSigningRequest)" 
class="form-control-feedback"><i
+                                    class="fa fa-times"></i></span>
+                        </div>
+                    </div>
+                    <div class="form-group"
+                         ng-class="{'has-error': 
hasError(dsSslKeyForm.certificate), 'has-feedback': 
hasError(dsSslKeyForm.certificate)}">
+                        <label for="certificate" class="control-label col-md-2 
col-sm-2 col-xs-12">Certificate *</label>
+                        <div class="col-md-10 col-sm-10 col-xs-12">
+                    <textarea id="certificate" name="certificate" type="text" 
class="form-control"
+                              ng-model="sslKeys.certificate.crt" rows="25" 
required></textarea>
+                            <small class="input-error"
+                                   
ng-show="hasPropertyError(dsSslKeyForm.certificate, 
'required')">Required</small>
+                            <span ng-show="hasError(dsSslKeyForm.certificate)" 
class="form-control-feedback"><i
+                                    class="fa fa-times"></i></span>
+                        </div>
+                    </div>
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-success"
+                                ng-disabled="dsSslKeyForm.$pristine || 
dsSslKeyForm.$invalid" ng-click="save()">Update Keys
+                        </button>
+                    </div>
+                </form>
             </div>
-            <div class="form-group" ng-class="{'has-error': 
hasError(dsSslKeyForm.hostname), 'has-feedback': 
hasError(dsSslKeyForm.hostname)}">
-                <label for="hostname" class="control-label col-md-2 col-sm-2 
col-xs-12">Common Name *</label>
-                <div class="col-md-10 col-sm-10 col-xs-12">
-                    <input id="hostname" name="hostname" type="text" 
class="form-control" ng-model="sslKeys.hostname" required autofocus>
-                    <small class="input-error" 
ng-show="hasPropertyError(dsSslKeyForm.hostname, 'required')">Required</small>
-                    <span ng-show="hasError(dsSslKeyForm.hostname)" 
class="form-control-feedback"><i class="fa fa-times"></i></span>
-                </div>
+            <div class="tab-pane" role="tabpanel" ng-class="{show: navState 
=== 1 }">
+                <cert-viewer-controller 
chain="{{sslKeys.certificate.crt}}"></cert-viewer-controller>
             </div>
-                       <div class="form-group">
-                               <label for="expiration" class="control-label 
col-md-2 col-sm-2 col-xs-12">Expiration</label>
-                               <div class="col-md-10 col-sm-10 col-xs-12">
-                                       <output name="expiration" 
class="form-control">{{formattedExpiration}}</output>
-                               </div>
-                       </div>
-            <div class="form-group">
-                <label for="sans" class="control-label col-md-2 col-sm-2 
col-xs-12">SANs</label>
-                <div class="col-md-10 col-sm-10 col-xs-12">
-                    <output name="sans" class="form-control">{{sans}}</output>
-                </div>
-            </div>
-                       <div class="form-group" ng-class="{'has-error': 
hasError(dsSslKeyForm.authType), 'has-feedback': 
hasError(dsSslKeyForm.authType)}">
-                               <label for="authType" class="control-label 
col-md-2 col-sm-2 col-xs-12">Certificate Source (Self Signed, CA, etc) *</label>
-                               <div class="col-md-10 col-sm-10 col-xs-12">
-                                       <select id="authType" name="authType" 
type="text" class="form-control" ng-model="acmeProvider" 
ng-options="acmeProviderOption as acmeProviderOption for acmeProviderOption in 
acmeProviders" ng-change="updateProvider()" required></select>
-                                       <small class="input-error" 
ng-show="hasPropertyError(dsSslKeyForm.authType, 'required')">Required</small>
-                                       <span 
ng-show="hasError(dsSslKeyForm.authType)" class="form-control-feedback"><i 
class="fa fa-times"></i></span>
-                               </div>
-                       </div>
-            <div class="form-group" ng-class="{'has-error': 
hasError(dsSslKeyForm.privateKey), 'has-feedback': 
hasError(dsSslKeyForm.privateKey)}">
-                <label for="privateKey" class="control-label col-md-2 col-sm-2 
col-xs-12">Private Key *</label>
-                <div class="col-md-10 col-sm-10 col-xs-12">
-                    <textarea id="privateKey" name="privateKey" type="text" 
class="form-control private-text" ng-model="sslKeys.certificate.key" rows="25" 
required></textarea>
-                    <small class="input-error" 
ng-show="hasPropertyError(dsSslKeyForm.privateKey, 'required')">Required</small>
-                    <span ng-show="hasError(dsSslKeyForm.privateKey)" 
class="form-control-feedback"><i class="fa fa-times"></i></span>
-                </div>
-            </div>
-            <div class="form-group" ng-class="{'has-error': 
hasError(dsSslKeyForm.certificateSigningRequest), 'has-feedback': 
hasError(dsSslKeyForm.certificateSigningRequest)}" ng-if="sslKeys.authType !== 
'Lets Encrypt'">
-                <label for="certificateSigningRequest" class="control-label 
col-md-2 col-sm-2 col-xs-12">Certificate Signing Request *</label>
-                <div class="col-md-10 col-sm-10 col-xs-12">
-                    <textarea id="certificateSigningRequest" 
name="certificateSigningRequest" type="text" class="form-control" 
ng-model="sslKeys.certificate.csr" rows="25" ng-required="sslKeys.authType !== 
'Lets Encrypt'"></textarea>
-                    <small class="input-error" 
ng-show="hasPropertyError(dsSslKeyForm.certificateSigningRequest, 
'required')">Required</small>
-                    <span 
ng-show="hasError(dsSslKeyForm.certificateSigningRequest)" 
class="form-control-feedback"><i class="fa fa-times"></i></span>
-                </div>
-            </div>
-            <div class="form-group" ng-class="{'has-error': 
hasError(dsSslKeyForm.certificate), 'has-feedback': 
hasError(dsSslKeyForm.certificate)}">
-                <label for="certificate" class="control-label col-md-2 
col-sm-2 col-xs-12">Certificate *</label>
-                <div class="col-md-10 col-sm-10 col-xs-12">
-                    <textarea id="certificate" name="certificate" type="text" 
class="form-control" ng-model="sslKeys.certificate.crt" rows="25" 
required></textarea>
-                    <small class="input-error" 
ng-show="hasPropertyError(dsSslKeyForm.certificate, 
'required')">Required</small>
-                    <span ng-show="hasError(dsSslKeyForm.certificate)" 
class="form-control-feedback"><i class="fa fa-times"></i></span>
-                </div>
-            </div>
-            <div class="modal-footer">
-                <button type="button" class="btn btn-success" 
ng-disabled="dsSslKeyForm.$pristine || dsSslKeyForm.$invalid" 
ng-click="save()">Update Keys</button>
-            </div>
-        </form>
+        </div>
     </div>
 </div>
diff --git 
a/traffic_portal/app/src/common/modules/form/ssl/CertInspectController.js 
b/traffic_portal/app/src/common/modules/form/ssl/CertInspectController.js
new file mode 100644
index 0000000000..7851525d35
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/form/ssl/CertInspectController.js
@@ -0,0 +1,35 @@
+/*
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+let CertInspectController = function ($scope) {
+       $scope.navState = 0;
+       $scope.inputCert = "";
+       $scope.loadCert = "";
+
+       $scope.load = function() {
+               $scope.loadCert = $scope.inputCert;
+               $scope.updateState(1);
+       }
+
+       $scope.updateState = function(state) {
+               if(state === 1 && $scope.loadCert === "") {
+                       return;
+               }
+               $scope.navState = state;
+       }
+}
+
+CertInspectController.$inject = ["$scope"];
+module.exports = CertInspectController;
diff --git a/traffic_portal/app/src/common/modules/form/ssl/_inspect-ssl.scss 
b/traffic_portal/app/src/common/modules/form/ssl/_inspect-ssl.scss
new file mode 100644
index 0000000000..7ca5e3ecde
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/form/ssl/_inspect-ssl.scss
@@ -0,0 +1,25 @@
+/*!
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+#dropZone {
+  border: 2px dashed #bbb;
+  -moz-border-radius: 5px;
+  -webkit-border-radius: 5px;
+  border-radius: 5px;
+  padding: 25px;
+  text-align: center;
+  font: 16pt bold;
+  color: #bbb;
+}
diff --git 
a/traffic_portal/app/src/common/modules/form/ssl/cert-inspect.tpl.html 
b/traffic_portal/app/src/common/modules/form/ssl/cert-inspect.tpl.html
new file mode 100644
index 0000000000..f8c88b4e53
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/form/ssl/cert-inspect.tpl.html
@@ -0,0 +1,47 @@
+<!--
+  ~  Licensed under the Apache License, Version 2.0 (the "License");
+  ~  you may not use this file except in compliance with the License.
+  ~  You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~  Unless required by applicable law or agreed to in writing, software
+  ~  distributed under the License is distributed on an "AS IS" BASIS,
+  ~  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~  See the License for the specific language governing permissions and
+  ~  limitations under the License.
+  ~
+  -->
+<div class="x_panel">
+    <div class="x_title">
+        <ol class="breadcrumb pull-left">
+            <li class="active">Inspect Certificate</li>
+        </ol>
+        <div class="clearfix"></div>
+    </div>
+    <div class="x_content">
+        <ul class="nav nav-tabs" role="navlist">
+            <li role="presentation" ng-class="{active: navState === 0}">
+                <a data-toggle="tab" ng-click="updateState(0)">Upload</a>
+            </li>
+            <li role="presentation" ng-class="{active: navState === 1, 
disabled: loadCert === ''}">
+                <a data-toggle="tab" ng-click="updateState(1)">Processed</a>
+            </li>
+        </ul>
+        <br />
+        <div class="tab-content">
+            <div class="tab-pane" role="tabpanel" ng-class="{show: navState 
=== 0}">
+                <div>
+                    <textarea id="fileContent" name="fileContent" 
ng-model="inputCert" rows="25" cols="25" class="form-control" 
required></textarea>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-success"
+                            ng-disabled="inputCert === ''" 
ng-click="load()">Load</button>
+                </div>
+            </div>
+            <div class="tab-pane" role="tabpanel" ng-class="{show: navState 
=== 1}">
+                <cert-viewer-controller ng-if="loadCert !== ''" 
chain="{{loadCert}}"></cert-viewer-controller>
+            </div>
+        </div>
+    </div>
+</div>
diff --git a/traffic_portal/app/src/common/modules/form/ssl/index.js 
b/traffic_portal/app/src/common/modules/form/ssl/index.js
new file mode 100644
index 0000000000..df8e0305ff
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/form/ssl/index.js
@@ -0,0 +1,16 @@
+/*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+module.exports = angular.module("trafficPortal.form.ssl", [])
+       .controller("CertInspectController", 
require("./CertInspectController"));
diff --git 
a/traffic_portal/app/src/common/modules/navigation/navigation.tpl.html 
b/traffic_portal/app/src/common/modules/navigation/navigation.tpl.html
index d14e3e5a43..88f27e2492 100644
--- a/traffic_portal/app/src/common/modules/navigation/navigation.tpl.html
+++ b/traffic_portal/app/src/common/modules/navigation/navigation.tpl.html
@@ -68,6 +68,7 @@ under the License.
                     <ul class="nav child_menu" style="display: none">
                         <li class="side-menu-category-item" 
ng-if="hasCapability('jobs-read')" ng-class="{'current-page': 
isState('trafficPortal.private.jobs')}"><a href="/#!/jobs">Invalidate 
Content</a></li>
                         <li class="side-menu-category-item" 
ng-if="hasCapability('iso-generate')" ng-class="{'current-page': 
isState('trafficPortal.private.iso')}"><a href="/#!/iso">Generate ISO</a></li>
+                        <li class="side-menu-category-item" 
ng-class="{'current-page': isState('trafficPortal.private.ssl')}"><a 
href="/#!/inspect-cert">Inspect Cert</a></li>
                     </ul>
                 </li>
                 <li class="side-menu-category"><a 
href="javascript:void(0);"><i class="fa fa-sm fa-chevron-right"></i> User 
Admin</a>
diff --git a/traffic_portal/app/src/common/modules/ssl/CertAuthorController.js 
b/traffic_portal/app/src/common/modules/ssl/CertAuthorController.js
new file mode 100644
index 0000000000..bf1129a305
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/ssl/CertAuthorController.js
@@ -0,0 +1,36 @@
+/*
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/** @typedef { import('./CertViewer').Certs } Certs */
+
+let CertAuthorController = function ($scope) {
+       /** @typedef Certs.Author */
+       this.author = {};
+       /** @typedef string */
+       this.authorType = "";
+       this.show = true;
+}
+
+angular.module("trafficPortal.ssl").component("certAuthorController", {
+       templateUrl: "common/modules/ssl/cert-author.tpl.html",
+       controller: CertAuthorController,
+       bindings: {
+               author: "<",
+               authorType: "@"
+       }
+});
+
+CertAuthorController.$inject = ["$scope"];
+module.exports = CertAuthorController;
diff --git a/traffic_portal/app/src/common/modules/ssl/CertViewer.d.ts 
b/traffic_portal/app/src/common/modules/ssl/CertViewer.d.ts
new file mode 100644
index 0000000000..655a94ea0d
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/ssl/CertViewer.d.ts
@@ -0,0 +1,44 @@
+/*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+import * as forge from "node-forge";
+
+export namespace Certs {
+       type CertType = "Root" | "Client" | "Intermediate" | "Unknown" | 
"Error";
+       type CertOrder = "Client -> Root" | "Root -> Client" | "Unknown" | 
"Single";
+
+       /**
+        * Author contains the information about an author from a cert 
issuer/subject
+        */
+       export interface Author {
+               countryName?: string | undefined;
+               stateOrProvince?: string | undefined;
+               localityName?: string | undefined;
+               orgName?: string | undefined;
+               orgUnit?: string | undefined;
+               commonName: string;
+       }
+
+       export interface Certificate extends forge.pki.Certificate {
+               issuerParsed:  Author;
+               subjectParsed: Author;
+               type: CertType;
+               parseError: boolean;
+               sha1: forge.Hex;
+               sha256: forge.Hex;
+               oidName: string;
+               hidden: boolean;
+               miscHidden: boolean;
+               validityHidden: boolean;
+       }
+}
diff --git a/traffic_portal/app/src/common/modules/ssl/CertViewerController.js 
b/traffic_portal/app/src/common/modules/ssl/CertViewerController.js
new file mode 100644
index 0000000000..5e36fa3c44
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/ssl/CertViewerController.js
@@ -0,0 +1,200 @@
+/*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/** @typedef { import('./CertViewer').Certs } Certs */
+
+let CertViewerController = function ($scope) {
+       /** @typedef Certs.Certificate[] */
+       this.certChain = [];
+       /** @typedef Certs.CertOrder */
+       this.certOrder = "";
+       /** @typedef string */
+       this.chain = "";
+
+       /** @type Certs.Certificate */
+       this.nullCert = window.forge.pki.createCertificate();
+       this.nullCert.type = "Error";
+       this.nullCert.parseError = true;
+
+       this.$onChanges = function() {
+               if(this.chain === "") {
+                       return;
+               }
+               this.chain = this.chain.replace(/\r\n/g, "\n");
+               const parts = this.chain.split("-\n-");
+               const certs = new Array(parts.length);
+               for (let i = 1; i < parts.length; ++i) {
+                       parts[i - 1] += "-";
+                       parts[i] = `-${parts[i]}`;
+                       certs[i - 1] = this.newCert(parts[i - 1]);
+               }
+               certs[certs.length - 1] = this.newCert(parts[parts.length - 1]);
+               const assignExtraInfo = (c, i) => {
+                       if (c.parseError) {
+                               return;
+                       }
+                       if (i === 0) {
+                               c.type = "Root";
+                       } else if (i === certs.length - 1) {
+                               c.type = "Client";
+                       } else {
+                               c.type = "Intermediate";
+                       }
+                       c.sha1 = this.pkiCertToSHA1(c);
+                       c.sha256 = this.pkiCertToSHA256(c);
+                       c.parsedIssuer = 
this.processAttributes(c.issuer.attributes);
+                       c.parsedSubject = 
this.processAttributes(c.subject.attributes);
+                       c.oidName = this.oidToName(c.signatureOid);
+               };
+               const chain = this.reOrderRootFirst(certs);
+               chain.forEach(assignExtraInfo);
+               this.certChain = chain;
+       }
+
+       /**
+        * Converts a given oid to it's human-readable name
+        */
+       this.oidToName = function(oid) {
+               if (oid in window.forge.pki.oids) {
+                       return window.forge.pki.oids[oid];
+               }
+               return "";
+       }
+
+       /**
+        * processAttributes converts attributes into an author
+        */
+       this.processAttributes = function(attrs) {
+               /** @typedef Certs.Author */
+               const a = {commonName: ""};
+               for (const attr of attrs) {
+                       if (attr.name && attr.value) {
+                               if (typeof attr.value !== "string") {
+                                       console.warn(`Unknown attribute value 
${attr.value}`);
+                                       continue;
+                               }
+                               switch (attr.name) {
+                                       case "commonName":
+                                               a.commonName = attr.value;
+                                               break;
+                                       case "countryName":
+                                               a.countryName = attr.value;
+                                               break;
+                                       case "stateOrProvinceName":
+                                               a.stateOrProvince = attr.value;
+                                               break;
+                                       case "localityName":
+                                               a.localityName = attr.value;
+                                               break;
+                                       case "organizationName":
+                                               a.orgName = attr.value;
+                                               break;
+                                       case "organizationUnitName":
+                                               a.orgUnit = attr.value;
+                                               break;
+                               }
+                       }
+               }
+               return a;
+       }
+
+       /**
+        * pkiCertToSHA1 calculates the sha 1 of a cert
+        */
+       this.pkiCertToSHA1 = function (cert) {
+               const md = window.forge.md.sha1.create();
+               
md.update(forge.asn1.toDer(window.forge.pki.certificateToAsn1(cert)).getBytes());
+               return md.digest().toHex();
+       }
+
+       /**
+        * pkiCertToSHA256 calculates the sha 256 of a cert
+        */
+       this.pkiCertToSHA256 = function (cert) {
+               const md = forge.md.sha256.create();
+               
md.update(forge.asn1.toDer(window.forge.pki.certificateToAsn1(cert)).getBytes());
+               return md.digest().toHex();
+       }
+
+       /**
+        * newCert creates a cert from an input string.
+        */
+       this.newCert = function (input) {
+               try {
+                       return forge.pki.certificateFromPem(input);
+               } catch (e) {
+                       console.error(`ran into issue creating certificate from 
input ${input}`, e);
+                       return this.nullCert;
+               }
+       }
+
+       /**
+        * reOrderRootFirst sorts a cert chain with the root being first if 
possible.
+        */
+       this.reOrderRootFirst = function (certs) {
+               let rootFirst = false;
+               let invalid = false;
+               for (let i = 1; i < certs.length; ++i) {
+                       const first = certs[i - 1];
+                       const next = certs[i];
+                       if (first.parseError) {
+                               invalid = true;
+                               continue;
+                       } else if (next.parseError) {
+                               invalid = true;
+                               continue;
+                       }
+                       if (first.issued(next)) {
+                               rootFirst = true;
+                       } else if (next.issued(first)) {
+                               rootFirst = false;
+                       } else {
+                               invalid = true;
+                               console.error(`Cert chain is invalid, cert ${i 
- 1} and ${i} are not related`);
+                       }
+               }
+
+               if (certs.length === 1) {
+                       if (certs[0].parseError) {
+                               invalid = true;
+                       } else {
+                               this.certOrder = "Single";
+                               return certs;
+                       }
+               }
+               if (invalid) {
+                       this.certOrder = "Unknown";
+                       return certs;
+               }
+
+               if (rootFirst) {
+                       this.certOrder = "Root -> Client";
+                       return certs;
+               }
+               this.certOrder = "Client -> Root";
+               certs = certs.reverse();
+               return certs;
+       }
+}
+
+angular.module("trafficPortal.ssl").component("certViewerController", {
+       templateUrl: "common/modules/ssl/cert-view.tpl.html",
+       controller: CertViewerController,
+       bindings: {
+               chain: "@"
+       }
+});
+
+CertViewerController.$inject = ["$scope"];
+module.exports = CertViewerController;
diff --git a/traffic_portal/app/src/common/modules/ssl/_ssl.scss 
b/traffic_portal/app/src/common/modules/ssl/_ssl.scss
new file mode 100644
index 0000000000..a5bf059d77
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/ssl/_ssl.scss
@@ -0,0 +1,28 @@
+/*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+fieldset {
+  fieldset legend {
+    font-size: 16px !important;
+  }
+  legend {
+    font-size: 18px !important;
+    font-weight: bold;
+
+    .fa {
+      float: right;
+      margin-right: 10px;
+    }
+  }
+}
diff --git a/traffic_portal/app/src/common/modules/ssl/cert-author.tpl.html 
b/traffic_portal/app/src/common/modules/ssl/cert-author.tpl.html
new file mode 100644
index 0000000000..01e53455ba
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/ssl/cert-author.tpl.html
@@ -0,0 +1,66 @@
+<!--
+  ~  Licensed under the Apache License, Version 2.0 (the "License");
+  ~  you may not use this file except in compliance with the License.
+  ~  You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~  Unless required by applicable law or agreed to in writing, software
+  ~  distributed under the License is distributed on an "AS IS" BASIS,
+  ~  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~  See the License for the specific language governing permissions and
+  ~  limitations under the License.
+  ~
+  -->
+<div>
+    <fieldset>
+        <legend ng-click="$ctrl.show = !$ctrl.show">
+            {{$ctrl.authorType}}
+            <i class="fa" ng-class="$ctrl.show ? 'fa-caret-down' : 
'fa-caret-up'"></i>
+        </legend>
+        <div ng-show="$ctrl.show">
+            <div class="form-group">
+                <label for="commonName" class="control-label col-md-2 col-sm-2 
col-xs-12">Common Name</label>
+                <div class="col-md-10 col-sm-10 col-xs-12">
+                    <input readonly name="commonName" id="commonName" 
type="text" class="form-control"
+                           ng-model="$ctrl.author.commonName"/>
+                </div>
+            </div>
+            <div class="form-group">
+                <label for="countryName" class="control-label col-md-2 
col-sm-2 col-xs-12">Country Name</label>
+                <div class="col-md-10 col-sm-10 col-xs-12">
+                    <input readonly name="countryName" id="countryName" 
type="text" class="form-control"
+                           ng-model="$ctrl.author.countryName"/>
+                </div>
+            </div>
+            <div class="form-group">
+                <label for="stateOrProvince" class="control-label col-md-2 
col-sm-2 col-xs-12">State/Province</label>
+                <div class="col-md-10 col-sm-10 col-xs-12">
+                    <input readonly name="stateOrProvince" 
id="stateOrProvince" type="text" class="form-control"
+                           ng-model="$ctrl.author.stateOrProvince"/>
+                </div>
+            </div>
+            <div class="form-group">
+                <label for="locality" class="control-label col-md-2 col-sm-2 
col-xs-12">Locality</label>
+                <div class="col-md-10 col-sm-10 col-xs-12">
+                    <input readonly name="locality" id="locality" type="text" 
class="form-control"
+                           ng-model="$ctrl.author.localityName"/>
+                </div>
+            </div>
+            <div class="form-group">
+                <label for="orgName" class="control-label col-md-2 col-sm-2 
col-xs-12">Organization</label>
+                <div class="col-md-10 col-sm-10 col-xs-12">
+                    <input readonly name="orgName" id="orgName" type="text" 
class="form-control"
+                           ng-model="$ctrl.author.orgName"/>
+                </div>
+            </div>
+            <div class="form-group">
+                <label for="orgUnit" class="control-label col-md-2 col-sm-2 
col-xs-12">Organization Unit</label>
+                <div class="col-md-10 col-sm-10 col-xs-12">
+                    <input readonly name="orgUnit" id="orgUnit" type="text" 
class="form-control"
+                           ng-model="$ctrl.author.orgUnit"/>
+                </div>
+            </div>
+        </div>
+    </fieldset>
+</div>
diff --git a/traffic_portal/app/src/common/modules/ssl/cert-view.tpl.html 
b/traffic_portal/app/src/common/modules/ssl/cert-view.tpl.html
new file mode 100644
index 0000000000..307f08f100
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/ssl/cert-view.tpl.html
@@ -0,0 +1,99 @@
+<!--
+  ~  Licensed under the Apache License, Version 2.0 (the "License");
+  ~  you may not use this file except in compliance with the License.
+  ~  You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~  Unless required by applicable law or agreed to in writing, software
+  ~  distributed under the License is distributed on an "AS IS" BASIS,
+  ~  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~  See the License for the specific language governing permissions and
+  ~  limitations under the License.
+  ~
+  -->
+
+<form name="certViewerForm" class="form-horizontal form-label-left" novalidate>
+    <div class="form-group">
+        <label for="certOrder" class="control-label col-md-2 col-sm-2 
col-xs-12">Detected Order</label>
+        <div class="col-md-10 col-sm-10 col-xs-12">
+            <input readonly name="certOrder" id="certOrder" type="text" 
class="form-control"
+                   ng-model="$ctrl.certOrder"/>
+        </div>
+    </div>
+    <hr/>
+    <fieldset ng-repeat="cert in $ctrl.certChain">
+        <legend ng-click="cert.hidden = !cert.hidden">
+            {{cert.type}}
+            <i class="fa" ng-class="cert.hidden ? 'fa-caret-up' : 
'fa-caret-down'"></i>
+        </legend>
+        <div ng-show="!cert.hidden">
+            <cert-author-controller author-type="Issuer" 
author="cert.parsedIssuer"></cert-author-controller>
+            <cert-author-controller author-type="Subject" 
author="cert.parsedSubject"></cert-author-controller>
+            <fieldset>
+                <legend ng-click="cert.validityHidden = !cert.validityHidden">
+                    Validity
+                    <i class="fa" ng-class="cert.validityHidden ? 
'fa-caret-up' : 'fa-caret-down'"></i>
+                </legend>
+                <div ng-show="!cert.validityHidden">
+                    <div class="form-group">
+                        <label for="validBefore" class="control-label col-md-2 
col-sm-2 col-xs-12">Not Valid Before</label>
+                        <div class="col-md-10 col-sm-10 col-xs-12">
+                            <input readonly name="validBefore" 
id="validBefore" type="text" class="form-control"
+                                   ng-model="cert.validity.notBefore"/>
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label for="validAfter" class="control-label col-md-2 
col-sm-2 col-xs-12">Not Valid After</label>
+                        <div class="col-md-10 col-sm-10 col-xs-12">
+                            <input readonly name="validAfter" id="validAfter" 
type="text" class="form-control"
+                                   ng-model="cert.validity.notAfter"/>
+                        </div>
+                    </div>
+                </div>
+            </fieldset>
+            <fieldset>
+                <legend ng-click="cert.miscHidden = !cert.miscHidden">
+                    Misc
+                    <i class="fa" ng-class="cert.miscHidden ? 'fa-caret-up' : 
'fa-caret-down'"></i>
+                </legend>
+                <div ng-show="!cert.miscHidden">
+                    <div class="form-group">
+                        <label for="sigType" class="control-label col-md-2 
col-sm-2 col-xs-12">Signature Algorithm</label>
+                        <div class="col-md-10 col-sm-10 col-xs-12">
+                            <input readonly name="sigType" id="sigType" 
type="text" class="form-control"
+                                   ng-model="cert.oidName"/>
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label for="version" class="control-label col-md-2 
col-sm-2 col-xs-12">Version</label>
+                        <div class="col-md-10 col-sm-10 col-xs-12">
+                            <input readonly name="version" id="version" 
type="text" class="form-control"
+                                   ng-model="cert.version"/>
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label for="serial" class="control-label col-md-2 
col-sm-2 col-xs-12">Serial Number</label>
+                        <div class="col-md-10 col-sm-10 col-xs-12">
+                            <input readonly name="serial" id="serial" 
type="text" class="form-control"
+                                   ng-model="cert.serialNumber"/>
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label for="sha1" class="control-label col-md-2 
col-sm-2 col-xs-12">SHA-1</label>
+                        <div class="col-md-10 col-sm-10 col-xs-12">
+                            <input readonly name="sha1" id="sha1" type="text" 
class="form-control" ng-model="cert.sha1"/>
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label for="sha256" class="control-label col-md-2 
col-sm-2 col-xs-12">SHA-256</label>
+                        <div class="col-md-10 col-sm-10 col-xs-12">
+                            <input readonly name="sha256" id="sha256" 
type="text" class="form-control"
+                                   ng-model="cert.sha256"/>
+                        </div>
+                    </div>
+                </div>
+            </fieldset>
+        </div>
+    </fieldset>
+</form>
diff --git a/traffic_portal/app/src/common/modules/ssl/index.js 
b/traffic_portal/app/src/common/modules/ssl/index.js
new file mode 100644
index 0000000000..91524c433e
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/ssl/index.js
@@ -0,0 +1,17 @@
+/*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+module.exports = angular.module("trafficPortal.ssl", [])
+       .controller("CertViewerController", require("./CertViewerController"))
+       .controller("CertAuthorController", require("./CertAuthorController"));
diff --git a/traffic_portal/app/src/modules/private/ssl/index.js 
b/traffic_portal/app/src/modules/private/ssl/index.js
new file mode 100644
index 0000000000..3f6c8bfdf5
--- /dev/null
+++ b/traffic_portal/app/src/modules/private/ssl/index.js
@@ -0,0 +1,28 @@
+/*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+module.exports = angular.module("trafficPortal.private.ssl", [])
+       .config(function($stateProvider, $urlRouterProvider) {
+               $stateProvider
+                       .state("trafficPortal.private.ssl", {
+                               url: "inspect-cert",
+                               views: {
+                                       privateContent: {
+                                               templateUrl: 
"common/modules/form/ssl/cert-inspect.tpl.html",
+                                               controller: 
"CertInspectController",
+                                       }
+                               }
+                       });
+               $urlRouterProvider.otherwise("/");
+       });
diff --git a/traffic_portal/app/src/modules/private/ssl/inspect-cert.tpl.html 
b/traffic_portal/app/src/modules/private/ssl/inspect-cert.tpl.html
new file mode 100644
index 0000000000..059211e369
--- /dev/null
+++ b/traffic_portal/app/src/modules/private/ssl/inspect-cert.tpl.html
@@ -0,0 +1,18 @@
+<!--
+  ~  Licensed under the Apache License, Version 2.0 (the "License");
+  ~  you may not use this file except in compliance with the License.
+  ~  You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~  Unless required by applicable law or agreed to in writing, software
+  ~  distributed under the License is distributed on an "AS IS" BASIS,
+  ~  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~  See the License for the specific language governing permissions and
+  ~  limitations under the License.
+  ~
+  -->
+
+<div id="inspectCertContainer">
+    <div ui-view="certInspect"></div>
+</div>
diff --git a/traffic_portal/app/src/scripts/shared-libs.js 
b/traffic_portal/app/src/scripts/shared-libs.js
index 3a362d8348..d97d6928b0 100644
--- a/traffic_portal/app/src/scripts/shared-libs.js
+++ b/traffic_portal/app/src/scripts/shared-libs.js
@@ -52,3 +52,6 @@ require('jquery-flot-stack');
 require('jquery-flot-time');
 require('jquery-flot-tooltip');
 require('jquery-flot-axislabels');
+
+/** @typedef { import("node-forge") } forge */
+window.forge = require("node-forge");
diff --git a/traffic_portal/app/src/styles/main.scss 
b/traffic_portal/app/src/styles/main.scss
index c4f0e6aab4..219f6cdf83 100644
--- a/traffic_portal/app/src/styles/main.scss
+++ b/traffic_portal/app/src/styles/main.scss
@@ -32,8 +32,10 @@ $fa-font-path: "../assets/fonts";
 @import "../common/modules/header/header";
 @import "../common/modules/form/cacheGroup/form.cacheGroup";
 @import "../common/modules/form/form";
+@import "../common/modules/form/ssl/inspect-ssl";
 @import "../common/modules/locks/locks";
 @import "../common/modules/navigation/navigation";
+@import "../common/modules/ssl/ssl";
 @import "../common/modules/table/table";
 @import "../common/modules/table/parameters/table.parameters";
 @import "../common/modules/release/release";
diff --git a/traffic_portal/package-lock.json b/traffic_portal/package-lock.json
index 59413f620b..bcadf4a55b 100644
--- a/traffic_portal/package-lock.json
+++ b/traffic_portal/package-lock.json
@@ -23,12 +23,14 @@
         "flot": "2.3.2",
         "font-awesome": "4.7.0",
         "jquery": "3.6.1",
-        "jquery.flot.tooltip": "0.9.0"
+        "jquery.flot.tooltip": "0.9.0",
+        "node-forge": "^1.3.1"
       },
       "devDependencies": {
         "@types/angular": "^1.8.4",
         "@types/jquery": "^3.5.14",
         "@types/moment": "^2.13.0",
+        "@types/node-forge": "^1.3.2",
         "angular-mocks": "1.8.3",
         "browserify": "16.5.1",
         "connect-livereload": "0.6.1",
@@ -102,6 +104,21 @@
         "moment": "*"
       }
     },
+    "node_modules/@types/node": {
+      "version": "20.4.0",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.0.tgz";,
+      "integrity": 
"sha512-jfT7iTf/4kOQ9S7CHV9BIyRaQqHu67mOjsIQBC3BKZvzvUB6zLxEwJ6sBE3ozcvP8kF6Uk5PXN0Q+c0dfhGX0g==",
+      "dev": true
+    },
+    "node_modules/@types/node-forge": {
+      "version": "1.3.2",
+      "resolved": 
"https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.2.tgz";,
+      "integrity": 
"sha512-TzX3ahoi9xbmaoT58smrBu7oa6dQXb/+PTNCslZyD/55tlJ/osofIMClzZsoo6buDFrg7e4DvVGkZqVgv6OLxw==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
     "node_modules/@types/sizzle": {
       "version": "2.3.3",
       "resolved": 
"https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz";,
@@ -6034,6 +6051,14 @@
       "integrity": 
"sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
       "dev": true
     },
+    "node_modules/node-forge": {
+      "version": "1.3.1",
+      "resolved": 
"https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz";,
+      "integrity": 
"sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
+      "engines": {
+        "node": ">= 6.13.0"
+      }
+    },
     "node_modules/nopt": {
       "version": "3.0.6",
       "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz";,
@@ -7179,12 +7204,6 @@
         "@types/node": "*"
       }
     },
-    "node_modules/socket.io/node_modules/@types/node": {
-      "version": "18.11.18",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz";,
-      "integrity": 
"sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==",
-      "dev": true
-    },
     "node_modules/socket.io/node_modules/base64id": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz";,
@@ -8427,6 +8446,21 @@
         "moment": "*"
       }
     },
+    "@types/node": {
+      "version": "20.4.0",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.0.tgz";,
+      "integrity": 
"sha512-jfT7iTf/4kOQ9S7CHV9BIyRaQqHu67mOjsIQBC3BKZvzvUB6zLxEwJ6sBE3ozcvP8kF6Uk5PXN0Q+c0dfhGX0g==",
+      "dev": true
+    },
+    "@types/node-forge": {
+      "version": "1.3.2",
+      "resolved": 
"https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.2.tgz";,
+      "integrity": 
"sha512-TzX3ahoi9xbmaoT58smrBu7oa6dQXb/+PTNCslZyD/55tlJ/osofIMClzZsoo6buDFrg7e4DvVGkZqVgv6OLxw==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*"
+      }
+    },
     "@types/sizzle": {
       "version": "2.3.3",
       "resolved": 
"https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz";,
@@ -13204,6 +13238,11 @@
       "integrity": 
"sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
       "dev": true
     },
+    "node-forge": {
+      "version": "1.3.1",
+      "resolved": 
"https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz";,
+      "integrity": 
"sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
+    },
     "nopt": {
       "version": "3.0.6",
       "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz";,
@@ -14144,12 +14183,6 @@
             "@types/node": "*"
           }
         },
-        "@types/node": {
-          "version": "18.11.18",
-          "resolved": 
"https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz";,
-          "integrity": 
"sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==",
-          "dev": true
-        },
         "base64id": {
           "version": "2.0.0",
           "resolved": 
"https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz";,
diff --git a/traffic_portal/package.json b/traffic_portal/package.json
index 429ac89c82..407a1f0463 100644
--- a/traffic_portal/package.json
+++ b/traffic_portal/package.json
@@ -18,6 +18,7 @@
     "@types/angular": "^1.8.4",
     "@types/jquery": "^3.5.14",
     "@types/moment": "^2.13.0",
+    "@types/node-forge": "^1.3.2",
     "angular-mocks": "1.8.3",
     "browserify": "16.5.1",
     "connect-livereload": "0.6.1",
@@ -63,6 +64,7 @@
     "flot": "2.3.2",
     "font-awesome": "4.7.0",
     "jquery": "3.6.1",
-    "jquery.flot.tooltip": "0.9.0"
+    "jquery.flot.tooltip": "0.9.0",
+    "node-forge": "^1.3.1"
   }
 }

Reply via email to