-
-
Notifications
You must be signed in to change notification settings - Fork 443
Implement OCPP 1.6 Security #1854
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
e9b8666 to
81ba853
Compare
|
@juherr i will take a look at this ASAP when i am finished with some other duties. on a more general note: i had a conversation with @florinmandache after his initial big PR. he said that he has many things going on and is short on time, and he wanted to contribute on good will. since he is a busy person, he was not interested in follow-up, review rounds and eventual clean-ups (i.e. the usual process of PR and reviewing). due to the substantial nature of the contribution, i accepted it as-is and took it upon me to do whatever is necessary to bring these features to main branch. in this context, i thank you for doing one part of it. |
|
@goekay Thanks for the clarification. I think Gemini did an interesting initial job exploring the different topics, but it definitely needs more work based on what I’ve seen so far (especially regarding OCPP 1.6 Security and OICP). Since OCPI and OCPP 2.x are an even tougher challenge, and my time is just as limited, I’ll let you take the first shot on those ones 😉 |
# Conflicts: # src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java # src/test/java/de/rwth/idsg/steve/utils/__DatabasePreparer__.java
|
FYI @juherr: steve-community/ocpp-jaxb#18 naming of the json schema files differ a bit from the original (see links from comment). do you know why? |
|
Ah, you mean the request suffix in some filenames? |
* clean up resources folder and pom file * and do necessary changes for project to compile
|
i did some "low-hanging fruit" refactoring to reduce the size of the PR (i.e. moving the data models and their generation to ocpp-jaxb library). will do some more. i think this is very important in order to able to properly review the PR and separate the good parts from inaccurate AI slop. while being on this... i did a litmus test and tried to understand how "http basic auth" (security profile 1) is supposed to work in the impl. ChargePointRepository has 2 new methods moreover, there is no change in |
* and also make getRegistrationStatus return the password additionally, in order prevent another DB lookup later
036e217 to
3878731
Compare
|
WIP - Implementation status of new operations:
Other ToDos:
|
|
ignoring and removing the following method completely, since it has no correlation with reality IMO: the
the table actually continues in the next page with more types, but there is no type with INFO, ATTACK (heh?), BREACH (what?) etc. so this whole thing is made up. moreover, the PR invents new types (that are not in the list), such as these usages feel also wrong. actually, the motivation is nice: if there is an error during the signing of certificate, create a security event and store it in DB. but, these security events are NOT coming from station, and intermixing these with regular events is problematic IMO. @juherr do you happen to know whether these are retrofits coming from ocpp 2.x ? |
I haven’t looked deeply into OCPP 2.x or even the current PR once I understood where it originated from. According to the whitepaper, only critical events are supposed to be sent by the station — and I believe all of those should indeed be stored. |
|
so, i can assume that these additions were a mistake and i can remove them
? |
* various simplifications and improvements * certificate is still as is. this is a future TODO
db70668 to
59c1c8d
Compare
* remove unnecessary and invalid parts
|
In my opinion, security events triggered by the backend are a separate concern, and a warning log should be enough for now. |
d54af17 to
d84e47c
Compare
juherr
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some remarks after a first review of the changes.
I should be able to make them by myself as it comes from my branch, but I would like your review first.
src/main/java/de/rwth/idsg/steve/ocpp/ChargePointServiceInvokerImpl.java
Outdated
Show resolved
Hide resolved
src/main/java/de/rwth/idsg/steve/service/CentralSystemService16_Service.java
Outdated
Show resolved
Hide resolved
| try { | ||
| if (parameters.getRequestId() == null) { | ||
| log.warn("No requestId in {}", parameters); | ||
| } else { | ||
| securityRepository.insertLogUploadStatus( | ||
| chargeBoxIdentity, | ||
| parameters.getRequestId(), | ||
| parameters.getStatus().value(), | ||
| DateTime.now() | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| try { | |
| if (parameters.getRequestId() == null) { | |
| log.warn("No requestId in {}", parameters); | |
| } else { | |
| securityRepository.insertLogUploadStatus( | |
| chargeBoxIdentity, | |
| parameters.getRequestId(), | |
| parameters.getStatus().value(), | |
| DateTime.now() | |
| ); | |
| } | |
| if (parameters.getRequestId() == null) { | |
| throw new InvalidArgumentException("RequestId is null"); | |
| } | |
| try { | |
| securityRepository.insertLogUploadStatus( | |
| chargeBoxIdentity, | |
| parameters.getRequestId(), | |
| parameters.getStatus().value(), | |
| DateTime.now() | |
| ); | |
| } |
| try { | ||
| if (parameters.getRequestId() == null) { | ||
| log.warn("No requestId in {}", parameters); | ||
| } else { | ||
| securityRepository.insertFirmwareUpdateStatus( | ||
| chargeBoxIdentity, | ||
| parameters.getRequestId(), | ||
| parameters.getStatus().value(), | ||
| DateTime.now() | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| try { | |
| if (parameters.getRequestId() == null) { | |
| log.warn("No requestId in {}", parameters); | |
| } else { | |
| securityRepository.insertFirmwareUpdateStatus( | |
| chargeBoxIdentity, | |
| parameters.getRequestId(), | |
| parameters.getStatus().value(), | |
| DateTime.now() | |
| ); | |
| } | |
| if (parameters.getRequestId() == null) { | |
| throw new InvalidArgumentException("RequestId is null"); | |
| } | |
| try { | |
| securityRepository.insertFirmwareUpdateStatus( | |
| chargeBoxIdentity, | |
| parameters.getRequestId(), | |
| parameters.getStatus().value(), | |
| DateTime.now() | |
| ); | |
| } |
| String keystorePath = securityConfig.getKeystorePath(); | ||
| String keystorePassword = securityConfig.getKeystorePassword(); | ||
| String keystoreType = securityConfig.getKeystoreType(); | ||
|
|
||
| KeyStore keystore = KeyStore.getInstance(keystoreType); | ||
| try (FileInputStream fis = new FileInputStream(keystorePath)) { | ||
| keystore.load(fis, keystorePassword.toCharArray()); | ||
| } | ||
|
|
||
| String alias = keystore.aliases().nextElement(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| String keystorePath = securityConfig.getKeystorePath(); | |
| String keystorePassword = securityConfig.getKeystorePassword(); | |
| String keystoreType = securityConfig.getKeystoreType(); | |
| KeyStore keystore = KeyStore.getInstance(keystoreType); | |
| try (FileInputStream fis = new FileInputStream(keystorePath)) { | |
| keystore.load(fis, keystorePassword.toCharArray()); | |
| } | |
| String alias = keystore.aliases().nextElement(); | |
| var keystorePath = securityConfig.getKeystorePath(); | |
| var keystorePassword = securityConfig.getKeystorePassword(); | |
| var keystoreType = securityConfig.getKeystoreType(); | |
| var keystore = KeyStore.getInstance(keystoreType); | |
| try (var fis = new FileInputStream(keystorePath)) { | |
| keystore.load(fis, keystorePassword.toCharArray()); | |
| } | |
| var alias = keystore.aliases().nextElement(); |
src/main/java/de/rwth/idsg/steve/web/dto/ocpp/CertificateSignedParams.java
Outdated
Show resolved
Hide resolved
src/main/java/de/rwth/idsg/steve/web/dto/ocpp/InstallCertificateParams.java
Outdated
Show resolved
Hide resolved
src/main/java/de/rwth/idsg/steve/web/dto/ocpp/SignedUpdateFirmwareParams.java
Outdated
Show resolved
Hide resolved
|
@juherr pls do not review yet. i did not go over all changes yet. some of your remarks will be obsolete when i finally arrive at these areas to improve. pls see table in #1854 (comment) |
…server.Ssl and remove configuration page to render security config details.
design decision: this function will not be triggered over Operations page. there will be a "installed certificates" page under "security management". each row (and therefore cert) will have a delete button.
comment in JSP should not be in final html
... connecting new operations with "events and certificates" pages
|
FYI: the implementation work is finished, with the exception of security profile 3 (mTLS). i need to investigate how to do it. |
|
@goekay Congratulations, it looks great! If you're fine with it, I can review the changes now. Regarding profile 3, please share your thoughts. I’ve had similar reflections on that topic. |
yes, please do. when reviewing this, could you focus mainly on the logic, correctness, and functional aspects? i’d like to prevent drifting into stylistic and preference discussions ;)
there is many. as starters: it should actually work with current state of this implementation as long as the user (the user of steve) configures and activates SSL and maintains the certs in truststore in a file. then, spring boot will configure SSL for jetty including the SSLContext using the truststore. then, the underlying standard TLS implementation will handle all mTLS stuff. but there are many issues with this:
i am thinking about a solution approach that involves following ideas:
|
* remove security impl status doc, since not useful * remove security profiled doc, since moved to wiki: https://github.com/steve-community/steve/wiki/OCPP-1.6J-Security-Configuration
juherr
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't review the JSP files.
Aside from a few style inconsistencies (like mixed use of var and occasional silent errors), everything looks good to me.
src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java
Outdated
Show resolved
Hide resolved
| // ------------------------------------------------------------------------- | ||
|
|
||
| AdditionalRootCertificateCheck("boolean", R, newHashSet(V_16)), | ||
| AuthorizationKey("string", W, newHashSet(V_16)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like WebSocketPingInterval , this one is defined in Specification OCPPJ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like WebSocketPingInterval , this one is defined in Specification OCPPJ
i dont know what to do with this comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may move it before the comment because it was not defined first in security extension.
I need to check it again because it could be an issue in my file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it was not defined first in security extension.
WebSocketPingInterval was there in ocpp 1.6 spec (no extension). the enum was added years ago at the right place.
AuthorizationKey was added with the ocpp 1.6 security extension. it is there in edition 3 (final). this enum was added in this work at the right place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AuthorizationKey is referenced in
ocpp-j-1.6-specification - section 6.2.2. Charge point authentication

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fair enough. it comes up in text, but not in the list of
- "9. Standard Configuration Key Names & Values" section of
ocpp-1.6 edition 2.pdf, or - "7. Configuration" section of
ocpp-j-1.6-specification.pdf
which i took as basis for the initial ocpp 1.6 impl.
now, it is part of "7. Configuration Keys" of OCPP 1.6 security whitepaper edition 3.pdf which i took as the basis for the security extension impl.
moreover, since we did not initially support basic auth (and AuthorizationKey) and this is such a bikeshedding topic, i have no problem having it at the current place of the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since SteVe is one of the reference OCPP implementations, it's worth adding a comment here with a bit of context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for your careful review and the detailed thoughts you’ve shared. i appreciate your attention to detail.
however, this is such a small and not-so-important thing we are discussing, i.e. the place of the enum in the source code. we should not dwell on it. i propose we move on. as i said earlier: i’d like to prevent drifting into stylistic and preference discussions. would you be okay with this? thank you!
lets put our precious time and energy into things that matter. lets talk about the more important (core) stuff where i am not sure how to continue. for example this. WDYT? (we should talk about it in a separate thread)
reason: we don't want to silently ignore (or skip) non-Json chargers. ChargePointServiceJsonInvoker.runPipeline(..) has the necessary error-handling in case of exceptions. so, we can do the check there.
reason: preparation for security profile 3 (mTLS) impl. we need to validate CpoName of the certificate, which means the info has to exist somewhere within steve beforehand.
The TLS/mTLS topic is much broader than choosing a coding convention. It is a strategic architectural decision that affects how the entire project is deployed and operated. Since you asked for my view: TLS/mTLS termination provided by the CSMS itself is fundamentally an infrastructure concern, not an application concern. Because of that, I don’t think steve should try to solve it internally or make it a core responsibility. This is exactly what edge components (reverse proxies, ingress controllers, API gateways, service meshes) are designed for. Steve’s responsibility is on the business side of the certificate logic (e.g. mapping certificate → chargeBoxId → security profile), not on performing low-level TLS operations. If, after further analysis, we still conclude that steve must handle TLS/mTLS directly, then the clean solution would require a multi-layer, event-driven architecture with dedicated gateway nodes and a separate backend for the business logic. Christian from Monta has written several useful posts about this approach: But I don’t see how an all-in-one product could reliably manage TLS, mTLS, certificate lifecycle, OCPP logic, scaling, and cloud-native deployment without breaking how Spring Boot is supposed to work or without rebuilding infrastructure features inside the application. |

@florinmandache I built on your work and made the minimal required changes.
Some tests would be very welcome. I also noticed that CSMS security requests aren’t implemented yet.
@goekay It seems to be working (at least it starts correctly), but I haven’t fully tested it yet and it could use some polishing.
Feel free to rework or improve it as you see fit.
Fix #100
Fix #1353