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..f7bb53e3 100644
--- a/pw/pw-csp-nonce/client/src/app/app.component.ts
+++ b/pw/pw-csp-nonce/client/src/app/app.component.ts
@@ -1,5 +1,6 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
+import { CspConfig } from './services/cspConfigService';
import { UserService } from './services/userService';
@Component({
@@ -9,7 +10,51 @@ import { UserService } from './services/userService';
providers: [],
})
export class AppComponent {
- constructor(private router: Router, public userService: UserService) {}
+ private csp: string;
+ private nonce: string;
+
+ constructor(
+ private router: Router,
+ public userService: UserService,
+ public cspConfig: CspConfig) {
+ this.csp = this.nonce = '';
+
+ 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();
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..5baa086a 100644
--- a/pw/pw-csp-nonce/client/src/app/app.module.ts
+++ b/pw/pw-csp-nonce/client/src/app/app.module.ts
@@ -14,6 +14,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Router } from '@angular/router';
+import { CspConfig } from './services/cspConfigService';
import { UserService } from './services/userService';
import { BooksService } from './services/booksService';
import { DataContainerService } from './services/dataContainerService';
@@ -50,6 +51,7 @@ import { Login } from './login/login';
],
providers: [
UserService,
+ CspConfig,
BooksService,
DataContainerService,
ContactService,
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..64fd5a8d 100644
--- a/pw/pw-csp-nonce/client/src/app/services/cspConfigService.ts
+++ b/pw/pw-csp-nonce/client/src/app/services/cspConfigService.ts
@@ -3,7 +3,36 @@ 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: any) => {
+ this._config = data['value'] ?? '';
+ this._nonce = data['nonce'] ?? '';
+ return data;
+ })
+ }
+
+ get config(): any {
+ return this._config;
+ }
+ get nonce(): any {
+ return this._nonce;
+ }
}
diff --git a/pw/pw-csp-nonce/server/pom.xml b/pw/pw-csp-nonce/server/pom.xml
index cf2a3b68..a7a38f1e 100644
--- a/pw/pw-csp-nonce/server/pom.xml
+++ b/pw/pw-csp-nonce/server/pom.xml
@@ -271,6 +271,12 @@
spring-boot-starter-validation
+
+ io.dropwizard.metrics
+ metrics-annotation
+ 4.2.15
+
+
org.springframework.boot
spring-boot-starter-web
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..d060fc95 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
@@ -1,8 +1,7 @@
package com.worldline.bookstore.web.rest;
public class CSP {
-
- /*private String value;
+ private String value;
private String nonce;
public String getNonce() {
@@ -29,5 +28,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..08e7bccf 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
@@ -24,6 +24,64 @@
@RestController
@RequestMapping("/api")
public class CSPResource {
+ private final Logger log = LoggerFactory.getLogger(CSPResource.class);
-
+ /** Used for Script Nonce */
+ private SecureRandom prng = null;
+
+ @GetMapping("/csp")
+ @Timed
+ // 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);
+ }
}
diff --git a/pw/pw-csp/client/src/index.html b/pw/pw-csp/client/src/index.html
index 2cdf679a..e5f168c1 100644
--- a/pw/pw-csp/client/src/index.html
+++ b/pw/pw-csp/client/src/index.html
@@ -1,10 +1,11 @@
-
+
+
Test
@@ -18,9 +19,8 @@
Uncomment the line below for PW-CSP solution
- This is inline scripting. Not recommended, only for test purpose !
- To secure inline scripting, use CSP 3 sha256 hash syntax : "script-src ... 'sha256-lK+Y3vDnNUrD/ZPLGsnM6B+euoBxZ/MyiIbY2G5VoPw='
-
+ -->
- -->
diff --git a/pw/pw-csp/server/src/main/java/com/worldline/bookstore/config/SecurityConfiguration.java b/pw/pw-csp/server/src/main/java/com/worldline/bookstore/config/SecurityConfiguration.java
index 7b4343bc..05ee2310 100644
--- a/pw/pw-csp/server/src/main/java/com/worldline/bookstore/config/SecurityConfiguration.java
+++ b/pw/pw-csp/server/src/main/java/com/worldline/bookstore/config/SecurityConfiguration.java
@@ -105,7 +105,7 @@ protected void configure(HttpSecurity http) throws Exception {
;
// TODO uncomment this line to activate JWT filter
- // setCspConfig(http);
+ setCspConfig(http);
}
@@ -116,34 +116,13 @@ 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();
+ "default-src 'none';" +
+ "connect-src 'self';" +
+ "font-src 'self';" +
+ "img-src 'self';" +
+ "style-src 'self' 'unsafe-inline';" +
+ "script-src 'sha256-lK+Y3vDnNUrD/ZPLGsnM6B+euoBxZ/MyiIbY2G5VoPw=' 'strict-dynamic';");
+ //.reportOnly();
}
private JWTConfigurer securityConfigurerAdapter() {