Skip to content

Commit af382ab

Browse files
authored
Add certificate installer and updater (#3)
* Add certificate installer and updater * Update abaplint.json * Update abaplint.json * lint * Update abaplint.json * Lint * lint
1 parent 36f081d commit af382ab

File tree

6 files changed

+559
-62
lines changed

6 files changed

+559
-62
lines changed

abaplint.json

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,23 @@
1313
{
1414
"files": "/src/**/*.*",
1515
"folder": "/deps3",
16+
"url": "https://github.com/abapPM/ABAP-HTTP"
17+
},
18+
{
19+
"files": "/src/**/*.*",
20+
"folder": "/deps4",
1621
"url": "https://github.com/abapPM/ABAP-Distinguished-Name"
1722
},
23+
{
24+
"files": "/src/**/*.*",
25+
"folder": "/deps5",
26+
"url": "https://github.com/sbcgua/ajson"
27+
},
28+
{
29+
"files": "/src/**/*.*",
30+
"folder": "/deps6",
31+
"url": "https://github.com/sbcgua/abap-string-map"
32+
}
1833
],
1934
"global": {
2035
"exclude": [],
@@ -118,7 +133,14 @@
118133
"implement_methods": true,
119134
"implicit_start_of_selection": true,
120135
"in_statement_indentation": false,
121-
"indentation": true,
136+
"indentation": {
137+
"ignoreExceptions": true,
138+
"alignTryCatch": false,
139+
"selectionScreenBlockIndentation": true,
140+
"globalClassSkipFirst": false,
141+
"ignoreGlobalClassDefinition": false,
142+
"ignoreGlobalInterface": false
143+
},
122144
"inline_data_old_versions": true,
123145
"intf_referencing_clas": true,
124146
"invalid_table_index": true,

src/z_strust_installer.prog.abap

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
REPORT z_strust_installer LINE-SIZE 255.
2+
3+
************************************************************************
4+
* Trust Management: Certificate Installer
5+
*
6+
* Copyright 2025 apm.to Inc. <https://apm.to>
7+
* SPDX-License-Identifier: MIT
8+
************************************************************************
9+
10+
SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-t01.
11+
PARAMETERS:
12+
p_cont TYPE psecontext DEFAULT 'SSLC' OBLIGATORY,
13+
p_appl TYPE ssfappl DEFAULT 'ANONYM' OBLIGATORY.
14+
SELECTION-SCREEN END OF BLOCK b1.
15+
16+
SELECTION-SCREEN BEGIN OF BLOCK b2 WITH FRAME TITLE TEXT-t02.
17+
PARAMETERS p_domain TYPE string OBLIGATORY LOWER CASE.
18+
SELECTION-SCREEN END OF BLOCK b2.
19+
20+
SELECTION-SCREEN BEGIN OF BLOCK b3 WITH FRAME TITLE TEXT-t03.
21+
PARAMETERS:
22+
p_passwd TYPE string LOWER CASE,
23+
p_root AS CHECKBOX DEFAULT abap_false,
24+
p_test AS CHECKBOX DEFAULT abap_true.
25+
SELECTION-SCREEN END OF BLOCK b3.
26+
27+
CONSTANTS c_api TYPE string VALUE 'https://tools.abappm.com/api/v1/certificates'.
28+
29+
INITIALIZATION.
30+
31+
DATA(subrc) = cl_abap_pse=>authority_check( iv_activity = '01' )
32+
+ cl_abap_pse=>authority_check( iv_activity = '02' )
33+
+ cl_abap_pse=>authority_check( iv_activity = '06' ).
34+
IF subrc <> 0.
35+
MESSAGE 'You are not authorized to update certificates' TYPE 'E'.
36+
STOP.
37+
ENDIF.
38+
39+
START-OF-SELECTION.
40+
41+
CALL FUNCTION 'SSFPSE_PARAMETER'
42+
EXPORTING
43+
context = p_cont
44+
applic = p_appl
45+
EXCEPTIONS
46+
pse_not_found = 1
47+
OTHERS = 2.
48+
IF sy-subrc <> 0.
49+
MESSAGE 'PSE not found' TYPE 'E'.
50+
STOP.
51+
ENDIF.
52+
53+
TRY.
54+
DATA(strust) = zcl_strust2=>create(
55+
context = p_cont
56+
application = p_appl
57+
password = p_passwd ).
58+
CATCH zcx_error INTO DATA(error).
59+
MESSAGE error TYPE 'E'.
60+
STOP.
61+
ENDTRY.
62+
63+
64+
WRITE: /'Domain:', p_domain COLOR COL_POSITIVE.
65+
SKIP.
66+
67+
" We can't query wildcard domains so we try with "api" sub-domain
68+
" TODO: if this fails, loop over a couple other common sub-domains
69+
DATA(query) = replace( val = p_domain sub = '*' with = 'api' ).
70+
query = cl_abap_dyn_prg=>escape_xss_url( query ).
71+
72+
" Get certificate data from tools.abappm.com
73+
DATA(agent) = zcl_http_agent=>create( ).
74+
75+
agent->global_headers( )->set(
76+
iv_key = zif_http_agent=>c_header-accept
77+
iv_val = zif_http_agent=>c_content_type-json ).
78+
79+
DATA(response) = agent->request( |{ c_api }?domain={ query }| ).
80+
81+
IF response->is_ok( ) = abap_false.
82+
WRITE: / 'Error getting certificates from API:' COLOR COL_NEGATIVE, response->error( ).
83+
STOP.
84+
ENDIF.
85+
86+
TRY.
87+
DATA(ajson) = zcl_ajson=>parse( response->cdata( ) ).
88+
CATCH zcx_ajson_error INTO DATA(ajson_error).
89+
WRITE: / 'Error parsing API response:' COLOR COL_NEGATIVE, ajson_error->get_text( ).
90+
STOP.
91+
ENDTRY.
92+
93+
IF ajson->get( '/error' ) IS NOT INITIAL.
94+
WRITE: / 'Error getting certificates from API:' COLOR COL_NEGATIVE, ajson->get( '/error' ).
95+
STOP.
96+
ENDIF.
97+
98+
" Keep fingers crossed that the response matches what we need for the update
99+
DATA(cert_domain) = ajson->get( '/domain' ).
100+
101+
IF cert_domain <> p_domain AND p_domain NA '*'.
102+
WRITE: / 'Certificates domain does not match request:' COLOR COL_TOTAL, cert_domain.
103+
STOP.
104+
ENDIF.
105+
106+
" We finally have a certificate that can be used for the update, yay!
107+
TRY.
108+
" Root and intermediate certificates
109+
IF p_root = abap_true.
110+
111+
LOOP AT ajson->members( '/intermediateCertificates' ) INTO DATA(member).
112+
113+
DATA(inter_pem) = ajson->get( '/intermediateCertificates/' && member && '/pem' ).
114+
DATA(inter_date_from) = ajson->get( '/intermediateCertificates/' && member && '/validFrom' ).
115+
DATA(inter_date_to) = ajson->get( '/intermediateCertificates/' && member && '/validTo' ).
116+
DATA(inter_subject) = 'CN=' && ajson->get( '/intermediateCertificates/' && member && '/subject/CN' ).
117+
IF inter_subject = 'CN='.
118+
inter_subject = 'O=' && ajson->get( '/intermediateCertificates/' && member && '/subject/O' ).
119+
ENDIF.
120+
IF strlen( inter_subject ) > 78.
121+
inter_subject = inter_subject(75) && '...'.
122+
ENDIF.
123+
124+
IF p_test = abap_false.
125+
strust->add_pem( inter_pem ).
126+
ENDIF.
127+
128+
WRITE: /10 'Root/intermediate certificate added:' COLOR COL_POSITIVE,
129+
AT 50 inter_subject,
130+
AT 130 inter_date_from(10),
131+
AT 145 inter_date_to(10),
132+
AT 158 ''.
133+
134+
ENDLOOP.
135+
SKIP.
136+
137+
ENDIF.
138+
139+
" Main certificate
140+
DATA(peer_pem) = ajson->get( '/peerCertificate/pem' ).
141+
DATA(peer_date_from) = ajson->get( '/peerCertificate/validFrom' ).
142+
DATA(peer_date_to) = ajson->get( '/peerCertificate/validTo' ).
143+
DATA(peer_subject) = 'CN=' && ajson->get( '/peerCertificate/subject/CN' ).
144+
IF peer_subject = 'CN='.
145+
peer_subject = 'O=' && ajson->get( '/peerCertificate/subject/O' ).
146+
ENDIF.
147+
IF strlen( peer_subject ) > 78.
148+
peer_subject = peer_subject(75) && '...'.
149+
ENDIF.
150+
151+
IF p_test = abap_false.
152+
strust->add_pem( peer_pem ).
153+
ENDIF.
154+
155+
WRITE: /10 'New certificate added:' COLOR COL_POSITIVE,
156+
AT 50 peer_subject,
157+
AT 130 peer_date_from(10),
158+
AT 145 peer_date_to(10),
159+
AT 158 ''.
160+
161+
ULINE.
162+
163+
IF p_test = abap_true.
164+
WRITE: / 'Test run' COLOR COL_TOTAL, '(changes were not saved)'.
165+
STOP.
166+
ENDIF.
167+
168+
" Save changes
169+
strust->update( ).
170+
171+
WRITE / 'Certificates saved' COLOR COL_POSITIVE.
172+
173+
CATCH zcx_error INTO error.
174+
WRITE: / 'Error updating certificate:' COLOR COL_NEGATIVE, error->get_text( ).
175+
ENDTRY.

src/z_strust_installer.prog.xml

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<abapGit version="v1.0.0" serializer="LCL_OBJECT_PROG" serializer_version="v1.0.0">
3+
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
4+
<asx:values>
5+
<PROGDIR>
6+
<NAME>Z_STRUST_INSTALLER</NAME>
7+
<SUBC>1</SUBC>
8+
<RLOAD>E</RLOAD>
9+
<FIXPT>X</FIXPT>
10+
<UCCHECK>X</UCCHECK>
11+
</PROGDIR>
12+
<TPOOL>
13+
<item>
14+
<ID>H</ID>
15+
<KEY>001</KEY>
16+
<ENTRY>Subject Valid From Valid to</ENTRY>
17+
<LENGTH>152</LENGTH>
18+
</item>
19+
<item>
20+
<ID>I</ID>
21+
<KEY>T01</KEY>
22+
<ENTRY>Category</ENTRY>
23+
<LENGTH>50</LENGTH>
24+
</item>
25+
<item>
26+
<ID>I</ID>
27+
<KEY>T02</KEY>
28+
<ENTRY>Certificate</ENTRY>
29+
<LENGTH>50</LENGTH>
30+
</item>
31+
<item>
32+
<ID>I</ID>
33+
<KEY>T03</KEY>
34+
<ENTRY>Options</ENTRY>
35+
<LENGTH>50</LENGTH>
36+
</item>
37+
<item>
38+
<ID>R</ID>
39+
<ENTRY>Trust Management: Certificate Installer</ENTRY>
40+
<LENGTH>39</LENGTH>
41+
</item>
42+
<item>
43+
<ID>S</ID>
44+
<KEY>P_APPL</KEY>
45+
<ENTRY>Application</ENTRY>
46+
<LENGTH>19</LENGTH>
47+
</item>
48+
<item>
49+
<ID>S</ID>
50+
<KEY>P_CONT</KEY>
51+
<ENTRY>Context</ENTRY>
52+
<LENGTH>15</LENGTH>
53+
</item>
54+
<item>
55+
<ID>S</ID>
56+
<KEY>P_DOMAIN</KEY>
57+
<ENTRY>Domain</ENTRY>
58+
<LENGTH>14</LENGTH>
59+
</item>
60+
<item>
61+
<ID>S</ID>
62+
<KEY>P_PASSWD</KEY>
63+
<ENTRY>Password</ENTRY>
64+
<LENGTH>16</LENGTH>
65+
</item>
66+
<item>
67+
<ID>S</ID>
68+
<KEY>P_ROOT</KEY>
69+
<ENTRY>Add root/intermediate certs</ENTRY>
70+
<LENGTH>38</LENGTH>
71+
</item>
72+
<item>
73+
<ID>S</ID>
74+
<KEY>P_TEST</KEY>
75+
<ENTRY>Test run</ENTRY>
76+
<LENGTH>16</LENGTH>
77+
</item>
78+
</TPOOL>
79+
</asx:values>
80+
</asx:abap>
81+
</abapGit>

0 commit comments

Comments
 (0)