-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathRSASigner.cfc
More file actions
270 lines (200 loc) · 5.91 KB
/
RSASigner.cfc
File metadata and controls
270 lines (200 loc) · 5.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
component
output = false
hint = "I provide sign and verify methods for RSA-based signing."
{
/**
* I create a new RSA Signer using the given algorithm and public and private keys.
*
* NOTE: The algorithm uses the names provided in the Java Cryptography Architecture
* Standard Algorithm Name documentation:
*
* - SHA256withRSA
* - SHA384withRSA
* - SHA512withRSA
*
* CAUTION: The keys are assumed to be in PEM format.
*
* @algorithm I am the RSA-based signature algorithm being used.
* @publicKey I am the plain-text public key.
* @privateKey I am the plain-text private key.
* @output false
*/
public any function init(
required string algorithm,
required string publicKey,
required string privateKey
) {
setAlgorithm( algorithm );
setPublicKeyFromText( publicKey );
setPrivateKeyFromText( privateKey );
return( this );
}
// ---
// STATIC METHODS.
// ---
/**
* I add the BounceCastleProvider to the underlying crypto APIs.
*
* CAUTION: I don't really understand why this is [sometimes] required. But, if you
* run into the error, "Invalid RSA private key encoding.", adding BouncyCastle may
* solve the problem.
*
* This method only needs to be called once per ColdFusion application life-cycle.
* But, it can be called multiple times without error.
*
* @output false
*/
public void function addBouncyCastleProvider() {
createObject( "java", "java.security.Security" )
.addProvider( createObject( "java", "org.bouncycastle.jce.provider.BouncyCastleProvider" ).init() )
;
}
// ---
// PUBLIC METHODS.
// ---
/**
* I get the current algorithm name.
*
* @output false
*/
public string function getAlgorithm() {
return( algorithm );
}
/**
* I set the given RSA algorithm. Returns [this].
*
* NOTE: The algorithm uses the names provided in the Java Cryptography Architecture
* Standard Algorithm Name documentation:
*
* - SHA256withRSA
* - SHA384withRSA
* - SHA512withRSA
*
* @newAlgorithm I am the RSA-based signature algorithm being set.
* @output false
*/
private any function setAlgorithm( required string newAlgorithm ) {
testAlgorithm( newAlgorithm );
algorithm = newAlgorithm;
return( this );
}
/**
* I set the public key using the plain-text public key content. Returns [this].
*
* NOTE: Keys are expected to be in PEM format.
*
* @newPublicKeyText I am the plain-text public key.
* @output false
*/
public any function setPublicKeyFromText( required string newPublicKeyText ) {
testKey( newPublicKeyText );
var binaryPublicKey = binaryDecode( stripKeyDelimiters( newPublicKeyText ), "base64" );
var bis = createObject("java", "java.io.ByteArrayInputStream").init(binaryPublicKey);
var certificate = createObject("java", "java.security.cert.CertificateFactory")
.getInstance( javaCast( "string", "X509" ) )
.generateCertificate(bis);
publicKey = certificate.getPublicKey();
return( this );
}
/**
* I set the private key using the plain-text private key content. Returns [this].
*
* NOTE: Keys are expected to be in PEM format.
*
* @newPrivateKeyText I am the plain-text private key.
* @output false
*/
public any function setPrivateKeyFromText( required string newPrivateKeyText ) {
testKey( newPrivateKeyText );
var privateKeySpec = createObject( "java", "java.security.spec.PKCS8EncodedKeySpec" ).init(
binaryDecode( stripKeyDelimiters( newPrivateKeyText ), "base64" )
);
privateKey = createObject( "java", "java.security.KeyFactory" )
.getInstance( javaCast( "string", "RSA" ) )
.generatePrivate( privateKeySpec )
;
return( this );
}
/**
* I test the given algorithm. If the algorithm is not valid, I throw an error.
*
* @newAlgorithm I am the new RSA algorithm being tested.
* @output false
*/
public void function testAlgorithm( required string newAlgorithm ) {
switch ( newAlgorithm ) {
case "SHA256withRSA":
case "SHA384withRSA":
case "SHA512withRSA":
return;
break;
}
throw(
type = "JsonWebTokens.RSASigner.InvalidAlgorithm",
message = "The given algorithm is not supported.",
detail = "The given algorithm [#newAlgorithm#] is not supported."
);
}
/**
* I test the given key (public or private). If the key is not valid, I throw an error.
*
* @newKey I am the new key being tested.
* @output false
*/
public void function testKey( required string newKey ) {
if ( ! len( stripKeyDelimiters( newKey ) ) ) {
throw(
type = "JsonWebTokens.RSASigner.InvalidKey",
message = "The key cannot be blank."
);
}
}
/**
* I sign the given binary message using the current algorithm and private key.
*
* @message I am the message being signed.
* @output false
*/
public binary function sign( required binary message ) {
var signer = createObject( "java", "java.security.Signature" )
.getInstance( javaCast( "string", algorithm ) )
;
signer.initSign( privateKey );
signer.update( message );
return( signer.sign() );
}
/**
* I verify that the given signature was generated from the given message.
*
* @message I am the binary message input.
* @signature I am the binary signature being verified.
* @output false
*/
public boolean function verify(
required binary message,
required binary signature
) {
var verifier = createObject( "java", "java.security.Signature" )
.getInstance( javaCast( "string", algorithm ) )
;
verifier.initVerify( publicKey );
verifier.update( message );
return( verifier.verify( signature ) );
}
// ---
// PRIVATE METHODS.
// ---
/**
* I strip the plain-text key delimiters, to isolate the key content.
*
* @keyText I am the plain-text key input.
* @output false
*/
private string function stripKeyDelimiters( required string keyText ) {
// Strips out the leading / trailing boundaries:
keyText = reReplace( keyText, "-----(BEGIN|END)[^\r\n]+", "", "all" );
// Strips out newline characters
keyText = reReplace( keyText, "[\r\n]", "", "all" );
return( trim( keyText ) );
}
}