1+ /*
2+ * Copyright (c) 2022, Red Hat, Inc.
3+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+ *
5+ * This code is free software; you can redistribute it and/or modify it
6+ * under the terms of the GNU General Public License version 2 only, as
7+ * published by the Free Software Foundation. Oracle designates this
8+ * particular file as subject to the "Classpath" exception as provided
9+ * by Oracle in the LICENSE file that accompanied this code.
10+ *
11+ * This code is distributed in the hope that it will be useful, but WITHOUT
12+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+ * version 2 for more details (a copy is included in the LICENSE file that
15+ * accompanied this code).
16+ *
17+ * You should have received a copy of the GNU General Public License version
18+ * 2 along with this work; if not, write to the Free Software Foundation,
19+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+ *
21+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+ * or visit www.oracle.com if you need additional information or have any
23+ * questions.
24+ */
25+
26+ package sun .security .pkcs11 ;
27+
28+ import java .io .BufferedReader ;
29+ import java .io .ByteArrayInputStream ;
30+ import java .io .InputStream ;
31+ import java .io .InputStreamReader ;
32+ import java .io .IOException ;
33+ import java .nio .charset .StandardCharsets ;
34+ import java .nio .file .Files ;
35+ import java .nio .file .Path ;
36+ import java .nio .file .Paths ;
37+ import java .nio .file .StandardOpenOption ;
38+ import java .security .ProviderException ;
39+
40+ import javax .security .auth .callback .Callback ;
41+ import javax .security .auth .callback .CallbackHandler ;
42+ import javax .security .auth .callback .PasswordCallback ;
43+ import javax .security .auth .callback .UnsupportedCallbackException ;
44+
45+ import sun .security .util .Debug ;
46+ import sun .security .util .SecurityProperties ;
47+
48+ final class FIPSTokenLoginHandler implements CallbackHandler {
49+
50+ private static final String FIPS_NSSDB_PIN_PROP = "fips.nssdb.pin" ;
51+
52+ private static final Debug debug = Debug .getInstance ("sunpkcs11" );
53+
54+ public void handle (Callback [] callbacks )
55+ throws IOException , UnsupportedCallbackException {
56+ if (!(callbacks [0 ] instanceof PasswordCallback )) {
57+ throw new UnsupportedCallbackException (callbacks [0 ]);
58+ }
59+ PasswordCallback pc = (PasswordCallback )callbacks [0 ];
60+ pc .setPassword (getFipsNssdbPin ());
61+ }
62+
63+ private static char [] getFipsNssdbPin () throws ProviderException {
64+ if (debug != null ) {
65+ debug .println ("FIPS: Reading NSS DB PIN for token..." );
66+ }
67+ String pinProp = SecurityProperties
68+ .privilegedGetOverridable (FIPS_NSSDB_PIN_PROP );
69+ if (pinProp != null && !pinProp .isEmpty ()) {
70+ String [] pinPropParts = pinProp .split (":" , 2 );
71+ if (pinPropParts .length < 2 ) {
72+ throw new ProviderException ("Invalid " + FIPS_NSSDB_PIN_PROP +
73+ " property value." );
74+ }
75+ String prefix = pinPropParts [0 ].toLowerCase ();
76+ String value = pinPropParts [1 ];
77+ String pin = null ;
78+ if (prefix .equals ("env" )) {
79+ if (debug != null ) {
80+ debug .println ("FIPS: PIN value from the '" + value +
81+ "' environment variable." );
82+ }
83+ pin = System .getenv (value );
84+ } else if (prefix .equals ("file" )) {
85+ if (debug != null ) {
86+ debug .println ("FIPS: PIN value from the '" + value +
87+ "' file." );
88+ }
89+ pin = getPinFromFile (Paths .get (value ));
90+ } else if (prefix .equals ("pin" )) {
91+ if (debug != null ) {
92+ debug .println ("FIPS: PIN value from the " +
93+ FIPS_NSSDB_PIN_PROP + " property." );
94+ }
95+ pin = value ;
96+ } else {
97+ throw new ProviderException ("Unsupported prefix for " +
98+ FIPS_NSSDB_PIN_PROP + "." );
99+ }
100+ if (pin != null && !pin .isEmpty ()) {
101+ if (debug != null ) {
102+ debug .println ("FIPS: non-empty PIN." );
103+ }
104+ /*
105+ * C_Login in libj2pkcs11 receives the PIN in a char[] and
106+ * discards the upper byte of each char, before passing
107+ * the value to the NSS Software Token. However, the
108+ * NSS Software Token accepts any UTF-8 PIN value. Thus,
109+ * expand the PIN here to account for later truncation.
110+ */
111+ byte [] pinUtf8 = pin .getBytes (StandardCharsets .UTF_8 );
112+ char [] pinChar = new char [pinUtf8 .length ];
113+ for (int i = 0 ; i < pinChar .length ; i ++) {
114+ pinChar [i ] = (char )(pinUtf8 [i ] & 0xFF );
115+ }
116+ return pinChar ;
117+ }
118+ }
119+ if (debug != null ) {
120+ debug .println ("FIPS: empty PIN." );
121+ }
122+ return null ;
123+ }
124+
125+ /*
126+ * This method extracts the token PIN from the first line of a password
127+ * file in the same way as NSS modutil. See for example the -newpwfile
128+ * argument used to change the password for an NSS DB.
129+ */
130+ private static String getPinFromFile (Path f ) throws ProviderException {
131+ try (InputStream is =
132+ Files .newInputStream (f , StandardOpenOption .READ )) {
133+ /*
134+ * SECU_FilePasswd in NSS (nss/cmd/lib/secutil.c), used by modutil,
135+ * reads up to 4096 bytes. In addition, the NSS Software Token
136+ * does not accept PINs longer than 500 bytes (see SFTK_MAX_PIN
137+ * in nss/lib/softoken/pkcs11i.h).
138+ */
139+ BufferedReader in =
140+ new BufferedReader (new InputStreamReader (
141+ new ByteArrayInputStream (is .readNBytes (4096 )),
142+ StandardCharsets .UTF_8 ));
143+ return in .readLine ();
144+ } catch (IOException ioe ) {
145+ throw new ProviderException ("Error reading " + FIPS_NSSDB_PIN_PROP +
146+ " from the '" + f + "' file." , ioe );
147+ }
148+ }
149+ }
0 commit comments