diff --git a/pw/pw-csp-nonce/client/src/app/app.component.ts b/pw/pw-csp-nonce/client/src/app/app.component.ts
index 8f99046d..ae194067 100644
--- a/pw/pw-csp-nonce/client/src/app/app.component.ts
+++ b/pw/pw-csp-nonce/client/src/app/app.component.ts
@@ -1,6 +1,7 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { UserService } from './services/userService';
+import { CspConfig } from './services/cspConfigService';
@Component({
selector: 'app-root',
@@ -8,6 +9,59 @@ import { UserService } from './services/userService';
templateUrl: './app.component.html',
providers: [],
})
+
+export class AppComponent {
+ private csp: string = "";
+ private nonce: string = "";
+
+ constructor(
+ private router: Router,
+ public userService: UserService,
+ public cspConfig: CspConfig) {
+
+ cspConfig.load().then(
+ data => {
+ this.csp = data['value'];
+ this.nonce = data['nonce'];
+
+ console.debug('csp : ' + this.csp);
+ console.debug('nonce : ' + this.nonce);
+
+ // can't use the Meta#addTags() method to set CSP because it will insert the meta tag too late, so we add it "manually"
+ var meta = "";
+ this.renderHtml(meta, 'head');
+ console.log('content-security-policy meta : ' + meta);
+
+ // Add secure inline scripting (a script block with a nonce)
+ // The script will just render a message at the bottom of the page
+ // (here, we don't use document.write method otherwise it will replace the whole page rendering)
+ var yourHtmlString =
+ "";
+ this.renderHtml(yourHtmlString, 'head');
+ console.log('inline scripting !!! ', yourHtmlString);
+ });
+ }
+
+ /**
+ *
+ * Renders an html portion inside a given html tag
+ * @param message: a string which represents the html portion to render in the page
+ * @param parentTag : the html tag name in which the html portion will be inserted as a first child
+ */
+ private renderHtml(message: string, parentTag: string) {
+ var fragment = document.createRange().createContextualFragment(message);
+ document.getElementsByTagName(parentTag)[0].appendChild(fragment);
+ }
+
+ logout() {
+ this.userService.logout();
+ }
+}
+
+/*
export class AppComponent {
constructor(private router: Router, public userService: UserService) {}
@@ -15,3 +69,4 @@ export class AppComponent {
this.userService.logout();
}
}
+*/
\ No newline at end of file
diff --git a/pw/pw-csp-nonce/client/src/app/app.module.ts b/pw/pw-csp-nonce/client/src/app/app.module.ts
index 78f979d7..aeb4cd4c 100644
--- a/pw/pw-csp-nonce/client/src/app/app.module.ts
+++ b/pw/pw-csp-nonce/client/src/app/app.module.ts
@@ -37,7 +37,7 @@ import { Basket } from './basket/basket';
import { Profile } from './profile/profile';
import { Login } from './login/login';
-// import {CspConfig} from './services/cspConfigService';
+import { CspConfig } from './services/cspConfigService';
@NgModule({
imports: [
@@ -49,6 +49,7 @@ import { Login } from './login/login';
HttpClientModule,
],
providers: [
+ CspConfig,
UserService,
BooksService,
DataContainerService,
@@ -86,4 +87,4 @@ import { Login } from './login/login';
],
bootstrap: [AppComponent],
})
-export class AppModule {}
+export class AppModule { }
diff --git a/pw/pw-csp-nonce/client/src/app/services/cspConfigService.ts b/pw/pw-csp-nonce/client/src/app/services/cspConfigService.ts
index ce133679..2534a4b6 100644
--- a/pw/pw-csp-nonce/client/src/app/services/cspConfigService.ts
+++ b/pw/pw-csp-nonce/client/src/app/services/cspConfigService.ts
@@ -4,6 +4,34 @@ import { HttpClient, HttpResponse } from '@angular/common/http';
@Injectable()
// This service gets the Content-Security-Policy and a random nonce from a REST api endpoint /api/csp
export class CspConfig {
+ private _config: any;
+ private _nonce: any;
+ private http: HttpClient;
+ // can't use classical Angular DI for HttpClient here, because of "cyclic dependency" issues
+ // Use Injector service to instanciate HttpClient
+ constructor(injector: Injector) {
+ this.http = injector.get(HttpClient);
+ }
-}
+ // Load Content-Security-Policy from a REST api endpoint
+ // The returned data will contain the CSP configuration ('value') and the a random generated nonce ('nonce')
+ load(): Promise {
+ return this.http.get('/api/csp')
+ .toPromise()
+ .then(data => {
+
+ this._config = JSON.parse(JSON.stringify(data))['value'];
+ this._nonce = JSON.parse(JSON.stringify(data))['nonce'];
+ return data;
+ })
+ }
+
+ get config(): any {
+ return this._config;
+ }
+
+ get nonce(): any {
+ return this._nonce;
+ }
+}
\ No newline at end of file
diff --git a/pw/pw-csp-nonce/server/pom.xml b/pw/pw-csp-nonce/server/pom.xml
index cf2a3b68..6d3fd45e 100644
--- a/pw/pw-csp-nonce/server/pom.xml
+++ b/pw/pw-csp-nonce/server/pom.xml
@@ -91,6 +91,11 @@
+
+ io.dropwizard.metrics
+ metrics-annotation
+ 4.2.15
+
javax.xml.bind
jaxb-api
diff --git a/pw/pw-csp-nonce/server/src/main/java/com/worldline/bookstore/config/SecurityConfiguration.java b/pw/pw-csp-nonce/server/src/main/java/com/worldline/bookstore/config/SecurityConfiguration.java
index 4ce8239d..51d6f467 100644
--- a/pw/pw-csp-nonce/server/src/main/java/com/worldline/bookstore/config/SecurityConfiguration.java
+++ b/pw/pw-csp-nonce/server/src/main/java/com/worldline/bookstore/config/SecurityConfiguration.java
@@ -105,8 +105,52 @@ protected void configure(HttpSecurity http) throws Exception {
;
// TODO uncomment this line to activate JWT filter
+ // setCspConfig(http);
+
}
+ // Add CSP hash for the following inline scripting (see
+ // https://report-uri.io/home/hash) :
+ // - "document.write('Inline scripting is not recommended! But if you
+ // have not the choice, secure your app with CSP
');" ==>
+ // 'sha256-lK+Y3vDnNUrD/ZPLGsnM6B+euoBxZ/MyiIbY2G5VoPw='
+ // - inline style ...
+
+ /*
+ * private void setCspConfig(HttpSecurity http) throws Exception {
+ * http
+ * .headers()
+ * .contentSecurityPolicy(
+ * "script-src" +
+ * " 'none' " +
+ * // "'unsafe-eval' 'unsafe-inline' " +
+ * ";" +
+ * // add connect-src directive to adapt CSP over cross-origin requests (CORS)
+ * "connect-src" +
+ * " 'self'" +
+ * ";" +
+ * " style-src" +
+ * " 'self' 'unsafe-inline'" +
+ * ";" +
+ * " font-src" +
+ * " 'self' " +
+ * ";" +
+ * " img-src" +
+ * " 'self' " +
+ * ";" +
+ * " child-src" +
+ * " 'self' " +
+ * ";" +
+ * " object-src" +
+ * " 'none' " +
+ * ";" +
+ * " report-uri" +
+ * " 'http://localhost:4200' " +
+ * ";" +
+ * " default-src" +
+ * " 'self' ");// .reportOnly();
+ * }
+ */
private JWTConfigurer securityConfigurerAdapter() {
return new JWTConfigurer(tokenProvider);
}
diff --git a/pw/pw-csp-nonce/server/src/main/java/com/worldline/bookstore/web/rest/CSP.java b/pw/pw-csp-nonce/server/src/main/java/com/worldline/bookstore/web/rest/CSP.java
index 8b8474eb..6eab5ba9 100644
--- a/pw/pw-csp-nonce/server/src/main/java/com/worldline/bookstore/web/rest/CSP.java
+++ b/pw/pw-csp-nonce/server/src/main/java/com/worldline/bookstore/web/rest/CSP.java
@@ -2,7 +2,7 @@
public class CSP {
- /*private String value;
+ public String value;
private String nonce;
public String getNonce() {
@@ -29,5 +29,5 @@ public void setValue(String value) {
@Override
public String toString() {
return "CSP [value=" + value + ", nonce=" + nonce + "]";
- }*/
+ }
}
diff --git a/pw/pw-csp-nonce/server/src/main/java/com/worldline/bookstore/web/rest/CSPResource.java b/pw/pw-csp-nonce/server/src/main/java/com/worldline/bookstore/web/rest/CSPResource.java
index 44c10b2d..c99827ce 100644
--- a/pw/pw-csp-nonce/server/src/main/java/com/worldline/bookstore/web/rest/CSPResource.java
+++ b/pw/pw-csp-nonce/server/src/main/java/com/worldline/bookstore/web/rest/CSPResource.java
@@ -16,14 +16,114 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-import com.codahale.metrics.annotation.Timed;
+//import com.codahale.metrics.annotation.Timed;
/**
- * REST controller for managing Content-Security-Policy confuguration with random nonce.
+ * REST controller for managing Content-Security-Policy confuguration with
+ * random nonce.
*/
@RestController
@RequestMapping("/api")
public class CSPResource {
+ // public CSPwrapper test = CSPwrapper();
+ private final Logger log = LoggerFactory.getLogger(CSPResource.class);
+
+ /** Used for Script Nonce */
+ private SecureRandom prng = null;
+
+ @GetMapping("/csp")
+ // Add Script Nonce CSP Policy
+ public ResponseEntity> generateCSP(HttpServletResponse response) {
+ // --Get its digest
+ MessageDigest sha;
+ // --Generate a random number
+ String randomNum;
+ try {
+ this.prng = SecureRandom.getInstance("SHA1PRNG");
+ randomNum = new Integer(this.prng.nextInt()).toString();
+ sha = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException e) {
+ return new ResponseEntity<>(Collections.singletonMap("CSPException", e.getLocalizedMessage()),
+ HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+
+ byte[] digest = sha.digest(randomNum.getBytes());
+
+ // --Encode it into HEXA
+ char[] scriptNonce = Hex.encode(digest);
+
+ String csp = "script-src" +
+ " 'unsafe-eval' 'strict-dynamic' " +
+ " 'nonce-" + String.valueOf(scriptNonce) + "'" +
+ " 'sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4='" +
+ // SRI hashes for
+ // https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js (work only
+ // for Chrome)
+ ";" +
+ // add connect-src directive to adapt CSP over cross-origin requests (CORS)
+ "connect-src" +
+ " http://localhost:8080 http://localhost:4200 ws://localhost:4200"
+ + ";" +
+ " style-src" +
+ " 'self' 'unsafe-inline'" +
+ ";" +
+ " font-src" +
+ " 'self' " +
+ ";" +
+ " img-src" +
+ " 'self' data:" +
+ ";" +
+ " child-src" +
+ " 'self' " +
+ ";" +
+ " object-src" +
+ " 'none' " +
+ ";" +
+ " default-src" +
+ " 'self' ";
+
+ CSP conf = new CSP(csp);
+ conf.setNonce(String.valueOf(scriptNonce));
+
+ log.debug(conf.toString());
+
+ return ResponseEntity.ok(conf);
+ }
}
+
+/*
+ * package com.worldline.bookstore.web.rest;
+ *
+ * import java.security.MessageDigest;
+ * import java.security.NoSuchAlgorithmException;
+ * import java.security.SecureRandom;
+ * import java.util.Collections;
+ *
+ * import javax.servlet.http.HttpServletResponse;
+ *
+ * import org.slf4j.Logger;
+ * import org.slf4j.LoggerFactory;
+ * import org.springframework.http.HttpStatus;
+ * import org.springframework.http.ResponseEntity;
+ * import org.springframework.security.crypto.codec.Hex;
+ * import org.springframework.web.bind.annotation.GetMapping;
+ * import org.springframework.web.bind.annotation.RequestMapping;
+ * import org.springframework.web.bind.annotation.RestController;
+ */
+
+// import com.codahale.metrics.annotation.Timed;
+
+/**
+ * REST controller for managing Content-Security-Policy confuguration with
+ * random nonce.
+ */
+/*
+ * @RestController
+ *
+ * @RequestMapping("/api")
+ * public class CSPResource {
+ *
+ * }
+ */
\ No newline at end of file